Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics and java.beans.Introspector

Given the following code skeleton, is it possible to determine that the property foo is in fact of type String?

public class TestIntrospection {
    public static class SuperBean<T> {
        private T foo;

        public T getFoo() { return foo; }
        public void setFoo(T foo) { this.foo = foo; }
    }

    public static class SubBean extends SuperBean<String> {
    }

    public static void main(String[] args) throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(SubBean.class);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor prop : propertyDescriptors) {
            if ("foo".equals(prop.getName())) {
                System.out.printf("%s of %s\n", prop.getName(), prop.getPropertyType());

                Method readMethod = prop.getReadMethod();
                Type returnType = prop.getReadMethod().getGenericReturnType();
                if (returnType instanceof TypeVariable) {
                    TypeVariable t = (TypeVariable) returnType;
                    GenericDeclaration d = t.getGenericDeclaration();
                    System.out.println("TypeVariable : " + t.getName() + " " + t.getBounds()[0]);
                }
            }
        }
    }
}

The actual output is

foo of class java.lang.Object
TypeVariable : T class java.lang.Object

Edit: I should have mentionend that I know about type erasure and that the method is in fact returning an Object on the bytecode level. Nevertheless, the metadata about generic types is available in the class file and can be queried by reflection as in the sample code. Here is another snippet that shows that SubBean in fact has a type parameter of type String:

                Type superClass = SubBean.class.getGenericSuperclass();
                ParameterizedType pt = (ParameterizedType) superClass;
                System.out.println(pt.getActualTypeArguments()[0]);

output:

class java.lang.String

The question then remains, how do I relate this actual type argument to the type variable? If I know that there is only one type parameter this is simple, but I would like this code to work also for beans having multiple generic type parameters.

like image 751
Jörn Horstmann Avatar asked Feb 02 '11 15:02

Jörn Horstmann


People also ask

What is introspector Java?

The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.

What is introspection in JavaBeans?

Introspection is the automatic process of analyzing a bean's design patterns to reveal the bean's properties, events, and methods. This process controls the publishing and discovery of bean operations and properties.

What are customizers in Java?

Customizers give you complete GUI control over bean customization. Customizers are used where property editors are not practical or applicable. Unlike a property editor, which is associated with a property, a customizer is associated with a bean.

What is introspection What are the two ways in which bean introspection can be done?

With the first method, simple naming conventions are used. These allow the introspection mechanisms to infer information about a Bean. In the second way, an additional class that extends the BeanInfo interface is provided that explicitly supplies this information. Both approaches are examined here.


1 Answers

As long as the object's runtime class determines the value of the type parameter, you can infer its actual value by recursively replacing formal type parameters by the actual ones obtained from Class.getGenericSuperClass():

class Substitution extends HashMap<String, TypeExpr> {
    Substitution(TypeVariable[] formals, TypeExpr[] actuals) {
        for (int i = 0; i < actuals.length; i++) {
            put(formals[i].getName(),actuals[i]);
        }
    }

}

abstract class TypeExpr {
    abstract TypeExpr apply(Substitution s);

    public abstract String toString(); 

    static TypeExpr from(Type type) {
        if (type instanceof TypeVariable) {
            return new TypeVar((TypeVariable) type);
        } else if (type instanceof Class) {
            return new ClassType((Class) type);
        } else if (type instanceof ParameterizedType) {
            return new ClassType((ParameterizedType) type);
        } else if (type instanceof GenericArrayType) {
            return new ArrayType((GenericArrayType) type);
        } else if (type instanceof WildcardType) {
            return new WildcardTypeExpr((WildcardType) type);
        }
        throw new IllegalArgumentException(type.toString());
    }

    static TypeExpr[] from(Type[] types) {
        TypeExpr[] t = new TypeExpr[types.length];
        for (int i = 0; i < types.length; i++) {
            t[i] = from(types[i]);
        }
        return t;
    }

    static TypeExpr[] apply(TypeExpr[] types, Substitution s) {
        TypeExpr[] t = new TypeExpr[types.length];
        for (int i = 0; i < types.length; i++) {
            t[i] = types[i].apply(s);
        }
        return t;
    }

    static void append(StringBuilder sb, String sep, Object[] os) {
        String s = "";
        for (Object o : os) {
            sb.append(s);
            s = sep;
            sb.append(o);
        }
    }
}

class TypeVar extends TypeExpr {
    final String name;

    public TypeVar(String name) {
        this.name = name;
    }

    public TypeVar(TypeVariable var) {
        name = var.getName();
    }

    @Override
    public String toString() {
        return name;
    }

    @Override
    TypeExpr apply(Substitution s) {
        TypeExpr e = s.get(name);
        return e == null ? this : e;
    }
}

class ClassType extends TypeExpr {
    final Class clazz;
    final TypeExpr[] arguments; // empty if the class is not generic

    public ClassType(Class clazz, TypeExpr[] arguments) {
        this.clazz = clazz;
        this.arguments = arguments;
    }

    public ClassType(Class clazz) {
        this.clazz = clazz;
        arguments = from(clazz.getTypeParameters());
    }

    @Override
    public String toString() {
        String name = clazz.getSimpleName();
        if (arguments.length == 0) {
            return name;
        }

        StringBuilder sb = new StringBuilder();
        sb.append(name);
        sb.append("<");
        append(sb, ", ", arguments);
        sb.append(">");
        return sb.toString();
    }

    public ClassType(ParameterizedType pt) {
        clazz = (Class) pt.getRawType();
        Type[] args = pt.getActualTypeArguments();
        arguments = TypeExpr.from(args);
    }

    @Override
    ClassType apply(Substitution s) {
        return new ClassType(clazz, apply(arguments, s));
    }
}

class ArrayType extends TypeExpr {
    final TypeExpr componentType;

    public ArrayType(TypeExpr componentType) {
        this.componentType = componentType;
    }

    public ArrayType(GenericArrayType gat) {
        this.componentType = TypeExpr.from(gat.getGenericComponentType());
    }

    @Override
    public String toString() {
        return componentType + "[]";
    }

    @Override
    TypeExpr apply(Substitution s) {
        return new ArrayType(componentType.apply(s));
    }
}

class WildcardTypeExpr extends TypeExpr {
    final TypeExpr[] lowerBounds;
    final TypeExpr[] upperBounds;

    public WildcardTypeExpr(TypeExpr[] lowerBounds, TypeExpr[] upperBounds) {
        this.lowerBounds = lowerBounds;
        this.upperBounds = upperBounds;
    }

    WildcardTypeExpr(WildcardType wct) {
        lowerBounds = from(wct.getLowerBounds());
        upperBounds = from(wct.getUpperBounds());
    }

    @Override
    TypeExpr apply(Substitution s) {
        return new WildcardTypeExpr(
            apply(lowerBounds, s), 
            apply(upperBounds, s)
        );
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("?");
        if (lowerBounds.length > 0) {
            sb.append(" super ");
            append(sb, " & ", lowerBounds);
        }
        if (upperBounds.length > 0) {
            sb.append(" extends ");
            append(sb, " & ", upperBounds);
        }
        return sb.toString();
    }
}

public class Test {

    /**
     * @return {@code superClazz}, with the replaced type parameters it has for
     *         instances of {@code ct}, or {@code null}, if {@code superClazz}
     *         is not a super class or interface of {@code ct}
     */
    static ClassType getSuperClassType(ClassType ct, Class superClazz) {
        if (ct.clazz == superClazz) {
            return ct;
        }

        Substitution sub = new Substitution(ct.clazz.getTypeParameters(), ct.arguments);

        Type gsc = ct.clazz.getGenericSuperclass();
        if (gsc != null) {
            ClassType sct = (ClassType) TypeExpr.from(gsc);
            sct = sct.apply(sub);
            ClassType result = getSuperClassType(sct, superClazz);
            if (result != null) {
                return result;
            }
        }

        for (Type gi : ct.clazz.getGenericInterfaces()) {
            ClassType st = (ClassType) TypeExpr.from(gi);
            st = st.apply(sub);
            ClassType result = getSuperClassType(st, superClazz);
            if (result != null) {
                return result;
            }

        }
        return null;
    }

    public static ClassType getSuperClassType(Class clazz, Class superClazz) {
        return getSuperClassType((ClassType) TypeExpr.from(clazz), superClazz);
    }

Test code:

    public static void check(Class c, Class sc, String expected) {
        String actual = getSuperClassType(c, sc).toString();
        if (!actual.equals(expected)) {
            throw new AssertionError(actual + " != " + expected);
        }
    }

    public static void main(String[] args) {
        check(Substitution.class, Map.class, "Map<String, TypeExpr>");
        check(HashMap.class, Map.class, "Map<K, V>");
        check(Bar.class, Foo.class, "Foo<List<? extends String[]>>");
    }
}

interface Foo<X> {

}
class SuperBar<X, Y> implements Foo<List<? extends Y[]>> {

}

class Bar<X> extends SuperBar<X, String> { }

If on the other hand the class does not determine the value of the type parameter, you'll have to extend your bean to retain the class object for the actual type parameter at runtime by other means, e.g. by doing:

class Super<T> {
    final Class<T> clazz;

    T foo;

    Super(Class<T> clazz) {
        this.clazz = clazz;
    }

    public T getFoo() {
        return foo;
    }

    public T setFoo() {
        this.foo = foo;
    }
}
like image 100
meriton Avatar answered Sep 20 '22 10:09

meriton