for a code generation tool, I want to find a string that represents best a variable declaration.
For example, if I get an empty ArrayList-instance, it is clear that no information about the type is available, so the output should be ArrayList<? extends Object>. However, if I get an ArrayList-instance that contains a String-Element, I assume, that only Strings are inserted and the output should be ArrayList<? extends String.
Here is a small unit test case that shows what the behavior should be:
public class ObjectAnalyserTest {
public ObjectAnalyserTest() {
}
@Test
public void testCreateVariableDeclaration() {
String o1 = "";
assertEquals("String", ObjectAnalyser.createVariableDeclaration(o1, true));
Value<String> v1 = new Value<>(null);
assertEquals("Value<? extends Object>", ObjectAnalyser.createVariableDeclaration(v1, true));
Value<String> v2 = new Value<>("");
assertEquals("Value<? extends String>", ObjectAnalyser.createVariableDeclaration(v2, true));
Value<Dummy> v3 = new Value<>(new Dummy());
assertEquals("Value<? extends Dummy>", ObjectAnalyser.createVariableDeclaration(v3, true));
Pair<? extends Number, ? extends Thread> p1 = new Pair<>(new Integer(5), new Thread(""));
assertEquals("Pair<? extends Integer, ? extends Thread>", ObjectAnalyser.createVariableDeclaration(p1, true));
Pair<? extends Number, Dummy2> p2 = new Pair<>(new Integer(5), null);
assertEquals("Pair<? extends Integer, ? extends Object>", ObjectAnalyser.createVariableDeclaration(p2, true));
Value2<Integer> v4 = new Value2<>(null);
assertEquals("Value2<? extends Comparable<? extends Comparable & Serializable> & Serializable>", ObjectAnalyser.createVariableDeclaration(v4, true));
Value2<Integer> v5 = new Value2<>(1);
assertEquals("Value2<? extends Integer>", ObjectAnalyser.createVariableDeclaration(v5, true)); // <-- FAILURE
Array<? extends Object> a1 = new Array<>("Foo");
assertEquals("Array<? extends String>", ObjectAnalyser.createVariableDeclaration(a1, true));
}
public static class Dummy {}
public static class Dummy2 extends Dummy {}
public static class Value<T> {
private final T val;
public Value(T val) {
this.val = val;
}
public T getVal() {
return val;
}
}
public static class Value2<V extends Comparable<V> & Serializable> extends Value<V> {
public Value2(V val) {
super(val);
}
}
public static class Pair<A, B> {
private final A a;
private final B b;
public Pair(A a, B b) {
this.a = a;
this.b = b;
}
public A getA() {
return a;
}
public B getB() {
return b;
}
}
public static class Array<T> {
private final T[] values;
public Array(T... values) {
this.values = values;
}
public T[] getValues() {
return values;
}
}
}
My current implementation goes as follows:
public class ObjectAnalyser {
public static String createVariableDeclaration(Object obj, boolean useSimpleNames) {
Class<? extends Object> clazz = obj.getClass();
TypeVariable<? extends Object>[] classTypes = clazz.getTypeParameters();
if (classTypes.length == 0) {
//no generic parameter
if (useSimpleNames) {
return clazz.getSimpleName();
} else {
return clazz.getCanonicalName();
}
}
Type superType = clazz.getGenericSuperclass();
Type[] interfTypes = clazz.getGenericInterfaces();
Type[] superTypes = new Type[interfTypes.length + 1];
superTypes[0] = superType;
System.arraycopy(interfTypes, 0, superTypes, 1, interfTypes.length);
StringBuilder str = new StringBuilder(useSimpleNames ? clazz.getSimpleName() : clazz.getCanonicalName());
str.append("<");
for (int i=0; i<classTypes.length; ++i) {
if (i>0) {
str.append(", ");
}
//Search a Method that returns an object of that type
Class<?> implClass = null;
for (Method m : clazz.getMethods()) {
if (m.getReturnType()==Void.TYPE || m.getParameterTypes().length > 0) {
continue; //no getter
}
//TODO: test if this method is declared in this class (clazz.getDeclaredMethods)
// if not, use the method from the superclass where it is defined
Type retType = m.getGenericReturnType();
Object retObj;
try {
retObj = m.invoke(obj);
implClass = findObject(retObj, classTypes[i], superTypes, retType);
if (implClass != null) {
break;
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
if (implClass != null) {
//we found one
//if (!Modifier.isFinal(implClass.getClass().getModifiers())) {
str.append("? extends ");
//}
str.append(useSimpleNames ? implClass.getSimpleName() : implClass.getCanonicalName());
} else {
//use generic upper bound
Type[] upperBounds = classTypes[i].getBounds();
assert upperBounds.length > 0;
str.append("? extends ");
for (int j=0; j<upperBounds.length; ++j) {
if (j>0) {
str.append(" & ");
}
printType(str, upperBounds[j], useSimpleNames, new HashSet<Type>());
}
}
}
str.append(">");
return str.toString();
}
private static void printType(StringBuilder str, Type type, boolean useSimpleNames, Set<Type> typeStack) {
if (type instanceof Class) {
str.append(useSimpleNames ? ((Class<?>) type).getSimpleName() : ((Class<?>) type).getCanonicalName());
} else if (type instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) type;
printType(str, p.getRawType(), useSimpleNames, typeStack);
if (!typeStack.add(type)) { //to prevent endless loops
return;
}
Type[] actualT = p.getActualTypeArguments();
if (actualT.length > 0) {
str.append("<");
for (int i=0; i<actualT.length; ++i) {
if (i>0) {
str.append(", ");
}
printType(str, actualT[i], useSimpleNames, typeStack);
}
str.append(">");
}
typeStack.remove(type);
} else if (type instanceof GenericArrayType) {
//TODO
} else if (type instanceof WildcardType) {
//TODO
} else if (type instanceof TypeVariable) {
if (typeStack.contains(type)) {
str.append("?");
return;
}
Type[] upperBounds = ((TypeVariable<?>) type).getBounds();
assert upperBounds.length > 0;
str.append("? extends ");
typeStack.add(type); //to prevent endless loops
for (int j=0; j<upperBounds.length; ++j) {
if (j>0) {
str.append(" & ");
}
printType(str, upperBounds[j], useSimpleNames, typeStack);
}
typeStack.remove(type);
}
}
private static Class<? extends Object> findObject(Object methodReturn, TypeVariable<? extends Object> searchedType, Type[] superTypes, Type currentType) {
if (methodReturn == null) {
return null;
}
if (currentType.equals(searchedType)) {
//we found the searched type
//TODO: Does not work if the method comes from the superclass
return methodReturn.getClass();
}
//search in super classes
for (Type t : superTypes) {
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
for (Type t2 : pt.getActualTypeArguments()) {
if (t2 instanceof TypeVariable) {
TypeVariable<? extends Object> tv = (TypeVariable<? extends Object>) t2;
if (tv.getName().equals(searchedType.getName()) && tv.getGenericDeclaration().equals(searchedType.getGenericDeclaration())) {
// return methodReturn.getClass(); //we found it
//TODO: does not detect that this is also the currentType
}
}
}
}
}
return null;
}
}
However, the test case fails in the second last statement. I found out that the problem is that when the generic getter, that is used to retrieve the actual type information, comes from a superclass. Then my code does not detect this in the method findObject.
Does anyone knows a way how to handle these inherited methods correctly?
Thank you
Aucun commentaire:
Enregistrer un commentaire