You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by mb...@apache.org on 2013/08/04 20:40:19 UTC

svn commit: r1510300 - in /commons/proper/lang/trunk/src: main/java/org/apache/commons/lang3/reflect/TypeUtils.java test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java

Author: mbenson
Date: Sun Aug  4 18:40:18 2013
New Revision: 1510300

URL: http://svn.apache.org/r1510300
Log:
[LANG-906] new type util features

Modified:
    commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
    commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java

Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java?rev=1510300&r1=1510299&r2=1510300&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java (original)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java Sun Aug  4 18:40:18 2013
@@ -18,6 +18,7 @@ package org.apache.commons.lang3.reflect
 
 import java.lang.reflect.Array;
 import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.GenericDeclaration;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
@@ -29,7 +30,11 @@ import java.util.List;
 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;
 
 /**
  * <p> Utility methods focusing on type inspection, particularly with regard to
@@ -41,6 +46,156 @@ import org.apache.commons.lang3.ClassUti
 public class TypeUtils {
 
     /**
+     * {@link WildcardType} builder.
+     */
+    public static class WildcardTypeBuilder implements Builder<WildcardType> {
+        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();
+
+    /**
      * <p> TypeUtils instances should NOT be constructed in standard
      * programming. Instead, the class should be used as
      * <code>TypeUtils.isAssignable(cls, toClass)</code>. </p> <p> This
@@ -1098,4 +1253,365 @@ public class TypeUtils {
         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<TypeVariable<?>, 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<TypeVariable<?>, Type> parameterizedTypeArguments;
+                if (p.getOwnerType() == null) {
+                    parameterizedTypeArguments = typeArguments;
+                } else {
+                    parameterizedTypeArguments = new HashMap<TypeVariable<?>, 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<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;
+            }
+        }
+        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<TypeVariable<?>, 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<TypeVariable<?>, Type> typeArgMappings) {
+        return parameterizeWithOwner(owner, raw, extractTypeArgumentsFrom(typeArgMappings, raw.getTypeParameters()));
+    }
+
+    private static Type[] extractTypeArgumentsFrom(Map<TypeVariable<?>, 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;
+    }
+
 }

Modified: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java?rev=1510300&r1=1510299&r2=1510300&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java (original)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java Sun Aug  4 18:40:18 2013
@@ -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 @@ import org.junit.Test;
  * Test TypeUtils
  * @version $Id$
  */
-@SuppressWarnings({ "unchecked", "unused" , "rawtypes", "null"})
+@SuppressWarnings({ "unchecked", "unused" , "rawtypes" })
 //raw types, where used, are used purposely
 public class TypeUtilsTest<B> {
 
@@ -672,6 +678,44 @@ public class TypeUtilsTest<B> {
         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<java.lang.String>", 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<Integer, ? extends Collection<?>>> iterable;
 
     public static <G extends Comparable<G>> G stub() {