[LANG-906] new type util features

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1510300 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Matthew Jason Benson 2013-08-04 18:40:18 +00:00
parent eb45c596a8
commit 019256539d
2 changed files with 561 additions and 1 deletions

View File

@ -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;
/**
* <p> 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<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
@ -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<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;
}
}

View File

@ -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<B> {
@ -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<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() {