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..3a3294c097fae 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 @@ -50,121 +50,113 @@ public void testAbstractSingle() { @Test public void testSimplestImpl() { final Index index = index(Single.class, SingleImpl.class); - final DotName impl = DotName.createSimple(SingleImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index); - assertThat(result).extracting("name").containsOnly(STRING); + checkRepoArg(index, SingleImpl.class, Single.class, String.class); } @Test public void testSimplestImplWithBound() { final Index index = index(SingleWithBound.class, SingleWithBoundImpl.class); - final DotName impl = DotName.createSimple(SingleWithBoundImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, - DotName.createSimple(SingleWithBound.class.getName()), index); - assertThat(result).extracting("name").containsOnly(DotName.createSimple(List.class.getName())); + checkRepoArg(index, SingleWithBoundImpl.class, SingleWithBound.class, List.class); } @Test public void testSimpleImplMultipleParams() { final Index index = index(Multiple.class, MultipleImpl.class); - final DotName impl = DotName.createSimple(MultipleImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, MULTIPLE, index); - assertThat(result).extracting("name").containsExactly(INTEGER, STRING); + checkRepoArg(index, MultipleImpl.class, Multiple.class, Integer.class, String.class); } @Test public void testInverseParameterNames() { final Index index = index(Multiple.class, InverseMultiple.class, InverseMultipleImpl.class); - final DotName impl = DotName.createSimple(InverseMultipleImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, MULTIPLE, index); - assertThat(result).extracting("name").containsExactly(DOUBLE, INTEGER); + checkRepoArg(index, InverseMultipleImpl.class, Multiple.class, Double.class, Integer.class); } @Test public void testImplExtendsSimplestImplementation() { final Index index = index(Single.class, SingleImpl.class, SingleImplImpl.class); - final DotName impl = DotName.createSimple(SingleImplImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index); - assertThat(result).extracting("name").containsOnly(STRING); + checkRepoArg(index, SingleImplImpl.class, Single.class, String.class); } @Test public void testImplementationOfInterfaceThatExtendsSimpleWithoutParam() { final Index index = index(Single.class, ExtendsSimpleNoParam.class, ExtendsSimpleNoParamImpl.class); - final DotName impl = DotName.createSimple(ExtendsSimpleNoParamImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index); - assertThat(result).extracting("name").containsOnly(DOUBLE); + checkRepoArg(index, ExtendsSimpleNoParamImpl.class, Single.class, Double.class); } @Test public void testImplExtendsImplOfInterfaceThatExtendsSimpleWithoutParams() { final Index index = index(Single.class, ExtendsSimpleNoParam.class, ExtendsSimpleNoParamImpl.class, ExtendsSimpleNoParamImplImpl.class); - final DotName impl = DotName.createSimple(ExtendsSimpleNoParamImplImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index); - assertThat(result).extracting("name").containsOnly(DOUBLE); + checkRepoArg(index, ExtendsSimpleNoParamImplImpl.class, Single.class, Double.class); } @Test public void testImplOfInterfaceThatExtendsSimpleWithParam() { final Index index = index(Single.class, ExtendsSimpleWithParam.class, ExtendsSimpleWithParamImpl.class); - final DotName impl = DotName.createSimple(ExtendsSimpleWithParamImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index); - assertThat(result).extracting("name").containsOnly(INTEGER); + checkRepoArg(index, ExtendsSimpleWithParamImpl.class, Single.class, Integer.class); } @Test public void testImplOfInterfaceThatExtendsSimpleWithParamInMultipleLevels() { final Index index = index(Single.class, ExtendsSimpleWithParam.class, ExtendsExtendsSimpleWithParam.class, ExtendsExtendsSimpleWithParamImpl.class); - final DotName impl = DotName.createSimple(ExtendsExtendsSimpleWithParamImpl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index); - assertThat(result).extracting("name").containsOnly(DOUBLE); + checkRepoArg(index, ExtendsExtendsSimpleWithParamImpl.class, Single.class, Double.class); } @Test public void testImplOfInterfaceThatExtendsSimpleWithGenericParamInMultipleLevels() { final Index index = index(Single.class, ExtendsSimpleWithParam.class, ExtendsExtendsSimpleWithParam.class, ExtendsExtendsSimpleGenericParam.class); - final DotName impl = DotName.createSimple(ExtendsExtendsSimpleGenericParam.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index); - assertThat(result).extracting("name").containsOnly(DotName.createSimple(Map.class.getName())); + checkRepoArg(index, ExtendsExtendsSimpleGenericParam.class, Single.class, Map.class); } @Test public void testImplOfMultipleWithParamsInDifferentLevels() { final Index index = index(Multiple.class, MultipleT1.class, ExtendsMultipleT1Impl.class); - final DotName impl = DotName.createSimple(ExtendsMultipleT1Impl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, MULTIPLE, index); - assertThat(result).extracting("name").containsOnly(INTEGER, STRING); + checkRepoArg(index, ExtendsMultipleT1Impl.class, Multiple.class, Integer.class, String.class); } @Test public void testImplOfAbstractMultipleWithParamsInDifferentLevels() { final Index index = index(Multiple.class, MultipleT1.class, AbstractMultipleT1Impl.class, ExtendsAbstractMultipleT1Impl.class); - final DotName impl = DotName.createSimple(ExtendsAbstractMultipleT1Impl.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, MULTIPLE, index); - assertThat(result).extracting("name").containsOnly(INTEGER, STRING); + checkRepoArg(index, ExtendsAbstractMultipleT1Impl.class, Multiple.class, Integer.class, String.class); } @Test public void testMultiplePathsToSingle() { final Index index = index(Single.class, SingleImpl.class, SingleFromInterfaceAndSuperClass.class); - final DotName impl = DotName.createSimple(SingleFromInterfaceAndSuperClass.class.getName()); - final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index); - assertThat(result).extracting("name").containsOnly(STRING); + checkRepoArg(index, SingleFromInterfaceAndSuperClass.class, Single.class, String.class); } @Test public void testExtendsAbstractClass() { - final DotName abstractSingle = DotName.createSimple(AbstractSingle.class.getName()); final Index index = index(Single.class, AbstractSingle.class, AbstractSingleImpl.class, ExtendsAbstractSingleImpl.class); - assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(AbstractSingleImpl.class.getName()), abstractSingle, - index)).extracting("name").containsOnly(INTEGER); - assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ExtendsAbstractSingleImpl.class.getName()), - abstractSingle, index)).extracting("name").containsOnly(INTEGER); + checkRepoArg(index, AbstractSingleImpl.class, AbstractSingle.class, Integer.class); + checkRepoArg(index, ExtendsAbstractSingleImpl.class, AbstractSingle.class, Integer.class); + } + + @Test + public void testArrayGenerics() { + final Index index = index(Repo.class, ArrayRepo.class, GenericArrayRepo.class); + checkRepoArg(index, ArrayRepo.class, Repo.class, Integer[].class); + } + + @Test + public void testCompositeGenerics() { + final Index index = index(Repo.class, Repo2.class, CompositeRepo.class, CompositeRepo2.class, + GenericCompositeRepo.class, GenericCompositeRepo2.class); + checkRepoArg(index, CompositeRepo.class, Repo.class, Repo.class.getName() + ""); + checkRepoArg(index, CompositeRepo2.class, Repo2.class, Repo.class.getName() + ""); + } + + @Test + public void testErasedGenerics() { + final Index index = index(Repo.class, BoundedRepo.class, ErasedRepo1.class, MultiBoundedRepo.class, ErasedRepo2.class, + A.class); + checkRepoArg(index, ErasedRepo1.class, Repo.class, A.class); + checkRepoArg(index, ErasedRepo2.class, Repo.class, A.class); } public interface Single { @@ -246,6 +238,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) { @@ -261,4 +307,32 @@ private static Index index(Class... classes) { return indexer.complete(); } + private void checkRepoArg(Index index, Class baseClass, Class soughtClass, Class expectedArg) { + List args = JandexUtil.resolveTypeParameters(name(baseClass), name(soughtClass), + index); + assertThat(args).extracting("name").containsOnly(name(expectedArg)); + } + + private void checkRepoArg(Index index, Class baseClass, Class soughtClass, Class... expectedArgs) { + List args = JandexUtil.resolveTypeParameters(name(baseClass), name(soughtClass), + index); + Object[] expectedArgNames = new Object[expectedArgs.length]; + for (int i = 0; i < expectedArgs.length; i++) { + expectedArgNames[i] = name(expectedArgs[i]); + } + assertThat(args).extracting("name").containsOnly(expectedArgNames); + } + + private void checkRepoArg(Index index, Class baseClass, Class soughtClass, String expectedArg) { + List args = JandexUtil.resolveTypeParameters(name(baseClass), name(soughtClass), + index); + assertThat(args).hasOnlyOneElementSatisfying(t -> { + assertThat(t.toString()).isEqualTo(expectedArg); + }); + } + + private static DotName name(Class klass) { + return DotName.createSimple(klass.getName()); + } + }