spoon
spoon copied to clipboard
Generics Type Variable Resolution
consider the following
class Generic <T> {
public T field;
}
class UseGeneric extends Generic<String> {
public Generic<Integer> f;
}
is there a way, via TypeFactory (reflection), to obtain UseGeneric.field
with T
resolved to the correct type (String)?
the intent is for type resolution of Generics without knowing in advanced what the Type Variable actually is, but being infered from some root class such as UseGeneric
eg
UseGeneric
would resolve
UseGeneric u = new UseGeneric();
u.field == String
u.f.field == Integer
either by supplying UseGeneric.class
or by converting UseGeneric.class
into abstract source code (with no decompilation of method bodies as we do not need them) which is then analyzed by spoon's java code type analyser to obtain correct types
the entire class and dependant classes/types can be converted into abstract source code via
CtType<Object> objectCtType = new TypeFactory().get(UseGeneric.class);
List<? extends CtType<?>> collect = objectCtType.getReferencedTypes().parallelStream()
// reduce generics to single declaration
// toString replaces Foo<String> with Foo<T> leading to multiple declarations of class Foo<T>
.map(ctTypeReference -> ctTypeReference.getTypeDeclaration().getReference())
.distinct()
.map(CtTypeReference::getTypeDeclaration)
.collect(Collectors.toList());
for (CtType<?> ctType : collect) {
println("TYPE:\n" + ctType);
}
which outputs
(without the map(CtTypeReference::getTypeDeclaration)
)
TYPE:
java.lang.Float
TYPE:
java.lang.Integer
TYPE:
void
TYPE:
java.lang.String
TYPE:
smallville7123.Main.UseGeneric
TYPE:
smallville7123.Main.Generic
(with the map(CtTypeReference::getTypeDeclaration)
)
TYPE:
@jdk.internal.ValueBased
public final class Float extends java.lang.Number implements java.lang.constant.ConstantDesc , java.lang.Comparable<java.lang.Float> , java.lang.constant.Constable {
@java.lang.Deprecated(forRemoval = true, since = "9")
public Float(java.lang.String arg0) throws java.lang.NumberFormatException {
}
@java.lang.Deprecated(forRemoval = true, since = "9")
public Float(double arg0) {
}
// ...
public boolean isInfinite() {
}
public static boolean isInfinite(float arg0) {
}
public static boolean isFinite(float arg0) {
}
public static final float POSITIVE_INFINITY = InfinityF;
public static final float NEGATIVE_INFINITY = -InfinityF;
public static final float NaN = NaNF;
public static final float MAX_VALUE = 3.4028235E38F;
public static final float MIN_NORMAL = 1.17549435E-38F;
public static final float MIN_VALUE = 1.4E-45F;
public static final int MAX_EXPONENT = 127;
public static final int MIN_EXPONENT = -126;
public static final int SIZE = 32;
public static final int BYTES = 4;
public static final java.lang.Class<java.lang.Float> TYPE;
private final float value;
private static final long serialVersionUID;
}
TYPE:
@jdk.internal.ValueBased
public final class Integer extends java.lang.Number implements java.lang.constant.ConstantDesc , java.lang.Comparable<java.lang.Integer> , java.lang.constant.Constable {
@java.lang.Deprecated(forRemoval = true, since = "9")
public Integer(java.lang.String arg0) throws java.lang.NumberFormatException {
}
@java.lang.Deprecated(forRemoval = true, since = "9")
public Integer(int arg0) {
}
@jdk.internal.vm.annotation.IntrinsicCandidate
public static int numberOfLeadingZeros(int arg0) {
}
// and so on
unfortunately it does not correctly stub it (as return types require actual returns, such as return null
or return 0
)
but still, it should be sufficent
and we can collect all types including superclasses and interfaces by searching the types recursively
private static List<?> collectTypesRecursive(Class<?> clazz) {
CtType<Object> objectCtType = getCtType(clazz);
List<CtTypeReference<?>> objects = new ArrayList<>();
collectTypesRecursive(objectCtType, objects, 0);
return objects.stream().map(CtTypeReference::getTypeDeclaration).collect(Collectors.toList());
}
private static void collectTypesRecursive(CtType<?> objectCtType, List<CtTypeReference<?>> collected, int depth) {
if (objectCtType == null) return;
Set<CtTypeReference<?>> collect = objectCtType.getReferencedTypes();
// remove already collected
Set<CtTypeReference<?>> tmp = new HashSet<>();
for (CtTypeReference<?> ctTypeReference : collect) {
boolean f = false;
for (CtTypeReference<?> typeReference : collected) {
String targ = ctTypeReference.getQualifiedName();
String coll = typeReference.getQualifiedName();
if (targ.contentEquals(coll)) {
f = true;
}
}
if (!f) {
tmp.add(ctTypeReference);
}
}
// remove duplicates
Set<CtTypeReference<?>> tmp1 = new HashSet<>();
for (CtTypeReference<?> ctTypeReference : tmp) {
boolean f = false;
for (CtTypeReference<?> typeReference : tmp1) {
String targ = ctTypeReference.getQualifiedName();
String coll = typeReference.getQualifiedName();
if (targ.contentEquals(coll)) {
f = true;
}
}
if (!f) {
tmp1.add(ctTypeReference);
}
}
collect = tmp1;
// collect whatever is left
if (collect.size() == 0) {
return;
}
collected.addAll(collect);
println("COLLECTING: " + objectCtType.getReference().getQualifiedName());
for (CtTypeReference<?> ctType : collect) {
collectTypesRecursive(ctType.getTypeDeclaration(), collected, depth+1);
}
}
private static CtType<Object> getCtType(Class<?> clazz) {
return new TypeFactory().get(clazz);
}