All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.lang3.reflect.TypeUtils Maven / Gradle / Ivy

Go to download

Apache Commons Lang, a package of Java utility classes for the classes that are in java.lang's hierarchy, or are considered to be so standard as to justify existence in java.lang.

There is a newer version: 3.14.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.lang3.reflect;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.Builder;

/**
 * Utility methods focusing on type inspection, particularly with regard to
 * generics.
 *
 * @since 3.0
 */
public class TypeUtils {

    /**
     * GenericArrayType implementation class.
     * @since 3.2
     */
    private static final class GenericArrayTypeImpl implements GenericArrayType {
        private final Type componentType;

        /**
         * Constructor
         * @param componentType of this array type
         */
        private GenericArrayTypeImpl(final Type componentType) {
            this.componentType = componentType;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(final Object obj) {
            return obj == this || obj instanceof GenericArrayType && TypeUtils.equals(this, (GenericArrayType) obj);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Type getGenericComponentType() {
            return componentType;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            int result = 67 << 4;
            result |= componentType.hashCode();
            return result;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return TypeUtils.toString(this);
        }
    }

    /**
     * ParameterizedType implementation class.
     * @since 3.2
     */
    private static final class ParameterizedTypeImpl implements ParameterizedType {
        private final Class raw;
        private final Type useOwner;
        private final Type[] typeArguments;

        /**
         * Constructor
         * @param rawClass type
         * @param useOwner owner type to use, if any
         * @param typeArguments formal type arguments
         */
        private ParameterizedTypeImpl(final Class rawClass, final Type useOwner, final Type[] typeArguments) {
            this.raw = rawClass;
            this.useOwner = useOwner;
            this.typeArguments = Arrays.copyOf(typeArguments, typeArguments.length, Type[].class);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(final Object obj) {
            return obj == this || obj instanceof ParameterizedType && TypeUtils.equals(this, ((ParameterizedType) obj));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Type[] getActualTypeArguments() {
            return typeArguments.clone();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Type getOwnerType() {
            return useOwner;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Type getRawType() {
            return raw;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            int result = 71 << 4;
            result |= raw.hashCode();
            result <<= 4;
            result |= Objects.hashCode(useOwner);
            result <<= 8;
            result |= Arrays.hashCode(typeArguments);
            return result;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return TypeUtils.toString(this);
        }
    }

    /**
     * {@link WildcardType} builder.
     * @since 3.2
     */
    public static class WildcardTypeBuilder implements Builder {
        private Type[] upperBounds;

        private Type[] lowerBounds;
        /**
         * Constructor
         */
        private WildcardTypeBuilder() {
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public WildcardType build() {
            return new WildcardTypeImpl(upperBounds, lowerBounds);
        }

        /**
         * Specify lower bounds of the wildcard type to build.
         * @param bounds to set
         * @return {@code this}
         */
        public WildcardTypeBuilder withLowerBounds(final Type... bounds) {
            this.lowerBounds = bounds;
            return this;
        }

        /**
         * Specify upper bounds of the wildcard type to build.
         * @param bounds to set
         * @return {@code this}
         */
        public WildcardTypeBuilder withUpperBounds(final Type... bounds) {
            this.upperBounds = bounds;
            return this;
        }
    }

    /**
     * WildcardType implementation class.
     * @since 3.2
     */
    private static final class WildcardTypeImpl implements WildcardType {
        private final Type[] upperBounds;
        private final Type[] lowerBounds;

        /**
         * Constructor
         * @param upperBounds of this type
         * @param lowerBounds of this type
         */
        private WildcardTypeImpl(final Type[] upperBounds, final Type[] lowerBounds) {
            this.upperBounds = ObjectUtils.defaultIfNull(upperBounds, ArrayUtils.EMPTY_TYPE_ARRAY);
            this.lowerBounds = ObjectUtils.defaultIfNull(lowerBounds, ArrayUtils.EMPTY_TYPE_ARRAY);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(final Object obj) {
            return obj == this || obj instanceof WildcardType && TypeUtils.equals(this, (WildcardType) obj);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Type[] getLowerBounds() {
            return lowerBounds.clone();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Type[] getUpperBounds() {
            return upperBounds.clone();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            int result = 73 << 8;
            result |= Arrays.hashCode(upperBounds);
            result <<= 8;
            result |= Arrays.hashCode(lowerBounds);
            return result;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return TypeUtils.toString(this);
        }
    }

    /**
     * A wildcard instance matching {@code ?}.
     * @since 3.2
     */
    public static final WildcardType WILDCARD_ALL = wildcardType().withUpperBounds(Object.class).build();

    /**
     * Appends {@code types} to {@code builder} with separator {@code sep}.
     *
     * @param builder destination
     * @param sep separator
     * @param types to append
     * @return {@code builder}
     * @since 3.2
     */
    private static  StringBuilder appendAllTo(final StringBuilder builder, final String sep,
        @SuppressWarnings("unchecked") final T... types) {
        Validate.notEmpty(Validate.noNullElements(types));
        if (types.length > 0) {
            builder.append(toString(types[0]));
            for (int i = 1; i < types.length; i++) {
                builder.append(sep).append(toString(types[i]));
            }
        }
        return builder;
    }

    private static void appendRecursiveTypes(final StringBuilder builder, final int[] recursiveTypeIndexes,
        final Type[] argumentTypes) {
        for (int i = 0; i < recursiveTypeIndexes.length; i++) {
            appendAllTo(builder.append('<'), ", ", argumentTypes[i].toString()).append('>');
        }

        final Type[] argumentsFiltered = ArrayUtils.removeAll(argumentTypes, recursiveTypeIndexes);

        if (argumentsFiltered.length > 0) {
            appendAllTo(builder.append('<'), ", ", argumentsFiltered).append('>');
        }
    }

    /**
     * Formats a {@link Class} as a {@link String}.
     *
     * @param cls {@code Class} to format
     * @return String
     * @since 3.2
     */
    private static String classToString(final Class cls) {
        if (cls.isArray()) {
            return toString(cls.getComponentType()) + "[]";
        }

        final StringBuilder buf = new StringBuilder();

        if (cls.getEnclosingClass() != null) {
            buf.append(classToString(cls.getEnclosingClass())).append('.').append(cls.getSimpleName());
        } else {
            buf.append(cls.getName());
        }
        if (cls.getTypeParameters().length > 0) {
            buf.append('<');
            appendAllTo(buf, ", ", cls.getTypeParameters());
            buf.append('>');
        }
        return buf.toString();
    }

    /**
     * Tests, recursively, whether any of the type parameters associated with {@code type} are bound to variables.
     *
     * @param type the type to check for type variables
     * @return boolean
     * @since 3.2
     */
    public static boolean containsTypeVariables(final Type type) {
        if (type instanceof TypeVariable) {
            return true;
        }
        if (type instanceof Class) {
            return ((Class) type).getTypeParameters().length > 0;
        }
        if (type instanceof ParameterizedType) {
            for (final Type arg : ((ParameterizedType) type).getActualTypeArguments()) {
                if (containsTypeVariables(arg)) {
                    return true;
                }
            }
            return false;
        }
        if (type instanceof WildcardType) {
            final WildcardType wild = (WildcardType) type;
            return containsTypeVariables(getImplicitLowerBounds(wild)[0])
                || containsTypeVariables(getImplicitUpperBounds(wild)[0]);
        }
        if (type instanceof GenericArrayType) {
            return containsTypeVariables(((GenericArrayType) type).getGenericComponentType());
        }
        return false;
    }

    private static boolean containsVariableTypeSameParametrizedTypeBound(final TypeVariable typeVariable,
        final ParameterizedType parameterizedType) {
        return ArrayUtils.contains(typeVariable.getBounds(), parameterizedType);
    }

    /**
     * Tries to determine the type arguments of a class/interface based on a
     * super parameterized type's type arguments. This method is the inverse of
     * {@link #getTypeArguments(Type, Class)} which gets a class/interface's
     * type arguments based on a subtype. It is far more limited in determining
     * the type arguments for the subject class's type variables in that it can
     * only determine those parameters that map from the subject {@link Class}
     * object to the supertype.
     *
     * 

* Example: {@link java.util.TreeSet * TreeSet} sets its parameter as the parameter for * {@link java.util.NavigableSet NavigableSet}, which in turn sets the * parameter of {@link java.util.SortedSet}, which in turn sets the * parameter of {@link Set}, which in turn sets the parameter of * {@link java.util.Collection}, which in turn sets the parameter of * {@link java.lang.Iterable}. Since {@code TreeSet}'s parameter maps * (indirectly) to {@code Iterable}'s parameter, it will be able to * determine that based on the super type {@code Iterable>>}, the parameter of * {@code TreeSet} is {@code ? extends Map>}. *

* * @param cls the class whose type parameters are to be determined, not {@code null} * @param superParameterizedType the super type from which {@code cls}'s type * arguments are to be determined, not {@code null} * @return a {@code Map} of the type assignments that could be determined * for the type variables in each type in the inheritance hierarchy from * {@code type} to {@code toClass} inclusive. */ public static Map, Type> determineTypeArguments(final Class cls, final ParameterizedType superParameterizedType) { Validate.notNull(cls, "cls"); Validate.notNull(superParameterizedType, "superParameterizedType"); final Class superClass = getRawType(superParameterizedType); // compatibility check if (!isAssignable(cls, superClass)) { return null; } if (cls.equals(superClass)) { return getTypeArguments(superParameterizedType, superClass, null); } // get the next class in the inheritance hierarchy final Type midType = getClosestParentType(cls, superClass); // can only be a class or a parameterized type if (midType instanceof Class) { return determineTypeArguments((Class) midType, superParameterizedType); } final ParameterizedType midParameterizedType = (ParameterizedType) midType; final Class midClass = getRawType(midParameterizedType); // get the type variables of the mid class that map to the type // arguments of the super class final Map, Type> typeVarAssigns = determineTypeArguments(midClass, superParameterizedType); // map the arguments of the mid type to the class type variables mapTypeVariablesToArguments(cls, midParameterizedType, typeVarAssigns); return typeVarAssigns; } /** * Tests whether {@code t} equals {@code a}. * * @param genericArrayType LHS * @param type RHS * @return boolean * @since 3.2 */ private static boolean equals(final GenericArrayType genericArrayType, final Type type) { return type instanceof GenericArrayType && equals(genericArrayType.getGenericComponentType(), ((GenericArrayType) type).getGenericComponentType()); } /** * Tests whether {@code t} equals {@code p}. * * @param parameterizedType LHS * @param type RHS * @return boolean * @since 3.2 */ private static boolean equals(final ParameterizedType parameterizedType, final Type type) { if (type instanceof ParameterizedType) { final ParameterizedType other = (ParameterizedType) type; if (equals(parameterizedType.getRawType(), other.getRawType()) && equals(parameterizedType.getOwnerType(), other.getOwnerType())) { return equals(parameterizedType.getActualTypeArguments(), other.getActualTypeArguments()); } } return false; } /** * Tests equality of types. * * @param type1 the first type * @param type2 the second type * @return boolean * @since 3.2 */ public static boolean equals(final Type type1, final Type type2) { if (Objects.equals(type1, type2)) { return true; } if (type1 instanceof ParameterizedType) { return equals((ParameterizedType) type1, type2); } if (type1 instanceof GenericArrayType) { return equals((GenericArrayType) type1, type2); } if (type1 instanceof WildcardType) { return equals((WildcardType) type1, type2); } return false; } /** * Tests whether {@code t1} equals {@code t2}. * * @param type1 LHS * @param type2 RHS * @return boolean * @since 3.2 */ private static boolean equals(final Type[] type1, final Type[] type2) { if (type1.length == type2.length) { for (int i = 0; i < type1.length; i++) { if (!equals(type1[i], type2[i])) { return false; } } return true; } return false; } /** * Tests whether {@code t} equals {@code w}. * * @param wildcardType LHS * @param type RHS * @return boolean * @since 3.2 */ private static boolean equals(final WildcardType wildcardType, final Type type) { if (type instanceof WildcardType) { final WildcardType other = (WildcardType) type; return equals(getImplicitLowerBounds(wildcardType), getImplicitLowerBounds(other)) && equals(getImplicitUpperBounds(wildcardType), getImplicitUpperBounds(other)); } return false; } /** * Helper method to establish the formal parameters for a parameterized type. * * @param mappings map containing the assignments * @param variables expected map keys * @return array of map values corresponding to specified keys */ private static Type[] extractTypeArgumentsFrom(final Map, Type> mappings, final TypeVariable[] variables) { final Type[] result = new Type[variables.length]; int index = 0; for (final TypeVariable var : variables) { Validate.isTrue(mappings.containsKey(var), "missing argument mapping for %s", toString(var)); result[index++] = mappings.get(var); } return result; } private static int[] findRecursiveTypes(final ParameterizedType parameterizedType) { final Type[] filteredArgumentTypes = Arrays.copyOf(parameterizedType.getActualTypeArguments(), parameterizedType.getActualTypeArguments().length); int[] indexesToRemove = {}; for (int i = 0; i < filteredArgumentTypes.length; i++) { if ((filteredArgumentTypes[i] instanceof TypeVariable) && containsVariableTypeSameParametrizedTypeBound( ((TypeVariable) filteredArgumentTypes[i]), parameterizedType)) { indexesToRemove = ArrayUtils.add(indexesToRemove, i); } } return indexesToRemove; } /** * Creates a generic array type instance. * * @param componentType the type of the elements of the array. For example the component type of {@code boolean[]} * is {@code boolean} * @return {@link GenericArrayType} * @since 3.2 */ public static GenericArrayType genericArrayType(final Type componentType) { return new GenericArrayTypeImpl(Validate.notNull(componentType, "componentType")); } /** * Formats a {@link GenericArrayType} as a {@link String}. * * @param genericArrayType {@code GenericArrayType} to format * @return String * @since 3.2 */ private static String genericArrayTypeToString(final GenericArrayType genericArrayType) { return String.format("%s[]", toString(genericArrayType.getGenericComponentType())); } /** * Gets the array component type of {@code type}. * * @param type the type to be checked * @return component type or null if type is not an array type */ public static Type getArrayComponentType(final Type type) { if (type instanceof Class) { final Class cls = (Class) type; return cls.isArray() ? cls.getComponentType() : null; } if (type instanceof GenericArrayType) { return ((GenericArrayType) type).getGenericComponentType(); } return null; } /** * Gets the closest parent type to the * super class specified by {@code superClass}. * * @param cls the class in question * @param superClass the super class * @return the closes parent type */ private static Type getClosestParentType(final Class cls, final Class superClass) { // only look at the interfaces if the super class is also an interface if (superClass.isInterface()) { // get the generic interfaces of the subject class final Type[] interfaceTypes = cls.getGenericInterfaces(); // will hold the best generic interface match found Type genericInterface = null; // find the interface closest to the super class for (final Type midType : interfaceTypes) { Class midClass = null; if (midType instanceof ParameterizedType) { midClass = getRawType((ParameterizedType) midType); } else if (midType instanceof Class) { midClass = (Class) midType; } else { throw new IllegalStateException("Unexpected generic" + " interface type found: " + midType); } // check if this interface is further up the inheritance chain // than the previously found match if (isAssignable(midClass, superClass) && isAssignable(genericInterface, (Type) midClass)) { genericInterface = midType; } } // found a match? if (genericInterface != null) { return genericInterface; } } // none of the interfaces were descendants of the target class, so the // super class has to be one, instead return cls.getGenericSuperclass(); } /** * Gets an array containing the sole type of {@link Object} if * {@link TypeVariable#getBounds()} returns an empty array. Otherwise, it * returns the result of {@link TypeVariable#getBounds()} passed into * {@link #normalizeUpperBounds}. * * @param typeVariable the subject type variable, not {@code null} * @return a non-empty array containing the bounds of the type variable. */ public static Type[] getImplicitBounds(final TypeVariable typeVariable) { Validate.notNull(typeVariable, "typeVariable"); final Type[] bounds = typeVariable.getBounds(); return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds); } /** * Gets an array containing a single value of {@code null} if * {@link WildcardType#getLowerBounds()} returns an empty array. Otherwise, * it returns the result of {@link WildcardType#getLowerBounds()}. * * @param wildcardType the subject wildcard type, not {@code null} * @return a non-empty array containing the lower bounds of the wildcard * type. */ public static Type[] getImplicitLowerBounds(final WildcardType wildcardType) { Validate.notNull(wildcardType, "wildcardType"); final Type[] bounds = wildcardType.getLowerBounds(); return bounds.length == 0 ? new Type[] { null } : bounds; } /** * Gets an array containing the sole value of {@link Object} if * {@link WildcardType#getUpperBounds()} returns an empty array. Otherwise, * it returns the result of {@link WildcardType#getUpperBounds()} * passed into {@link #normalizeUpperBounds}. * * @param wildcardType the subject wildcard type, not {@code null} * @return a non-empty array containing the upper bounds of the wildcard * type. */ public static Type[] getImplicitUpperBounds(final WildcardType wildcardType) { Validate.notNull(wildcardType, "wildcardType"); final Type[] bounds = wildcardType.getUpperBounds(); return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds); } /** * Transforms the passed in type to a {@link Class} object. Type-checking method of convenience. * * @param parameterizedType the type to be converted * @return the corresponding {@code Class} object * @throws IllegalStateException if the conversion fails */ private static Class getRawType(final ParameterizedType parameterizedType) { final Type rawType = parameterizedType.getRawType(); // check if raw type is a Class object // not currently necessary, but since the return type is Type instead of // Class, there's enough reason to believe that future versions of Java // may return other Type implementations. And type-safety checking is // rarely a bad idea. if (!(rawType instanceof Class)) { throw new IllegalStateException("Wait... What!? Type of rawType: " + rawType); } return (Class) rawType; } /** * Gets the raw type of a Java type, given its context. Primarily for use * with {@link TypeVariable}s and {@link GenericArrayType}s, or when you do * not know the runtime type of {@code type}: if you know you have a * {@link Class} instance, it is already raw; if you know you have a * {@link ParameterizedType}, its raw type is only a method call away. * * @param type to resolve * @param assigningType type to be resolved against * @return the resolved {@link Class} object or {@code null} if * the type could not be resolved */ public static Class getRawType(final Type type, final Type assigningType) { if (type instanceof Class) { // it is raw, no problem return (Class) type; } if (type instanceof ParameterizedType) { // simple enough to get the raw type of a ParameterizedType return getRawType((ParameterizedType) type); } if (type instanceof TypeVariable) { if (assigningType == null) { return null; } // get the entity declaring this type variable final Object genericDeclaration = ((TypeVariable) type).getGenericDeclaration(); // can't get the raw type of a method- or constructor-declared type // variable if (!(genericDeclaration instanceof Class)) { return null; } // get the type arguments for the declaring class/interface based // on the enclosing type final Map, Type> typeVarAssigns = getTypeArguments(assigningType, (Class) genericDeclaration); // enclosingType has to be a subclass (or subinterface) of the // declaring type if (typeVarAssigns == null) { return null; } // get the argument assigned to this type variable final Type typeArgument = typeVarAssigns.get(type); if (typeArgument == null) { return null; } // get the argument for this type variable return getRawType(typeArgument, assigningType); } if (type instanceof GenericArrayType) { // get raw component type final Class rawComponentType = getRawType(((GenericArrayType) type) .getGenericComponentType(), assigningType); // create array type from raw component type and return its class return Array.newInstance(rawComponentType, 0).getClass(); } // (hand-waving) this is not the method you're looking for if (type instanceof WildcardType) { return null; } throw new IllegalArgumentException("unknown type: " + type); } /** * Gets a map of the type arguments of a class in the context of {@code toClass}. * * @param cls the class in question * @param toClass the context class * @param subtypeVarAssigns a map with type variables * @return the {@code Map} with type arguments */ private static Map, Type> getTypeArguments(Class cls, final Class toClass, final Map, Type> subtypeVarAssigns) { // make sure they're assignable if (!isAssignable(cls, toClass)) { return null; } // can't work with primitives if (cls.isPrimitive()) { // both classes are primitives? if (toClass.isPrimitive()) { // dealing with widening here. No type arguments to be // harvested with these two types. return new HashMap<>(); } // work with wrapper the wrapper class instead of the primitive cls = ClassUtils.primitiveToWrapper(cls); } // create a copy of the incoming map, or an empty one if it's null final HashMap, Type> typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>() : new HashMap<>(subtypeVarAssigns); // has target class been reached? if (toClass.equals(cls)) { return typeVarAssigns; } // walk the inheritance hierarchy until the target class is reached return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns); } /** * Gets all the type arguments for this parameterized type * including owner hierarchy arguments such as * {@code Outer.Inner.DeepInner} . * The arguments are returned in a * {@link Map} specifying the argument type for each {@link TypeVariable}. * * @param type specifies the subject parameterized type from which to * harvest the parameters. * @return a {@code Map} of the type arguments to their respective type * variables. */ public static Map, Type> getTypeArguments(final ParameterizedType type) { return getTypeArguments(type, getRawType(type), null); } /** * Gets a map of the type arguments of a parameterized type in the context of {@code toClass}. * * @param parameterizedType the parameterized type * @param toClass the class * @param subtypeVarAssigns a map with type variables * @return the {@code Map} with type arguments */ private static Map, Type> getTypeArguments( final ParameterizedType parameterizedType, final Class toClass, final Map, Type> subtypeVarAssigns) { final Class cls = getRawType(parameterizedType); // make sure they're assignable if (!isAssignable(cls, toClass)) { return null; } final Type ownerType = parameterizedType.getOwnerType(); final Map, Type> typeVarAssigns; if (ownerType instanceof ParameterizedType) { // get the owner type arguments first final ParameterizedType parameterizedOwnerType = (ParameterizedType) ownerType; typeVarAssigns = getTypeArguments(parameterizedOwnerType, getRawType(parameterizedOwnerType), subtypeVarAssigns); } else { // no owner, prep the type variable assignments map typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>() : new HashMap<>(subtypeVarAssigns); } // get the subject parameterized type's arguments final Type[] typeArgs = parameterizedType.getActualTypeArguments(); // and get the corresponding type variables from the raw class final TypeVariable[] typeParams = cls.getTypeParameters(); // map the arguments to their respective type variables for (int i = 0; i < typeParams.length; i++) { final Type typeArg = typeArgs[i]; typeVarAssigns.put( typeParams[i], typeVarAssigns.getOrDefault(typeArg, typeArg) ); } if (toClass.equals(cls)) { // target class has been reached. Done. return typeVarAssigns; } // walk the inheritance hierarchy until the target class is reached return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns); } /** * Gets the type arguments of a class/interface based on a subtype. For * instance, this method will determine that both of the parameters for the * interface {@link Map} are {@link Object} for the subtype * {@link java.util.Properties Properties} even though the subtype does not * directly implement the {@code Map} interface. * *

* This method returns {@code null} if {@code type} is not assignable to * {@code toClass}. It returns an empty map if none of the classes or * interfaces in its inheritance hierarchy specify any type arguments. *

* *

* A side effect of this method is that it also retrieves the type * arguments for the classes and interfaces that are part of the hierarchy * between {@code type} and {@code toClass}. So with the above * example, this method will also determine that the type arguments for * {@link java.util.Hashtable Hashtable} are also both {@code Object}. * In cases where the interface specified by {@code toClass} is * (indirectly) implemented more than once (e.g. where {@code toClass} * specifies the interface {@link java.lang.Iterable Iterable} and * {@code type} specifies a parameterized type that implements both * {@link java.util.Set Set} and {@link java.util.Collection Collection}), * this method will look at the inheritance hierarchy of only one of the * implementations/subclasses; the first interface encountered that isn't a * subinterface to one of the others in the {@code type} to * {@code toClass} hierarchy. *

* * @param type the type from which to determine the type parameters of * {@code toClass} * @param toClass the class whose type parameters are to be determined based * on the subtype {@code type} * @return a {@code Map} of the type assignments for the type variables in * each type in the inheritance hierarchy from {@code type} to * {@code toClass} inclusive. */ public static Map, Type> getTypeArguments(final Type type, final Class toClass) { return getTypeArguments(type, toClass, null); } /** * Gets a map of the type arguments of {@code type} in the context of {@code toClass}. * * @param type the type in question * @param toClass the class * @param subtypeVarAssigns a map with type variables * @return the {@code Map} with type arguments */ private static Map, Type> getTypeArguments(final Type type, final Class toClass, final Map, Type> subtypeVarAssigns) { if (type instanceof Class) { return getTypeArguments((Class) type, toClass, subtypeVarAssigns); } if (type instanceof ParameterizedType) { return getTypeArguments((ParameterizedType) type, toClass, subtypeVarAssigns); } if (type instanceof GenericArrayType) { return getTypeArguments(((GenericArrayType) type).getGenericComponentType(), toClass .isArray() ? toClass.getComponentType() : toClass, subtypeVarAssigns); } // since wildcard types are not assignable to classes, should this just // return null? if (type instanceof WildcardType) { for (final Type bound : getImplicitUpperBounds((WildcardType) type)) { // find the first bound that is assignable to the target class if (isAssignable(bound, toClass)) { return getTypeArguments(bound, toClass, subtypeVarAssigns); } } return null; } if (type instanceof TypeVariable) { for (final Type bound : getImplicitBounds((TypeVariable) type)) { // find the first bound that is assignable to the target class if (isAssignable(bound, toClass)) { return getTypeArguments(bound, toClass, subtypeVarAssigns); } } return null; } throw new IllegalStateException("found an unhandled type: " + type); } /** * Tests whether the specified type denotes an array type. * * @param type the type to be checked * @return {@code true} if {@code type} is an array class or a {@link GenericArrayType}. */ public static boolean isArrayType(final Type type) { return type instanceof GenericArrayType || type instanceof Class && ((Class) type).isArray(); } /** * Tests if the subject type may be implicitly cast to the target class * following the Java generics rules. * * @param type the subject type to be assigned to the target type * @param toClass the target class * @return {@code true} if {@code type} is assignable to {@code toClass}. */ private static boolean isAssignable(final Type type, final Class toClass) { if (type == null) { // consistency with ClassUtils.isAssignable() behavior return toClass == null || !toClass.isPrimitive(); } // only a null type can be assigned to null type which // would have cause the previous to return true if (toClass == null) { return false; } // all types are assignable to themselves if (toClass.equals(type)) { return true; } if (type instanceof Class) { // just comparing two classes return ClassUtils.isAssignable((Class) type, toClass); } if (type instanceof ParameterizedType) { // only have to compare the raw type to the class return isAssignable(getRawType((ParameterizedType) type), toClass); } // * if (type instanceof TypeVariable) { // if any of the bounds are assignable to the class, then the // type is assignable to the class. for (final Type bound : ((TypeVariable) type).getBounds()) { if (isAssignable(bound, toClass)) { return true; } } return false; } // the only classes to which a generic array type can be assigned // are class Object and array classes if (type instanceof GenericArrayType) { return toClass.equals(Object.class) || toClass.isArray() && isAssignable(((GenericArrayType) type).getGenericComponentType(), toClass .getComponentType()); } // wildcard types are not assignable to a class (though one would think // "? super Object" would be assignable to Object) if (type instanceof WildcardType) { return false; } throw new IllegalStateException("found an unhandled type: " + type); } /** * Tests if the subject type may be implicitly cast to the target * generic array type following the Java generics rules. * * @param type the subject type to be assigned to the target type * @param toGenericArrayType the target generic array type * @param typeVarAssigns a map with type variables * @return {@code true} if {@code type} is assignable to * {@code toGenericArrayType}. */ private static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType, final Map, Type> typeVarAssigns) { if (type == null) { return true; } // only a null type can be assigned to null type which // would have cause the previous to return true if (toGenericArrayType == null) { return false; } // all types are assignable to themselves if (toGenericArrayType.equals(type)) { return true; } final Type toComponentType = toGenericArrayType.getGenericComponentType(); if (type instanceof Class) { final Class cls = (Class) type; // compare the component types return cls.isArray() && isAssignable(cls.getComponentType(), toComponentType, typeVarAssigns); } if (type instanceof GenericArrayType) { // compare the component types return isAssignable(((GenericArrayType) type).getGenericComponentType(), toComponentType, typeVarAssigns); } if (type instanceof WildcardType) { // so long as one of the upper bounds is assignable, it's good for (final Type bound : getImplicitUpperBounds((WildcardType) type)) { if (isAssignable(bound, toGenericArrayType)) { return true; } } return false; } if (type instanceof TypeVariable) { // probably should remove the following logic and just return false. // type variables cannot specify arrays as bounds. for (final Type bound : getImplicitBounds((TypeVariable) type)) { if (isAssignable(bound, toGenericArrayType)) { return true; } } return false; } if (type instanceof ParameterizedType) { // the raw type of a parameterized type is never an array or // generic array, otherwise the declaration would look like this: // Collection[]< ? extends String > collection; return false; } throw new IllegalStateException("found an unhandled type: " + type); } /** * Tests if the subject type may be implicitly cast to the target * parameterized type following the Java generics rules. * * @param type the subject type to be assigned to the target type * @param toParameterizedType the target parameterized type * @param typeVarAssigns a map with type variables * @return {@code true} if {@code type} is assignable to {@code toType}. */ private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType, final Map, Type> typeVarAssigns) { if (type == null) { return true; } // only a null type can be assigned to null type which // would have cause the previous to return true if (toParameterizedType == null) { return false; } // cannot cast an array type to a parameterized type. if (type instanceof GenericArrayType) { return false; } // all types are assignable to themselves if (toParameterizedType.equals(type)) { return true; } // get the target type's raw type final Class toClass = getRawType(toParameterizedType); // get the subject type's type arguments including owner type arguments // and supertype arguments up to and including the target class. final Map, Type> fromTypeVarAssigns = getTypeArguments(type, toClass, null); // null means the two types are not compatible if (fromTypeVarAssigns == null) { return false; } // compatible types, but there's no type arguments. this is equivalent // to comparing Map< ?, ? > to Map, and raw types are always assignable // to parameterized types. if (fromTypeVarAssigns.isEmpty()) { return true; } // get the target type's type arguments including owner type arguments final Map, Type> toTypeVarAssigns = getTypeArguments(toParameterizedType, toClass, typeVarAssigns); // now to check each type argument for (final TypeVariable var : toTypeVarAssigns.keySet()) { final Type toTypeArg = unrollVariableAssignments(var, toTypeVarAssigns); final Type fromTypeArg = unrollVariableAssignments(var, fromTypeVarAssigns); if (toTypeArg == null && fromTypeArg instanceof Class) { continue; } // parameters must either be absent from the subject type, within // the bounds of the wildcard type, or be an exact match to the // parameters of the target type. if (fromTypeArg != null && toTypeArg != null && !toTypeArg.equals(fromTypeArg) && !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg, typeVarAssigns))) { return false; } } return true; } /** * Tests if the subject type may be implicitly cast to the target type * following the Java generics rules. If both types are {@link Class} * objects, the method returns the result of * {@link ClassUtils#isAssignable(Class, Class)}. * * @param type the subject type to be assigned to the target type * @param toType the target type * @return {@code true} if {@code type} is assignable to {@code toType}. */ public static boolean isAssignable(final Type type, final Type toType) { return isAssignable(type, toType, null); } /** * Tests if the subject type may be implicitly cast to the target type * following the Java generics rules. * * @param type the subject type to be assigned to the target type * @param toType the target type * @param typeVarAssigns optional map of type variable assignments * @return {@code true} if {@code type} is assignable to {@code toType}. */ private static boolean isAssignable(final Type type, final Type toType, final Map, Type> typeVarAssigns) { if (toType == null || toType instanceof Class) { return isAssignable(type, (Class) toType); } if (toType instanceof ParameterizedType) { return isAssignable(type, (ParameterizedType) toType, typeVarAssigns); } if (toType instanceof GenericArrayType) { return isAssignable(type, (GenericArrayType) toType, typeVarAssigns); } if (toType instanceof WildcardType) { return isAssignable(type, (WildcardType) toType, typeVarAssigns); } if (toType instanceof TypeVariable) { return isAssignable(type, (TypeVariable) toType, typeVarAssigns); } throw new IllegalStateException("found an unhandled type: " + toType); } /** * Tests if the subject type may be implicitly cast to the target type * variable following the Java generics rules. * * @param type the subject type to be assigned to the target type * @param toTypeVariable the target type variable * @param typeVarAssigns a map with type variables * @return {@code true} if {@code type} is assignable to * {@code toTypeVariable}. */ private static boolean isAssignable(final Type type, final TypeVariable toTypeVariable, final Map, Type> typeVarAssigns) { if (type == null) { return true; } // only a null type can be assigned to null type which // would have cause the previous to return true if (toTypeVariable == null) { return false; } // all types are assignable to themselves if (toTypeVariable.equals(type)) { return true; } if (type instanceof TypeVariable) { // a type variable is assignable to another type variable, if // and only if the former is the latter, extends the latter, or // is otherwise a descendant of the latter. final Type[] bounds = getImplicitBounds((TypeVariable) type); for (final Type bound : bounds) { if (isAssignable(bound, toTypeVariable, typeVarAssigns)) { return true; } } } if (type instanceof Class || type instanceof ParameterizedType || type instanceof GenericArrayType || type instanceof WildcardType) { return false; } throw new IllegalStateException("found an unhandled type: " + type); } /** * Tests if the subject type may be implicitly cast to the target * wildcard type following the Java generics rules. * * @param type the subject type to be assigned to the target type * @param toWildcardType the target wildcard type * @param typeVarAssigns a map with type variables * @return {@code true} if {@code type} is assignable to * {@code toWildcardType}. */ private static boolean isAssignable(final Type type, final WildcardType toWildcardType, final Map, Type> typeVarAssigns) { if (type == null) { return true; } // only a null type can be assigned to null type which // would have cause the previous to return true if (toWildcardType == null) { return false; } // all types are assignable to themselves if (toWildcardType.equals(type)) { return true; } final Type[] toUpperBounds = getImplicitUpperBounds(toWildcardType); final Type[] toLowerBounds = getImplicitLowerBounds(toWildcardType); if (type instanceof WildcardType) { final WildcardType wildcardType = (WildcardType) type; final Type[] upperBounds = getImplicitUpperBounds(wildcardType); final Type[] lowerBounds = getImplicitLowerBounds(wildcardType); for (Type toBound : toUpperBounds) { // if there are assignments for unresolved type variables, // now's the time to substitute them. toBound = substituteTypeVariables(toBound, typeVarAssigns); // each upper bound of the subject type has to be assignable to // each // upper bound of the target type for (final Type bound : upperBounds) { if (!isAssignable(bound, toBound, typeVarAssigns)) { return false; } } } for (Type toBound : toLowerBounds) { // if there are assignments for unresolved type variables, // now's the time to substitute them. toBound = substituteTypeVariables(toBound, typeVarAssigns); // each lower bound of the target type has to be assignable to // each // lower bound of the subject type for (final Type bound : lowerBounds) { if (!isAssignable(toBound, bound, typeVarAssigns)) { return false; } } } return true; } for (final Type toBound : toUpperBounds) { // if there are assignments for unresolved type variables, // now's the time to substitute them. if (!isAssignable(type, substituteTypeVariables(toBound, typeVarAssigns), typeVarAssigns)) { return false; } } for (final Type toBound : toLowerBounds) { // if there are assignments for unresolved type variables, // now's the time to substitute them. if (!isAssignable(substituteTypeVariables(toBound, typeVarAssigns), type, typeVarAssigns)) { return false; } } return true; } /** * Tests if the given value can be assigned to the target type * following the Java generics rules. * * @param value the value to be checked * @param type the target type * @return {@code true} if {@code value} is an instance of {@code type}. */ public static boolean isInstance(final Object value, final Type type) { if (type == null) { return false; } return value == null ? !(type instanceof Class) || !((Class) type).isPrimitive() : isAssignable(value.getClass(), type, null); } /** * Maps type variables. * * @param the generic type of the class in question * @param cls the class in question * @param parameterizedType the parameterized type * @param typeVarAssigns the map to be filled */ private static void mapTypeVariablesToArguments(final Class cls, final ParameterizedType parameterizedType, final Map, Type> typeVarAssigns) { // capture the type variables from the owner type that have assignments final Type ownerType = parameterizedType.getOwnerType(); if (ownerType instanceof ParameterizedType) { // recursion to make sure the owner's owner type gets processed mapTypeVariablesToArguments(cls, (ParameterizedType) ownerType, typeVarAssigns); } // parameterizedType is a generic interface/class (or it's in the owner // hierarchy of said interface/class) implemented/extended by the class // cls. Find out which type variables of cls are type arguments of // parameterizedType: final Type[] typeArgs = parameterizedType.getActualTypeArguments(); // of the cls's type variables that are arguments of parameterizedType, // find out which ones can be determined from the super type's arguments final TypeVariable[] typeVars = getRawType(parameterizedType).getTypeParameters(); // use List view of type parameters of cls so the contains() method can be used: final List>> typeVarList = Arrays.asList(cls .getTypeParameters()); for (int i = 0; i < typeArgs.length; i++) { final TypeVariable typeVar = typeVars[i]; final Type typeArg = typeArgs[i]; // argument of parameterizedType is a type variable of cls if (typeVarList.contains(typeArg) // type variable of parameterizedType has an assignment in // the super type. && typeVarAssigns.containsKey(typeVar)) { // map the assignment to the cls's type variable typeVarAssigns.put((TypeVariable) typeArg, typeVarAssigns.get(typeVar)); } } } /** * Strips out the redundant upper bound types in type * variable types and wildcard types (or it would with wildcard types if * multiple upper bounds were allowed). * *

* Example, with the variable type declaration: *

* *
<K extends java.util.Collection<String> &
     * java.util.List<String>>
* *

* since {@code List} is a subinterface of {@code Collection}, * this method will return the bounds as if the declaration had been: *

* *
<K extends java.util.List<String>>
* * @param bounds an array of types representing the upper bounds of either * {@link WildcardType} or {@link TypeVariable}, not {@code null}. * @return an array containing the values from {@code bounds} minus the * redundant types. */ public static Type[] normalizeUpperBounds(final Type[] bounds) { Validate.notNull(bounds, "bounds"); // don't bother if there's only one (or none) type if (bounds.length < 2) { return bounds; } final Set types = new HashSet<>(bounds.length); for (final Type type1 : bounds) { boolean subtypeFound = false; for (final Type type2 : bounds) { if (type1 != type2 && isAssignable(type2, type1, null)) { subtypeFound = true; break; } } if (!subtypeFound) { types.add(type1); } } return types.toArray(ArrayUtils.EMPTY_TYPE_ARRAY); } /** * Creates a parameterized type instance. * * @param rawClass the raw class to create a parameterized type instance for * @param typeVariableMap the map used for parameterization * @return {@link ParameterizedType} * @since 3.2 */ public static final ParameterizedType parameterize(final Class rawClass, final Map, Type> typeVariableMap) { Validate.notNull(rawClass, "rawClass"); Validate.notNull(typeVariableMap, "typeVariableMap"); return parameterizeWithOwner(null, rawClass, extractTypeArgumentsFrom(typeVariableMap, rawClass.getTypeParameters())); } /** * Creates a parameterized type instance. * * @param rawClass the raw class to create a parameterized type instance for * @param typeArguments the types used for parameterization * @return {@link ParameterizedType} * @since 3.2 */ public static final ParameterizedType parameterize(final Class rawClass, final Type... typeArguments) { return parameterizeWithOwner(null, rawClass, typeArguments); } /** * Formats a {@link ParameterizedType} as a {@link String}. * * @param parameterizedType {@code ParameterizedType} to format * @return String * @since 3.2 */ private static String parameterizedTypeToString(final ParameterizedType parameterizedType) { final StringBuilder builder = new StringBuilder(); final Type useOwner = parameterizedType.getOwnerType(); final Class raw = (Class) parameterizedType.getRawType(); if (useOwner == null) { builder.append(raw.getName()); } else { if (useOwner instanceof Class) { builder.append(((Class) useOwner).getName()); } else { builder.append(useOwner.toString()); } builder.append('.').append(raw.getSimpleName()); } final int[] recursiveTypeIndexes = findRecursiveTypes(parameterizedType); if (recursiveTypeIndexes.length > 0) { appendRecursiveTypes(builder, recursiveTypeIndexes, parameterizedType.getActualTypeArguments()); } else { appendAllTo(builder.append('<'), ", ", parameterizedType.getActualTypeArguments()).append('>'); } return builder.toString(); } /** * Creates a parameterized type instance. * * @param owner the owning type * @param rawClass the raw class to create a parameterized type instance for * @param typeVariableMap the map used for parameterization * @return {@link ParameterizedType} * @since 3.2 */ public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class rawClass, final Map, Type> typeVariableMap) { Validate.notNull(rawClass, "rawClass"); Validate.notNull(typeVariableMap, "typeVariableMap"); return parameterizeWithOwner(owner, rawClass, extractTypeArgumentsFrom(typeVariableMap, rawClass.getTypeParameters())); } /** * Creates a parameterized type instance. * * @param owner the owning type * @param rawClass the raw class to create a parameterized type instance for * @param typeArguments the types used for parameterization * * @return {@link ParameterizedType} * @since 3.2 */ public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class rawClass, final Type... typeArguments) { Validate.notNull(rawClass, "rawClass"); final Type useOwner; if (rawClass.getEnclosingClass() == null) { Validate.isTrue(owner == null, "no owner allowed for top-level %s", rawClass); useOwner = null; } else if (owner == null) { useOwner = rawClass.getEnclosingClass(); } else { Validate.isTrue(isAssignable(owner, rawClass.getEnclosingClass()), "%s is invalid owner type for parameterized %s", owner, rawClass); useOwner = owner; } Validate.noNullElements(typeArguments, "null type argument at index %s"); Validate.isTrue(rawClass.getTypeParameters().length == typeArguments.length, "invalid number of type parameters specified: expected %d, got %d", rawClass.getTypeParameters().length, typeArguments.length); return new ParameterizedTypeImpl(rawClass, useOwner, typeArguments); } /** * Finds the mapping for {@code type} in {@code typeVarAssigns}. * * @param type the type to be replaced * @param typeVarAssigns the map with type variables * @return the replaced type * @throws IllegalArgumentException if the type cannot be substituted */ private static Type substituteTypeVariables(final Type type, final Map, Type> typeVarAssigns) { if (type instanceof TypeVariable && typeVarAssigns != null) { final Type replacementType = typeVarAssigns.get(type); if (replacementType == null) { throw new IllegalArgumentException("missing assignment type for type variable " + type); } return replacementType; } return type; } /** * Formats a {@link TypeVariable} including its {@link GenericDeclaration}. * * @param typeVariable the type variable to create a String representation for, not {@code null} * @return String * @since 3.2 */ public static String toLongString(final TypeVariable typeVariable) { Validate.notNull(typeVariable, "typeVariable"); final StringBuilder buf = new StringBuilder(); final GenericDeclaration d = typeVariable.getGenericDeclaration(); if (d instanceof Class) { Class c = (Class) d; while (true) { if (c.getEnclosingClass() == null) { buf.insert(0, c.getName()); break; } buf.insert(0, c.getSimpleName()).insert(0, '.'); c = c.getEnclosingClass(); } } else if (d instanceof Type) {// not possible as of now buf.append(toString((Type) d)); } else { buf.append(d); } return buf.append(':').append(typeVariableToString(typeVariable)).toString(); } private static String toString(final T object) { return object instanceof Type ? toString((Type) object) : object.toString(); } /** * Formats a given type as a Java-esque String. * * @param type the type to create a String representation for, not {@code null} * @return String * @since 3.2 */ public static String toString(final Type type) { Validate.notNull(type); if (type instanceof Class) { return classToString((Class) type); } if (type instanceof ParameterizedType) { return parameterizedTypeToString((ParameterizedType) type); } if (type instanceof WildcardType) { return wildcardTypeToString((WildcardType) type); } if (type instanceof TypeVariable) { return typeVariableToString((TypeVariable) type); } if (type instanceof GenericArrayType) { return genericArrayTypeToString((GenericArrayType) type); } throw new IllegalArgumentException(ObjectUtils.identityToString(type)); } /** * Determines whether or not specified types satisfy the bounds of their * mapped type variables. When a type parameter extends another (such as * {@code }), uses another as a type parameter (such as * {@code >}), or otherwise depends on * another type variable to be specified, the dependencies must be included * in {@code typeVarAssigns}. * * @param typeVariableMap specifies the potential types to be assigned to the * type variables, not {@code null}. * @return whether or not the types can be assigned to their respective type * variables. */ public static boolean typesSatisfyVariables(final Map, Type> typeVariableMap) { Validate.notNull(typeVariableMap, "typeVariableMap"); // all types must be assignable to all the bounds of their mapped // type variable. for (final Map.Entry, Type> entry : typeVariableMap.entrySet()) { final TypeVariable typeVar = entry.getKey(); final Type type = entry.getValue(); for (final Type bound : getImplicitBounds(typeVar)) { if (!isAssignable(type, substituteTypeVariables(bound, typeVariableMap), typeVariableMap)) { return false; } } } return true; } /** * Formats a {@link TypeVariable} as a {@link String}. * * @param typeVariable {@code TypeVariable} to format * @return String * @since 3.2 */ private static String typeVariableToString(final TypeVariable typeVariable) { final StringBuilder buf = new StringBuilder(typeVariable.getName()); final Type[] bounds = typeVariable.getBounds(); if (bounds.length > 0 && !(bounds.length == 1 && Object.class.equals(bounds[0]))) { buf.append(" extends "); appendAllTo(buf, " & ", typeVariable.getBounds()); } return buf.toString(); } /** * Unrolls variables in a type bounds array. * * @param typeArguments assignments {@link Map} * @param bounds in which to expand variables * @return {@code bounds} with any variables reassigned * @since 3.2 */ private static Type[] unrollBounds(final Map, Type> typeArguments, final Type[] bounds) { Type[] result = bounds; int i = 0; for (; i < result.length; i++) { final Type unrolled = unrollVariables(typeArguments, result[i]); if (unrolled == null) { result = ArrayUtils.remove(result, i--); } else { result[i] = unrolled; } } return result; } /** * Look up {@code var} in {@code typeVarAssigns} transitively, * i.e. keep looking until the value found is not a type variable. * * @param typeVariable the type variable to look up * @param typeVarAssigns the map used for the look up * @return Type or {@code null} if some variable was not in the map * @since 3.2 */ private static Type unrollVariableAssignments(TypeVariable typeVariable, final Map, Type> typeVarAssigns) { Type result; do { result = typeVarAssigns.get(typeVariable); if (result instanceof TypeVariable && !result.equals(typeVariable)) { typeVariable = (TypeVariable) result; continue; } break; } while (true); return result; } /** * Gets a type representing {@code type} with variable assignments "unrolled." * * @param typeArguments as from {@link TypeUtils#getTypeArguments(Type, Class)} * @param type the type to unroll variable assignments for * @return Type * @since 3.2 */ public static Type unrollVariables(Map, Type> typeArguments, final Type type) { if (typeArguments == null) { typeArguments = Collections.emptyMap(); } if (containsTypeVariables(type)) { if (type instanceof TypeVariable) { return unrollVariables(typeArguments, typeArguments.get(type)); } if (type instanceof ParameterizedType) { final ParameterizedType p = (ParameterizedType) type; final Map, Type> parameterizedTypeArguments; if (p.getOwnerType() == null) { parameterizedTypeArguments = typeArguments; } else { parameterizedTypeArguments = new HashMap<>(typeArguments); parameterizedTypeArguments.putAll(getTypeArguments(p)); } final Type[] args = p.getActualTypeArguments(); for (int i = 0; i < args.length; i++) { final Type unrolled = unrollVariables(parameterizedTypeArguments, args[i]); if (unrolled != null) { args[i] = unrolled; } } return parameterizeWithOwner(p.getOwnerType(), (Class) p.getRawType(), args); } if (type instanceof WildcardType) { final WildcardType wild = (WildcardType) type; return wildcardType().withUpperBounds(unrollBounds(typeArguments, wild.getUpperBounds())) .withLowerBounds(unrollBounds(typeArguments, wild.getLowerBounds())).build(); } } return type; } /** * Gets a {@link WildcardTypeBuilder}. * * @return {@link WildcardTypeBuilder} * @since 3.2 */ public static WildcardTypeBuilder wildcardType() { return new WildcardTypeBuilder(); } /** * Formats a {@link WildcardType} as a {@link String}. * * @param wildcardType {@code WildcardType} to format * @return String * @since 3.2 */ private static String wildcardTypeToString(final WildcardType wildcardType) { final StringBuilder buf = new StringBuilder().append('?'); final Type[] lowerBounds = wildcardType.getLowerBounds(); final Type[] upperBounds = wildcardType.getUpperBounds(); if (lowerBounds.length > 1 || lowerBounds.length == 1 && lowerBounds[0] != null) { appendAllTo(buf.append(" super "), " & ", lowerBounds); } else if (upperBounds.length > 1 || upperBounds.length == 1 && !Object.class.equals(upperBounds[0])) { appendAllTo(buf.append(" extends "), " & ", upperBounds); } return buf.toString(); } /** * Wraps the specified {@link Class} in a {@link Typed} wrapper. * * @param generic type * @param type to wrap * @return Typed<T> * @since 3.2 */ public static Typed wrap(final Class type) { return wrap((Type) type); } /** * Wraps the specified {@link Type} in a {@link Typed} wrapper. * * @param inferred generic type * @param type to wrap * @return Typed<T> * @since 3.2 */ public static Typed wrap(final Type type) { return () -> type; } /** * {@code TypeUtils} instances should NOT be constructed in standard * programming. Instead, the class should be used as * {@code TypeUtils.isAssignable(cls, toClass)}. *

* This constructor is public to permit tools that require a JavaBean instance * to operate. *

*/ public TypeUtils() { } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy