Skip to content

Commit

Permalink
Merge pull request quarkusio#6161 from geoand/more-generics
Browse files Browse the repository at this point in the history
Introduce handling of more advanced generics cases in JandexUtil
  • Loading branch information
FroMage authored Dec 16, 2019
2 parents 228f446 + 1bb4b9e commit 535e033
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 73 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 @@ -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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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<Type> 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() + "<java.lang.Integer>");
checkRepoArg(index, CompositeRepo2.class, Repo2.class, 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);
checkRepoArg(index, ErasedRepo1.class, Repo.class, A.class);
checkRepoArg(index, ErasedRepo2.class, Repo.class, A.class);
}

public interface Single<T> {
Expand Down Expand Up @@ -246,6 +238,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 All @@ -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<Type> 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<Type> 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<Type> 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());
}

}

0 comments on commit 535e033

Please sign in to comment.