Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,19 @@ private static Map<TypeVariable<?>, Type> getTypeArguments(final ParameterizedTy
return typeVarAssigns;
}
// walk the inheritance hierarchy until the target class is reached
return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns);
final Type parentType = getClosestParentType(cls, toClass);
if (parentType instanceof ParameterizedType) {
final ParameterizedType parameterizedParentType = (ParameterizedType) parentType;
final Type[] parentTypeArgs = parameterizedParentType.getActualTypeArguments().clone();
for (int i = 0; i < parentTypeArgs.length; i++) {
final Type unrolled = unrollVariables(typeVarAssigns, parentTypeArgs[i]);
if (unrolled != null) {
parentTypeArgs[i] = unrolled;
}
}
return getTypeArguments(parameterizeWithOwner(parameterizedParentType.getOwnerType(), (Class<?>) parameterizedParentType.getRawType(), parentTypeArgs), toClass, typeVarAssigns);
}
return getTypeArguments(parentType, toClass, typeVarAssigns);
}

/**
Expand Down Expand Up @@ -1672,9 +1684,17 @@ public static Type unrollVariables(Map<TypeVariable<?>, Type> typeArguments, fin
if (typeArguments == null) {
typeArguments = Collections.emptyMap();
}
return unrollVariables(typeArguments, type, new HashSet<>());
}

private static Type unrollVariables(final Map<TypeVariable<?>, Type> typeArguments, final Type type, final Set<TypeVariable<?>> visited) {
if (containsTypeVariables(type)) {
if (type instanceof TypeVariable<?>) {
return unrollVariables(typeArguments, typeArguments.get(type));
final TypeVariable<?> var = (TypeVariable<?>) type;
if (!visited.add(var)) {
return var;
}
return unrollVariables(typeArguments, typeArguments.get(type), visited);
}
if (type instanceof ParameterizedType) {
final ParameterizedType p = (ParameterizedType) type;
Expand All @@ -1685,9 +1705,9 @@ public static Type unrollVariables(Map<TypeVariable<?>, Type> typeArguments, fin
parameterizedTypeArguments = new HashMap<>(typeArguments);
parameterizedTypeArguments.putAll(getTypeArguments(p));
}
final Type[] args = p.getActualTypeArguments();
final Type[] args = p.getActualTypeArguments().clone();
for (int i = 0; i < args.length; i++) {
final Type unrolled = unrollVariables(parameterizedTypeArguments, args[i]);
final Type unrolled = unrollVariables(parameterizedTypeArguments, args[i], visited);
if (unrolled != null) {
args[i] = unrolled;
}
Expand Down
44 changes: 44 additions & 0 deletions src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
Expand Down Expand Up @@ -368,6 +369,49 @@ void test_LANG_1702() throws NoSuchMethodException, SecurityException {
final Type unrolledType = TypeUtils.unrollVariables(typeArguments, type);
}

static class MyException extends Exception implements Iterable<Throwable> {

private static final long serialVersionUID = 1L;

@Override
public Iterator<Throwable> iterator() {
return null;
}
}

static class MyNonTransientException extends MyException {
private static final long serialVersionUID = 1L;
}

interface MyComparator<T> {
}

static class MyOrdering<T> implements MyComparator<T> {
}

static class LexOrdering<T> extends MyOrdering<Iterable<T>> implements Serializable {
private static final long serialVersionUID = 1L;
}

/**
* Tests that a parameterized type with a nested generic argument is correctly
* evaluated for assignability to a wildcard lower-bounded type.
*
* @see <a href="https://issues.apache.org/jira/browse/LANG-1700">LANG-1700</a>
*/
@Test
public void test_LANG_1700() {
final ParameterizedType from = TypeUtils.parameterize(LexOrdering.class, MyNonTransientException.class);
// MyComparator<? super MyNonTransientException>
final ParameterizedType to = TypeUtils.parameterize(MyComparator.class,
TypeUtils.wildcardType().withLowerBounds(MyNonTransientException.class).build());
// This is MyComparator<Iterable<MyNonTransientException>>
// It should NOT be assignable to MyComparator<? super MyNonTransientException>
// because Iterable<MyNonTransientException> is NOT a supertype of MyNonTransientException
assertFalse(TypeUtils.isAssignable(from, to),
() -> String.format("Type %s should not be assignable to %s", TypeUtils.toString(from), TypeUtils.toString(to)));
}

@Test
void testContainsTypeVariables() throws NoSuchMethodException {
assertFalse(TypeUtils.containsTypeVariables(Test1.class.getMethod("m0").getGenericReturnType()));
Expand Down