From 5585dd162e57e472eb640c8c324b164f7348dab8 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 13 Dec 2019 16:22:42 +0200 Subject: [PATCH] Introduce handling of more advanced generics cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stéphane Épardaud --- .../quarkus/deployment/util/JandexUtil.java | 104 +++++++++++++----- .../deployment/util/JandexUtilTest.java | 83 ++++++++++++++ 2 files changed, 159 insertions(+), 28 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java index aa6192c89a905..ae4fd3fe1aa42 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java @@ -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; @@ -63,46 +64,80 @@ public static List resolveTypeParameters(DotName input, DotName target, In if (recursiveMatchResult == null) { return Collections.emptyList(); } + final List result = resolveTypeParameters(recursiveMatchResult); + + if (result.size() != recursiveMatchResult.argumentsOfMatch.size()) { + throw new IllegalStateException("Unable to properly match generic types"); + } + + return result; + } + + private static List resolveTypeParameters(RecursiveMatchResult recursiveMatchResult) { final List 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 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) { @@ -172,6 +207,19 @@ private static List 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 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; } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java index dd164739ffdec..4bb590cbbacb1 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java @@ -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() + ""); + }); + assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(CompositeRepo2.class.getName()), DotName.createSimple(Repo2.class.getName()), + index)).hasOnlyOneElementSatisfying(t -> { + assertThat(t.toString()).isEqualTo(Repo.class.getName() + ""); + }); + } + + @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 { } @@ -246,6 +275,60 @@ public static class ExtendsMultipleT1Impl implements MultipleT1 { public static class SingleFromInterfaceAndSuperClass extends SingleImpl implements Single { } + public interface Repo { + } + + public interface Repo2 { + } + + public static class DirectRepo implements Repo { + } + + public static class IndirectRepo extends DirectRepo { + } + + public static class GenericRepo implements Repo { + } + + public static class IndirectGenericRepo extends GenericRepo { + } + + public static class GenericArrayRepo implements Repo { + } + + public static class ArrayRepo extends GenericArrayRepo { + } + + public static class GenericCompositeRepo implements Repo> { + } + + public static class GenericCompositeRepo2 implements Repo2> { + } + + public static class CompositeRepo extends GenericCompositeRepo { + } + + public static class CompositeRepo2 extends GenericCompositeRepo2 { + } + + public static class BoundedRepo implements Repo { + } + + public static class ErasedRepo1 extends BoundedRepo { + } + + public interface A { + } + + public interface B { + } + + public static class MultiBoundedRepo implements Repo { + } + + public static class ErasedRepo2 extends MultiBoundedRepo { + } + private static Index index(Class... classes) { Indexer indexer = new Indexer(); for (Class clazz : classes) {