How can you map and pass unknown types in Java when using reflection?
I am using reflection to map getters from one class to setters in another, i.e. I have the form classes used by stuts1 to display mostly text (strings), and I have pure Java objects used by the trailing end that holds values in their specific type. I currently have a mapping between getters and setters, which was easy, but I'm having trouble with mixed types. I am using the parameter type from the setter to find out what was expected and so I need to determine the type of the object from the receiver and specify it as the expected type.
eg.
HomePageForm -> HomePageData
Name="Alexei" -> String name; (Maps correctly)
Age="22" -> int age; (Needs casting from string to int and visa-vera in reverse)
Below is my code
/**
* Map the two given objects by reflecting the methods from the mapTo object and finding its setter methods. Then
* find the corresponding getter method in the mapFrom class and invoke them to obtain each attribute value.
* Finally invoke the setter method for the mapTo class to set the attribute value.
*
* @param mapFrom The object to map attribute values from
* @param mapTo The object to map attribute values to
*/
private void map(Object mapFrom, Object mapTo) {
Method [] methods = mapTo.getClass().getMethods();
for (Method methodTo : methods) {
if (isSetter(methodTo)) {
try {
//The attribute to map to setter from getter
String attName = methodTo.getName().substring(3);
//Find the corresponding getter method to retrieve the attribute value from
Method methodFrom = mapFrom.getClass().getMethod("get" + attName, new Class<?>[0]);
//If the methodFrom is a valid getter, set the attValue
if (isGetter(methodFrom)) {
//Invoke the getter to get the attribute to set
Object attValue = methodFrom.invoke(mapFrom, new Object[0]);
Class<?> fromType = attValue.getClass();
//Need to cast/parse type here
if (fromType != methodTo.getParameterTypes()[0]){
//!!Do something to case/parse type!!
} //if
//Invoke the setter to set the attribute value
methodTo.invoke(mapTo, attValue);
} //if
} catch (Exception e) {
Logger.getLogger(Constants.APP_LOGGER).fine("Exception in DataFormMappingService.map: "
+ "IllegalArgumentException" + e.getMessage());
continue;
}
} //if
} //for
} //map
Thanks in advance, Alexey Blue.
source to share
In the end, I implemented the best solution:
/**
* Map to given objects taking into account the inclusion and exclusion sets.
*
* @param mapFrom The object to map attribute values from
* @param mapTo The object to map attribute values to
*/
private void map(Object mapFrom, Object mapTo)
{
setMapFilter(mapFrom, mapTo);
for (Method sMethod : filterMap.keySet())
{
try
{
//Find the corresponding getter method to retrieve the attribute value from
Method gMethod = filterMap.get(sMethod);
//Invoke the getter to get the attribute to set
Object attValue = gMethod.invoke(mapFrom, new Object[0]);
//Get the mapping types and check their compatibility, if incompatible convert attValue type to toType
Class<?> fromType = gMethod.getReturnType();
Class<?> toType = sMethod.getParameterTypes()[0];
if(!toType.isAssignableFrom(fromType) && Objects.isNotNull(attValue))
{
attValue = parseType(attValue, toType);
} //if
//Invoke the setter to set the attribute value
sMethod.invoke(mapTo, attValue);
}
catch (IllegalArgumentException e)
{
Logger.getLogger(Constants.APP_LOGGER).fine("Exception in DataFormMappingService.map: "
+ "IllegalArgumentException " + e.getMessage());
continue;
}
catch (IllegalAccessException e)
{
Logger.getLogger(Constants.APP_LOGGER).fine("Exception in DataFormMappingService.map: "
+ "IllegalAccessException " + e.getMessage());
continue;
}
catch (InvocationTargetException e)
{
Logger.getLogger(Constants.APP_LOGGER).fine("Exception in DataFormMappingService.map: "
+ "InvocationTargetException " + e.getMessage());
continue;
}
} //for each
} //map
/**
* Refactored method to parse an object, attValue, from it unknown type to the type expected.
*
* @param attValue The attribute value to parse
* @param type The type expected/to convert to
* @return The attribute value in the expected type, null otherwise
*/
private Object parseType(Object attValue, Class<?> type)
{
Object newAttValue = null;
if (isLiteral(type))
{
newAttValue = attValue.toString();
}
else if (isByte(type))
{
newAttValue = Byte.valueOf(attValue.toString());
}
else if (isInt(type))
{
newAttValue = Integer.valueOf(attValue.toString());
}
else if (isShort(type))
{
newAttValue = Short.valueOf(attValue.toString());
}
else if (isLong(type))
{
newAttValue = Long.valueOf(attValue.toString());
}
else if (isFloat(type))
{
newAttValue = Float.valueOf(attValue.toString());
}
else if (isDouble(type))
{
newAttValue = Double.valueOf(attValue.toString());
}
else if (isBoolean(type))
{
newAttValue = Boolean.valueOf(attValue.toString());
} //if-else if*
return newAttValue;
} //parseType
This is cleaner than my original solution, but essentially when mapping, the filter is built from methods for the mapping that just loops and then displays. The parse method just checks the types and uses the valueOf method for Object.toString (), which works for standard Java types.
Greetings,
Alexey Blue.
source to share
Yes, @Molske is the place. The problem is promoting the return value int
as Integer
and then this comparison:
if (fromType != methodTo.getParameterTypes()[0])
I just quickly checked your code and I get the exception:
java.lang.AssertionError: expected:<class java.lang.Integer> but was:<int>
Instead fromType
, when compared to methodFrom.getReturnType()
, then the code should work:
if (methodFrom.getReturnType() != methodTo.getParameterTypes()[0])
Here's the test I worked with:
@Test
public void testException() throws Exception {
Foo foo = new Foo();
Bar bar = new Bar();
for (Method methodTo : Bar.class.getMethods()) {
if (methodTo.getName().startsWith("set")) {
String attName = methodTo.getName().substring(3);
Method methodFrom = Foo.class.getMethod("get" + attName);
if (methodFrom != null && methodFrom.getName().startsWith("get")) {
Object attValue = methodFrom.invoke(foo);
// this ignores the auto-boxing issues
assertEquals(methodFrom.getReturnType(),
methodTo.getParameterTypes()[0]);
methodTo.invoke(bar, attValue);
}
}
}
}
private static class Foo {
public int getFoo() { return 1; }
}
private static class Bar {
public void setFoo(int i) { System.out.println("i = " + i); }
}
source to share