diff --git a/src/java/org/apache/commons/lang/reflect/TypeUtils.java b/src/java/org/apache/commons/lang/reflect/TypeUtils.java new file mode 100644 index 000000000..79d3a4d4e --- /dev/null +++ b/src/java/org/apache/commons/lang/reflect/TypeUtils.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang.reflect; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +import org.apache.commons.lang.Validate; + +/** + *
Utility methods focusing on type inspection, particularly with regard to + * generics.
+ * @author James Carman + * @author Matt Benson + * @since 3.0 + * @version $Id$ + */ +public class TypeUtils { + + /** + * Get 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 oftype
: 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 enclosingType context
+ * @param type to read
+ * @return Class>
+ */
+ // original code stolen from commons [proxy]'s 2.0 branch, then kneaded until firm
+ public static Class> getRawType(Type enclosingType, Type type) {
+ if (type instanceof Class>) {
+ // it is raw, no problem
+ return (Class>) type;
+ }
+ if (type instanceof ParameterizedType) {
+ // simple enough to get the raw type of a ParameterizedType
+ return (Class>) ((ParameterizedType) type).getRawType();
+ }
+ if (type instanceof TypeVariable>) {
+ Validate.notNull(enclosingType,
+ "Cannot get raw type of TypeVariable without enclosing type");
+ // resolve the variable against the enclosing type, hope for the best (casting)
+ return (Class>) resolveVariable(enclosingType, (TypeVariable>) type);
+ }
+ if (type instanceof GenericArrayType) {
+ Validate.notNull(enclosingType,
+ "Cannot get raw type of GenericArrayType without enclosing type");
+ // not included in original code, but not too difficult: just have to get raw component type...
+ Class> rawComponentType = getRawType(enclosingType, ((GenericArrayType) type)
+ .getGenericComponentType());
+ // ...and know how to reflectively create array types, uncommon but not unheard of:
+ return Array.newInstance(rawComponentType, 0).getClass();
+ }
+ throw new IllegalArgumentException(String.valueOf(type));
+ }
+
+ /**
+ * We plan to return Class> from the top-level call, as evidenced by the
+ * cast in the above method, but to handle recursion and falling back up the
+ * graph, as it were, return Type
+ * @param enclosingType
+ * @param typeVar
+ * @return Type resolved
+ */
+ // original code stolen from commons [proxy]'s 2.0 branch, then kneaded until firm
+ private static Type resolveVariable(Type enclosingType, TypeVariable> typeVar) {
+ if (enclosingType instanceof ParameterizedType) {
+ ParameterizedType parameterizedEnclosingType = (ParameterizedType) enclosingType;
+ TypeVariable>[] typeVariables = getRawType(null,
+ parameterizedEnclosingType.getRawType()).getTypeParameters();
+ //look for the matching variable:
+ for (int i = 0; i < typeVariables.length; i++) {
+ if (typeVariables[i].equals(typeVar)) {
+ return parameterizedEnclosingType.getActualTypeArguments()[i];
+ }
+ }
+ //otherwise recurse to try against raw class
+ Type result = resolveVariable(parameterizedEnclosingType.getRawType(), typeVar);
+ //unroll variable if returned
+ if (result instanceof TypeVariable>) {
+ return resolveVariable(enclosingType, (TypeVariable>) result);
+ }
+ return result;
+ }
+ if (enclosingType instanceof Class>) {
+ Class> enclosingClass = (Class>) enclosingType;
+ Type result = null;
+ Type genericSuperclass = enclosingClass.getGenericSuperclass();
+ if (genericSuperclass != null && !Object.class.equals(genericSuperclass)) {
+ result = resolveVariable(genericSuperclass, typeVar);
+ }
+ if (result == null) {
+ for (Type genericInterface : enclosingClass.getGenericInterfaces()) {
+ result = resolveVariable(genericInterface, typeVar);
+ if (result != null) {
+ break;
+ }
+ }
+ }
+ if (result != null) {
+ return result;
+ }
+ }
+ throw new IllegalArgumentException(String.valueOf(typeVar));
+ }
+
+}
diff --git a/src/test/org/apache/commons/lang/reflect/TypeUtilsTest.java b/src/test/org/apache/commons/lang/reflect/TypeUtilsTest.java
new file mode 100644
index 000000000..dc67cbd3a
--- /dev/null
+++ b/src/test/org/apache/commons/lang/reflect/TypeUtilsTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.reflect;
+
+import static junit.framework.Assert.*;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.TypeVariable;
+import java.util.List;
+
+import org.apache.commons.lang.reflect.testbed.*;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test TypeUtils
+ * @author mbenson
+ * @version $Id$
+ */
+public class TypeUtilsTest {
+ private Field stringParentField;
+ private Field integerParentField;
+ private Field foosField;
+ private Field barParentsField;
+ private TypeVariable> genericParentT;
+ private TypeVariable> listType;
+ private TypeVariable> iterableType;
+
+ @Before
+ public void setup() throws Exception {
+ stringParentField = GenericTypeHolder.class.getDeclaredField("stringParent");
+ integerParentField = GenericTypeHolder.class.getDeclaredField("integerParent");
+ foosField = GenericTypeHolder.class.getDeclaredField("foos");
+ barParentsField = GenericTypeHolder.class.getDeclaredField("barParents");
+ genericParentT = GenericParent.class.getTypeParameters()[0];
+ listType = List.class.getTypeParameters()[0];
+ iterableType = Iterable.class.getTypeParameters()[0];
+ }
+
+ @Test
+ public void testGetRawTypeClass() throws Exception {
+ assertEquals(GenericParent.class, TypeUtils.getRawType(null, GenericParent.class));
+ }
+
+ @Test
+ public void testGetRawTypeParameterizedType() throws Exception {
+ assertEquals(GenericParent.class, TypeUtils.getRawType(GenericTypeHolder.class,
+ stringParentField.getGenericType()));
+ assertEquals(GenericParent.class, TypeUtils.getRawType(GenericTypeHolder.class,
+ integerParentField.getGenericType()));
+ assertEquals(List.class, TypeUtils.getRawType(GenericTypeHolder.class, foosField
+ .getGenericType()));
+ }
+
+ @Test
+ public void testGetRawTypeTypeVariable() throws Exception {
+ assertEquals(String.class, TypeUtils.getRawType(StringParameterizedChild.class,
+ genericParentT));
+ assertEquals(String.class, TypeUtils.getRawType(stringParentField.getGenericType(),
+ genericParentT));
+ assertEquals(Foo.class, TypeUtils.getRawType(foosField.getGenericType(), iterableType));
+ assertEquals(Foo.class, TypeUtils.getRawType(foosField.getGenericType(), listType));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetRawTypeUnresolvableTypeVariable() {
+ TypeUtils.getRawType(GenericParent.class, genericParentT);
+ }
+
+ @Test
+ public void testGetRawTypeGenericArray() throws Exception {
+ assertEquals(GenericParent[].class, TypeUtils.getRawType(GenericTypeHolder.class,
+ barParentsField.getGenericType()));
+ }
+}
diff --git a/src/test/org/apache/commons/lang/reflect/testbed/GenericParent.java b/src/test/org/apache/commons/lang/reflect/testbed/GenericParent.java
new file mode 100644
index 000000000..092b69704
--- /dev/null
+++ b/src/test/org/apache/commons/lang/reflect/testbed/GenericParent.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang.reflect.testbed;
+
+/**
+ * Class declaring a parameter variable.
+ * @author mbenson
+ * @version $Id$
+ */
+public class GenericParent