Skip to content

Commit

Permalink
Introduce handling of more advanced generics cases
Browse files Browse the repository at this point in the history
Co-authored-by: Stéphane Épardaud <[email protected]>
  • Loading branch information
geoand and FroMage committed Dec 13, 2019
1 parent 34a5deb commit 5585dd1
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.LinkedList;
import java.util.List;

import org.jboss.jandex.ArrayType;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
Expand Down Expand Up @@ -63,46 +64,80 @@ public static List<Type> resolveTypeParameters(DotName input, DotName target, In
if (recursiveMatchResult == null) {
return Collections.emptyList();
}
final List<Type> result = resolveTypeParameters(recursiveMatchResult);

if (result.size() != recursiveMatchResult.argumentsOfMatch.size()) {
throw new IllegalStateException("Unable to properly match generic types");
}

return result;
}

private static List<Type> resolveTypeParameters(RecursiveMatchResult recursiveMatchResult) {
final List<Type> result = new ArrayList<>();
for (int i = 0; i < recursiveMatchResult.argumentsOfMatch.size(); i++) {
final Type argument = recursiveMatchResult.argumentsOfMatch.get(i);
if (isDirectlyHandledType(argument)) {
if (argument instanceof ClassType) {
result.add(argument);
} else if (argument instanceof TypeVariable) {

String unmatchedParameter = argument.asTypeVariable().identifier();

for (RecursiveMatchLevel recursiveMatchLevel : recursiveMatchResult.recursiveMatchLevels) {
Type matchingCapturedType = null;
for (int j = 0; j < recursiveMatchLevel.definitions.size(); j++) {
final Type definition = recursiveMatchLevel.definitions.get(j);
if ((definition instanceof TypeVariable)
&& unmatchedParameter.equals(definition.asTypeVariable().identifier())) {
matchingCapturedType = recursiveMatchLevel.captures.get(j);
break; // out of the definitions loop
}
}
// at this point their MUST be a match, if there isn't we have made some mistake in the implementation
if (matchingCapturedType == null) {
throw new IllegalStateException("Error retrieving generic types");
} else if (argument instanceof ParameterizedType) {
ParameterizedType argumentParameterizedType = argument.asParameterizedType();
List<Type> resolvedTypes = new ArrayList<>(argumentParameterizedType.arguments().size());
for (Type argType : argumentParameterizedType.arguments()) {
if (argType instanceof TypeVariable) {
resolvedTypes.add(findTypeFromTypeVariable(recursiveMatchResult, argType.asTypeVariable()));
} else {
resolvedTypes.add(argType);
}
if (isDirectlyHandledType(matchingCapturedType)) {
result.add(matchingCapturedType);
break; // out of level loop
}
if (matchingCapturedType instanceof TypeVariable) {
// continue the search in the lower levels using the new name
unmatchedParameter = matchingCapturedType.asTypeVariable().identifier();
}
result.add(ParameterizedType.create(argumentParameterizedType.name(), resolvedTypes.toArray(new Type[0]),
argumentParameterizedType.owner()));
} else if (argument instanceof TypeVariable) {
Type typeFromTypeVariable = findTypeFromTypeVariable(recursiveMatchResult, argument.asTypeVariable());
if (typeFromTypeVariable != null) {
result.add(typeFromTypeVariable);
}
} else if (argument instanceof ArrayType) {
ArrayType argumentAsArrayType = argument.asArrayType();
Type componentType = argumentAsArrayType.component();
if (componentType instanceof TypeVariable) { // should always be the case
Type typeFromTypeVariable = findTypeFromTypeVariable(recursiveMatchResult, componentType.asTypeVariable());
if (typeFromTypeVariable != null) {
result.add(ArrayType.create(typeFromTypeVariable, argumentAsArrayType.dimensions()));
}
}
}
}
return result;
}

if (result.size() != recursiveMatchResult.argumentsOfMatch.size()) {
throw new IllegalStateException("Unable to properly match generic types");
private static Type findTypeFromTypeVariable(RecursiveMatchResult recursiveMatchResult, TypeVariable typeVariable) {
String unmatchedParameter = typeVariable.identifier();

for (RecursiveMatchLevel recursiveMatchLevel : recursiveMatchResult.recursiveMatchLevels) {
Type matchingCapturedType = null;
for (int j = 0; j < recursiveMatchLevel.definitions.size(); j++) {
final Type definition = recursiveMatchLevel.definitions.get(j);
if ((definition instanceof TypeVariable)
&& unmatchedParameter.equals(definition.asTypeVariable().identifier())) {
matchingCapturedType = recursiveMatchLevel.captures.get(j);
break; // out of the definitions loop
}
}
// at this point their MUST be a match, if there isn't we have made some mistake in the implementation
if (matchingCapturedType == null) {
throw new IllegalStateException("Error retrieving generic types");
}
if (isDirectlyHandledType(matchingCapturedType)) {
// search is over
return matchingCapturedType;
}
if (matchingCapturedType instanceof TypeVariable) {
// continue the search in the lower levels using the new name
unmatchedParameter = matchingCapturedType.asTypeVariable().identifier();
}
}

return result;
return null;
}

private static boolean isDirectlyHandledType(Type matchingCapturedType) {
Expand Down Expand Up @@ -172,6 +207,19 @@ private static List<RecursiveMatchLevel> addArgumentIfNeeded(Type type, IndexVie
final RecursiveMatchLevel recursiveMatchLevel = new RecursiveMatchLevel(classInfo.typeParameters(),
type.asParameterizedType().arguments());
newVisitedTypes.add(0, recursiveMatchLevel);
} else if (type instanceof ClassType) {
// we need to check if the class contains any bounds
final ClassInfo classInfo = index.getClassByName(type.name());
if ((classInfo != null) && !classInfo.typeParameters().isEmpty()) {
// in this case we just use the first bound as the capture type
// TODO do we need something more sophisticated than this?
List<Type> captures = new ArrayList<>(classInfo.typeParameters().size());
for (TypeVariable typeParameter : classInfo.typeParameters()) {
captures.add(typeParameter.bounds().get(0));
}
final RecursiveMatchLevel recursiveMatchLevel = new RecursiveMatchLevel(classInfo.typeParameters(), captures);
newVisitedTypes.add(0, recursiveMatchLevel);
}
}
return newVisitedTypes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,35 @@ public void testExtendsAbstractClass() {
abstractSingle, index)).extracting("name").containsOnly(INTEGER);
}

@Test
public void testArrayGenerics() {
final Index index = index(Repo.class, ArrayRepo.class, GenericArrayRepo.class);
assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ArrayRepo.class.getName()), DotName.createSimple(Repo.class.getName()),
index)).extracting("name").containsOnly(DotName.createSimple(Integer[].class.getName()));
}

@Test
public void testCompositeGenerics() {
final Index index = index(Repo.class, Repo2.class, CompositeRepo.class, CompositeRepo2.class, GenericCompositeRepo.class, GenericCompositeRepo2.class);
assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(CompositeRepo.class.getName()), DotName.createSimple(Repo.class.getName()),
index)).hasOnlyOneElementSatisfying(t -> {
assertThat(t.toString()).isEqualTo(Repo.class.getName() + "<java.lang.Integer>");
});
assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(CompositeRepo2.class.getName()), DotName.createSimple(Repo2.class.getName()),
index)).hasOnlyOneElementSatisfying(t -> {
assertThat(t.toString()).isEqualTo(Repo.class.getName() + "<java.lang.Integer>");
});
}

@Test
public void testErasedGenerics() {
final Index index = index(Repo.class, BoundedRepo.class, ErasedRepo1.class, MultiBoundedRepo.class, ErasedRepo2.class, A.class);
assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ErasedRepo1.class.getName()), DotName.createSimple(Repo.class.getName()),
index)).extracting("name").containsOnly(DotName.createSimple(A.class.getName()));
assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ErasedRepo2.class.getName()), DotName.createSimple(Repo.class.getName()),
index)).extracting("name").containsOnly(DotName.createSimple(A.class.getName()));
}

public interface Single<T> {
}

Expand Down Expand Up @@ -246,6 +275,60 @@ public static class ExtendsMultipleT1Impl implements MultipleT1<String> {
public static class SingleFromInterfaceAndSuperClass<W> extends SingleImpl implements Single<String> {
}

public interface Repo<T> {
}

public interface Repo2<T> {
}

public static class DirectRepo implements Repo<Integer> {
}

public static class IndirectRepo extends DirectRepo {
}

public static class GenericRepo<X> implements Repo<X> {
}

public static class IndirectGenericRepo extends GenericRepo<Integer> {
}

public static class GenericArrayRepo<X> implements Repo<X[]> {
}

public static class ArrayRepo extends GenericArrayRepo<Integer> {
}

public static class GenericCompositeRepo<X> implements Repo<Repo<X>> {
}

public static class GenericCompositeRepo2<X> implements Repo2<Repo<X>> {
}

public static class CompositeRepo extends GenericCompositeRepo<Integer> {
}

public static class CompositeRepo2 extends GenericCompositeRepo2<Integer> {
}

public static class BoundedRepo<X extends A> implements Repo<X> {
}

public static class ErasedRepo1 extends BoundedRepo {
}

public interface A {
}

public interface B {
}

public static class MultiBoundedRepo<X extends A & B> implements Repo<X> {
}

public static class ErasedRepo2 extends MultiBoundedRepo {
}

private static Index index(Class<?>... classes) {
Indexer indexer = new Indexer();
for (Class<?> clazz : classes) {
Expand Down

0 comments on commit 5585dd1

Please sign in to comment.