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.
The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.
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.
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.
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.
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With