[ 
https://issues.apache.org/jira/browse/LANG-1836?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

William Degrange updated LANG-1836:
-----------------------------------
    Description: 
*Description*

{{TypeUtils.containsTypeVariables(Type)}} returns {{false}} for a 
{{ParameterizedType}} whose owner type contains a type variable. The 
{{ParameterizedType}} branch only checks {{{}getActualTypeArguments(){}}}; 
{{getOwnerType()}} is never inspected.

This contradicts the Javadoc ("Tests, recursively, whether any of the type 
parameters associated with {{type}} are bound to variables"): the type 
parameters of the owner are part of the type's parameterization, and 
{{unrollVariables}} already handles the owner explicitly.

*Steps to reproduce*
{code:java}
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import org.apache.commons.lang3.reflect.TypeUtils;public class 
ContainsTypeVariablesOwnerRepro {    

    static class Outer<T> {
        class Inner {}
        Inner inner; // generic type is Outer<T>.Inner
    }

    public static void main(String[] args) throws Exception {
        Type fromReflection = 
Outer.class.getDeclaredField("inner").getGenericType();
        System.out.println(TypeUtils.containsTypeVariables(fromReflection)); // 
false, expected true

        TypeVariable<?> t = Outer.class.getTypeParameters()[0];
        Type built = 
TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class, t), 
Outer.Inner.class);
        System.out.println(TypeUtils.containsTypeVariables(built)); // false, 
expected true  
      
        // Consequence: unrollVariables short-circuits and does not substitute T
        
System.out.println(TypeUtils.unrollVariables(Collections.singletonMap(t, 
String.class), fromReflection));
        // prints Outer<T>.Inner, expected Outer<String>.Inner
    }

}{code}
*Actual:* {{{}false{}}}, {{{}false{}}}, {{Outer<T>.Inner}}
*Expected:* {{{}true{}}}, {{{}true{}}}, {{Outer<String>.Inner}}

*Suggested fix*

Recurse into the owner type, but only when it is not a raw {{{}Class{}}}. For a 
static nested type such as {{{}Map.Entry<String, Integer>{}}}, the owner is the 
raw {{{}Map.class{}}}, and the {{Class}} branch would return {{true}} because 
{{Map}} declares type parameters:
{code:java}
if (type instanceof ParameterizedType) {
    final ParameterizedType parameterizedType = (ParameterizedType) type;
    for (final Type arg : parameterizedType.getActualTypeArguments()) {
        if (containsTypeVariables(arg)) {
            return true;
        }
    }
    final Type ownerType = parameterizedType.getOwnerType();
    return ownerType != null && !(ownerType instanceof Class<?>) && 
containsTypeVariables(ownerType);
} {code}
 
Suggested regression tests:
 * {{Outer<T>.Inner}} → {{true}}
 * {{Outer<String>.Inner}} → {{false}}
 * {{Map.Entry<String, Integer>}} → {{false}} (the owner is the raw 
{{{}Map.class{}}})

  was:
*Description*

{{TypeUtils.containsTypeVariables(Type)}} returns {{false}} for a 
{{ParameterizedType}} whose owner type contains a type variable. The 
{{ParameterizedType}} branch only checks {{{}getActualTypeArguments(){}}}; 
{{getOwnerType()}} is never inspected.

This contradicts the Javadoc ("Tests, recursively, whether any of the type 
parameters associated with {{type}} are bound to variables"): the type 
parameters of the owner are part of the type's parameterization, and 
{{unrollVariables}} already handles the owner explicitly.

*Steps to reproduce*
{code:java}
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import org.apache.commons.lang3.reflect.TypeUtils;public class 
ContainsTypeVariablesOwnerRepro {    static class Outer<T> {
        class Inner {}
        Inner inner; // generic type is Outer<T>.Inner
    }    public static void main(String[] args) throws Exception {
        Type fromReflection = 
Outer.class.getDeclaredField("inner").getGenericType();
        System.out.println(TypeUtils.containsTypeVariables(fromReflection)); // 
false, expected true        TypeVariable<?> t = 
Outer.class.getTypeParameters()[0];
        Type built = 
TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class, t), 
Outer.Inner.class);
        System.out.println(TypeUtils.containsTypeVariables(built)); // false, 
expected true        // Consequence: unrollVariables short-circuits and does 
not substitute T
        
System.out.println(TypeUtils.unrollVariables(Collections.singletonMap(t, 
String.class), fromReflection));
        // prints Outer<T>.Inner, expected Outer<String>.Inner
    }
}{code}
*Actual:* {{{}false{}}}, {{{}false{}}}, {{Outer<T>.Inner}}
*Expected:* {{{}true{}}}, {{{}true{}}}, {{Outer<String>.Inner}}

*Suggested fix*

Recurse into the owner type, but only when it is not a raw {{{}Class{}}}. For a 
static nested type such as {{{}Map.Entry<String, Integer>{}}}, the owner is the 
raw {{{}Map.class{}}}, and the {{Class}} branch would return {{true}} because 
{{Map}} declares type parameters:
{code:java}
if (type instanceof ParameterizedType) {
    final ParameterizedType parameterizedType = (ParameterizedType) type;
    for (final Type arg : parameterizedType.getActualTypeArguments()) {
        if (containsTypeVariables(arg)) {
            return true;
        }
    }
    final Type ownerType = parameterizedType.getOwnerType();
    return ownerType != null && !(ownerType instanceof Class<?>) && 
containsTypeVariables(ownerType);
} {code}
 
Suggested regression tests:
 * {{Outer<T>.Inner}} → {{true}}
 * {{Outer<String>.Inner}} → {{false}}
 * {{Map.Entry<String, Integer>}} → {{false}} (the owner is the raw 
{{{}Map.class{}}})


> TypeUtils.containsTypeVariables(Type) ignores the owner type of a 
> ParameterizedType
> -----------------------------------------------------------------------------------
>
>                 Key: LANG-1836
>                 URL: https://issues.apache.org/jira/browse/LANG-1836
>             Project: Commons Lang
>          Issue Type: Bug
>    Affects Versions: 3.21.0
>            Reporter: William Degrange
>            Priority: Major
>
> *Description*
> {{TypeUtils.containsTypeVariables(Type)}} returns {{false}} for a 
> {{ParameterizedType}} whose owner type contains a type variable. The 
> {{ParameterizedType}} branch only checks {{{}getActualTypeArguments(){}}}; 
> {{getOwnerType()}} is never inspected.
> This contradicts the Javadoc ("Tests, recursively, whether any of the type 
> parameters associated with {{type}} are bound to variables"): the type 
> parameters of the owner are part of the type's parameterization, and 
> {{unrollVariables}} already handles the owner explicitly.
> *Steps to reproduce*
> {code:java}
> import java.lang.reflect.Type;
> import java.lang.reflect.TypeVariable;
> import java.util.Collections;
> import org.apache.commons.lang3.reflect.TypeUtils;public class 
> ContainsTypeVariablesOwnerRepro {    
>     static class Outer<T> {
>         class Inner {}
>         Inner inner; // generic type is Outer<T>.Inner
>     }
>     public static void main(String[] args) throws Exception {
>         Type fromReflection = 
> Outer.class.getDeclaredField("inner").getGenericType();
>         System.out.println(TypeUtils.containsTypeVariables(fromReflection)); 
> // false, expected true
>         TypeVariable<?> t = Outer.class.getTypeParameters()[0];
>         Type built = 
> TypeUtils.parameterizeWithOwner(TypeUtils.parameterize(Outer.class, t), 
> Outer.Inner.class);
>         System.out.println(TypeUtils.containsTypeVariables(built)); // false, 
> expected true  
>       
>         // Consequence: unrollVariables short-circuits and does not 
> substitute T
>         
> System.out.println(TypeUtils.unrollVariables(Collections.singletonMap(t, 
> String.class), fromReflection));
>         // prints Outer<T>.Inner, expected Outer<String>.Inner
>     }
> }{code}
> *Actual:* {{{}false{}}}, {{{}false{}}}, {{Outer<T>.Inner}}
> *Expected:* {{{}true{}}}, {{{}true{}}}, {{Outer<String>.Inner}}
> *Suggested fix*
> Recurse into the owner type, but only when it is not a raw {{{}Class{}}}. For 
> a static nested type such as {{{}Map.Entry<String, Integer>{}}}, the owner is 
> the raw {{{}Map.class{}}}, and the {{Class}} branch would return {{true}} 
> because {{Map}} declares type parameters:
> {code:java}
> if (type instanceof ParameterizedType) {
>     final ParameterizedType parameterizedType = (ParameterizedType) type;
>     for (final Type arg : parameterizedType.getActualTypeArguments()) {
>         if (containsTypeVariables(arg)) {
>             return true;
>         }
>     }
>     final Type ownerType = parameterizedType.getOwnerType();
>     return ownerType != null && !(ownerType instanceof Class<?>) && 
> containsTypeVariables(ownerType);
> } {code}
>  
> Suggested regression tests:
>  * {{Outer<T>.Inner}} → {{true}}
>  * {{Outer<String>.Inner}} → {{false}}
>  * {{Map.Entry<String, Integer>}} → {{false}} (the owner is the raw 
> {{{}Map.class{}}})



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to