I am working on a message parsing library where the message body comes back as a string in every case. I want to create a function that will automatically convert that result to the proper type, but I am not sure if its possible.
My structure is like this: I have an enum called ReturnType. This contains this this contains the class variable of the return type.
I have a function map which maps the function that the user is trying to call with the return type of the message response.
I think I have it working to some point, but the problem is I have to include the class I want into the getTypedValue() method call, which I think is redundant since I already know the return type based on the ReturnType variable within the function map. Here is some code:
public enum ReturnType {
NA(String.class),
INTEGER(Integer.class),
JSON(JsonElement.class),
STRING(String.class),
FLOAT(Float.class),
DOUBLE(Double.class),
IP_ADDRESS(InetAddress.class),
UNKNOWN(String.class),
BOOLEAN(Boolean.class);
private Class<?> c;
<T> ReturnType(Class<T> clazz) {
this.c = clazz;
}
public Class<?> getClazz() {
return this.c;
}
}
Here is my getTypedValue() method:
public <T> T getTypedValue(String functionName, String value, Class<T> clazz) {
ReturnType type = this.getReturnType(functionName);
//TODO: Finish this
if(type.getClazz().equals(clazz)) {
switch(type) {
case DOUBLE:
break;
case FLOAT:
break;
case INTEGER:
return clazz.cast(Integer.parseInt(value));
case IP_ADDRESS:
break;
case JSON:
break;
case STRING:
return clazz.cast(value);
case UNKNOWN:
return clazz.cast(value);
default:
return clazz.cast(value);
}
return null;
}else {
throw new UnsupportedOperationException(functionName +" does not support that class("+clazz.getSimpleName()+")");
}
}
Here is how a user would call get typed value. I put in a static value(15) for simplification but that will actually be the a method call that gets the result from the message response:
Integer volume = fm.getTypedValue("VOLUME", "15", Integer.class);
Is there any way to make it return the correct type without providing the class? i.e.
Integer volume = fm.getTypedValue("VOLUME", "15");
Double minutes = fm.getTypedValue("MINUTES", "15.25");
Boolean isRunning = fm.getTypedValue("IS_RUNNING","true");
I'm unsure if its possible, and even if it is, the code I currently have I think might be a little ugly and difficult to follow.
You can achieve that, but you'll loose the advantage that the compiler gives you, of checking that the types are correct at compile time.
Using a getTypedValue
implementation similar to:
@SuppressWarnings({ "unchecked" })
public <T> T getTypedValue(String functionName, String value) {
ReturnType type = this.getReturnType(functionName);
switch(type) {
case DOUBLE:
case FLOAT:
case JSON:
case IP_ADDRESS:
break;
case INTEGER:
return (T) Integer.valueOf(value);
case STRING:
case UNKNOWN:
default:
return (T) value;
}
return null;
}
You can use this without any problem:
int volume = fm.getTypedValue("VOLUME", "15");
But, by the other hand you can also use this (and the compiler won't show any error)
String volume = fm.getTypedValue("VOLUME", "15");
You instead will receive a runtime exception of type ClassCastException
saying something like:
java.lang.Integer cannot be cast to java.lang.String
If you want something probably less error prone (that shows errors at compile time instead of runtime), you can define all the functions that you handle/know/provide in a class (the enum does not handle generics).
public final class Function<T> {
private final Class<T> type;
private Function(Class<T> type) {
this.type = type;
}
public Class<T> getType() {
return type;
}
public static final Function<Integer> VOLUME = new Function<>(Integer.class);
public static final Function<Boolean> IS_RUNNING = new Function<>(Boolean.class);
public static final Function<String> NAME = new Function<>(String.class);
}
(You can refactor as you want, maybe adding ReturnType
. You can add some custom logic in the Function
. Also can pass the name of the function in the constructor parameters, in case you need the name later)
Then change a little the getTypedValue
method:
public <T> T getTypedValue(Function<T> function, String value) {
ReturnType returnType = ReturnType.getReturnTypeByClass(function.getType());
// ... same as before
}
You can create the method getReturnTypeByClass()
to obtain the returnType
depending on the received class.
And this will work:
int volume = test2.getTypedValue(Function.VOLUME, "123");
And this will show a compile time error, very easy to detect and fix:
String volume = test2.getTypedValue(Function.VOLUME, "123");
Finally, you can separate the definition of the Function
class and the declarations of the functions in another class names for example Functions
.