You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2019/01/16 18:07:12 UTC
tapestry-5 git commit: Revert "TAP5-2453: move GenericsUtils (and
tests) to plastic"
Repository: tapestry-5
Updated Branches:
refs/heads/master ebdb52a19 -> faf3b20c9
Revert "TAP5-2453: move GenericsUtils (and tests) to plastic"
This reverts commit 5c617af6b3bfef81d79c573aec0acf29e1d0eb8c.
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/faf3b20c
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/faf3b20c
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/faf3b20c
Branch: refs/heads/master
Commit: faf3b20c9c8f59e890bea4eff5ce8e42df083815
Parents: ebdb52a
Author: Thiago H. de Paula Figueiredo <th...@arsmachina.com.br>
Authored: Wed Jan 16 16:06:45 2019 -0200
Committer: Thiago H. de Paula Figueiredo <th...@arsmachina.com.br>
Committed: Wed Jan 16 16:06:45 2019 -0200
----------------------------------------------------------------------
.../ioc/internal/util/GenericsUtils.java | 643 +++++++++++++++++++
.../ioc/internal/util/GenericsUtils.java | 643 -------------------
.../groovy/ioc/specs/GenericUtilsSpec.groovy | 40 --
.../ioc/internal/util/BaseGenericBean.java | 30 -
.../ioc/internal/util/NonGenericBean.java | 30 -
.../tapestry5/ioc/internal/util/Pair.java | 42 --
.../tapestry5/ioc/internal/util/StringBean.java | 20 -
.../ioc/internal/util/StringLongPair.java | 19 -
.../groovy/ioc/specs/GenericUtilsSpec.groovy | 40 ++
.../ioc/internal/util/BaseGenericBean.java | 30 +
.../ioc/internal/util/NonGenericBean.java | 30 +
.../tapestry5/ioc/internal/util/StringBean.java | 20 +
12 files changed, 763 insertions(+), 824 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
new file mode 100644
index 0000000..abbb767
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
@@ -0,0 +1,643 @@
+// Licensed 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.tapestry5.ioc.internal.util;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.GenericDeclaration;
+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.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Static methods related to the use of JDK 1.5 generics.
+ */
+@SuppressWarnings("unchecked")
+public class GenericsUtils
+{
+ /**
+ * Analyzes the method in the context of containingClass and returns the Class that is represented by
+ * the method's generic return type. Any parameter information in the generic return type is lost. If you want
+ * to preserve the type parameters of the return type consider using
+ * {@link #extractActualType(java.lang.reflect.Type, java.lang.reflect.Method)}.
+ *
+ * @param containingClass class which either contains or inherited the method
+ * @param method method from which to extract the return type
+ * @return the class represented by the methods generic return type, resolved based on the context .
+ * @see #extractActualType(java.lang.reflect.Type, java.lang.reflect.Method)
+ * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+ * @see #asClass(java.lang.reflect.Type)
+ */
+ public static Class<?> extractGenericReturnType(Class<?> containingClass, Method method)
+ {
+ return asClass(resolve(method.getGenericReturnType(), containingClass));
+ }
+
+
+ /**
+ * Analyzes the field in the context of containingClass and returns the Class that is represented by
+ * the field's generic type. Any parameter information in the generic type is lost, if you want
+ * to preserve the type parameters of the return type consider using
+ * {@link #getTypeVariableIndex(java.lang.reflect.TypeVariable)}.
+ *
+ * @param containingClass class which either contains or inherited the field
+ * @param field field from which to extract the type
+ * @return the class represented by the field's generic type, resolved based on the containingClass.
+ * @see #extractActualType(java.lang.reflect.Type, java.lang.reflect.Field)
+ * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+ * @see #asClass(java.lang.reflect.Type)
+ */
+ public static Class extractGenericFieldType(Class containingClass, Field field)
+ {
+ return asClass(resolve(field.getGenericType(), containingClass));
+ }
+
+ /**
+ * Analyzes the method in the context of containingClass and returns the Class that is represented by
+ * the method's generic return type. Any parameter information in the generic return type is lost.
+ *
+ * @param containingType Type which is/represents the class that either contains or inherited the method
+ * @param method method from which to extract the generic return type
+ * @return the generic type represented by the methods generic return type, resolved based on the containingType.
+ * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+ */
+ public static Type extractActualType(Type containingType, Method method)
+ {
+ return resolve(method.getGenericReturnType(), containingType);
+ }
+
+ /**
+ * Analyzes the method in the context of containingClass and returns the Class that is represented by
+ * the method's generic return type. Any parameter information in the generic return type is lost.
+ *
+ * @param containingType Type which is/represents the class that either contains or inherited the field
+ * @param field field from which to extract the generic return type
+ * @return the generic type represented by the methods generic return type, resolved based on the containingType.
+ * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+ */
+ public static Type extractActualType(Type containingType, Field field)
+ {
+ return resolve(field.getGenericType(), containingType);
+ }
+
+ /**
+ * Resolves the type parameter based on the context of the containingType.
+ *
+ * {@link java.lang.reflect.TypeVariable} will be unwrapped to the type argument resolved form the class
+ * hierarchy. This may be something other than a simple Class if the type argument is a ParameterizedType for
+ * instance (e.g. {@code List<E>; List<Map<Long, String>>}, E would be returned as a ParameterizedType with the raw
+ * type Map and type arguments Long and String.
+ *
+ *
+ * @param type
+ * the generic type (ParameterizedType, GenericArrayType, WildcardType, TypeVariable) to be resolved
+ * @param containingType
+ * the type which his
+ * @return
+ * the type resolved to the best of our ability.
+ * @since 5.2.?
+ */
+ public static Type resolve(final Type type, final Type containingType)
+ {
+ // The type isn't generic. (String, Long, etc)
+ if (type instanceof Class)
+ return type;
+
+ // List<T>, List<String>, List<T extends Number>
+ if (type instanceof ParameterizedType)
+ return resolve((ParameterizedType) type, containingType);
+
+ // T[], List<String>[], List<T>[]
+ if (type instanceof GenericArrayType)
+ return resolve((GenericArrayType) type, containingType);
+
+ // List<? extends T>, List<? extends Object & Comparable & Serializable>
+ if (type instanceof WildcardType)
+ return resolve((WildcardType) type, containingType);
+
+ // T
+ if (type instanceof TypeVariable)
+ return resolve((TypeVariable) type, containingType);
+
+ // I'm leaning towards an exception here.
+ return type;
+ }
+
+
+ /**
+ * Determines if the suspected super type is assignable from the suspected sub type.
+ *
+ * @param suspectedSuperType
+ * e.g. {@code GenericDAO<Pet, String>}
+ * @param suspectedSubType
+ * e.g. {@code PetDAO extends GenericDAO<Pet,String>}
+ * @return
+ * true if (sourceType)targetClass is a valid cast
+ */
+ public static boolean isAssignableFrom(Type suspectedSuperType, Type suspectedSubType)
+ {
+ final Class suspectedSuperClass = asClass(suspectedSuperType);
+ final Class suspectedSubClass = asClass(suspectedSubType);
+
+ // The raw types need to be compatible.
+ if (!suspectedSuperClass.isAssignableFrom(suspectedSubClass))
+ {
+ return false;
+ }
+
+ // From this point we know that the raw types are assignable.
+ // We need to figure out what the generic parameters in the targetClass are
+ // as they pertain to the sourceType.
+
+ if (suspectedSuperType instanceof WildcardType)
+ {
+ // ? extends Number
+ // needs to match all the bounds (there will only be upper bounds or lower bounds
+ for (Type t : ((WildcardType) suspectedSuperType).getUpperBounds())
+ {
+ if (!isAssignableFrom(t, suspectedSubType)) return false;
+ }
+ for (Type t : ((WildcardType) suspectedSuperType).getLowerBounds())
+ {
+ if (!isAssignableFrom(suspectedSubType, t)) return false;
+ }
+ return true;
+ }
+
+ Type curType = suspectedSubType;
+ Class curClass;
+
+ while (curType != null && !curType.equals(Object.class))
+ {
+ curClass = asClass(curType);
+
+ if (curClass.equals(suspectedSuperClass))
+ {
+ final Type resolved = resolve(curType, suspectedSubType);
+
+ if (suspectedSuperType instanceof Class)
+ {
+ if ( resolved instanceof Class )
+ return suspectedSuperType.equals(resolved);
+
+ // They may represent the same class, but the suspectedSuperType is not parameterized. The parameter
+ // types default to Object so they must be a match.
+ // e.g. Pair p = new StringLongPair();
+ // Pair p = new Pair<? extends Number, String>
+
+ return true;
+ }
+
+ if (suspectedSuperType instanceof ParameterizedType)
+ {
+ if (resolved instanceof ParameterizedType)
+ {
+ final Type[] type1Arguments = ((ParameterizedType) suspectedSuperType).getActualTypeArguments();
+ final Type[] type2Arguments = ((ParameterizedType) resolved).getActualTypeArguments();
+ if (type1Arguments.length != type2Arguments.length) return false;
+
+ for (int i = 0; i < type1Arguments.length; ++i)
+ {
+ if (!isAssignableFrom(type1Arguments[i], type2Arguments[i])) return false;
+ }
+ return true;
+ }
+ }
+ else if (suspectedSuperType instanceof GenericArrayType)
+ {
+ if (resolved instanceof GenericArrayType)
+ {
+ return isAssignableFrom(
+ ((GenericArrayType) suspectedSuperType).getGenericComponentType(),
+ ((GenericArrayType) resolved).getGenericComponentType()
+ );
+ }
+ }
+
+ return false;
+ }
+
+ final Type[] types = curClass.getGenericInterfaces();
+ for (Type t : types)
+ {
+ final Type resolved = resolve(t, suspectedSubType);
+ if (isAssignableFrom(suspectedSuperType, resolved))
+ return true;
+ }
+
+ curType = curClass.getGenericSuperclass();
+ }
+ return false;
+ }
+
+ /**
+ * Get the class represented by the reflected type.
+ * This method is lossy; You cannot recover the type information from the class that is returned.
+ *
+ * {@code TypeVariable} the first bound is returned. If your type variable extends multiple interfaces that information
+ * is lost.
+ *
+ * {@code WildcardType} the first lower bound is returned. If the wildcard is defined with upper bounds
+ * then {@code Object} is returned.
+ *
+ * @param actualType
+ * a Class, ParameterizedType, GenericArrayType
+ * @return the un-parameterized class associated with the type.
+ */
+ public static Class asClass(Type actualType)
+ {
+ if (actualType instanceof Class) return (Class) actualType;
+
+ if (actualType instanceof ParameterizedType)
+ {
+ final Type rawType = ((ParameterizedType) actualType).getRawType();
+ // The sun implementation returns getRawType as Class<?>, but there is room in the interface for it to be
+ // some other Type. We'll assume it's a Class.
+ // TODO: consider logging or throwing our own exception for that day when "something else" causes some confusion
+ return (Class) rawType;
+ }
+
+ if (actualType instanceof GenericArrayType)
+ {
+ final Type type = ((GenericArrayType) actualType).getGenericComponentType();
+ return Array.newInstance(asClass(type), 0).getClass();
+ }
+
+ if (actualType instanceof TypeVariable)
+ {
+ // Support for List<T extends Number>
+ // There is always at least one bound. If no bound is specified in the source then it will be Object.class
+ return asClass(((TypeVariable) actualType).getBounds()[0]);
+ }
+
+ if (actualType instanceof WildcardType)
+ {
+ final WildcardType wildcardType = (WildcardType) actualType;
+ final Type[] bounds = wildcardType.getLowerBounds();
+ if (bounds != null && bounds.length > 0)
+ {
+ return asClass(bounds[0]);
+ }
+ // If there is no lower bounds then the only thing that makes sense is Object.
+ return Object.class;
+ }
+
+ throw new RuntimeException(String.format("Unable to convert %s to Class.", actualType));
+ }
+
+ /**
+ * Convert the type into a string. The string representation approximates the code that would be used to define the
+ * type.
+ *
+ * @param type - the type.
+ * @return a string representation of the type, similar to how it was declared.
+ */
+ public static String toString(Type type)
+ {
+ if ( type instanceof ParameterizedType ) return toString((ParameterizedType)type);
+ if ( type instanceof WildcardType ) return toString((WildcardType)type);
+ if ( type instanceof GenericArrayType) return toString((GenericArrayType)type);
+ if ( type instanceof Class )
+ {
+ final Class theClass = (Class) type;
+ return (theClass.isArray() ? theClass.getName() + "[]" : theClass.getName());
+ }
+ return type.toString();
+ }
+
+ /**
+ * Method to resolve a TypeVariable to its most
+ * <a href="http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#112582">reifiable</a> form.
+ *
+ *
+ * How to resolve a TypeVariable:<br/>
+ * All of the TypeVariables defined by a generic class will be given a Type by any class that extends it. The Type
+ * given may or may not be reifiable; it may be another TypeVariable for instance.
+ *
+ * Consider <br/>
+ * <i>class Pair>A,B> { A getA(){...}; ...}</i><br/>
+ * <i>class StringLongPair extends Pair>String, Long> { }</i><br/>
+ *
+ * To resolve the actual return type of Pair.getA() you must first resolve the TypeVariable "A".
+ * We can do that by first finding the index of "A" in the Pair.class.getTypeParameters() array of TypeVariables.
+ *
+ * To get to the Type provided by StringLongPair you access the generics information by calling
+ * StringLongPair.class.getGenericSuperclass; this will be a ParameterizedType. ParameterizedType gives you access
+ * to the actual type arguments provided to Pair by StringLongPair. The array is in the same order as the array in
+ * Pair.class.getTypeParameters so you can use the index we discovered earlier to extract the Type; String.class.
+ *
+ * When extracting Types we only have to consider the superclass hierarchy and not the interfaces implemented by
+ * the class. When a class implements a generic interface it must provide types for the interface and any generic
+ * methods implemented from the interface will be re-defined by the class with its generic type variables.
+ *
+ * @param typeVariable - the type variable to resolve.
+ * @param containingType - the shallowest class in the class hierarchy (furthest from Object) where typeVariable is defined.
+ * @return a Type that has had all possible TypeVariables resolved that have been defined between the type variable
+ * declaration and the containingType.
+ */
+ private static Type resolve(TypeVariable typeVariable, Type containingType)
+ {
+ // The generic declaration is either a Class, Method or Constructor
+ final GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
+
+ if (!(genericDeclaration instanceof Class))
+ {
+ // It's a method or constructor. The best we can do here is try to resolve the bounds
+ // e.g. <T extends E> T getT(T param){} where E is defined by the class.
+ final Type bounds0 = typeVariable.getBounds()[0];
+ return resolve(bounds0, containingType);
+ }
+
+ final Class typeVariableOwner = (Class) genericDeclaration;
+
+ // find the typeOwner in the containingType's hierarchy
+ final LinkedList<Type> stack = new LinkedList<Type>();
+
+ // If you pass a List<Long> as the containingType then the TypeVariable is going to be resolved by the
+ // containingType and not the super class.
+ if (containingType instanceof ParameterizedType)
+ {
+ stack.add(containingType);
+ }
+
+ Class theClass = asClass(containingType);
+ addGenericSuperclasses(theClass, typeVariableOwner, stack);
+
+ int i = getTypeVariableIndex(typeVariable);
+ Type resolved = typeVariable;
+ for (Type t : stack)
+ {
+ if (t instanceof ParameterizedType)
+ {
+ resolved = ((ParameterizedType) t).getActualTypeArguments()[i];
+ if (resolved instanceof Class) return resolved;
+ if (resolved instanceof TypeVariable)
+ {
+ // Need to look at the next class in the hierarchy
+ i = getTypeVariableIndex((TypeVariable) resolved);
+ continue;
+ }
+ return resolve(resolved, containingType);
+ }
+ }
+
+ // the only way we get here is if resolved is still a TypeVariable, otherwise an
+ // exception is thrown or a value is returned.
+ return ((TypeVariable) resolved).getBounds()[0];
+ }
+
+
+ private static void addGenericSuperclasses(Class theClass, final Class typeVariableOwner, final LinkedList<Type> stack) {
+ Type genericSuperclass = theClass.getGenericSuperclass();
+ while (genericSuperclass != null && // true for interfaces with no superclass
+ !theClass.equals(Object.class) &&
+ !theClass.equals(typeVariableOwner))
+ {
+ stack.addFirst(genericSuperclass);
+ theClass = asClass(genericSuperclass);
+ genericSuperclass = theClass.getGenericSuperclass();
+ }
+ for (Type type : theClass.getGenericInterfaces()) {
+ stack.add(type);
+ }
+ for (Class implementedInterface : getAllImplementedInterfaces(theClass)) {
+ addGenericSuperclasses(implementedInterface, typeVariableOwner, stack);
+ }
+ }
+
+ private static List<Class> getAllImplementedInterfaces(Class theClass) {
+ List<Class> list = new ArrayList<>();
+ for (Class implementedInterface : theClass.getInterfaces()) {
+ list.add(implementedInterface);
+ list.addAll(getAllImplementedInterfaces(implementedInterface));
+ }
+ return list;
+ }
+
+ /**
+ * @param type - something like List<T>[] or List<? extends T>[] or T[]
+ * @param containingType - the shallowest type in the hierarchy where type is defined.
+ * @return either the passed type if no changes required or a copy with a best effort resolve of the component type.
+ */
+ private static GenericArrayType resolve(GenericArrayType type, Type containingType)
+ {
+ final Type componentType = type.getGenericComponentType();
+
+ if (!(componentType instanceof Class))
+ {
+ final Type resolved = resolve(componentType, containingType);
+ return create(resolved);
+ }
+
+ return type;
+ }
+
+ /**
+ * @param type - something like List<T>, List<T extends Number>
+ * @param containingType - the shallowest type in the hierarchy where type is defined.
+ * @return the passed type if nothing to resolve or a copy of the type with the type arguments resolved.
+ */
+ private static ParameterizedType resolve(ParameterizedType type, Type containingType)
+ {
+ // Use a copy because we're going to modify it.
+ final Type[] types = type.getActualTypeArguments().clone();
+
+ boolean modified = resolve(types, containingType);
+ return modified ? create(type.getRawType(), type.getOwnerType(), types) : type;
+ }
+
+ /**
+ * @param type - something like List<? super T>, List<<? extends T>, List<? extends T & Comparable<? super T>>
+ * @param containingType - the shallowest type in the hierarchy where type is defined.
+ * @return the passed type if nothing to resolve or a copy of the type with the upper and lower bounds resolved.
+ */
+ private static WildcardType resolve(WildcardType type, Type containingType)
+ {
+ // Use a copy because we're going to modify them.
+ final Type[] upper = type.getUpperBounds().clone();
+ final Type[] lower = type.getLowerBounds().clone();
+
+ boolean modified = resolve(upper, containingType);
+ modified = modified || resolve(lower, containingType);
+
+ return modified ? create(upper, lower) : type;
+ }
+
+ /**
+ * @param types - Array of types to resolve. The unresolved type is replaced in the array with the resolved type.
+ * @param containingType - the shallowest type in the hierarchy where type is defined.
+ * @return true if any of the types were resolved.
+ */
+ private static boolean resolve(Type[] types, Type containingType)
+ {
+ boolean modified = false;
+ for (int i = 0; i < types.length; ++i)
+ {
+ Type t = types[i];
+ if (!(t instanceof Class))
+ {
+ modified = true;
+ final Type resolved = resolve(t, containingType);
+ if (!resolved.equals(t))
+ {
+ types[i] = resolved;
+ modified = true;
+ }
+ }
+ }
+ return modified;
+ }
+
+ /**
+ * @param rawType - the un-parameterized type.
+ * @param ownerType - the outer class or null if the class is not defined within another class.
+ * @param typeArguments - type arguments.
+ * @return a copy of the type with the typeArguments replaced.
+ */
+ static ParameterizedType create(final Type rawType, final Type ownerType, final Type[] typeArguments)
+ {
+ return new ParameterizedType()
+ {
+ @Override
+ public Type[] getActualTypeArguments()
+ {
+ return typeArguments;
+ }
+
+ @Override
+ public Type getRawType()
+ {
+ return rawType;
+ }
+
+ @Override
+ public Type getOwnerType()
+ {
+ return ownerType;
+ }
+
+ @Override
+ public String toString()
+ {
+ return GenericsUtils.toString(this);
+ }
+ };
+ }
+
+ static GenericArrayType create(final Type componentType)
+ {
+ return new GenericArrayType()
+ {
+ @Override
+ public Type getGenericComponentType()
+ {
+ return componentType;
+ }
+
+ @Override
+ public String toString()
+ {
+ return GenericsUtils.toString(this);
+ }
+ };
+ }
+
+ /**
+ * @param upperBounds - e.g. ? extends Number
+ * @param lowerBounds - e.g. ? super Long
+ * @return An new copy of the type with the upper and lower bounds replaced.
+ */
+ static WildcardType create(final Type[] upperBounds, final Type[] lowerBounds)
+ {
+
+ return new WildcardType()
+ {
+ @Override
+ public Type[] getUpperBounds()
+ {
+ return upperBounds;
+ }
+
+ @Override
+ public Type[] getLowerBounds()
+ {
+ return lowerBounds;
+ }
+
+ @Override
+ public String toString()
+ {
+ return GenericsUtils.toString(this);
+ }
+ };
+ }
+
+ static String toString(ParameterizedType pt)
+ {
+ String s = toString(pt.getActualTypeArguments());
+ return String.format("%s<%s>", toString(pt.getRawType()), s);
+ }
+
+ static String toString(GenericArrayType gat)
+ {
+ return String.format("%s[]", toString(gat.getGenericComponentType()));
+ }
+
+ static String toString(WildcardType wt)
+ {
+ final boolean isSuper = wt.getLowerBounds().length > 0;
+ return String.format("? %s %s",
+ isSuper ? "super" : "extends",
+ toString(wt.getLowerBounds()));
+ }
+
+ static String toString(Type[] types)
+ {
+ StringBuilder sb = new StringBuilder();
+ for ( Type t : types )
+ {
+ sb.append(toString(t)).append(", ");
+ }
+ return sb.substring(0, sb.length() - 2);// drop last ,
+ }
+
+ /**
+ * Find the index of the TypeVariable in the classes parameters. The offset can be used on a subclass to find
+ * the actual type.
+ *
+ * @param typeVariable - the type variable in question.
+ * @return the index of the type variable in its declaring class/method/constructor's type parameters.
+ */
+ private static int getTypeVariableIndex(final TypeVariable typeVariable)
+ {
+ // the label from the class (the T in List<T>, the K or V in Map<K,V>, etc)
+ final String typeVarName = typeVariable.getName();
+ final TypeVariable[] typeParameters = typeVariable.getGenericDeclaration().getTypeParameters();
+ for (int typeArgumentIndex = 0; typeArgumentIndex < typeParameters.length; typeArgumentIndex++)
+ {
+ // The .equals for TypeVariable may not be compatible, a name check should be sufficient.
+ if (typeParameters[typeArgumentIndex].getName().equals(typeVarName))
+ return typeArgumentIndex;
+ }
+
+ // The only way this could happen is if the TypeVariable is hand built incorrectly, or it's corrupted.
+ throw new RuntimeException(
+ String.format("%s does not have a TypeVariable matching %s", typeVariable.getGenericDeclaration(), typeVariable));
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
----------------------------------------------------------------------
diff --git a/plastic/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java b/plastic/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
deleted file mode 100644
index abbb767..0000000
--- a/plastic/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
+++ /dev/null
@@ -1,643 +0,0 @@
-// Licensed 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.tapestry5.ioc.internal.util;
-
-import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.lang.reflect.GenericArrayType;
-import java.lang.reflect.GenericDeclaration;
-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.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Static methods related to the use of JDK 1.5 generics.
- */
-@SuppressWarnings("unchecked")
-public class GenericsUtils
-{
- /**
- * Analyzes the method in the context of containingClass and returns the Class that is represented by
- * the method's generic return type. Any parameter information in the generic return type is lost. If you want
- * to preserve the type parameters of the return type consider using
- * {@link #extractActualType(java.lang.reflect.Type, java.lang.reflect.Method)}.
- *
- * @param containingClass class which either contains or inherited the method
- * @param method method from which to extract the return type
- * @return the class represented by the methods generic return type, resolved based on the context .
- * @see #extractActualType(java.lang.reflect.Type, java.lang.reflect.Method)
- * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
- * @see #asClass(java.lang.reflect.Type)
- */
- public static Class<?> extractGenericReturnType(Class<?> containingClass, Method method)
- {
- return asClass(resolve(method.getGenericReturnType(), containingClass));
- }
-
-
- /**
- * Analyzes the field in the context of containingClass and returns the Class that is represented by
- * the field's generic type. Any parameter information in the generic type is lost, if you want
- * to preserve the type parameters of the return type consider using
- * {@link #getTypeVariableIndex(java.lang.reflect.TypeVariable)}.
- *
- * @param containingClass class which either contains or inherited the field
- * @param field field from which to extract the type
- * @return the class represented by the field's generic type, resolved based on the containingClass.
- * @see #extractActualType(java.lang.reflect.Type, java.lang.reflect.Field)
- * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
- * @see #asClass(java.lang.reflect.Type)
- */
- public static Class extractGenericFieldType(Class containingClass, Field field)
- {
- return asClass(resolve(field.getGenericType(), containingClass));
- }
-
- /**
- * Analyzes the method in the context of containingClass and returns the Class that is represented by
- * the method's generic return type. Any parameter information in the generic return type is lost.
- *
- * @param containingType Type which is/represents the class that either contains or inherited the method
- * @param method method from which to extract the generic return type
- * @return the generic type represented by the methods generic return type, resolved based on the containingType.
- * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
- */
- public static Type extractActualType(Type containingType, Method method)
- {
- return resolve(method.getGenericReturnType(), containingType);
- }
-
- /**
- * Analyzes the method in the context of containingClass and returns the Class that is represented by
- * the method's generic return type. Any parameter information in the generic return type is lost.
- *
- * @param containingType Type which is/represents the class that either contains or inherited the field
- * @param field field from which to extract the generic return type
- * @return the generic type represented by the methods generic return type, resolved based on the containingType.
- * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
- */
- public static Type extractActualType(Type containingType, Field field)
- {
- return resolve(field.getGenericType(), containingType);
- }
-
- /**
- * Resolves the type parameter based on the context of the containingType.
- *
- * {@link java.lang.reflect.TypeVariable} will be unwrapped to the type argument resolved form the class
- * hierarchy. This may be something other than a simple Class if the type argument is a ParameterizedType for
- * instance (e.g. {@code List<E>; List<Map<Long, String>>}, E would be returned as a ParameterizedType with the raw
- * type Map and type arguments Long and String.
- *
- *
- * @param type
- * the generic type (ParameterizedType, GenericArrayType, WildcardType, TypeVariable) to be resolved
- * @param containingType
- * the type which his
- * @return
- * the type resolved to the best of our ability.
- * @since 5.2.?
- */
- public static Type resolve(final Type type, final Type containingType)
- {
- // The type isn't generic. (String, Long, etc)
- if (type instanceof Class)
- return type;
-
- // List<T>, List<String>, List<T extends Number>
- if (type instanceof ParameterizedType)
- return resolve((ParameterizedType) type, containingType);
-
- // T[], List<String>[], List<T>[]
- if (type instanceof GenericArrayType)
- return resolve((GenericArrayType) type, containingType);
-
- // List<? extends T>, List<? extends Object & Comparable & Serializable>
- if (type instanceof WildcardType)
- return resolve((WildcardType) type, containingType);
-
- // T
- if (type instanceof TypeVariable)
- return resolve((TypeVariable) type, containingType);
-
- // I'm leaning towards an exception here.
- return type;
- }
-
-
- /**
- * Determines if the suspected super type is assignable from the suspected sub type.
- *
- * @param suspectedSuperType
- * e.g. {@code GenericDAO<Pet, String>}
- * @param suspectedSubType
- * e.g. {@code PetDAO extends GenericDAO<Pet,String>}
- * @return
- * true if (sourceType)targetClass is a valid cast
- */
- public static boolean isAssignableFrom(Type suspectedSuperType, Type suspectedSubType)
- {
- final Class suspectedSuperClass = asClass(suspectedSuperType);
- final Class suspectedSubClass = asClass(suspectedSubType);
-
- // The raw types need to be compatible.
- if (!suspectedSuperClass.isAssignableFrom(suspectedSubClass))
- {
- return false;
- }
-
- // From this point we know that the raw types are assignable.
- // We need to figure out what the generic parameters in the targetClass are
- // as they pertain to the sourceType.
-
- if (suspectedSuperType instanceof WildcardType)
- {
- // ? extends Number
- // needs to match all the bounds (there will only be upper bounds or lower bounds
- for (Type t : ((WildcardType) suspectedSuperType).getUpperBounds())
- {
- if (!isAssignableFrom(t, suspectedSubType)) return false;
- }
- for (Type t : ((WildcardType) suspectedSuperType).getLowerBounds())
- {
- if (!isAssignableFrom(suspectedSubType, t)) return false;
- }
- return true;
- }
-
- Type curType = suspectedSubType;
- Class curClass;
-
- while (curType != null && !curType.equals(Object.class))
- {
- curClass = asClass(curType);
-
- if (curClass.equals(suspectedSuperClass))
- {
- final Type resolved = resolve(curType, suspectedSubType);
-
- if (suspectedSuperType instanceof Class)
- {
- if ( resolved instanceof Class )
- return suspectedSuperType.equals(resolved);
-
- // They may represent the same class, but the suspectedSuperType is not parameterized. The parameter
- // types default to Object so they must be a match.
- // e.g. Pair p = new StringLongPair();
- // Pair p = new Pair<? extends Number, String>
-
- return true;
- }
-
- if (suspectedSuperType instanceof ParameterizedType)
- {
- if (resolved instanceof ParameterizedType)
- {
- final Type[] type1Arguments = ((ParameterizedType) suspectedSuperType).getActualTypeArguments();
- final Type[] type2Arguments = ((ParameterizedType) resolved).getActualTypeArguments();
- if (type1Arguments.length != type2Arguments.length) return false;
-
- for (int i = 0; i < type1Arguments.length; ++i)
- {
- if (!isAssignableFrom(type1Arguments[i], type2Arguments[i])) return false;
- }
- return true;
- }
- }
- else if (suspectedSuperType instanceof GenericArrayType)
- {
- if (resolved instanceof GenericArrayType)
- {
- return isAssignableFrom(
- ((GenericArrayType) suspectedSuperType).getGenericComponentType(),
- ((GenericArrayType) resolved).getGenericComponentType()
- );
- }
- }
-
- return false;
- }
-
- final Type[] types = curClass.getGenericInterfaces();
- for (Type t : types)
- {
- final Type resolved = resolve(t, suspectedSubType);
- if (isAssignableFrom(suspectedSuperType, resolved))
- return true;
- }
-
- curType = curClass.getGenericSuperclass();
- }
- return false;
- }
-
- /**
- * Get the class represented by the reflected type.
- * This method is lossy; You cannot recover the type information from the class that is returned.
- *
- * {@code TypeVariable} the first bound is returned. If your type variable extends multiple interfaces that information
- * is lost.
- *
- * {@code WildcardType} the first lower bound is returned. If the wildcard is defined with upper bounds
- * then {@code Object} is returned.
- *
- * @param actualType
- * a Class, ParameterizedType, GenericArrayType
- * @return the un-parameterized class associated with the type.
- */
- public static Class asClass(Type actualType)
- {
- if (actualType instanceof Class) return (Class) actualType;
-
- if (actualType instanceof ParameterizedType)
- {
- final Type rawType = ((ParameterizedType) actualType).getRawType();
- // The sun implementation returns getRawType as Class<?>, but there is room in the interface for it to be
- // some other Type. We'll assume it's a Class.
- // TODO: consider logging or throwing our own exception for that day when "something else" causes some confusion
- return (Class) rawType;
- }
-
- if (actualType instanceof GenericArrayType)
- {
- final Type type = ((GenericArrayType) actualType).getGenericComponentType();
- return Array.newInstance(asClass(type), 0).getClass();
- }
-
- if (actualType instanceof TypeVariable)
- {
- // Support for List<T extends Number>
- // There is always at least one bound. If no bound is specified in the source then it will be Object.class
- return asClass(((TypeVariable) actualType).getBounds()[0]);
- }
-
- if (actualType instanceof WildcardType)
- {
- final WildcardType wildcardType = (WildcardType) actualType;
- final Type[] bounds = wildcardType.getLowerBounds();
- if (bounds != null && bounds.length > 0)
- {
- return asClass(bounds[0]);
- }
- // If there is no lower bounds then the only thing that makes sense is Object.
- return Object.class;
- }
-
- throw new RuntimeException(String.format("Unable to convert %s to Class.", actualType));
- }
-
- /**
- * Convert the type into a string. The string representation approximates the code that would be used to define the
- * type.
- *
- * @param type - the type.
- * @return a string representation of the type, similar to how it was declared.
- */
- public static String toString(Type type)
- {
- if ( type instanceof ParameterizedType ) return toString((ParameterizedType)type);
- if ( type instanceof WildcardType ) return toString((WildcardType)type);
- if ( type instanceof GenericArrayType) return toString((GenericArrayType)type);
- if ( type instanceof Class )
- {
- final Class theClass = (Class) type;
- return (theClass.isArray() ? theClass.getName() + "[]" : theClass.getName());
- }
- return type.toString();
- }
-
- /**
- * Method to resolve a TypeVariable to its most
- * <a href="http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#112582">reifiable</a> form.
- *
- *
- * How to resolve a TypeVariable:<br/>
- * All of the TypeVariables defined by a generic class will be given a Type by any class that extends it. The Type
- * given may or may not be reifiable; it may be another TypeVariable for instance.
- *
- * Consider <br/>
- * <i>class Pair>A,B> { A getA(){...}; ...}</i><br/>
- * <i>class StringLongPair extends Pair>String, Long> { }</i><br/>
- *
- * To resolve the actual return type of Pair.getA() you must first resolve the TypeVariable "A".
- * We can do that by first finding the index of "A" in the Pair.class.getTypeParameters() array of TypeVariables.
- *
- * To get to the Type provided by StringLongPair you access the generics information by calling
- * StringLongPair.class.getGenericSuperclass; this will be a ParameterizedType. ParameterizedType gives you access
- * to the actual type arguments provided to Pair by StringLongPair. The array is in the same order as the array in
- * Pair.class.getTypeParameters so you can use the index we discovered earlier to extract the Type; String.class.
- *
- * When extracting Types we only have to consider the superclass hierarchy and not the interfaces implemented by
- * the class. When a class implements a generic interface it must provide types for the interface and any generic
- * methods implemented from the interface will be re-defined by the class with its generic type variables.
- *
- * @param typeVariable - the type variable to resolve.
- * @param containingType - the shallowest class in the class hierarchy (furthest from Object) where typeVariable is defined.
- * @return a Type that has had all possible TypeVariables resolved that have been defined between the type variable
- * declaration and the containingType.
- */
- private static Type resolve(TypeVariable typeVariable, Type containingType)
- {
- // The generic declaration is either a Class, Method or Constructor
- final GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
-
- if (!(genericDeclaration instanceof Class))
- {
- // It's a method or constructor. The best we can do here is try to resolve the bounds
- // e.g. <T extends E> T getT(T param){} where E is defined by the class.
- final Type bounds0 = typeVariable.getBounds()[0];
- return resolve(bounds0, containingType);
- }
-
- final Class typeVariableOwner = (Class) genericDeclaration;
-
- // find the typeOwner in the containingType's hierarchy
- final LinkedList<Type> stack = new LinkedList<Type>();
-
- // If you pass a List<Long> as the containingType then the TypeVariable is going to be resolved by the
- // containingType and not the super class.
- if (containingType instanceof ParameterizedType)
- {
- stack.add(containingType);
- }
-
- Class theClass = asClass(containingType);
- addGenericSuperclasses(theClass, typeVariableOwner, stack);
-
- int i = getTypeVariableIndex(typeVariable);
- Type resolved = typeVariable;
- for (Type t : stack)
- {
- if (t instanceof ParameterizedType)
- {
- resolved = ((ParameterizedType) t).getActualTypeArguments()[i];
- if (resolved instanceof Class) return resolved;
- if (resolved instanceof TypeVariable)
- {
- // Need to look at the next class in the hierarchy
- i = getTypeVariableIndex((TypeVariable) resolved);
- continue;
- }
- return resolve(resolved, containingType);
- }
- }
-
- // the only way we get here is if resolved is still a TypeVariable, otherwise an
- // exception is thrown or a value is returned.
- return ((TypeVariable) resolved).getBounds()[0];
- }
-
-
- private static void addGenericSuperclasses(Class theClass, final Class typeVariableOwner, final LinkedList<Type> stack) {
- Type genericSuperclass = theClass.getGenericSuperclass();
- while (genericSuperclass != null && // true for interfaces with no superclass
- !theClass.equals(Object.class) &&
- !theClass.equals(typeVariableOwner))
- {
- stack.addFirst(genericSuperclass);
- theClass = asClass(genericSuperclass);
- genericSuperclass = theClass.getGenericSuperclass();
- }
- for (Type type : theClass.getGenericInterfaces()) {
- stack.add(type);
- }
- for (Class implementedInterface : getAllImplementedInterfaces(theClass)) {
- addGenericSuperclasses(implementedInterface, typeVariableOwner, stack);
- }
- }
-
- private static List<Class> getAllImplementedInterfaces(Class theClass) {
- List<Class> list = new ArrayList<>();
- for (Class implementedInterface : theClass.getInterfaces()) {
- list.add(implementedInterface);
- list.addAll(getAllImplementedInterfaces(implementedInterface));
- }
- return list;
- }
-
- /**
- * @param type - something like List<T>[] or List<? extends T>[] or T[]
- * @param containingType - the shallowest type in the hierarchy where type is defined.
- * @return either the passed type if no changes required or a copy with a best effort resolve of the component type.
- */
- private static GenericArrayType resolve(GenericArrayType type, Type containingType)
- {
- final Type componentType = type.getGenericComponentType();
-
- if (!(componentType instanceof Class))
- {
- final Type resolved = resolve(componentType, containingType);
- return create(resolved);
- }
-
- return type;
- }
-
- /**
- * @param type - something like List<T>, List<T extends Number>
- * @param containingType - the shallowest type in the hierarchy where type is defined.
- * @return the passed type if nothing to resolve or a copy of the type with the type arguments resolved.
- */
- private static ParameterizedType resolve(ParameterizedType type, Type containingType)
- {
- // Use a copy because we're going to modify it.
- final Type[] types = type.getActualTypeArguments().clone();
-
- boolean modified = resolve(types, containingType);
- return modified ? create(type.getRawType(), type.getOwnerType(), types) : type;
- }
-
- /**
- * @param type - something like List<? super T>, List<<? extends T>, List<? extends T & Comparable<? super T>>
- * @param containingType - the shallowest type in the hierarchy where type is defined.
- * @return the passed type if nothing to resolve or a copy of the type with the upper and lower bounds resolved.
- */
- private static WildcardType resolve(WildcardType type, Type containingType)
- {
- // Use a copy because we're going to modify them.
- final Type[] upper = type.getUpperBounds().clone();
- final Type[] lower = type.getLowerBounds().clone();
-
- boolean modified = resolve(upper, containingType);
- modified = modified || resolve(lower, containingType);
-
- return modified ? create(upper, lower) : type;
- }
-
- /**
- * @param types - Array of types to resolve. The unresolved type is replaced in the array with the resolved type.
- * @param containingType - the shallowest type in the hierarchy where type is defined.
- * @return true if any of the types were resolved.
- */
- private static boolean resolve(Type[] types, Type containingType)
- {
- boolean modified = false;
- for (int i = 0; i < types.length; ++i)
- {
- Type t = types[i];
- if (!(t instanceof Class))
- {
- modified = true;
- final Type resolved = resolve(t, containingType);
- if (!resolved.equals(t))
- {
- types[i] = resolved;
- modified = true;
- }
- }
- }
- return modified;
- }
-
- /**
- * @param rawType - the un-parameterized type.
- * @param ownerType - the outer class or null if the class is not defined within another class.
- * @param typeArguments - type arguments.
- * @return a copy of the type with the typeArguments replaced.
- */
- static ParameterizedType create(final Type rawType, final Type ownerType, final Type[] typeArguments)
- {
- return new ParameterizedType()
- {
- @Override
- public Type[] getActualTypeArguments()
- {
- return typeArguments;
- }
-
- @Override
- public Type getRawType()
- {
- return rawType;
- }
-
- @Override
- public Type getOwnerType()
- {
- return ownerType;
- }
-
- @Override
- public String toString()
- {
- return GenericsUtils.toString(this);
- }
- };
- }
-
- static GenericArrayType create(final Type componentType)
- {
- return new GenericArrayType()
- {
- @Override
- public Type getGenericComponentType()
- {
- return componentType;
- }
-
- @Override
- public String toString()
- {
- return GenericsUtils.toString(this);
- }
- };
- }
-
- /**
- * @param upperBounds - e.g. ? extends Number
- * @param lowerBounds - e.g. ? super Long
- * @return An new copy of the type with the upper and lower bounds replaced.
- */
- static WildcardType create(final Type[] upperBounds, final Type[] lowerBounds)
- {
-
- return new WildcardType()
- {
- @Override
- public Type[] getUpperBounds()
- {
- return upperBounds;
- }
-
- @Override
- public Type[] getLowerBounds()
- {
- return lowerBounds;
- }
-
- @Override
- public String toString()
- {
- return GenericsUtils.toString(this);
- }
- };
- }
-
- static String toString(ParameterizedType pt)
- {
- String s = toString(pt.getActualTypeArguments());
- return String.format("%s<%s>", toString(pt.getRawType()), s);
- }
-
- static String toString(GenericArrayType gat)
- {
- return String.format("%s[]", toString(gat.getGenericComponentType()));
- }
-
- static String toString(WildcardType wt)
- {
- final boolean isSuper = wt.getLowerBounds().length > 0;
- return String.format("? %s %s",
- isSuper ? "super" : "extends",
- toString(wt.getLowerBounds()));
- }
-
- static String toString(Type[] types)
- {
- StringBuilder sb = new StringBuilder();
- for ( Type t : types )
- {
- sb.append(toString(t)).append(", ");
- }
- return sb.substring(0, sb.length() - 2);// drop last ,
- }
-
- /**
- * Find the index of the TypeVariable in the classes parameters. The offset can be used on a subclass to find
- * the actual type.
- *
- * @param typeVariable - the type variable in question.
- * @return the index of the type variable in its declaring class/method/constructor's type parameters.
- */
- private static int getTypeVariableIndex(final TypeVariable typeVariable)
- {
- // the label from the class (the T in List<T>, the K or V in Map<K,V>, etc)
- final String typeVarName = typeVariable.getName();
- final TypeVariable[] typeParameters = typeVariable.getGenericDeclaration().getTypeParameters();
- for (int typeArgumentIndex = 0; typeArgumentIndex < typeParameters.length; typeArgumentIndex++)
- {
- // The .equals for TypeVariable may not be compatible, a name check should be sufficient.
- if (typeParameters[typeArgumentIndex].getName().equals(typeVarName))
- return typeArgumentIndex;
- }
-
- // The only way this could happen is if the TypeVariable is hand built incorrectly, or it's corrupted.
- throw new RuntimeException(
- String.format("%s does not have a TypeVariable matching %s", typeVariable.getGenericDeclaration(), typeVariable));
- }
-}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
----------------------------------------------------------------------
diff --git a/plastic/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy b/plastic/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
deleted file mode 100644
index 6b527d2..0000000
--- a/plastic/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-package ioc.specs
-
-import org.apache.tapestry5.ioc.internal.util.GenericsUtils
-import org.apache.tapestry5.ioc.internal.util.NonGenericBean
-import org.apache.tapestry5.ioc.internal.util.StringBean
-import org.apache.tapestry5.ioc.internal.util.StringLongPair
-import spock.lang.Specification
-import spock.lang.Unroll
-
-class GenericUtilsSpec extends Specification {
-
- def find(clazz, name) {
- def method = clazz.methods.find { it.name.equalsIgnoreCase(name) }
-
- if (method == null) {
- throw new IllegalArgumentException("Unable to find method '$name' of ${clazz.name}.")
- }
-
- return method
- }
-
- @Unroll
- def "generic return type for #method is #expected"() {
-
- expect:
-
- GenericsUtils.extractGenericReturnType(clazz, method).is(expected)
-
- where:
-
- clazz | name | expected
- NonGenericBean | "getvalue" | String
- StringBean | "getvalue" | String
- StringLongPair | "getkey" | String
- StringLongPair | "getvalue" | Long
-
- method = find(clazz, name)
- }
-
-}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java
----------------------------------------------------------------------
diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java
deleted file mode 100644
index dc4c2c7..0000000
--- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2008 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.ioc.internal.util;
-
-public class BaseGenericBean<T>
-{
- private T value;
-
- public T getValue()
- {
- return value;
- }
-
- public void setValue(T value)
- {
- this.value = value;
- }
-}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java
----------------------------------------------------------------------
diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java
deleted file mode 100644
index 8cce49f..0000000
--- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2008 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.ioc.internal.util;
-
-public class NonGenericBean
-{
- private String value;
-
- public String getValue()
- {
- return value;
- }
-
- public void setValue(String value)
- {
- this.value = value;
- }
-}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/Pair.java
----------------------------------------------------------------------
diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/Pair.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/Pair.java
deleted file mode 100644
index 00722f4..0000000
--- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/Pair.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2008 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.ioc.internal.util;
-
-public class Pair<K, V>
-{
- private K key;
-
- private V value;
-
- public K getKey()
- {
- return key;
- }
-
- public void setKey(K key)
- {
- this.key = key;
- }
-
- public V getValue()
- {
- return value;
- }
-
- public void setValue(V value)
- {
- this.value = value;
- }
-}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java
----------------------------------------------------------------------
diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java
deleted file mode 100644
index 1b13856..0000000
--- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2008 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.ioc.internal.util;
-
-public class StringBean extends BaseGenericBean<String>
-{
-
-}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringLongPair.java
----------------------------------------------------------------------
diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringLongPair.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringLongPair.java
deleted file mode 100644
index 0071beb..0000000
--- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringLongPair.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2008 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.ioc.internal.util;
-
-public class StringLongPair extends Pair<String, Long>
-{
-}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
new file mode 100644
index 0000000..6b527d2
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
@@ -0,0 +1,40 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.util.GenericsUtils
+import org.apache.tapestry5.ioc.internal.util.NonGenericBean
+import org.apache.tapestry5.ioc.internal.util.StringBean
+import org.apache.tapestry5.ioc.internal.util.StringLongPair
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class GenericUtilsSpec extends Specification {
+
+ def find(clazz, name) {
+ def method = clazz.methods.find { it.name.equalsIgnoreCase(name) }
+
+ if (method == null) {
+ throw new IllegalArgumentException("Unable to find method '$name' of ${clazz.name}.")
+ }
+
+ return method
+ }
+
+ @Unroll
+ def "generic return type for #method is #expected"() {
+
+ expect:
+
+ GenericsUtils.extractGenericReturnType(clazz, method).is(expected)
+
+ where:
+
+ clazz | name | expected
+ NonGenericBean | "getvalue" | String
+ StringBean | "getvalue" | String
+ StringLongPair | "getkey" | String
+ StringLongPair | "getvalue" | Long
+
+ method = find(clazz, name)
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java
new file mode 100644
index 0000000..dc4c2c7
--- /dev/null
+++ b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java
@@ -0,0 +1,30 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.util;
+
+public class BaseGenericBean<T>
+{
+ private T value;
+
+ public T getValue()
+ {
+ return value;
+ }
+
+ public void setValue(T value)
+ {
+ this.value = value;
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java
new file mode 100644
index 0000000..8cce49f
--- /dev/null
+++ b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java
@@ -0,0 +1,30 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.util;
+
+public class NonGenericBean
+{
+ private String value;
+
+ public String getValue()
+ {
+ return value;
+ }
+
+ public void setValue(String value)
+ {
+ this.value = value;
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java
new file mode 100644
index 0000000..1b13856
--- /dev/null
+++ b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java
@@ -0,0 +1,20 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.util;
+
+public class StringBean extends BaseGenericBean<String>
+{
+
+}