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 of 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 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 { + +} diff --git a/src/test/org/apache/commons/lang/reflect/testbed/GenericTypeHolder.java b/src/test/org/apache/commons/lang/reflect/testbed/GenericTypeHolder.java new file mode 100644 index 000000000..d969003c4 --- /dev/null +++ b/src/test/org/apache/commons/lang/reflect/testbed/GenericTypeHolder.java @@ -0,0 +1,31 @@ +/* + * 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; + +import java.util.List; + +/** + * Holds generic testbed types. + * @author mbenson + * @version $Id$ + */ +public class GenericTypeHolder { + public GenericParent stringParent; + public GenericParent integerParent; + public List foos; + public GenericParent[] barParents; +} diff --git a/src/test/org/apache/commons/lang/reflect/testbed/StringParameterizedChild.java b/src/test/org/apache/commons/lang/reflect/testbed/StringParameterizedChild.java new file mode 100644 index 000000000..1bc1a2473 --- /dev/null +++ b/src/test/org/apache/commons/lang/reflect/testbed/StringParameterizedChild.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; + +/** + * {@link GenericParent} subclass that explicitly specifies as {@link String}. + * @author mbenson + * @version $Id$ + */ +public class StringParameterizedChild extends GenericParent { + +}