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 58eef3c7c..3c3517121 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
@@ -18,6 +18,7 @@
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
@@ -29,7 +30,11 @@
import java.util.Map;
import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.builder.Builder;
/**
*
Utility methods focusing on type inspection, particularly with regard to
@@ -40,6 +45,156 @@
*/
public class TypeUtils {
+ /**
+ * {@link WildcardType} builder.
+ */
+ public static class WildcardTypeBuilder implements Builder {
+ private WildcardTypeBuilder() {
+ }
+
+ private Type[] upperBounds;
+ private Type[] lowerBounds;
+
+ public WildcardTypeBuilder withUpperBounds(Type... bounds) {
+ this.upperBounds = bounds;
+ return this;
+ }
+
+ public WildcardTypeBuilder withLowerBounds(Type... bounds) {
+ this.lowerBounds = bounds;
+ return this;
+ }
+
+ @Override
+ public WildcardType build() {
+ return new WildcardTypeImpl(upperBounds, lowerBounds);
+ }
+ }
+
+ private static final class GenericArrayTypeImpl implements GenericArrayType {
+ private final Type componentType;
+
+ private GenericArrayTypeImpl(Type componentType) {
+ this.componentType = componentType;
+ }
+
+ @Override
+ public Type getGenericComponentType() {
+ return componentType;
+ }
+
+ @Override
+ public String toString() {
+ return TypeUtils.toString(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this || obj instanceof GenericArrayType && TypeUtils.equals(this, (GenericArrayType) obj);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 67 << 4;
+ result |= componentType.hashCode();
+ return result;
+ }
+ }
+
+ private static final class ParameterizedTypeImpl implements ParameterizedType {
+ private final Class> raw;
+ private final Type useOwner;
+ private final Type[] typeArguments;
+
+ private ParameterizedTypeImpl(Class> raw, Type useOwner, Type[] typeArguments) {
+ this.raw = raw;
+ this.useOwner = useOwner;
+ this.typeArguments = typeArguments;
+ }
+
+ @Override
+ public Type getRawType() {
+ return raw;
+ }
+
+ @Override
+ public Type getOwnerType() {
+ return useOwner;
+ }
+
+ @Override
+ public Type[] getActualTypeArguments() {
+ return typeArguments.clone();
+ }
+
+ @Override
+ public String toString() {
+ return TypeUtils.toString(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this || obj instanceof ParameterizedType && TypeUtils.equals(this, ((ParameterizedType) obj));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 71 << 4;
+ result |= raw.hashCode();
+ result <<= 4;
+ result |= ObjectUtils.hashCode(useOwner);
+ result <<= 8;
+ result |= Arrays.hashCode(typeArguments);
+ return result;
+ }
+ }
+
+ private static final class WildcardTypeImpl implements WildcardType {
+ private static final Type[] EMPTY_BOUNDS = new Type[0];
+
+ private final Type[] upperBounds;
+ private final Type[] lowerBounds;
+
+ private WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
+ this.upperBounds = ObjectUtils.defaultIfNull(upperBounds, EMPTY_BOUNDS);
+ this.lowerBounds = ObjectUtils.defaultIfNull(lowerBounds, EMPTY_BOUNDS);
+ }
+
+ @Override
+ public Type[] getUpperBounds() {
+ return upperBounds.clone();
+ }
+
+ @Override
+ public Type[] getLowerBounds() {
+ return lowerBounds.clone();
+ }
+
+ @Override
+ public String toString() {
+ return TypeUtils.toString(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this || obj instanceof WildcardType && TypeUtils.equals(this, (WildcardType) obj);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 73 << 8;
+ result |= Arrays.hashCode(upperBounds);
+ result <<= 8;
+ result |= Arrays.hashCode(lowerBounds);
+ return result;
+ }
+ }
+
+ /**
+ * A wildcard instance matching {@code ?}.
+ */
+ public static final WildcardType WILDCARD_ALL = wildcardType().withUpperBounds(Object.class).build();
+
/**
* TypeUtils instances should NOT be constructed in standard
* programming. Instead, the class should be used as
@@ -1098,4 +1253,365 @@ public static Type getArrayComponentType(final Type type) {
return null;
}
+ /**
+ * Get a type representing {@code type} with variable assignments "unrolled."
+ *
+ * @param typeArguments as from {@link TypeUtils#getTypeArguments(Type, Class)}
+ * @param type
+ * @return Type
+ */
+ public static Type unrollVariables(Map, Type> typeArguments, final Type type) {
+ if (containsTypeVariables(type)) {
+ if (type instanceof TypeVariable>) {
+ return unrollVariables(typeArguments, typeArguments.get(type));
+ }
+ if (type instanceof ParameterizedType) {
+ final ParameterizedType p = (ParameterizedType) type;
+ final Map, Type> parameterizedTypeArguments;
+ if (p.getOwnerType() == null) {
+ parameterizedTypeArguments = typeArguments;
+ } else {
+ parameterizedTypeArguments = new HashMap, Type>(typeArguments);
+ parameterizedTypeArguments.putAll(TypeUtils.getTypeArguments(p));
+ }
+ final Type[] args = p.getActualTypeArguments();
+ for (int i = 0; i < args.length; i++) {
+ final Type unrolled = unrollVariables(parameterizedTypeArguments, args[i]);
+ if (unrolled != null) {
+ args[i] = unrolled;
+ }
+ }
+ return parameterizeWithOwner(p.getOwnerType(), (Class>) p.getRawType(), args);
+ }
+ if (type instanceof WildcardType) {
+ final WildcardType wild = (WildcardType) type;
+ return wildcardType().withUpperBounds(unrollBounds(typeArguments, wild.getUpperBounds()))
+ .withLowerBounds(unrollBounds(typeArguments, wild.getLowerBounds())).build();
+ }
+ }
+ return type;
+ }
+
+ private static Type[] unrollBounds(final Map, Type> typeArguments, final Type[] bounds) {
+ Type[] result = bounds;
+ int i = 0;
+ for (; i < result.length; i++) {
+ final Type unrolled = unrollVariables(typeArguments, result[i]);
+ if (unrolled == null) {
+ result = ArrayUtils.remove(result, i--);
+ } else {
+ result[i] = unrolled;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Learn, recursively, whether any of the type parameters associated with {@code type} are bound to variables.
+ *
+ * @param type
+ * @return boolean
+ */
+ public static boolean containsTypeVariables(Type type) {
+ if (type instanceof TypeVariable>) {
+ return true;
+ }
+ if (type instanceof Class>) {
+ return ((Class>) type).getTypeParameters().length > 0;
+ }
+ if (type instanceof ParameterizedType) {
+ for (Type arg : ((ParameterizedType) type).getActualTypeArguments()) {
+ if (containsTypeVariables(arg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if (type instanceof WildcardType) {
+ WildcardType wild = (WildcardType) type;
+ return containsTypeVariables(TypeUtils.getImplicitLowerBounds(wild)[0])
+ || containsTypeVariables(TypeUtils.getImplicitUpperBounds(wild)[0]);
+ }
+ return false;
+ }
+
+ /**
+ * Create a parameterized type instance.
+ *
+ * @param raw
+ * @param typeArguments
+ * @return {@link ParameterizedType}
+ */
+ public static final ParameterizedType parameterize(final Class> raw, final Type... typeArguments) {
+ return parameterizeWithOwner(null, raw, typeArguments);
+ }
+
+ /**
+ * Create a parameterized type instance.
+ *
+ * @param raw
+ * @param typeArgMappings
+ * @return {@link ParameterizedType}
+ */
+ public static final ParameterizedType parameterize(final Class> raw,
+ final Map, Type> typeArgMappings) {
+ return parameterizeWithOwner(null, raw, extractTypeArgumentsFrom(typeArgMappings, raw.getTypeParameters()));
+ }
+
+ /**
+ * Create a parameterized type instance.
+ *
+ * @param owner
+ * @param raw
+ * @param typeArguments
+ *
+ * @return {@link ParameterizedType}
+ */
+ public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class> raw,
+ final Type... typeArguments) {
+ Validate.notNull(raw, "raw class");
+ final Type useOwner;
+ if (raw.getEnclosingClass() == null) {
+ Validate.isTrue(owner == null, "no owner allowed for top-level %s", raw);
+ useOwner = null;
+ } else if (owner == null) {
+ useOwner = raw.getEnclosingClass();
+ } else {
+ Validate.isTrue(TypeUtils.isAssignable(owner, raw.getEnclosingClass()),
+ "%s is invalid owner type for parameterized %s", owner, raw);
+ useOwner = owner;
+ }
+ Validate.isTrue(raw.getTypeParameters().length == Validate.noNullElements(typeArguments,
+ "null type argument at index %s").length);
+
+ return new ParameterizedTypeImpl(raw, useOwner, typeArguments);
+ }
+
+ /**
+ * Create a parameterized type instance.
+ *
+ * @param owner
+ * @param raw
+ * @param typeArgMappings
+ * @return {@link ParameterizedType}
+ */
+ public static final ParameterizedType parameterizeWithOwner(final Type owner, final Class> raw,
+ final Map, Type> typeArgMappings) {
+ return parameterizeWithOwner(owner, raw, extractTypeArgumentsFrom(typeArgMappings, raw.getTypeParameters()));
+ }
+
+ private static Type[] extractTypeArgumentsFrom(Map, Type> mappings, TypeVariable>[] variables) {
+ final Type[] result = new Type[variables.length];
+ int index = 0;
+ for (TypeVariable> var : variables) {
+ Validate.isTrue(mappings.containsKey(var), "missing argument mapping for %s", toString(var));
+ result[index++] = mappings.get(var);
+ }
+ return result;
+ }
+
+ /**
+ * Get a {@link WildcardTypeBuilder}.
+ * @return {@link WildcardTypeBuilder}
+ */
+ public static WildcardTypeBuilder wildcardType() {
+ return new WildcardTypeBuilder();
+ }
+
+ /**
+ * Create a generic array type instance.
+ *
+ * @param componentType
+ * @return {@link GenericArrayType}
+ */
+ public static GenericArrayType genericArrayType(final Type componentType) {
+ return new GenericArrayTypeImpl(componentType);
+ }
+
+ /**
+ * Check equality of types.
+ *
+ * @param t1
+ * @param t2
+ * @return boolean
+ */
+ public static boolean equals(Type t1, Type t2) {
+ if (ObjectUtils.equals(t1, t2)) {
+ return true;
+ }
+ if (t1 instanceof ParameterizedType) {
+ return equals((ParameterizedType) t1, t2);
+ }
+ if (t1 instanceof GenericArrayType) {
+ return equals((GenericArrayType) t1, t2);
+ }
+ if (t1 instanceof WildcardType) {
+ return equals((WildcardType) t1, t2);
+ }
+ return false;
+ }
+
+ private static boolean equals(ParameterizedType p, Type t) {
+ if (t instanceof ParameterizedType) {
+ final ParameterizedType other = (ParameterizedType) t;
+ if (equals(p.getRawType(), other.getRawType()) && equals(p.getOwnerType(), other.getOwnerType())) {
+ return equals(p.getActualTypeArguments(), other.getActualTypeArguments());
+ }
+ }
+ return false;
+ }
+
+ private static boolean equals(GenericArrayType a, Type t) {
+ return t instanceof GenericArrayType
+ && equals(a.getGenericComponentType(), ((GenericArrayType) t).getGenericComponentType());
+ }
+
+ private static boolean equals(WildcardType w, Type t) {
+ if (t instanceof WildcardType) {
+ final WildcardType other = (WildcardType) t;
+ return equals(w.getLowerBounds(), other.getLowerBounds())
+ && equals(TypeUtils.getImplicitUpperBounds(w), TypeUtils.getImplicitUpperBounds(other));
+ }
+ return true;
+ }
+
+ private static boolean equals(Type[] t1, Type[] t2) {
+ if (t1.length == t2.length) {
+ for (int i = 0; i < t1.length; i++) {
+ if (!equals(t1[i], t2[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Present a given type as a Java-esque String.
+ *
+ * @param type
+ * @return String
+ */
+ public static String toString(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));
+ }
+
+ /**
+ * Format a {@link TypeVariable} including its {@link GenericDeclaration}.
+ *
+ * @param var
+ * @return String
+ */
+ public static String toLongString(TypeVariable> var) {
+ final StringBuilder buf = new StringBuilder();
+ final GenericDeclaration d = ((TypeVariable>) var).getGenericDeclaration();
+ if (d instanceof Class>) {
+ Class> c = (Class>) d;
+ while (true) {
+ if (c.getEnclosingClass() == null) {
+ buf.insert(0, c.getName());
+ break;
+ }
+ buf.insert(0, c.getSimpleName()).insert(0, '.');
+ c = c.getEnclosingClass();
+ }
+ } else if (d instanceof Type) {// not possible as of now
+ buf.append(toString((Type) d));
+ } else {
+ buf.append(d);
+ }
+ return buf.append(':').append(typeVariableToString(var)).toString();
+ }
+
+ private static String classToString(Class> c) {
+ final StringBuilder buf = new StringBuilder();
+
+ if (c.getEnclosingClass() != null) {
+ buf.append(classToString(c.getEnclosingClass())).append('.').append(c.getSimpleName());
+ } else {
+ buf.append(c.getName());
+ }
+ if (c.getTypeParameters().length > 0) {
+ buf.append('<');
+ appendAllTo(buf, ", ", c.getTypeParameters());
+ buf.append('>');
+ }
+ return buf.toString();
+ }
+
+ private static String typeVariableToString(TypeVariable> v) {
+ final StringBuilder buf = new StringBuilder(v.getName());
+ final Type[] bounds = v.getBounds();
+ if (bounds.length > 0 && !(bounds.length == 1 && Object.class.equals(bounds[0]))) {
+ buf.append(" extends ");
+ appendAllTo(buf, " & ", v.getBounds());
+ }
+ return buf.toString();
+ }
+
+ private static String parameterizedTypeToString(ParameterizedType p) {
+ final StringBuilder buf = new StringBuilder();
+
+ final Type useOwner = p.getOwnerType();
+ final Class> raw = (Class>) p.getRawType();
+ final Type[] typeArguments = p.getActualTypeArguments();
+ if (useOwner == null) {
+ buf.append(raw.getName());
+ } else {
+ if (useOwner instanceof Class>) {
+ buf.append(((Class>) useOwner).getName());
+ } else {
+ buf.append(useOwner.toString());
+ }
+ buf.append('.').append(raw.getSimpleName());
+ }
+
+ appendAllTo(buf.append('<'), ", ", typeArguments).append('>');
+ return buf.toString();
+ }
+
+ private static String wildcardTypeToString(WildcardType w) {
+ final StringBuilder buf = new StringBuilder().append('?');
+ final Type[] lowerBounds = w.getLowerBounds();
+ final Type[] upperBounds = w.getUpperBounds();
+ if (lowerBounds.length > 0) {
+ appendAllTo(buf.append(" super "), " & ", lowerBounds);
+ } else if (!(upperBounds.length == 1 && Object.class.equals(upperBounds[0]))) {
+ appendAllTo(buf.append(" extends "), " & ", upperBounds);
+ }
+ return buf.toString();
+ }
+
+ private static String genericArrayTypeToString(GenericArrayType g) {
+ return String.format("%s[]", toString(g.getGenericComponentType()));
+ }
+
+ private static StringBuilder appendAllTo(StringBuilder buf, String sep, Type... types) {
+ Validate.notEmpty(Validate.noNullElements(types));
+ if (types.length > 0) {
+ buf.append(toString(types[0]));
+ for (int i = 1; i < types.length; i++) {
+ buf.append(sep).append(toString(types[i]));
+ }
+ }
+ return buf;
+ }
+
}
diff --git a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
index e65ba73f4..dc8de61e0 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
@@ -17,19 +17,25 @@
package org.apache.commons.lang3.reflect;
import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.lang3.reflect.TypeUtils.WildcardTypeBuilder;
import org.apache.commons.lang3.reflect.testbed.Foo;
import org.apache.commons.lang3.reflect.testbed.GenericParent;
import org.apache.commons.lang3.reflect.testbed.GenericTypeHolder;
@@ -41,7 +47,7 @@
* Test TypeUtils
* @version $Id$
*/
-@SuppressWarnings({ "unchecked", "unused" , "rawtypes", "null"})
+@SuppressWarnings({ "unchecked", "unused" , "rawtypes" })
//raw types, where used, are used purposely
public class TypeUtilsTest {
@@ -672,6 +678,44 @@ public void testLang820() throws Exception {
Assert.assertArrayEquals(expectedArray, TypeUtils.normalizeUpperBounds(typeArray));
}
+ @Test
+ public void testParameterize() throws Exception {
+ final ParameterizedType stringComparableType = TypeUtils.parameterize(Comparable.class, String.class);
+ Assert.assertTrue(TypeUtils.equals(getClass().getField("stringComparable").getGenericType(),
+ stringComparableType));
+ Assert.assertEquals("java.lang.Comparable", stringComparableType.toString());
+ }
+
+ @Test
+ public void testParameterizeWithOwner() throws Exception {
+ final Type owner = TypeUtils.parameterize(TypeUtilsTest.class, String.class);
+ final ParameterizedType dat2Type = TypeUtils.parameterizeWithOwner(owner, That.class, String.class, String.class);
+ Assert.assertTrue(TypeUtils.equals(getClass().getField("dat2").getGenericType(), dat2Type));
+ }
+
+ @Test
+ public void testWildcardType() throws Exception {
+ final WildcardType simpleWildcard = TypeUtils.wildcardType().withUpperBounds(String.class).build();
+ final Field cClass = AClass.class.getField("cClass");
+ Assert.assertTrue(TypeUtils.equals(((ParameterizedType) cClass.getGenericType()).getActualTypeArguments()[0],
+ simpleWildcard));
+ }
+
+ @Test
+ public void testGenericArrayType() throws Exception {
+ final Type expected = getClass().getField("intWildcardComparable").getGenericType();
+ final GenericArrayType actual =
+ TypeUtils.genericArrayType(TypeUtils.parameterize(Comparable.class, TypeUtils.wildcardType()
+ .withUpperBounds(Integer.class).build()));
+ Assert.assertTrue(TypeUtils.equals(expected, actual));
+ Assert.assertEquals("java.lang.Comparable extends java.lang.Integer>[]", actual.toString());
+ }
+
+ @Test
+ public void testToLongString() {
+ Assert.assertEquals(getClass().getName() + ":B", TypeUtils.toLongString(getClass().getTypeParameters()[0]));
+ }
+
public Iterable extends Map>> iterable;
public static > G stub() {