TAP5-2560: Error in GenericsUtils affecting property access

Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/9a4dd324
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/9a4dd324
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/9a4dd324

Branch: refs/heads/master
Commit: 9a4dd324549ff637d3cb021f16d28239393d7941
Parents: efd7eae
Author: Thiago H. de Paula Figueiredo <[email protected]>
Authored: Thu Jan 17 00:13:47 2019 -0200
Committer: Thiago H. de Paula Figueiredo <[email protected]>
Committed: Thu Jan 17 00:13:47 2019 -0200

----------------------------------------------------------------------
 55_RELEASE_NOTES.md                             |  11 +-
 .../internal/services/GenericsResolverImpl.java | 627 +++++++++++++
 .../ioc/internal/util/GenericsUtils.java        | 513 +----------
 .../tapestry5/services/GenericsResolver.java    | 160 ++++
 genericsresolver-guava/LICENSE.txt              | 202 +++++
 genericsresolver-guava/NOTICE.txt               |   2 +
 genericsresolver-guava/build.gradle             |   8 +
 .../GuavaGenericsResolver.java                  |  63 ++
 ...g.apache.tapestry5.services.GenericsResolver |   1 +
 genericsresolver-guava/src/test/conf/.gitignore |   1 +
 .../AbstractBeanModelSourceImplTest.java        | 872 +++++++++++++++++++
 .../GuavaBeanModelSourceImplTest.java           |  24 +
 .../src/test/resources/log4j.properties         |  10 +
 settings.gradle                                 |   2 +-
 .../AbstractBeanModelSourceImplTest.java        |   3 +-
 15 files changed, 2000 insertions(+), 499 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/55_RELEASE_NOTES.md
----------------------------------------------------------------------
diff --git a/55_RELEASE_NOTES.md b/55_RELEASE_NOTES.md
index 3dd11bd..b754fec 100644
--- a/55_RELEASE_NOTES.md
+++ b/55_RELEASE_NOTES.md
@@ -4,6 +4,7 @@ Scratch pad for changes destined for the 5.5 release notes page.
 The minimum Java release required to run apps created with Tapestry 5.5 is 
Java 8.
 
 # Java 8, 9, 10 and 11 supported
+With the ASM upgrade, now code compiled with Java 8 to 11 is supported.
 
 # Updates to embedded Tomcat and Jetty versions (TAP5-2548)
 With Java 8, we made the switch to servlet-api 3.0. We updated the embedded 
Tomcat and Jetty containers to the respective versions. Unfortunately, we had 
to rename Jetty7Runner to JettyRunner and Tomcat6Runner to TomcatRunner in the 
tapestry-runner package.
@@ -15,4 +16,12 @@ security needs. Three rules are added
 out-of-the-box and may be overriden:
 * `ClassFile`: blocks access to assets with `.class` endings (case 
insensitive).
 * `PropertiesFile`: blocks access to assets with `.properties` endings (case 
insensitive).
-* `XMLFile`: blocks access to assets with `.xml` endings (case insensitive).
\ No newline at end of file
+* `XMLFile`: blocks access to assets with `.xml` endings (case insensitive).
+
+# New subproject/JAR: genericsresolver-guava
+Tapestry's own code to resolve the bound types of generic types and methods, 
based around GenericsUtils,
+couldn't handle some cases, as discovered in TAP5-2560. Fixing the code to 
handle these cases
+turned out to not be feasible, so we introduced a new JAR, 
genericsresolver-java, 
+which replaces GenericsUtils with Google Guava's TypeResolver and associated 
classes.
+To use it, just add genericsresolver-java, which is versioned in the same way 
as the other Tapestry JARs,
+to the classpath of your projects and make sure a not too-old version of 
Google Guava is also in the classpath.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/commons/src/main/java/org/apache/tapestry5/internal/services/GenericsResolverImpl.java
----------------------------------------------------------------------
diff --git 
a/commons/src/main/java/org/apache/tapestry5/internal/services/GenericsResolverImpl.java
 
b/commons/src/main/java/org/apache/tapestry5/internal/services/GenericsResolverImpl.java
new file mode 100644
index 0000000..c0abfaf
--- /dev/null
+++ 
b/commons/src/main/java/org/apache/tapestry5/internal/services/GenericsResolverImpl.java
@@ -0,0 +1,627 @@
+// Copyright 2007 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.internal.services;
+
+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.LinkedList;
+
+import org.apache.tapestry5.services.GenericsResolver;
+
+/**
+ * Implementation copied from Tapestry 5.4's GenericUtils (commons package).
+ */
+@SuppressWarnings("rawtypes")
+public class GenericsResolverImpl implements GenericsResolver
+{
+    /**
+     * 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 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 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 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 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 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
+     */
+    @SuppressWarnings({ "unused", "unchecked" })
+    private 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 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&gt;A,B> { A getA(){...}; ...}</i><br/>
+     * <i>class StringLongPair extends Pair&gt;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 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);
+        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();
+        }
+
+        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];
+    }
+
+    /**
+     * @param type           - something like List&lt;T>[] or List&lt;? 
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 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&lt;T>, List&lt;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 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&lt;? super T>, List<&lt;? 
extends T>, List&lt;? extends T & Comparable&lt? 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 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 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 GenericsResolverImpl.toString(this);
+            }
+        };
+    }
+
+    static GenericArrayType create(final Type componentType)
+    {
+        return new GenericArrayType()
+        {
+            @Override
+            public Type getGenericComponentType()
+            {
+                return componentType;
+            }
+
+            @Override
+            public String toString()
+            {
+                return GenericsResolverImpl.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 GenericsResolverImpl.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/9a4dd324/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
index 9bf4d00..f01064b 100644
--- 
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
@@ -12,15 +12,20 @@
 
 package org.apache.tapestry5.ioc.internal.util;
 
-import java.lang.reflect.*;
-import java.util.LinkedList;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+import org.apache.tapestry5.services.GenericsResolver;
 
 /**
- * Static methods related to the use of JDK 1.5 generics.
+ * Static methods related to the use of JDK 1.5 generics. From Tapestry 5.5.0,
+ * this class just delegates to {@link GenericsResolver}.
  */
-@SuppressWarnings("unchecked")
 public class GenericsUtils
 {
+    final private static GenericsResolver GENERICS_RESOLVER = 
GenericsResolver.Provider.getInstance();
+    
     /**
      * 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
@@ -36,10 +41,9 @@ public class GenericsUtils
      */
     public static Class<?> extractGenericReturnType(Class<?> containingClass, 
Method method)
     {
-        return asClass(resolve(method.getGenericReturnType(), 
containingClass));
+        return GENERICS_RESOLVER.extractGenericReturnType(containingClass, 
method);
     }
 
-
     /**
      * 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
@@ -55,7 +59,7 @@ public class GenericsUtils
      */
     public static Class extractGenericFieldType(Class containingClass, Field 
field)
     {
-        return asClass(resolve(field.getGenericType(), containingClass));
+        return GENERICS_RESOLVER.extractGenericFieldType(containingClass, 
field);
     }
 
     /**
@@ -69,7 +73,7 @@ public class GenericsUtils
      */
     public static Type extractActualType(Type containingType, Method method)
     {
-        return resolve(method.getGenericReturnType(), containingType);
+        return GENERICS_RESOLVER.extractActualType(containingType, method);
     }
 
     /**
@@ -83,7 +87,7 @@ public class GenericsUtils
      */
     public static Type extractActualType(Type containingType, Field field)
     {
-        return resolve(field.getGenericType(), containingType);
+        return GENERICS_RESOLVER.extractActualType(containingType, field);
     }
 
     /**
@@ -105,137 +109,9 @@ public class GenericsUtils
      */
     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;
+        return GENERICS_RESOLVER.resolve(type, containingType);
     }
-
-
-    /**
-     * 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.
@@ -252,362 +128,7 @@ public class GenericsUtils
      */
     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&gt;A,B> { A getA(){...}; ...}</i><br/>
-     * <i>class StringLongPair extends Pair&gt;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);
-        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();
-        }
-
-        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];
-    }
-
-    /**
-     * @param type           - something like List&lt;T>[] or List&lt;? 
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&lt;T>, List&lt;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&lt;? super T>, List<&lt;? 
extends T>, List&lt;? extends T & Comparable&lt? 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));
+        return GENERICS_RESOLVER.asClass(actualType);
     }
+    
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/commons/src/main/java/org/apache/tapestry5/services/GenericsResolver.java
----------------------------------------------------------------------
diff --git 
a/commons/src/main/java/org/apache/tapestry5/services/GenericsResolver.java 
b/commons/src/main/java/org/apache/tapestry5/services/GenericsResolver.java
new file mode 100644
index 0000000..aa1e8de
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/services/GenericsResolver.java
@@ -0,0 +1,160 @@
+// 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.services;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import org.apache.tapestry5.internal.services.GenericsResolverImpl;
+
+/**
+ * <p>Methods related to the use of Java 5+ generics.
+ * Instances should be obtained through {@link 
GenericsResolver.Provider#getInstance()}.</p>
+ * 
+ * <p>
+ * If you have exceptions or bad results with classes using Generics, such as 
exceptions
+ * or missing BeanModel properties,
+ * you should try adding the <code>genericsresolver-guava<code> Tapestry 
subproject to our classpath.
+ * </p>
+ * 
+ * @since 5.5.0
+ */
+@SuppressWarnings("unchecked")
+public interface GenericsResolver
+{
+    /**
+     * 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)
+     */
+    Class<?> extractGenericReturnType(Class<?> containingClass, Method method);
+
+    /**
+     * 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)
+     */
+    Class extractGenericFieldType(Class containingClass, Field field);
+
+    /**
+     * 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)
+     */
+    Type extractActualType(Type containingType, Method method);
+
+    /**
+     * 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)
+     */
+    Type extractActualType(Type containingType, Field field);
+
+    /**
+     * 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.
+     */
+    Type resolve(final Type type, final Type containingType);
+    
+    /**
+     * Convenience class for getting a {@link GenericsResolver} instance.
+     */
+    final static public class Provider 
+    {
+
+        final private static GenericsResolver instance;
+        
+        static 
+        {
+            
+            ServiceLoader<GenericsResolver> serviceLoader = 
ServiceLoader.load(GenericsResolver.class);
+            Iterator<GenericsResolver> iterator = serviceLoader.iterator();
+            if (iterator.hasNext()) 
+            {
+                instance = iterator.next();
+            }
+            else 
+            {
+                instance = new GenericsResolverImpl();
+            }
+        }
+        
+        /**
+         * Returns a cached {@linkplain GenericsResolver} instance. 
+         * If {@link ServiceLoader} finds one instance, it returns the first 
one found. If not,
+         * it returns {@link GenericsResolverImpl}.
+         * @return a {@link GenericsResolver} instance.
+         */
+        public static GenericsResolver getInstance() 
+        {
+            return instance;
+        }
+        
+    }
+    
+    /**
+     * 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.
+     */
+    Class asClass(Type actualType);
+    
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/genericsresolver-guava/LICENSE.txt
----------------------------------------------------------------------
diff --git a/genericsresolver-guava/LICENSE.txt 
b/genericsresolver-guava/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/genericsresolver-guava/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/genericsresolver-guava/NOTICE.txt
----------------------------------------------------------------------
diff --git a/genericsresolver-guava/NOTICE.txt 
b/genericsresolver-guava/NOTICE.txt
new file mode 100644
index 0000000..3f59805
--- /dev/null
+++ b/genericsresolver-guava/NOTICE.txt
@@ -0,0 +1,2 @@
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/genericsresolver-guava/build.gradle
----------------------------------------------------------------------
diff --git a/genericsresolver-guava/build.gradle 
b/genericsresolver-guava/build.gradle
new file mode 100644
index 0000000..f7b037c
--- /dev/null
+++ b/genericsresolver-guava/build.gradle
@@ -0,0 +1,8 @@
+description = "Replaces the Tapestry Commons's own Java Generics resolution 
code with the one from Google Guava's one"
+
+dependencies {
+  compile project(':commons')
+  testCompile project(':tapestry-core')
+  testCompile project(':tapestry-test')
+  provided compile ('com.google.guava:guava:27.0.1-jre')
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/genericsresolver-guava/src/main/java/org/apache/tapestry5/internal/genericsresolverguava/GuavaGenericsResolver.java
----------------------------------------------------------------------
diff --git 
a/genericsresolver-guava/src/main/java/org/apache/tapestry5/internal/genericsresolverguava/GuavaGenericsResolver.java
 
b/genericsresolver-guava/src/main/java/org/apache/tapestry5/internal/genericsresolverguava/GuavaGenericsResolver.java
new file mode 100644
index 0000000..a7f97c5
--- /dev/null
+++ 
b/genericsresolver-guava/src/main/java/org/apache/tapestry5/internal/genericsresolverguava/GuavaGenericsResolver.java
@@ -0,0 +1,63 @@
+// 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.internal.genericsresolverguava;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+import org.apache.tapestry5.services.GenericsResolver;
+
+import com.google.common.reflect.TypeToken;
+
+/**
+ * {@link GuavaGenericsResolver} implementation using Guava.
+ */
+public class GuavaGenericsResolver implements GenericsResolver {
+
+    @Override
+    public Class<?> extractGenericReturnType(Class<?> containingClass, Method 
method) 
+    {
+        return 
TypeToken.of(containingClass).resolveType(method.getGenericReturnType()).getRawType();
+    }
+
+    @Override
+    public Class extractGenericFieldType(Class containingClass, Field field) 
+    {
+        return 
TypeToken.of(containingClass).resolveType(field.getGenericType()).getRawType();
+    }
+
+    @Override
+    public Type extractActualType(Type containingType, Method method) 
+    {
+        return 
TypeToken.of(containingType).resolveType(method.getGenericReturnType()).getType();
+    }
+
+    @Override
+    public Type extractActualType(Type containingType, Field field) 
+    {
+        return 
TypeToken.of(containingType).resolveType(field.getGenericType()).getType();
+    }
+
+    @Override
+    public Type resolve(Type type, Type containingType) 
+    {
+        return TypeToken.of(containingType).resolveType(type).getType();
+    }
+
+    @Override
+    public Class asClass(Type actualType) {
+        return TypeToken.of(actualType).getRawType();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/genericsresolver-guava/src/main/resources/META-INF/services/org.apache.tapestry5.services.GenericsResolver
----------------------------------------------------------------------
diff --git 
a/genericsresolver-guava/src/main/resources/META-INF/services/org.apache.tapestry5.services.GenericsResolver
 
b/genericsresolver-guava/src/main/resources/META-INF/services/org.apache.tapestry5.services.GenericsResolver
new file mode 100644
index 0000000..5085652
--- /dev/null
+++ 
b/genericsresolver-guava/src/main/resources/META-INF/services/org.apache.tapestry5.services.GenericsResolver
@@ -0,0 +1 @@
+org.apache.tapestry5.internal.genericsresolverguava.GuavaGenericsResolver
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/9a4dd324/genericsresolver-guava/src/test/conf/.gitignore
----------------------------------------------------------------------
diff --git a/genericsresolver-guava/src/test/conf/.gitignore 
b/genericsresolver-guava/src/test/conf/.gitignore
new file mode 100644
index 0000000..a3f142a
--- /dev/null
+++ b/genericsresolver-guava/src/test/conf/.gitignore
@@ -0,0 +1 @@
+/testng.xml

Reply via email to