2

I have an third-party RPC-API that provides an interface similar to that of java.sql.ResultSet (for reading values) and java.sql.PreparedStatement (for writing values). Assume it looks something like this:

public interface RemoteDeviceProxy {
    public void setBoolean(Boolean value);
    public void setInteger(Integer value);
    // ...

    public Boolean getBoolean();
    public Integer getInteger();
    // ...
}

I want to write a wrapper for this API that uses generics to create instances of specific types:

public class <T> RemoteVariable {
    private final RemoteDeviceProxy wrappedDevice;

    public RemoteVariable(RemoteDeviceProxy wrappedDevice) {
        this.wrappedDevice = wrappedDevice;
    }

    public T get() {
        // should call wrappedDevice.getBoolean() if T is Boolean, etc.
        // how to implement?
    }

    public void set(T newValue) {
        // should call wrappedDevice.setBoolean(newValue) if T is Boolean, etc.
        // implement using instanceof
    }
}

How can I implement the getter in my generic wrapper? I have found this answer which explains a similar scenario in depth, but I am not able to transfer this to my problem. Specifically, when I write this:

public T get() {
        Type[] actualTypeArguments = ((ParameterizedType) getClass())
                                         .getActualTypeArguments();
    }

I get a compiler error saying I cannot cast to ParameterizedType, and I do not understand why. Can anyone explain how to achieve this?

Community
  • 1
  • 1
Björn Pollex
  • 75,346
  • 28
  • 201
  • 283

2 Answers2

2

Here is one way:

public class <T> RemoteVariable {
    private final RemoteDeviceProxy wrappedDevice;
    private final Class<T> clazz;

    public RemoteVariable(RemoteDeviceProxy wrappedDevice, Class<T> clazz) {
        this.wrappedDevice = wrappedDevice;
        this.clazz = clazz;
    }

    public T get() {
        if(clazz == Boolean.class){return clazz.cast(wrappedDevice.getBoolean());}
        else if(clazz == Integer.class){return clazz.cast(wrappedDevice.getInteger());}
        // ...
    }

    // ...
}
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • I hadn't thought of that. Since instances of `RemoteVariable` are created from a factory anyway, this isn't even ugly in client-code. Thanks! – Björn Pollex Jun 26 '13 at 11:01
2

I thought over this quite a while and finally came up with a different approach:

First I added a getter to you RemoteVariable class:

protected RemoteDeviceProxy getWrappedProxy() {
    return wrappedProxy;
}

Second I created a builder interface that will be used by a factory later:

public interface RemoteVariableBuilder {
    public <T> RemoteVariable<T> buildNewVariable(RemoteDeviceProxy wrappedProxy);
}

Then I created non generic sub classes for Boolean...

public class RemoteBooleanVariable extends RemoteVariable<Boolean> implements RemoteVariableBuilder {

    public RemoteBooleanVariable(RemoteDeviceProxy wrappedProxy) {
        super(wrappedProxy);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> RemoteVariable<T> buildNewVariable(RemoteDeviceProxy wrappedProxy) {
        return (RemoteVariable<T>) new RemoteBooleanVariable(wrappedProxy);
    }

    @Override
    public Boolean get() {
        return getWrappedProxy().getBoolean();
    }

    @Override
    public void set(Boolean value) {
        getWrappedProxy().setBoolean(value);
    }

}

... and Integer ...

public class RemoteIntegerBuilder extends RemoteVariable<Integer> implements RemoteVariableBuilder {

    public RemoteIntegerBuilder(RemoteDeviceProxy wrappedProxy) {
        super(wrappedProxy);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> RemoteVariable<T> buildNewVariable(RemoteDeviceProxy wrappedProxy) {
        return (RemoteVariable<T>) new RemoteIntegerBuilder(wrappedProxy);
    }

    @Override
    public Integer get() {
        return getWrappedProxy().getInteger();
    }

    @Override
    public void set(Integer value) {
        getWrappedProxy().setInteger(value);
    }

}

actually eclipse created most of the code once it knew base class and interface.

The final step was to create a factory

public class RemoteVariableFactory {
    private static final Map<String, RemoteVariableBuilder> BUILDERS = new HashMap<>();

    static {
        BUILDERS.put(Boolean.class.getName(), new RemoteBooleanVariable(null));
        BUILDERS.put(Integer.class.getName(), new RemoteIntegerBuilder(null));
        // add more builders here
    }

    public static <T> RemoteVariable<T> getRemoteVariable(RemoteDeviceProxy wrappedProxy, Class<T> typeClass) {
        RemoteVariableBuilder remoteVariableBuilder = BUILDERS.get(typeClass.getName());

        if (remoteVariableBuilder == null) {
            return null; // or throw an exception whichever is better in your case 
        }
        return remoteVariableBuilder.buildNewVariable(wrappedProxy);
    }
}

Now we are ready to create new RemoteVariables...

RemoteVariable<Boolean> var1 = RemoteVariableFactory.getRemoteVariable(new RemoteDevice(), Boolean.class);
RemoteVariable<Integer> var2 = RemoteVariableFactory.getRemoteVariable(new RemoteDevice(), Integer.class);

To conclude this let's do a quick comparison to the answer of Eng.Fouad:

Disadvantage:

  • you need to create a new class for every datatype you provide

Advantage:

  • you only have to add one line to the static block of the factory and not two new if blocks to the getter and setter in RemoteVariable
  • get and set do not have to work through the if-else-blocks every time
Marco Forberg
  • 2,634
  • 5
  • 22
  • 33