You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2020/11/15 15:40:39 UTC

[commons-lang] 02/03: Sort members.

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git

commit fe1c7903a8b97badb59f4c63b440c35bee4cf6c1
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Nov 15 09:37:29 2020 -0500

    Sort members.
---
 .../apache/commons/lang3/reflect/TypeUtils.java    | 2390 ++++++++++----------
 1 file changed, 1195 insertions(+), 1195 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
index 35ad795..5fe03af 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
@@ -47,49 +47,6 @@ import org.apache.commons.lang3.builder.Builder;
 public class TypeUtils {
 
     /**
-     * {@link WildcardType} builder.
-     * @since 3.2
-     */
-    public static class WildcardTypeBuilder implements Builder<WildcardType> {
-        /**
-         * Constructor
-         */
-        private WildcardTypeBuilder() {
-        }
-
-        private Type[] upperBounds;
-        private Type[] lowerBounds;
-
-        /**
-         * 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;
-        }
-
-        /**
-         * 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;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public WildcardType build() {
-            return new WildcardTypeImpl(upperBounds, lowerBounds);
-        }
-    }
-
-    /**
      * GenericArrayType implementation class.
      * @since 3.2
      */
@@ -108,34 +65,34 @@ public class TypeUtils {
          * {@inheritDoc}
          */
         @Override
-        public Type getGenericComponentType() {
-            return componentType;
+        public boolean equals(final Object obj) {
+            return obj == this || obj instanceof GenericArrayType && TypeUtils.equals(this, (GenericArrayType) obj);
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public String toString() {
-            return TypeUtils.toString(this);
+        public Type getGenericComponentType() {
+            return componentType;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public boolean equals(final Object obj) {
-            return obj == this || obj instanceof GenericArrayType && TypeUtils.equals(this, (GenericArrayType) obj);
+        public int hashCode() {
+            int result = 67 << 4;
+            result |= componentType.hashCode();
+            return result;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public int hashCode() {
-            int result = 67 << 4;
-            result |= componentType.hashCode();
-            return result;
+        public String toString() {
+            return TypeUtils.toString(this);
         }
     }
 
@@ -164,16 +121,8 @@ public class TypeUtils {
          * {@inheritDoc}
          */
         @Override
-        public Type getRawType() {
-            return raw;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public Type getOwnerType() {
-            return useOwner;
+        public boolean equals(final Object obj) {
+            return obj == this || obj instanceof ParameterizedType && TypeUtils.equals(this, ((ParameterizedType) obj));
         }
 
         /**
@@ -188,16 +137,16 @@ public class TypeUtils {
          * {@inheritDoc}
          */
         @Override
-        public String toString() {
-            return TypeUtils.toString(this);
+        public Type getOwnerType() {
+            return useOwner;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public boolean equals(final Object obj) {
-            return obj == this || obj instanceof ParameterizedType && TypeUtils.equals(this, ((ParameterizedType) obj));
+        public Type getRawType() {
+            return raw;
         }
 
         /**
@@ -213,6 +162,57 @@ public class TypeUtils {
             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<WildcardType> {
+        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;
+        }
     }
 
     /**
@@ -237,8 +237,8 @@ public class TypeUtils {
          * {@inheritDoc}
          */
         @Override
-        public Type[] getUpperBounds() {
-            return upperBounds.clone();
+        public boolean equals(final Object obj) {
+            return obj == this || obj instanceof WildcardType && TypeUtils.equals(this, (WildcardType) obj);
         }
 
         /**
@@ -253,16 +253,8 @@ public class TypeUtils {
          * {@inheritDoc}
          */
         @Override
-        public String toString() {
-            return TypeUtils.toString(this);
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public boolean equals(final Object obj) {
-            return obj == this || obj instanceof WildcardType && TypeUtils.equals(this, (WildcardType) obj);
+        public Type[] getUpperBounds() {
+            return upperBounds.clone();
         }
 
         /**
@@ -276,6 +268,14 @@ public class TypeUtils {
             result |= Arrays.hashCode(lowerBounds);
             return result;
         }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public String toString() {
+            return TypeUtils.toString(this);
+        }
     }
 
     /**
@@ -285,455 +285,566 @@ public class TypeUtils {
     public static final WildcardType WILDCARD_ALL = wildcardType().withUpperBounds(Object.class).build();
 
     /**
-     * {@code TypeUtils} instances should NOT be constructed in standard
-     * programming. Instead, the class should be used as
-     * {@code TypeUtils.isAssignable(cls, toClass)}.
-     * <p>
-     * This constructor is public to permit tools that require a JavaBean instance
-     * to operate.
-     * </p>
+     * 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
      */
-    public TypeUtils() {
-        super();
+    private static <T> 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;
     }
 
-    /**
-     * 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);
+    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('>');
+        }
     }
 
     /**
-     * Tests if the subject type may be implicitly cast to the target type
-     * following the Java generics rules.
+     * Formats a {@link Class} as a {@link String}.
      *
-     * @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}.
+     * @param cls {@code Class} to format
+     * @return String
+     * @since 3.2
      */
-    private static boolean isAssignable(final Type type, final Type toType,
-            final Map<TypeVariable<?>, Type> typeVarAssigns) {
-        if (toType == null || toType instanceof Class<?>) {
-            return isAssignable(type, (Class<?>) toType);
-        }
-
-        if (toType instanceof ParameterizedType) {
-            return isAssignable(type, (ParameterizedType) toType, typeVarAssigns);
+    private static String classToString(final Class<?> cls) {
+        if (cls.isArray()) {
+            return toString(cls.getComponentType()) + "[]";
         }
 
-        if (toType instanceof GenericArrayType) {
-            return isAssignable(type, (GenericArrayType) toType, typeVarAssigns);
-        }
+        final StringBuilder buf = new StringBuilder();
 
-        if (toType instanceof WildcardType) {
-            return isAssignable(type, (WildcardType) toType, typeVarAssigns);
+        if (cls.getEnclosingClass() != null) {
+            buf.append(classToString(cls.getEnclosingClass())).append('.').append(cls.getSimpleName());
+        } else {
+            buf.append(cls.getName());
         }
-
-        if (toType instanceof TypeVariable<?>) {
-            return isAssignable(type, (TypeVariable<?>) toType, typeVarAssigns);
+        if (cls.getTypeParameters().length > 0) {
+            buf.append('<');
+            appendAllTo(buf, ", ", cls.getTypeParameters());
+            buf.append('>');
         }
-
-        throw new IllegalStateException("found an unhandled type: " + toType);
+        return buf.toString();
     }
 
     /**
-     * Tests if the subject type may be implicitly cast to the target class
-     * following the Java generics rules.
+     * Tests, recursively, whether any of the type parameters associated with {@code type} are bound to variables.
      *
-     * @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)) {
+     * @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<?>) {
-            // just comparing two classes
-            return ClassUtils.isAssignable((Class<?>) type, toClass);
+            return ((Class<?>) type).getTypeParameters().length > 0;
         }
-
         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)) {
+            for (final Type arg : ((ParameterizedType) type).getActualTypeArguments()) {
+                if (containsTypeVariables(arg)) {
                     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;
+            final WildcardType wild = (WildcardType) type;
+            return containsTypeVariables(getImplicitLowerBounds(wild)[0])
+                || containsTypeVariables(getImplicitUpperBounds(wild)[0]);
         }
+        return false;
+    }
 
-        throw new IllegalStateException("found an unhandled type: " + type);
+    private static boolean containsVariableTypeSameParametrizedTypeBound(final TypeVariable<?> typeVariable,
+        final ParameterizedType parameterizedType) {
+        return ArrayUtils.contains(typeVariable.getBounds(), parameterizedType);
     }
 
     /**
-     * Tests if the subject type may be implicitly cast to the target
-     * parameterized type following the Java generics rules.
+     * 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.
+     * 
+     * <p>
+     * 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<? extends
+     * Map<Integer, ? extends Collection<?>>>}, the parameter of
+     * {@code TreeSet} is {@code ? extends Map<Integer, ? extends
+     * Collection<?>>}.
+     * </p>
      *
-     * @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}.
+     * @param cls the class whose type parameters are to be determined, not {@code null}
+     * @param superType 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.
      */
-    private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType,
-            final Map<TypeVariable<?>, Type> typeVarAssigns) {
-        if (type == null) {
-            return true;
-        }
+    public static Map<TypeVariable<?>, Type> determineTypeArguments(final Class<?> cls,
+            final ParameterizedType superType) {
+        Validate.notNull(cls, "cls is null");
+        Validate.notNull(superType, "superType is null");
 
-        // only a null type can be assigned to null type which
-        // would have cause the previous to return true
-        if (toParameterizedType == null) {
-            return false;
-        }
+        final Class<?> superClass = getRawType(superType);
 
-        // all types are assignable to themselves
-        if (toParameterizedType.equals(type)) {
-            return true;
+        // compatibility check
+        if (!isAssignable(cls, superClass)) {
+            return null;
         }
 
-        // 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<TypeVariable<?>, Type> fromTypeVarAssigns = getTypeArguments(type, toClass, null);
-
-        // null means the two types are not compatible
-        if (fromTypeVarAssigns == null) {
-            return false;
+        if (cls.equals(superClass)) {
+            return getTypeArguments(superType, superClass, null);
         }
 
-        // 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 next class in the inheritance hierarchy
+        final Type midType = getClosestParentType(cls, superClass);
 
-        // get the target type's type arguments including owner type arguments
-        final Map<TypeVariable<?>, Type> toTypeVarAssigns = getTypeArguments(toParameterizedType,
-                toClass, typeVarAssigns);
+        // can only be a class or a parameterized type
+        if (midType instanceof Class<?>) {
+            return determineTypeArguments((Class<?>) midType, superType);
+        }
 
-        // now to check each type argument
-        for (final TypeVariable<?> var : toTypeVarAssigns.keySet()) {
-            final Type toTypeArg = unrollVariableAssignments(var, toTypeVarAssigns);
-            final Type fromTypeArg = unrollVariableAssignments(var, fromTypeVarAssigns);
+        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<TypeVariable<?>, Type> typeVarAssigns = determineTypeArguments(midClass, superType);
+        // map the arguments of the mid type to the class type variables
+        mapTypeVariablesToArguments(cls, midParameterizedType, typeVarAssigns);
 
-            if (toTypeArg == null && fromTypeArg instanceof Class) {
-                continue;
-            }
+        return typeVarAssigns;
+    }
 
-            // 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.equals(fromTypeArg)
-                    && !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg,
-                            typeVarAssigns))) {
-                return false;
-            }
-        }
-        return true;
+    /**
+     * 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());
     }
 
     /**
-     * Look up {@code var} in {@code typeVarAssigns} <em>transitively</em>,
-     * i.e. keep looking until the value found is <em>not</em> a type variable.
+     * Tests whether {@code t} equals {@code p}.
      *
-     * @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
+     * @param parameterizedType LHS
+     * @param type RHS
+     * @return boolean
      * @since 3.2
      */
-    private static Type unrollVariableAssignments(TypeVariable<?> typeVariable,
-        final Map<TypeVariable<?>, Type> typeVarAssigns) {
-        Type result;
-        do {
-            result = typeVarAssigns.get(typeVariable);
-            if (result instanceof TypeVariable<?> && !result.equals(typeVariable)) {
-                typeVariable = (TypeVariable<?>) result;
-                continue;
+    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());
             }
-            break;
-        } while (true);
-        return result;
+        }
+        return false;
     }
 
     /**
-     * Tests if the subject type may be implicitly cast to the target
-     * generic array type following the Java generics rules.
+     * Tests equality of types.
      *
-     * @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}.
+     * @param type1 the first type
+     * @param type2 the second type
+     * @return boolean
+     * @since 3.2
      */
-    private static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType,
-            final Map<TypeVariable<?>, Type> typeVarAssigns) {
-        if (type == null) {
+    public static boolean equals(final Type type1, final Type type2) {
+        if (Objects.equals(type1, type2)) {
             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;
+        if (type1 instanceof ParameterizedType) {
+            return equals((ParameterizedType) type1, type2);
         }
-
-        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 (type1 instanceof GenericArrayType) {
+            return equals((GenericArrayType) type1, type2);
         }
-
-        if (type instanceof GenericArrayType) {
-            // compare the component types
-            return isAssignable(((GenericArrayType) type).getGenericComponentType(),
-                    toComponentType, typeVarAssigns);
+        if (type1 instanceof WildcardType) {
+            return equals((WildcardType) type1, type2);
         }
+        return false;
+    }
 
-        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;
+    /**
+     * 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 false;
+            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;
-                }
-            }
+    /**
+     * 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;
+    }
 
-            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<TypeVariable<?>, 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;
+    }
 
-        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;
+    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<?>) {
+                if (containsVariableTypeSameParametrizedTypeBound(((TypeVariable<?>) filteredArgumentTypes[i]),
+                    parameterizedType)) {
+                    indexesToRemove = ArrayUtils.add(indexesToRemove, i);
+                }
+            }
         }
+        return indexesToRemove;
+    }
 
-        throw new IllegalStateException("found an unhandled type: " + type);
+    /**
+     * 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 is null"));
     }
 
     /**
-     * Tests if the subject type may be implicitly cast to the target
-     * wildcard type following the Java generics rules.
+     * Formats a {@link GenericArrayType} as a {@link String}.
      *
-     * @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}.
+     * @param genericArrayType {@code GenericArrayType} to format
+     * @return String
+     * @since 3.2
      */
-    private static boolean isAssignable(final Type type, final WildcardType toWildcardType,
-            final Map<TypeVariable<?>, Type> typeVarAssigns) {
-        if (type == null) {
-            return true;
-        }
+    private static String genericArrayTypeToString(final GenericArrayType genericArrayType) {
+        return String.format("%s[]", toString(genericArrayType.getGenericComponentType()));
+    }
 
-        // only a null type can be assigned to null type which
-        // would have cause the previous to return true
-        if (toWildcardType == null) {
-            return false;
+    /**
+     * 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;
         }
-
-        // all types are assignable to themselves
-        if (toWildcardType.equals(type)) {
-            return true;
+        if (type instanceof GenericArrayType) {
+            return ((GenericArrayType) type).getGenericComponentType();
         }
+        return null;
+    }
 
-        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);
+    /**
+     * 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;
 
-            for (Type toBound : toUpperBounds) {
-                // if there are assignments for unresolved type variables,
-                // now's the time to substitute them.
-                toBound = substituteTypeVariables(toBound, typeVarAssigns);
+            // find the interface closest to the super class
+            for (final Type midType : interfaceTypes) {
+                Class<?> midClass = null;
 
-                // 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;
-                    }
+                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);
                 }
-            }
 
-            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;
-                    }
+                // 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;
                 }
             }
-            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;
+            // found a match?
+            if (genericInterface != null) {
+                return genericInterface;
             }
         }
 
-        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;
-            }
+        // 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 is null");
+        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 is null");
+        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 is null");
+        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 true;
+
+        return (Class<?>) rawType;
     }
 
     /**
-     * Tests if the subject type may be implicitly cast to the target type
-     * variable following the Java generics rules.
+     * 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 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}.
+     * @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
      */
-    private static boolean isAssignable(final Type type, final TypeVariable<?> toTypeVariable,
-            final Map<TypeVariable<?>, Type> typeVarAssigns) {
-        if (type == null) {
-            return true;
+    public static Class<?> getRawType(final Type type, final Type assigningType) {
+        if (type instanceof Class<?>) {
+            // it is raw, no problem
+            return (Class<?>) type;
         }
 
-        // only a null type can be assigned to null type which
-        // would have cause the previous to return true
-        if (toTypeVariable == null) {
-            return false;
+        if (type instanceof ParameterizedType) {
+            // simple enough to get the raw type of a ParameterizedType
+            return getRawType((ParameterizedType) type);
         }
 
-        // all types are assignable to themselves
-        if (toTypeVariable.equals(type)) {
-            return true;
+        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<TypeVariable<?>, 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 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);
+        if (type instanceof GenericArrayType) {
+            // get raw component type
+            final Class<?> rawComponentType = getRawType(((GenericArrayType) type)
+                    .getGenericComponentType(), assigningType);
 
-            for (final Type bound : bounds) {
-                if (isAssignable(bound, toTypeVariable, typeVarAssigns)) {
-                    return true;
-                }
-            }
+            // create array type from raw component type and return its class
+            return Array.newInstance(rawComponentType, 0).getClass();
         }
 
-        if (type instanceof Class<?> || type instanceof ParameterizedType
-                || type instanceof GenericArrayType || type instanceof WildcardType) {
-            return false;
+        // (hand-waving) this is not the method you're looking for
+        if (type instanceof WildcardType) {
+            return null;
         }
 
-        throw new IllegalStateException("found an unhandled type: " + type);
+        throw new IllegalArgumentException("unknown type: " + type);
     }
 
     /**
-     * Finds the mapping for {@code type} in {@code typeVarAssigns}.
+     * Gets a map of the type arguments of a class in the context of {@code toClass}.
      *
-     * @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
+     * @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 Type substituteTypeVariables(final Type type, final Map<TypeVariable<?>, Type> typeVarAssigns) {
-        if (type instanceof TypeVariable<?> && typeVarAssigns != null) {
-            final Type replacementType = typeVarAssigns.get(type);
+    private static Map<TypeVariable<?>, Type> getTypeArguments(Class<?> cls, final Class<?> toClass,
+            final Map<TypeVariable<?>, Type> subtypeVarAssigns) {
+        // make sure they're assignable
+        if (!isAssignable(cls, toClass)) {
+            return null;
+        }
 
-            if (replacementType == null) {
-                throw new IllegalArgumentException("missing assignment type for type variable "
-                        + type);
+        // 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<>();
             }
-            return replacementType;
+
+            // work with wrapper the wrapper class instead of the primitive
+            cls = ClassUtils.primitiveToWrapper(cls);
         }
-        return type;
+
+        // create a copy of the incoming map, or an empty one if it's null
+        final HashMap<TypeVariable<?>, 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);
     }
 
     /**
@@ -753,6 +864,61 @@ public class TypeUtils {
     }
 
     /**
+     * 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<TypeVariable<?>, Type> getTypeArguments(
+            final ParameterizedType parameterizedType, final Class<?> toClass,
+            final Map<TypeVariable<?>, Type> subtypeVarAssigns) {
+        final Class<?> cls = getRawType(parameterizedType);
+
+        // make sure they're assignable
+        if (!isAssignable(cls, toClass)) {
+            return null;
+        }
+
+        final Type ownerType = parameterizedType.getOwnerType();
+        Map<TypeVariable<?>, 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
@@ -844,628 +1010,539 @@ public class TypeUtils {
     }
 
     /**
-     * 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<TypeVariable<?>, Type> getTypeArguments(
-            final ParameterizedType parameterizedType, final Class<?> toClass,
-            final Map<TypeVariable<?>, Type> subtypeVarAssigns) {
-        final Class<?> cls = getRawType(parameterizedType);
-
-        // make sure they're assignable
-        if (!isAssignable(cls, toClass)) {
-            return null;
-        }
-
-        final Type ownerType = parameterizedType.getOwnerType();
-        Map<TypeVariable<?>, 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 a map of the type arguments of a class in the context of {@code toClass}.
+     * Tests whether the specified type denotes an array type.
      *
-     * @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
+     * @param type the type to be checked
+     * @return {@code true} if {@code type} is an array class or a {@link GenericArrayType}.
      */
-    private static Map<TypeVariable<?>, Type> getTypeArguments(Class<?> cls, final Class<?> toClass,
-            final Map<TypeVariable<?>, 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<TypeVariable<?>, 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);
+    public static boolean isArrayType(final Type type) {
+        return type instanceof GenericArrayType || type instanceof Class<?> && ((Class<?>) type).isArray();
     }
 
     /**
-     * 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.
-     * 
-     * <p>
-     * 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<? extends
-     * Map<Integer, ? extends Collection<?>>>}, the parameter of
-     * {@code TreeSet} is {@code ? extends Map<Integer, ? extends
-     * Collection<?>>}.
-     * </p>
+     * Tests if the subject type may be implicitly cast to the target class
+     * following the Java generics rules.
      *
-     * @param cls the class whose type parameters are to be determined, not {@code null}
-     * @param superType 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.
+     * @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}.
      */
-    public static Map<TypeVariable<?>, Type> determineTypeArguments(final Class<?> cls,
-            final ParameterizedType superType) {
-        Validate.notNull(cls, "cls is null");
-        Validate.notNull(superType, "superType is null");
+    private static boolean isAssignable(final Type type, final Class<?> toClass) {
+        if (type == null) {
+            // consistency with ClassUtils.isAssignable() behavior
+            return toClass == null || !toClass.isPrimitive();
+        }
 
-        final Class<?> superClass = getRawType(superType);
+        // only a null type can be assigned to null type which
+        // would have cause the previous to return true
+        if (toClass == null) {
+            return false;
+        }
 
-        // compatibility check
-        if (!isAssignable(cls, superClass)) {
-            return null;
+        // all types are assignable to themselves
+        if (toClass.equals(type)) {
+            return true;
         }
 
-        if (cls.equals(superClass)) {
-            return getTypeArguments(superType, superClass, null);
+        if (type instanceof Class<?>) {
+            // just comparing two classes
+            return ClassUtils.isAssignable((Class<?>) type, toClass);
         }
 
-        // get the next class in the inheritance hierarchy
-        final Type midType = getClosestParentType(cls, superClass);
+        if (type instanceof ParameterizedType) {
+            // only have to compare the raw type to the class
+            return isAssignable(getRawType((ParameterizedType) type), toClass);
+        }
 
-        // can only be a class or a parameterized type
-        if (midType instanceof Class<?>) {
-            return determineTypeArguments((Class<?>) midType, superType);
+        // *
+        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;
         }
 
-        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<TypeVariable<?>, Type> typeVarAssigns = determineTypeArguments(midClass, superType);
-        // map the arguments of the mid type to the class type variables
-        mapTypeVariablesToArguments(cls, midParameterizedType, typeVarAssigns);
+        // 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());
+        }
 
-        return typeVarAssigns;
+        // 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);
     }
 
     /**
-     * Maps type variables.
+     * Tests if the subject type may be implicitly cast to the target
+     * generic array type following the Java generics rules.
      *
-     * @param <T> 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
+     * @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 <T> void mapTypeVariablesToArguments(final Class<T> cls,
-            final ParameterizedType parameterizedType, final Map<TypeVariable<?>, 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);
+    private static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType,
+            final Map<TypeVariable<?>, Type> typeVarAssigns) {
+        if (type == null) {
+            return true;
         }
 
-        // 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();
+        // only a null type can be assigned to null type which
+        // would have cause the previous to return true
+        if (toGenericArrayType == null) {
+            return false;
+        }
 
-        // 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();
+        // all types are assignable to themselves
+        if (toGenericArrayType.equals(type)) {
+            return true;
+        }
 
-        // use List view of type parameters of cls so the contains() method can be used:
-        final List<TypeVariable<Class<T>>> typeVarList = Arrays.asList(cls
-                .getTypeParameters());
+        final Type toComponentType = toGenericArrayType.getGenericComponentType();
 
-        for (int i = 0; i < typeArgs.length; i++) {
-            final TypeVariable<?> typeVar = typeVars[i];
-            final Type typeArg = typeArgs[i];
+        if (type instanceof Class<?>) {
+            final Class<?> cls = (Class<?>) type;
 
-            // 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));
-            }
+            // compare the component types
+            return cls.isArray()
+                    && isAssignable(cls.getComponentType(), toComponentType, typeVarAssigns);
         }
-    }
-
-    /**
-     * 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 (type instanceof GenericArrayType) {
+            // compare the component types
+            return isAssignable(((GenericArrayType) type).getGenericComponentType(),
+                    toComponentType, typeVarAssigns);
+        }
 
-                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);
+        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;
                 }
+            }
 
-                // 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;
+            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;
                 }
             }
 
-            // found a match?
-            if (genericInterface != null) {
-                return genericInterface;
-            }
+            return false;
         }
 
-        // none of the interfaces were descendants of the target class, so the
-        // super class has to be one, instead
-        return cls.getGenericSuperclass();
-    }
-
-    /**
-     * 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) {
+        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;
         }
 
-        return value == null ? !(type instanceof Class<?>) || !((Class<?>) type).isPrimitive()
-                : isAssignable(value.getClass(), type, null);
+        throw new IllegalStateException("found an unhandled type: " + type);
     }
 
     /**
-     * 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).
-     * 
-     * <p>
-     * Example, with the variable type declaration:
-     * </p>
-     *
-     * <pre>&lt;K extends java.util.Collection&lt;String&gt; &amp;
-     * java.util.List&lt;String&gt;&gt;</pre>
-     *
-     * <p>
-     * since {@code List} is a subinterface of {@code Collection},
-     * this method will return the bounds as if the declaration had been:
-     * </p>
-     *
-     * <pre>&lt;K extends java.util.List&lt;String&gt;&gt;</pre>
+     * Tests if the subject type may be implicitly cast to the target
+     * parameterized type following the Java generics rules.
      *
-     * @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.
+     * @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}.
      */
-    public static Type[] normalizeUpperBounds(final Type[] bounds) {
-        Validate.notNull(bounds, "null value specified for bounds array");
-        // don't bother if there's only one (or none) type
-        if (bounds.length < 2) {
-            return bounds;
+    private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType,
+            final Map<TypeVariable<?>, 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;
         }
 
-        final Set<Type> types = new HashSet<>(bounds.length);
+        // all types are assignable to themselves
+        if (toParameterizedType.equals(type)) {
+            return true;
+        }
 
-        for (final Type type1 : bounds) {
-            boolean subtypeFound = false;
+        // 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<TypeVariable<?>, Type> fromTypeVarAssigns = getTypeArguments(type, toClass, null);
 
-            for (final Type type2 : bounds) {
-                if (type1 != type2 && isAssignable(type2, type1, null)) {
-                    subtypeFound = true;
-                    break;
-                }
+        // 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<TypeVariable<?>, 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;
             }
 
-            if (!subtypeFound) {
-                types.add(type1);
+            // 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.equals(fromTypeArg)
+                    && !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg,
+                            typeVarAssigns))) {
+                return false;
             }
         }
-
-        return types.toArray(ArrayUtils.EMPTY_TYPE_ARRAY);
+        return true;
     }
 
     /**
-     * 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}.
+     * 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 typeVariable the subject type variable, not {@code null}
-     * @return a non-empty array containing the bounds of the type variable.
+     * @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 Type[] getImplicitBounds(final TypeVariable<?> typeVariable) {
-        Validate.notNull(typeVariable, "typeVariable is null");
-        final Type[] bounds = typeVariable.getBounds();
-
-        return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds);
+    public static boolean isAssignable(final Type type, final Type toType) {
+        return isAssignable(type, toType, null);
     }
 
     /**
-     * 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}.
+     * Tests if the subject type may be implicitly cast to the target type
+     * following the Java generics rules.
      *
-     * @param wildcardType the subject wildcard type, not {@code null}
-     * @return a non-empty array containing the upper bounds of the wildcard
-     * type.
+     * @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}.
      */
-    public static Type[] getImplicitUpperBounds(final WildcardType wildcardType) {
-        Validate.notNull(wildcardType, "wildcardType is null");
-        final Type[] bounds = wildcardType.getUpperBounds();
+    private static boolean isAssignable(final Type type, final Type toType,
+            final Map<TypeVariable<?>, Type> typeVarAssigns) {
+        if (toType == null || toType instanceof Class<?>) {
+            return isAssignable(type, (Class<?>) toType);
+        }
 
-        return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds);
-    }
+        if (toType instanceof ParameterizedType) {
+            return isAssignable(type, (ParameterizedType) toType, typeVarAssigns);
+        }
 
-    /**
-     * 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 is null");
-        final Type[] bounds = wildcardType.getLowerBounds();
+        if (toType instanceof GenericArrayType) {
+            return isAssignable(type, (GenericArrayType) toType, typeVarAssigns);
+        }
 
-        return bounds.length == 0 ? new Type[] { null } : bounds;
+        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);
     }
 
     /**
-     * Determines whether or not specified types satisfy the bounds of their
-     * mapped type variables. When a type parameter extends another (such as
-     * {@code <T, S extends T>}), uses another as a type parameter (such as
-     * {@code <T, S extends Comparable>>}), or otherwise depends on
-     * another type variable to be specified, the dependencies must be included
-     * in {@code typeVarAssigns}.
+     * Tests if the subject type may be implicitly cast to the target type
+     * variable following the Java generics rules.
      *
-     * @param typeVarAssigns 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.
+     * @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}.
      */
-    public static boolean typesSatisfyVariables(final Map<TypeVariable<?>, Type> typeVarAssigns) {
-        Validate.notNull(typeVarAssigns, "typeVarAssigns is null");
-        // all types must be assignable to all the bounds of their mapped
-        // type variable.
-        for (final Map.Entry<TypeVariable<?>, Type> entry : typeVarAssigns.entrySet()) {
-            final TypeVariable<?> typeVar = entry.getKey();
-            final Type type = entry.getValue();
+    private static boolean isAssignable(final Type type, final TypeVariable<?> toTypeVariable,
+            final Map<TypeVariable<?>, Type> typeVarAssigns) {
+        if (type == null) {
+            return true;
+        }
 
-            for (final Type bound : getImplicitBounds(typeVar)) {
-                if (!isAssignable(type, substituteTypeVariables(bound, typeVarAssigns),
-                        typeVarAssigns)) {
-                    return false;
+        // 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;
                 }
             }
         }
-        return true;
-    }
-
-    /**
-     * 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);
+        if (type instanceof Class<?> || type instanceof ParameterizedType
+                || type instanceof GenericArrayType || type instanceof WildcardType) {
+            return false;
         }
 
-        return (Class<?>) rawType;
+        throw new IllegalStateException("found an unhandled type: " + type);
     }
 
     /**
-     * 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.
+     * Tests if the subject type may be implicitly cast to the target
+     * wildcard type following the Java generics rules.
      *
-     * @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
+     * @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}.
      */
-    public static Class<?> getRawType(final Type type, final Type assigningType) {
-        if (type instanceof Class<?>) {
-            // it is raw, no problem
-            return (Class<?>) type;
+    private static boolean isAssignable(final Type type, final WildcardType toWildcardType,
+            final Map<TypeVariable<?>, Type> typeVarAssigns) {
+        if (type == null) {
+            return true;
         }
 
-        if (type instanceof ParameterizedType) {
-            // simple enough to get the raw type of a ParameterizedType
-            return getRawType((ParameterizedType) type);
+        // only a null type can be assigned to null type which
+        // would have cause the previous to return true
+        if (toWildcardType == null) {
+            return false;
         }
 
-        if (type instanceof TypeVariable<?>) {
-            if (assigningType == null) {
-                return null;
-            }
+        // all types are assignable to themselves
+        if (toWildcardType.equals(type)) {
+            return true;
+        }
 
-            // get the entity declaring this type variable
-            final Object genericDeclaration = ((TypeVariable<?>) type).getGenericDeclaration();
+        final Type[] toUpperBounds = getImplicitUpperBounds(toWildcardType);
+        final Type[] toLowerBounds = getImplicitLowerBounds(toWildcardType);
 
-            // can't get the raw type of a method- or constructor-declared type
-            // variable
-            if (!(genericDeclaration instanceof Class<?>)) {
-                return null;
-            }
+        if (type instanceof WildcardType) {
+            final WildcardType wildcardType = (WildcardType) type;
+            final Type[] upperBounds = getImplicitUpperBounds(wildcardType);
+            final Type[] lowerBounds = getImplicitLowerBounds(wildcardType);
 
-            // get the type arguments for the declaring class/interface based
-            // on the enclosing type
-            final Map<TypeVariable<?>, Type> typeVarAssigns = getTypeArguments(assigningType,
-                    (Class<?>) genericDeclaration);
+            for (Type toBound : toUpperBounds) {
+                // if there are assignments for unresolved type variables,
+                // now's the time to substitute them.
+                toBound = substituteTypeVariables(toBound, typeVarAssigns);
 
-            // enclosingType has to be a subclass (or subinterface) of the
-            // declaring type
-            if (typeVarAssigns == null) {
-                return null;
+                // 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;
+                    }
+                }
             }
 
-            // get the argument assigned to this type variable
-            final Type typeArgument = typeVarAssigns.get(type);
+            for (Type toBound : toLowerBounds) {
+                // if there are assignments for unresolved type variables,
+                // now's the time to substitute them.
+                toBound = substituteTypeVariables(toBound, typeVarAssigns);
 
-            if (typeArgument == null) {
-                return null;
+                // 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;
+                    }
+                }
             }
-
-            // get the argument for this type variable
-            return getRawType(typeArgument, assigningType);
+            return true;
         }
 
-        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();
+        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;
+            }
         }
 
-        // (hand-waving) this is not the method you're looking for
-        if (type instanceof WildcardType) {
-            return null;
+        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;
+            }
         }
-
-        throw new IllegalArgumentException("unknown 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();
+        return true;
     }
 
     /**
-     * Gets the array component type of {@code type}.
+     * Tests if the given value can be assigned to the target type
+     * following the Java generics rules.
      *
-     * @param type the type to be checked
-     * @return component type or null if type is not an array type
+     * @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 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();
+    public static boolean isInstance(final Object value, final Type type) {
+        if (type == null) {
+            return false;
         }
-        return null;
+
+        return value == null ? !(type instanceof Class<?>) || !((Class<?>) type).isPrimitive()
+                : isAssignable(value.getClass(), type, null);
     }
 
     /**
-     * Gets a type representing {@code type} with variable assignments "unrolled."
+     * Maps type variables.
      *
-     * @param typeArguments as from {@link TypeUtils#getTypeArguments(Type, Class)}
-     * @param type the type to unroll variable assignments for
-     * @return Type
-     * @since 3.2
+     * @param <T> 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
      */
-    public static Type unrollVariables(Map<TypeVariable<?>, 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<TypeVariable<?>, 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();
-            }
+    private static <T> void mapTypeVariablesToArguments(final Class<T> cls,
+            final ParameterizedType parameterizedType, final Map<TypeVariable<?>, 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);
         }
-        return type;
-    }
 
-    /**
-     * 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<TypeVariable<?>, 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;
+        // 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<TypeVariable<Class<T>>> 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));
             }
         }
-        return result;
     }
 
     /**
-     * Tests, recursively, whether any of the type parameters associated with {@code type} are bound to variables.
+     * 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).
+     * 
+     * <p>
+     * Example, with the variable type declaration:
+     * </p>
      *
-     * @param type the type to check for type variables
-     * @return boolean
-     * @since 3.2
+     * <pre>&lt;K extends java.util.Collection&lt;String&gt; &amp;
+     * java.util.List&lt;String&gt;&gt;</pre>
+     *
+     * <p>
+     * since {@code List} is a subinterface of {@code Collection},
+     * this method will return the bounds as if the declaration had been:
+     * </p>
+     *
+     * <pre>&lt;K extends java.util.List&lt;String&gt;&gt;</pre>
+     *
+     * @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 boolean containsTypeVariables(final Type type) {
-        if (type instanceof TypeVariable<?>) {
-            return true;
-        }
-        if (type instanceof Class<?>) {
-            return ((Class<?>) type).getTypeParameters().length > 0;
+    public static Type[] normalizeUpperBounds(final Type[] bounds) {
+        Validate.notNull(bounds, "null value specified for bounds array");
+        // don't bother if there's only one (or none) type
+        if (bounds.length < 2) {
+            return bounds;
         }
-        if (type instanceof ParameterizedType) {
-            for (final Type arg : ((ParameterizedType) type).getActualTypeArguments()) {
-                if (containsTypeVariables(arg)) {
-                    return true;
+
+        final Set<Type> 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;
                 }
             }
-            return false;
-        }
-        if (type instanceof WildcardType) {
-            final WildcardType wild = (WildcardType) type;
-            return containsTypeVariables(getImplicitLowerBounds(wild)[0])
-                || containsTypeVariables(getImplicitUpperBounds(wild)[0]);
+
+            if (!subtypeFound) {
+                types.add(type1);
+            }
         }
-        return false;
+
+        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 typeArgMappings the mapping used for parameterization
+     * @return {@link ParameterizedType}
+     * @since 3.2
+     */
+    public static final ParameterizedType parameterize(final Class<?> rawClass,
+        final Map<TypeVariable<?>, Type> typeArgMappings) {
+        Validate.notNull(rawClass, "raw class is null");
+        Validate.notNull(typeArgMappings, "typeArgMappings is null");
+        return parameterizeWithOwner(null, rawClass,
+            extractTypeArgumentsFrom(typeArgMappings, rawClass.getTypeParameters()));
     }
 
     /**
@@ -1481,18 +1558,54 @@ public class TypeUtils {
     }
 
     /**
+     * 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 typeArgMappings the mapping used for parameterization
      * @return {@link ParameterizedType}
      * @since 3.2
      */
-    public static final ParameterizedType parameterize(final Class<?> rawClass,
+    public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class<?> rawClass,
         final Map<TypeVariable<?>, Type> typeArgMappings) {
         Validate.notNull(rawClass, "raw class is null");
         Validate.notNull(typeArgMappings, "typeArgMappings is null");
-        return parameterizeWithOwner(null, rawClass,
+        return parameterizeWithOwner(owner, rawClass,
             extractTypeArgumentsFrom(typeArgMappings, rawClass.getTypeParameters()));
     }
 
@@ -1529,179 +1642,24 @@ public class TypeUtils {
     }
 
     /**
-     * Creates a parameterized type instance.
-     *
-     * @param owner the owning type
-     * @param rawClass the raw class to create a parameterized type instance for
-     * @param typeArgMappings the mapping used for parameterization
-     * @return {@link ParameterizedType}
-     * @since 3.2
-     */
-    public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class<?> rawClass,
-        final Map<TypeVariable<?>, Type> typeArgMappings) {
-        Validate.notNull(rawClass, "raw class is null");
-        Validate.notNull(typeArgMappings, "typeArgMappings is null");
-        return parameterizeWithOwner(owner, rawClass,
-            extractTypeArgumentsFrom(typeArgMappings, rawClass.getTypeParameters()));
-    }
-
-    /**
-     * 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<TypeVariable<?>, 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;
-    }
-
-    /**
-     * Gets a {@link WildcardTypeBuilder}.
-     *
-     * @return {@link WildcardTypeBuilder}
-     * @since 3.2
-     */
-    public static WildcardTypeBuilder wildcardType() {
-        return new WildcardTypeBuilder();
-    }
-
-    /**
-     * 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 is null"));
-    }
-
-    /**
-     * 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 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 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 w}.
+     * Finds the mapping for {@code type} in {@code typeVarAssigns}.
      *
-     * @param wildcardType LHS
-     * @param type RHS
-     * @return boolean
-     * @since 3.2
+     * @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 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;
-    }
+    private static Type substituteTypeVariables(final Type type, final Map<TypeVariable<?>, Type> typeVarAssigns) {
+        if (type instanceof TypeVariable<?> && typeVarAssigns != null) {
+            final Type replacementType = typeVarAssigns.get(type);
 
-    /**
-     * 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;
-                }
+            if (replacementType == null) {
+                throw new IllegalArgumentException("missing assignment type for type variable "
+                        + type);
             }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * 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);
+            return replacementType;
         }
-        throw new IllegalArgumentException(ObjectUtils.identityToString(type));
+        return type;
     }
 
     /**
@@ -1725,63 +1683,74 @@ public class TypeUtils {
                 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(var)).toString();
-    }
-
-    /**
-     * Wraps the specified {@link Type} in a {@link Typed} wrapper.
-     *
-     * @param <T> inferred generic type
-     * @param type to wrap
-     * @return Typed&lt;T&gt;
-     * @since 3.2
-     */
-    public static <T> Typed<T> wrap(final Type type) {
-        return () -> type;
+        } else if (d instanceof Type) {// not possible as of now
+            buf.append(toString((Type) d));
+        } else {
+            buf.append(d);
+        }
+        return buf.append(':').append(typeVariableToString(var)).toString();
     }
 
-    /**
-     * Wraps the specified {@link Class} in a {@link Typed} wrapper.
-     *
-     * @param <T> generic type
-     * @param type to wrap
-     * @return Typed&lt;T&gt;
-     * @since 3.2
-     */
-    public static <T> Typed<T> wrap(final Class<T> type) {
-        return wrap((Type) type);
+    private static <T> String toString(final T object) {
+        return object instanceof Type ? toString((Type) object) : object.toString();
     }
 
     /**
-     * Formats a {@link Class} as a {@link String}.
+     * Formats a given type as a Java-esque String.
      *
-     * @param cls {@code Class} to format
+     * @param type the type to create a String representation for, not {@code null}
      * @return String
      * @since 3.2
      */
-    private static String classToString(final Class<?> cls) {
-        if (cls.isArray()) {
-            return toString(cls.getComponentType()) + "[]";
+    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));
+    }
 
-        final StringBuilder buf = new StringBuilder();
+    /**
+     * Determines whether or not specified types satisfy the bounds of their
+     * mapped type variables. When a type parameter extends another (such as
+     * {@code <T, S extends T>}), uses another as a type parameter (such as
+     * {@code <T, S extends Comparable>>}), or otherwise depends on
+     * another type variable to be specified, the dependencies must be included
+     * in {@code typeVarAssigns}.
+     *
+     * @param typeVarAssigns 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<TypeVariable<?>, Type> typeVarAssigns) {
+        Validate.notNull(typeVarAssigns, "typeVarAssigns is null");
+        // all types must be assignable to all the bounds of their mapped
+        // type variable.
+        for (final Map.Entry<TypeVariable<?>, Type> entry : typeVarAssigns.entrySet()) {
+            final TypeVariable<?> typeVar = entry.getKey();
+            final Type type = entry.getValue();
 
-        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('>');
+            for (final Type bound : getImplicitBounds(typeVar)) {
+                if (!isAssignable(type, substituteTypeVariables(bound, typeVarAssigns),
+                        typeVarAssigns)) {
+                    return false;
+                }
+            }
         }
-        return buf.toString();
+        return true;
     }
 
     /**
@@ -1802,71 +1771,101 @@ public class TypeUtils {
     }
 
     /**
-     * Formats a {@link ParameterizedType} as a {@link String}.
+     * Unrolls variables in a type bounds array.
      *
-     * @param parameterizedType {@code ParameterizedType} to format
-     * @return String
+     * @param typeArguments assignments {@link Map}
+     * @param bounds in which to expand variables
+     * @return {@code bounds} with any variables reassigned
      * @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());
+    private static Type[] unrollBounds(final Map<TypeVariable<?>, 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 {
-                builder.append(useOwner.toString());
+                result[i] = unrolled;
             }
-            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();
+        return result;
     }
 
-    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('>');
-        }
+    /**
+     * Look up {@code var} in {@code typeVarAssigns} <em>transitively</em>,
+     * i.e. keep looking until the value found is <em>not</em> 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<TypeVariable<?>, 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;
     }
 
-    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<?>) {
-                if (containsVariableTypeSameParametrizedTypeBound(((TypeVariable<?>) filteredArgumentTypes[i]),
-                    parameterizedType)) {
-                    indexesToRemove = ArrayUtils.add(indexesToRemove, i);
+    /**
+     * 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<TypeVariable<?>, 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<TypeVariable<?>, 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 indexesToRemove;
+        return type;
     }
 
-    private static boolean containsVariableTypeSameParametrizedTypeBound(final TypeVariable<?> typeVariable,
-        final ParameterizedType parameterizedType) {
-        return ArrayUtils.contains(typeVariable.getBounds(), parameterizedType);
+    /**
+     * Gets a {@link WildcardTypeBuilder}.
+     *
+     * @return {@link WildcardTypeBuilder}
+     * @since 3.2
+     */
+    public static WildcardTypeBuilder wildcardType() {
+        return new WildcardTypeBuilder();
     }
 
     /**
@@ -1889,39 +1888,40 @@ public class TypeUtils {
     }
 
     /**
-     * Formats a {@link GenericArrayType} as a {@link String}.
+     * Wraps the specified {@link Class} in a {@link Typed} wrapper.
      *
-     * @param genericArrayType {@code GenericArrayType} to format
-     * @return String
+     * @param <T> generic type
+     * @param type to wrap
+     * @return Typed&lt;T&gt;
      * @since 3.2
      */
-    private static String genericArrayTypeToString(final GenericArrayType genericArrayType) {
-        return String.format("%s[]", toString(genericArrayType.getGenericComponentType()));
+    public static <T> Typed<T> wrap(final Class<T> type) {
+        return wrap((Type) type);
     }
 
     /**
-     * Appends {@code types} to {@code builder} with separator {@code sep}.
+     * Wraps the specified {@link Type} in a {@link Typed} wrapper.
      *
-     * @param builder destination
-     * @param sep separator
-     * @param types to append
-     * @return {@code builder}
+     * @param <T> inferred generic type
+     * @param type to wrap
+     * @return Typed&lt;T&gt;
      * @since 3.2
      */
-    private static <T> 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;
+    public static <T> Typed<T> wrap(final Type type) {
+        return () -> type;
     }
 
-    private static <T> String toString(final T object) {
-        return object instanceof Type ? toString((Type) object) : object.toString();
+    /**
+     * {@code TypeUtils} instances should NOT be constructed in standard
+     * programming. Instead, the class should be used as
+     * {@code TypeUtils.isAssignable(cls, toClass)}.
+     * <p>
+     * This constructor is public to permit tools that require a JavaBean instance
+     * to operate.
+     * </p>
+     */
+    public TypeUtils() {
+        super();
     }
 
 }