You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by jo...@apache.org on 2010/12/26 20:41:50 UTC

svn commit: r1052930 - in /tapestry/tapestry5/trunk: tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ tapestry-core/src/main/java/org/apache/tapestry5/internal/tran...

Author: joshcanfield
Date: Sun Dec 26 19:41:50 2010
New Revision: 1052930

URL: http://svn.apache.org/viewvc?rev=1052930&view=rev
Log:
Added support for generic component properties. 
Modified the Loop component to support generic source and value parameters.
Public interface changes: 
Added constructor to TransformMethodSignature to accept the methods generic signature. 
Added method to TransformField to get the fields generic signature

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Loop.java Sun Dec 26 19:41:50 2010
@@ -41,7 +41,7 @@ import java.util.List;
  */
 @SupportsInformalParameters
 @Events(EventConstants.SYNCHRONIZE_VALUES)
-public class Loop
+public class Loop<T>
 {
     /**
      * Setup command for non-volatile rendering.
@@ -209,7 +209,7 @@ public class Loop
      * container whose name matches the Loop cmponent's id.
      */
     @Parameter(required = true, principal = true, autoconnect = true)
-    private Iterable<?> source;
+    private Iterable<T> source;
 
     /**
      * Optional value converter; if provided (or defaulted) and inside a form and not volatile, then each iterated value
@@ -217,7 +217,7 @@ public class Loop
      * the value parameter.
      */
     @Parameter
-    private ValueEncoder<Object> encoder;
+    private ValueEncoder<T> encoder;
 
     /**
      * If true and the Loop is enclosed by a Form, then the normal state saving logic is turned off. Defaults to false,
@@ -253,7 +253,7 @@ public class Loop
      * The current value, set before the component renders its body.
      */
     @Parameter(principal = true)
-    private Object value;
+    private T value;
 
     /**
      * The index into the source items.
@@ -267,7 +267,7 @@ public class Loop
     @Parameter(defaultPrefix = BindingConstants.LITERAL)
     private Block empty;
 
-    private Iterator<?> iterator;
+    private Iterator<T> iterator;
 
     @Environmental
     private Heartbeat heartbeat;
@@ -286,7 +286,7 @@ public class Loop
      * Objects that have been recovered via {@link org.apache.tapestry5.ValueEncoder#toValue(String)} during the
      * processing of the loop. These are sent to the container via an event.
      */
-    private List<Object> synchonizedValues;
+    private List<T> synchonizedValues;
 
 
     LoopFormState defaultFormState()
@@ -439,7 +439,7 @@ public class Loop
     /**
      * Restores state previously stored by the Loop into a Form.
      */
-    private void restoreState(Object storedValue)
+    private void restoreState(T storedValue)
     {
         value = storedValue;
 
@@ -454,7 +454,7 @@ public class Loop
         // We assume that if an encoder is available when we rendered, that one will be available
         // when the form is submitted.
 
-        Object restoredValue = encoder.toValue(clientValue);
+        T restoredValue = encoder.toValue(clientValue);
 
         restoreState(restoredValue);
 
@@ -475,17 +475,17 @@ public class Loop
 
     // For testing:
 
-    int getIndex()
+    public int getIndex()
     {
         return index;
     }
 
-    Object getValue()
+    public T getValue()
     {
         return value;
     }
 
-    void setSource(Iterable<?> source)
+    void setSource(Iterable<T> source)
     {
         this.source = source;
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java Sun Dec 26 19:41:50 2010
@@ -24,6 +24,9 @@ import java.util.Map;
 import java.util.Set;
 
 import javassist.*;
+import javassist.bytecode.FieldInfo;
+import javassist.bytecode.MethodInfo;
+import javassist.bytecode.SignatureAttribute;
 import javassist.expr.ExprEditor;
 import javassist.expr.FieldAccess;
 
@@ -325,7 +328,7 @@ public final class InternalClassTransfor
          * The static method takes the same parameters as the main method, but takes
          * an instance object first. Invoking the static method turns into an invocation
          * of the proper method of the instance object.
-         * 
+         *
          * @return the name of the created static access method
          */
         private String createStaticAccessMethodForNonPublicMethod()
@@ -423,7 +426,7 @@ public final class InternalClassTransfor
 
         private final CtClass fieldType;
 
-        private final String name, type;
+        private final String name, type, signature;
 
         private boolean added;
 
@@ -446,6 +449,12 @@ public final class InternalClassTransfor
             try
             {
                 fieldType = field.getType();
+
+                final FieldInfo fieldInfo = field.getFieldInfo2();
+                SignatureAttribute sig = (SignatureAttribute) fieldInfo.getAttribute(SignatureAttribute.tag);
+                // This contains the generic type information
+                signature = sig != null ? sig.getSignature() : null;
+
                 type = fieldType.getName();
             }
             catch (NotFoundException ex)
@@ -486,6 +495,11 @@ public final class InternalClassTransfor
             return type;
         }
 
+        public String getSignature()
+        {
+            return signature;
+        }
+
         public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
         {
             failIfFrozen();
@@ -954,7 +968,7 @@ public final class InternalClassTransfor
      * Searches an array of objects (that are really annotations instances) to find one that is of
      * the correct type,
      * which is returned.
-     * 
+     *
      * @param <T>
      * @param annotationClass
      *            the annotation to search for
@@ -1080,7 +1094,7 @@ public final class InternalClassTransfor
      * degenerate cases that are not covered properly: these are related to base interfaces that may
      * be implemented by
      * base classes.
-     * 
+     *
      * @param ctInterface
      * @throws NotFoundException
      */
@@ -1243,7 +1257,12 @@ public final class InternalClassTransfor
 
             method.setBody(methodBody);
             method.setExceptionTypes(exceptions);
-
+            String sig = signature.getSignature();
+            if (sig != null)
+            {
+                final MethodInfo methodInfo = method.getMethodInfo();
+                methodInfo.addAttribute(new SignatureAttribute(methodInfo.getConstPool(), sig));
+            }
             ctClass.addMethod(method);
 
             result = recordMethod(method, addAsNew);
@@ -1594,7 +1613,10 @@ public final class InternalClassTransfor
             String[] parameters = toTypeNames(method.getParameterTypes());
             String[] exceptions = toTypeNames(method.getExceptionTypes());
 
-            return new TransformMethodSignature(method.getModifiers(), type, method.getName(), parameters, exceptions);
+            final SignatureAttribute attribute = (SignatureAttribute)method.getMethodInfo().getAttribute(SignatureAttribute.tag);
+            String sig = attribute != null ? attribute.getSignature() : null;
+
+            return new TransformMethodSignature(method.getModifiers(), type, sig, method.getName(), parameters, exceptions);
         }
         catch (NotFoundException ex)
         {
@@ -1809,7 +1831,7 @@ public final class InternalClassTransfor
     /**
      * Adds a parameter to the constructor for the class; the parameter is used to initialize the
      * value for a field.
-     * 
+     *
      * @param fieldName
      *            name of field to inject
      * @param fieldType
@@ -2261,7 +2283,7 @@ public final class InternalClassTransfor
 
     /**
      * Adds a new constructor argument to the transformed constructor.
-     * 
+     *
      * @param parameterType
      *            type of parameter
      * @param value
@@ -2372,9 +2394,9 @@ public final class InternalClassTransfor
             public void advise(ComponentMethodInvocation invocation)
             {
                 // Invoke the super-class implementation first.
-                
+
                 invocation.proceed();
-                
+
                 ComponentEvent event = (ComponentEvent) invocation.getParameter(0);
 
                 if (!event.isAborted() && event.matches(eventType, "", minContextValues))

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java Sun Dec 26 19:41:50 2010
@@ -129,7 +129,7 @@ public class PropertyConduitSourceImpl i
         /**
          * The return type of the method, or the type of the property.
          */
-        Class getType();
+        Type getType();
 
         /**
          * True if an explicit cast to the return type is needed (typically
@@ -453,7 +453,7 @@ public class PropertyConduitSourceImpl i
                     createSetter(navigateMethod, info);
                     createGetter(navigateMethod, node, info);
 
-                    conduitPropertyType = info.getType();
+                    conduitPropertyType = GenericsUtils.asClass(info.getType());
                     conduitPropertyName = info.getPropertyName();
                     annotationProvider = info;
 
@@ -564,7 +564,7 @@ public class PropertyConduitSourceImpl i
         /**
          * Evalutates the node as a sub expression, storing the result into a
          * new variable, whose name is returned.
-         * 
+         *
          * @param builder
          *            to receive generated code
          * @param node
@@ -648,7 +648,8 @@ public class PropertyConduitSourceImpl i
 
                         previousReference = generated.termReference;
                         activeType = GenericsUtils.asClass(generated.type);
-
+                        if ( activeType.isPrimitive() )
+                            activeType = ClassFabUtils.getWrapperType(activeType);
                         node = null;
 
                         break;
@@ -707,7 +708,7 @@ public class PropertyConduitSourceImpl i
 
             builder.addln("if (target == null) return;");
 
-            String propertyTypeName = ClassFabUtils.toJavaClassName(info.getType());
+            String propertyTypeName = ClassFabUtils.toJavaClassName(GenericsUtils.asClass(info.getType()));
 
             String reference = ClassFabUtils.castReference("$2", propertyTypeName);
 
@@ -766,7 +767,7 @@ public class PropertyConduitSourceImpl i
 
         /**
          * Creates a method invocation call for the given node (an INVOKE node).
-         * 
+         *
          * @param bodyBuilder
          *            may receive new code to define variables for some
          *            sub-expressions
@@ -790,7 +791,7 @@ public class PropertyConduitSourceImpl i
 
         /**
          * Creates a method invocation call for the given node
-         * 
+         *
          * @param bodyBuilder
          *            may receive new code to define variables for some
          *            sub-expressions
@@ -914,31 +915,19 @@ public class PropertyConduitSourceImpl i
                 String previousVariableName, String rootName, NullHandling nullHandling)
         {
             assertNodeType(term, IDENTIFIER, INVOKE);
-            Class activeClass = GenericsUtils.asClass(activeType);
-            // Get info about this property or method.
 
-            ExpressionTermInfo info = infoForMember(activeClass, term);
+            // Get info about this property or method.
 
-            Method method = info.getReadMethod();
+            final ExpressionTermInfo info = infoForMember(activeType, term);
 
+            final Method method = info.getReadMethod();
+            final Class activeClass = GenericsUtils.asClass(activeType);
             if (method == null && !info.isField())
                 throw new RuntimeException(String.format(
                         "Property '%s' of class %s is not readable (it has no read accessor method).",
                         info.getDescription(), activeClass.getName()));
 
-            Type termType;
-            /*
-             * It's not possible for the ClassPropertyAdapter to know about the generic info for all the properties of
-             * a class. For instance; if the type arguments of a field are provided by a subclass.
-             */
-            if (info.isField())
-            {
-                termType = GenericsUtils.extractActualType(activeType, info.getField());
-            }
-            else
-            {
-                termType = GenericsUtils.extractActualType(activeType, method);
-            }
+            Type termType = info.getType();
 
             Class termClass = GenericsUtils.asClass(termType);
 
@@ -960,7 +949,7 @@ public class PropertyConduitSourceImpl i
             {
                 builder.add(" ($w) ");
             }
-            else if (info.isCastRequired() || info.getType() != termClass)
+            else if (info.isCastRequired() )
             {
                 builder.add(" (%s) ", wrapperTypeName);
             }
@@ -983,7 +972,7 @@ public class PropertyConduitSourceImpl i
                     break;
             }
 
-            return new GeneratedTerm(wrappedType == termClass ? termType : wrappedType, variableName);
+            return new GeneratedTerm(termType, variableName);
         }
 
         private void assertNodeType(Tree node, int... expected)
@@ -1013,7 +1002,7 @@ public class PropertyConduitSourceImpl i
             return new RuntimeException(message);
         }
 
-        private ExpressionTermInfo infoForMember(Class activeType, Tree node)
+        private ExpressionTermInfo infoForMember(Type activeType, Tree node)
         {
             if (node.getType() == INVOKE)
                 return infoForInvokeNode(activeType, node);
@@ -1021,27 +1010,41 @@ public class PropertyConduitSourceImpl i
             return infoForPropertyOrPublicField(activeType, node);
         }
 
-        private ExpressionTermInfo infoForPropertyOrPublicField(Class activeType, Tree node)
+        private ExpressionTermInfo infoForPropertyOrPublicField(Type activeType, Tree node)
         {
             String propertyName = node.getText();
 
-            ClassPropertyAdapter classAdapter = access.getAdapter(activeType);
+            final Class activeClass = GenericsUtils.asClass(activeType);
+            ClassPropertyAdapter classAdapter = access.getAdapter(activeClass);
             final PropertyAdapter adapter = classAdapter.getPropertyAdapter(propertyName);
 
             if (adapter == null)
             {
-                List<String> names = classAdapter.getPropertyNames();
-
+                final List<String> names = classAdapter.getPropertyNames();
+                final String className = activeClass.getName();
                 throw new UnknownValueException(String.format(
-                        "Class %s does not contain a property (or public field) named '%s'.", activeType.getName(),
-                        propertyName), new AvailableValues("Properties (and public fields)", names));
+                        "Class %s does not contain a property (or public field) named '%s'.", className, propertyName),
+                        new AvailableValues("Properties (and public fields)", names));
             }
 
-            return createExpressionTermInfoForProperty(adapter);
-        }
+            final Type type;
+            final boolean isCastRequired;
+            if (adapter.getField() != null)
+            {
+                type = GenericsUtils.extractActualType(activeType, adapter.getField());
+                isCastRequired = !type.equals(adapter.getField().getType());
+            }
+            else if (adapter.getReadMethod() != null)
+            {
+                type = GenericsUtils.extractActualType(activeType, adapter.getReadMethod());
+                isCastRequired = !type.equals(adapter.getReadMethod().getReturnType());
+            }
+            else
+            {
+                type = adapter.getType();
+                isCastRequired = adapter.isCastRequired();
+            }
 
-        private ExpressionTermInfo createExpressionTermInfoForProperty(final PropertyAdapter adapter)
-        {
             return new ExpressionTermInfo()
             {
                 public Method getReadMethod()
@@ -1054,14 +1057,14 @@ public class PropertyConduitSourceImpl i
                     return adapter.getWriteMethod();
                 }
 
-                public Class getType()
+                public Type getType()
                 {
-                    return adapter.getType();
+                    return type;
                 }
 
                 public boolean isCastRequired()
                 {
-                    return adapter.isCastRequired();
+                    return isCastRequired;
                 }
 
                 public String getDescription()
@@ -1091,21 +1094,22 @@ public class PropertyConduitSourceImpl i
             };
         }
 
-        private ExpressionTermInfo infoForInvokeNode(Class activeType, Tree node)
+        private ExpressionTermInfo infoForInvokeNode(Type activeType, Tree node)
         {
             String methodName = node.getChild(0).getText();
 
             int parameterCount = node.getChildCount() - 1;
 
+            final Class activeClass = GenericsUtils.asClass(activeType);
             try
             {
-                final Method method = findMethod(activeType, methodName, parameterCount);
+                final Method method = findMethod(activeClass, methodName, parameterCount);
 
                 if (method.getReturnType().equals(void.class))
-                    throw new RuntimeException(String.format("Method %s.%s() returns void.", activeType.getName(),
+                    throw new RuntimeException(String.format("Method %s.%s() returns void.", activeClass.getName(),
                             methodName));
 
-                final Class genericType = GenericsUtils.extractGenericReturnType(activeType, method);
+                final Type genericType = GenericsUtils.extractActualType(activeType, method);
 
                 return new ExpressionTermInfo()
                 {
@@ -1119,7 +1123,7 @@ public class PropertyConduitSourceImpl i
                         return null;
                     }
 
-                    public Class getType()
+                    public Type getType()
                     {
                         return genericType;
                     }
@@ -1157,8 +1161,8 @@ public class PropertyConduitSourceImpl i
             }
             catch (NoSuchMethodException ex)
             {
-                throw new RuntimeException(String.format("No public method '%s()' in class %s.", methodName,
-                        activeType.getName()));
+                throw new RuntimeException(
+                        String.format("No public method '%s()' in class %s.", methodName, activeClass.getName()));
             }
         }
 
@@ -1246,7 +1250,7 @@ public class PropertyConduitSourceImpl i
      * conduits for the same
      * rootClass/expression, and it will get sorted out when the conduit is
      * stored into the cache.
-     * 
+     *
      * @param rootClass
      *            class of root object for expression evaluation
      * @param expression

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PropertyWorker.java Sun Dec 26 19:41:50 2010
@@ -31,7 +31,7 @@ import org.apache.tapestry5.services.Tra
  * Provides the getter and setter methods. The methods are added as "existing", meaning that field access to them will
  * be transformed as necessary by other annotations. This worker needs to be scheduled before any worker that might
  * delete a field.
- * 
+ *
  * @see org.apache.tapestry5.annotations.Property
  */
 public class PropertyWorker implements ComponentClassTransformWorker
@@ -86,8 +86,10 @@ public class PropertyWorker implements C
 
     private void addGetter(ClassTransformation transformation, TransformField field, String propertyName)
     {
-        TransformMethodSignature getter = new TransformMethodSignature(Modifier.PUBLIC, field.getType(), "get"
-                + propertyName, null, null);
+        final String methodSignature = field.getSignature() != null ? "()" + field.getSignature() : null;
+        TransformMethodSignature getter =
+                new TransformMethodSignature(Modifier.PUBLIC, field.getType(), methodSignature,
+                        "get" + propertyName, null, null);
 
         ensureNotOverride(transformation, getter);
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformField.java Sun Dec 26 19:41:50 2010
@@ -23,7 +23,7 @@ import org.apache.tapestry5.ioc.services
  * A field defined by (or created within) a {@link ClassTransformation},
  * allowing the details of the field to be
  * accessed or modified.
- * 
+ *
  * @since 5.2.0
  */
 public interface TransformField extends AnnotationProvider, Comparable<TransformField>
@@ -41,11 +41,19 @@ public interface TransformField extends 
     String getType();
 
     /**
+     * Returns the fields fully qualified generic type, or null if not defined.
+     * (in Java source syntax, i.e., "()Ljava/util/List<Ljava/lang/String;>;"
+     *
+     * @since 5.3.0
+     */
+    String getSignature();
+
+    /**
      * Claims the field so as to ensure that only a single annotation is applied to any single field.
      * When a transformation occurs (driven by a field annotation), the field is claimed (using the
      * annotation object as the tag). If a field has multiple conflicting annotations, this will be discovered when
      * the code attempts to claim the field a second time.
-     * 
+     *
      * @param tag
      *            a non-null object that represents why the field is being tagged (this is typically
      *            a specific annotation on the field)
@@ -56,7 +64,7 @@ public interface TransformField extends 
 
     /**
      * Replaces read and write field access with a conduit. The field will be deleted.
-     * 
+     *
      * @param conduitProvider
      *            provides the actual conduit at class instantiation time
      */
@@ -64,7 +72,7 @@ public interface TransformField extends 
 
     /**
      * Replaces read and write field access with a conduit. The field itself will be deleted.
-     * 
+     *
      * @param conduitField
      *            identifies the field containing (via injection) an instance of {@link FieldValueConduit}
      */
@@ -73,7 +81,7 @@ public interface TransformField extends 
     /**
      * Replaces read and write field access with a conduit. A new field is created for the conduit instance,
      * and the original field is deleted.
-     * 
+     *
      * @param conduit
      *            used to replace read and write access to the field
      */
@@ -81,7 +89,7 @@ public interface TransformField extends 
 
     /**
      * Returns the modifiers for the field.
-     * 
+     *
      * @see Field#getModifiers()
      */
     int getModifiers();
@@ -89,7 +97,7 @@ public interface TransformField extends 
     /**
      * Converts this field into a read only field whose value is the provided
      * value. This is used when converting an existing field into a read-only injected value.
-     * 
+     *
      * @param value
      *            the value provided by the field
      */
@@ -99,7 +107,7 @@ public interface TransformField extends 
      * Like {@link #inject(Object)}, except that the value to be injected is obtained
      * from a {@link ComponentValueProvider}. It is assumed that the provider will return an object
      * assignable to the field.
-     * 
+     *
      * @param <T>
      *            type of field
      * @param provider

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TransformMethodSignature.java Sun Dec 26 19:41:50 2010
@@ -33,7 +33,7 @@ public class TransformMethodSignature im
 
     private final int modifiers;
 
-    private final String returnType, methodName;
+    private final String returnType, methodName, signature;
 
     private final String[] parameterTypes, exceptionTypes;
 
@@ -47,12 +47,22 @@ public class TransformMethodSignature im
     }
 
     public TransformMethodSignature(int modifiers, String type, String name, String[] parameterTypes,
+                String[] exceptionTypes)
+    {
+        this(modifiers, type, null, name, parameterTypes, exceptionTypes);
+    }
+
+    /**
+     * @since 5.3.0
+     */
+    public TransformMethodSignature(int modifiers, String type, String signature, String name, String[] parameterTypes,
             String[] exceptionTypes)
     {
         assert InternalUtils.isNonBlank(name);
         assert InternalUtils.isNonBlank(type);
 
         this.modifiers = modifiers;
+        this.signature = signature;
 
         returnType = type;
         methodName = name;
@@ -89,7 +99,7 @@ public class TransformMethodSignature im
 
     /**
      * Returns the set of modifier flags for this method.
-     * 
+     *
      * @see java.lang.reflect.Modifier
      */
     public int getModifiers()
@@ -97,6 +107,10 @@ public class TransformMethodSignature im
         return modifiers;
     }
 
+    public String getSignature() {
+        return signature;
+    }
+
     /**
      * Returns an array of the type name for each parameter. Calling code should not modify the
      * array.
@@ -237,7 +251,7 @@ public class TransformMethodSignature im
      * Returns a shortened form of the string representation of the method. It lists just the name
      * of the method and the
      * types of any parameters, omitting return type, exceptions and modifiers.
-     * 
+     *
      * @return
      */
     public String getMediumDescription()

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml?rev=1052930&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GenericLoopDemo.tml Sun Dec 26 19:41:50 2010
@@ -0,0 +1,20 @@
+<html t:type="border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
+
+<h1>Generic Loop Demo</h1>
+
+<h2>Integer Loop</h2>
+<t:loop t:id="integerLoop" source="integerSource">
+    <div id="int_${integerLoop.index}">${integerLoop.value}</div>
+</t:loop>
+
+<h2>Person Loop</h2>
+<t:loop t:id="personLoop" source="personSource">
+    <div id="person_${personLoop.index}">${personLoop.value.name}</div>
+</t:loop>
+
+<h2>BaseClass parameterized type loop</h2>
+<t:loop t:id="inheritedLoop" source="inheritedLoopSource">
+    <div id="inherited_${inheritedLoop.index}"
+            >${inheritedLoop.value.type}:${inheritedLoop.value.age}:${inheritedLoop.value.name}</div>
+</t:loop>
+</html>
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/LoopTests.java Sun Dec 26 19:41:50 2010
@@ -44,6 +44,29 @@ public class LoopTests extends TapestryC
         test_loop_inside_form("ToDo List (Volatile)");
     }
 
+    @Test
+    public void generic_loop()
+    {
+        clickThru("Generic Loop Demo");
+        String[] strings = {"1", "3", "5", "7", "11"};
+        for ( int i = 0; i< strings.length; ++i)
+        {
+            assertText("int_" + i, strings[i]);
+        }
+
+        strings = new String[] {"John Doe", "Jane Dover", "James Jackson"};
+        for ( int i = 0; i< strings.length; ++i)
+        {
+            assertText("person_" + i, strings[i]);
+        }
+
+        strings = new String[] {"DOG:6:Dakota", "CAT:3:Jill", "CAT:3:Jack"};
+        for (int i = 0; i< strings.length; ++i)
+        {
+            assertText("inherited_" + i, strings[i]);
+        }
+    }
+
     private void test_loop_inside_form(String linkLabel)
     {
         clickThru(linkLabel);

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java?rev=1052930&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BaseGenericLoopDemo.java Sun Dec 26 19:41:50 2010
@@ -0,0 +1,25 @@
+package org.apache.tapestry5.integration.app1.pages;
+
+import org.apache.tapestry5.annotations.BeginRender;
+import org.apache.tapestry5.annotations.Component;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.corelib.components.Loop;
+
+import java.util.List;
+
+public abstract class BaseGenericLoopDemo<T> {
+
+    @Property
+    @Component
+    private Loop<T> inheritedLoop;
+
+    @Property
+    private List<T> inheritedLoopSource;
+
+    @BeginRender
+    void setupLoop() {
+        inheritedLoopSource = initInheritedLoop();        
+    }
+
+    abstract List<T> initInheritedLoop();
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java?rev=1052930&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/GenericLoopDemo.java Sun Dec 26 19:41:50 2010
@@ -0,0 +1,73 @@
+package org.apache.tapestry5.integration.app1.pages;
+
+import org.apache.tapestry5.annotations.Component;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.corelib.components.Loop;
+import org.apache.tapestry5.integration.app1.data.Person;
+import org.apache.tapestry5.integration.app1.data.Pet;
+import org.apache.tapestry5.integration.app1.data.PetType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class GenericLoopDemo extends BaseGenericLoopDemo<Pet> {
+
+    @Property
+    @Component(parameters = {"source=integerSource"})
+    private Loop<Integer> integerLoop;
+
+    @Property
+    private List<Integer> integerSource;
+
+    @Property
+    @Component(parameters = {"source=personSource"})
+    private Loop<Person> personLoop;
+
+    @Property
+    private List<Person> personSource;
+
+    void beginRender() {
+        integerSource = Arrays.asList(1, 3, 5, 7, 11);
+        personSource = new ArrayList<Person>();
+
+        Person person = new Person();
+        person.setAge(25);
+        person.setName("John Doe");
+        personSource.add(person);
+
+        person = new Person();
+        person.setAge(53);
+        person.setName("Jane Dover");
+        personSource.add(person);
+
+        person = new Person();
+        person.setAge(13);
+        person.setName("James Jackson");
+        personSource.add(person);
+    }
+
+    @Override
+    List<Pet> initInheritedLoop() {
+        final ArrayList<Pet> list = new ArrayList<Pet>();
+        Pet pet = new Pet();
+        pet.setAge(6);
+        pet.setName("Dakota");
+        pet.setType(PetType.DOG);
+        list.add(pet);
+
+        pet = new Pet();
+        pet.setAge(3);
+        pet.setName("Jill");
+        pet.setType(PetType.CAT);
+        list.add(pet);
+
+        pet = new Pet();
+        pet.setAge(3);
+        pet.setName("Jack");
+        pet.setType(PetType.CAT);
+        list.add(pet);
+        return list;
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java Sun Dec 26 19:41:50 2010
@@ -151,6 +151,8 @@ public class Index
 
                     new Item("EmptyLoopDemo", "Empty Loop Demo", "Use of empty parameter with the Loop component."),
 
+                    new Item("GenericLoopDemo", "Generic Loop Demo", "Use of generic parameters with the Loop component."),
+
                     new Item("BlankPasswordDemo", "Blank Password Demo",
                             "Show that a blank value in a PasswordField does not update the server side value."),
 

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java?rev=1052930&r1=1052929&r2=1052930&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java Sun Dec 26 19:41:50 2010
@@ -24,196 +24,586 @@ import java.util.LinkedList;
 public class GenericsUtils
 {
     /**
-     * Analyzes the method (often defined in a base class) in the context of a particular concrete implementation of the
-     * class to establish the generic type of a property. This works when the property type is defined as a class
-     * generic parameter.
-     * 
-     * @param containingClassType
-     *            class containing the method, used to reason about generics
-     * @param method
-     *            method (possibly from a base class of type) to extract
-     * @return the generic type if it may be determined, or the raw type (that is, with type erasure, most often
-     *         Object)
+     * 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 containingClassType, Method method)
+    public static Class<?> extractGenericReturnType(Class<?> containingClass, Method method)
     {
-        return extractActualTypeAsClass(containingClassType, method.getDeclaringClass(), method.getGenericReturnType(),
-                method.getReturnType());
-
+        return asClass(resolve(method.getGenericReturnType(), containingClass));
     }
 
+
     /**
-     * Analyzes the field in the context of a particular concrete implementation of the class to establish
-     * the generic type of a (public) field. This works when the field type is defined as a class
-     * generic parameter.
-     * 
-     * @param containingClassType
-     *            class containing the method, used to reason about generics
-     * @param field
-     *            public field to extract type from
-     * @return the generic type if it may be determined, or the raw type (that is, with type erasure, most often
-     * @since 5.2.0
+     * 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 containingClassType, Field field)
+    public static Class extractGenericFieldType(Class containingClass, Field field)
     {
-        return extractActualTypeAsClass(containingClassType, field.getDeclaringClass(), field.getGenericType(),
-                field.getType());
+        return asClass(resolve(field.getGenericType(), containingClass));
     }
 
     /**
-     * @param owner
-     *            - type that owns the field
-     * @param field
-     *            - field that is generic
-     * @return Type
+     * 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 owner, Field field)
+    public static Type extractActualType(Type containingType, Method method)
     {
-        return extractActualType(owner, field.getDeclaringClass(), field.getGenericType(), field.getType());
+        return resolve(method.getGenericReturnType(), containingType);
     }
 
     /**
-     * @param owner
-     *            - type that owns the field
-     * @param method
-     *            - method with generic return type
-     * @return Type
+     * 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 owner, Method method)
+    public static Type extractActualType(Type containingType, Field field)
     {
-        return extractActualType(owner, method.getDeclaringClass(), method.getGenericReturnType(),
-                method.getReturnType());
+        return resolve(field.getGenericType(), containingType);
     }
 
     /**
-     * Extracts the Class used as a type argument when declaring a
-     * 
-     * @param containingType
-     *            - the type which the method is being/will be called on
-     * @param declaringClass
-     *            - the class that the method is actually declared in (base class)
+     * Resolves the type parameter based on the context of the containingType.
+     * <p/>
+     * {@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. List&lt;E>; List&lt;Map&lt;Long, String>>, E would be returned as a ParameterizedType with the raw
+     * type Map and type arguments Long and String.
+     * <p/>
+     *
      * @param type
-     *            - the generic type from the field/method being inspected
-     * @param defaultType
-     *            - the default type to return if no parameterized type can be found
-     * @return a Class or ParameterizedType that the field/method can reliably be cast to.
+     *          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.?
      */
-    private static Type extractActualType(final Type containingType, final Class declaringClass, final Type type,
-            final Class defaultType)
+    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);
 
-        if (type instanceof ParameterizedType) { return type; }
-        if (!(type instanceof TypeVariable))
-            return defaultType;
+        // I'm leaning towards an exception here.
+        return type;
+    }
 
-        TypeVariable typeVariable = (TypeVariable) type;
 
-        if (!declaringClass.isAssignableFrom(asClass(containingType))) { throw new RuntimeException(String.format(
-                "%s must be a subclass of %s", declaringClass.getName(), asClass(containingType).getName())); }
+    /**
+     * Determines if the suspected super type is assignable from the suspected sub type.
+     *
+     * @param suspectedSuperType
+     *          e.g. GenericDAO&lt;Pet, String>
+     * @param suspectedSubType
+     *          e.g. PetDAO extends GenericDAO&lt;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);
 
-        // First, check to see if we are operating on a parameterized type already.
-        Type extractedType = type;
-        if (containingType instanceof ParameterizedType)
+        // The raw types need to be compatible.
+        if (!suspectedSuperClass.isAssignableFrom(suspectedSubClass))
         {
-            final int i = getTypeVariableIndex(asClass(containingType), typeVariable);
-            extractedType = ((ParameterizedType) containingType).getActualTypeArguments()[i];
-            if (extractedType instanceof Class || extractedType instanceof ParameterizedType) { return extractedType; }
+            return false;
         }
 
-        // Somewhere between declaringClass and containingClass are the parameter type arguments
-        // We are going to drop down the containingClassType until we find the declaring class.
-        // The class that extends declaringClass will define the ParameterizedType or a new TypeVariable
+        // 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.
 
-        final LinkedList<Type> classStack = new LinkedList<Type>();
-        Type cur = containingType;
-        while (cur != null && !asClass(cur).equals(declaringClass))
+        if (suspectedSuperType instanceof WildcardType)
         {
-            classStack.add(0, cur);
-            cur = asClass(cur).getSuperclass();
+            // ? 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;
         }
 
-        int typeArgumentIndex = getTypeVariableIndex(declaringClass, (TypeVariable) extractedType);
+        Type curType = suspectedSubType;
+        Class curClass;
 
-        for (Type descendant : classStack)
+        while (curType != null && !curType.equals(Object.class))
         {
-            final Class descendantClass = asClass(descendant);
-            final ParameterizedType parameterizedType = (ParameterizedType) descendantClass.getGenericSuperclass();
+            curClass = asClass(curType);
 
-            extractedType = parameterizedType.getActualTypeArguments()[typeArgumentIndex];
+            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()
+                        );
+                    }
+                }
 
-            if (extractedType instanceof Class || extractedType instanceof ParameterizedType) { return extractedType; }
+                return false;
+            }
 
-            if (extractedType instanceof TypeVariable)
+            final Type[] types = curClass.getGenericInterfaces();
+            for (Type t : types)
             {
-                typeArgumentIndex = getTypeVariableIndex(descendantClass, (TypeVariable) extractedType);
+                final Type resolved = resolve(t, suspectedSubType);
+                if (isAssignableFrom(suspectedSuperType, resolved))
+                    return true;
             }
-            else
+
+            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.
+     * <p/>
+     * {@code TypeVariable} the first bound is returned. If your type variable extends multiple interfaces that information
+     * is lost.
+     * <p/>
+     * {@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)
             {
-                // I don't know what else this could be?
-                break;
+                return asClass(bounds[0]);
             }
+            // If there is no lower bounds then the only thing that makes sense is Object.
+            return Object.class;
         }
 
-        return defaultType;
+        throw new RuntimeException(String.format("Unable to convert %s to Class.", actualType));
     }
 
     /**
-     * Convenience method to get actual type as raw class.
-     * 
-     * @param containingClassType
-     * @param declaringClass
-     * @param type
-     * @param defaultType
-     * @return
-     * @see #extractActualType(Type, Class, Type, Class)
+     * 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 it's most
+     * <a href="http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#112582">reifiable</a> form.
+     * <p/>
+     * <p/>
+     * 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.
+     * <p/>
+     * Consider <br/>
+     * <i>class Pair&gt;A,B> { A getA(){...}; ...}</i><br/>
+     * <i>class StringLongPair extends Pair&gt;String, Long> { }</i><br/>
+     * <p/>
+     * 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.
+     * <p/>
+     * 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.
+     * <p/>
+     * 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 it's 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 Class extractActualTypeAsClass(Class containingClassType, Class<?> declaringClass, Type type,
-            Class<?> defaultType)
+    private static Type resolve(TypeVariable typeVariable, Type containingType)
     {
-        final Type actualType = extractActualType(containingClassType, declaringClass, type, defaultType);
+        // 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);
+        }
 
-        return asClass(actualType);
+        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];
     }
 
-    public static Class asClass(Type actualType)
+    /**
+     * @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)
     {
-        if (actualType instanceof ParameterizedType)
+        final Type componentType = type.getGenericComponentType();
+
+        if (!(componentType instanceof Class))
         {
-            final Type rawType = ((ParameterizedType) actualType).getRawType();
-            if (rawType 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))
             {
-                // The sun implementation returns Class<?>, but there is room in the interface for it to be
-                // something else so to be safe ignore whatever "something else" might be.
-                // TODO: consider logging for that day when "something else" causes some confusion
-                return (Class) rawType;
+                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()
+        {
+            public Type[] getActualTypeArguments()
+            {
+                return typeArguments;
+            }
+
+            public Type getRawType()
+            {
+                return rawType;
+            }
+
+            public Type getOwnerType()
+            {
+                return ownerType;
+            }
+
+            @Override
+            public String toString()
+            {
+                return GenericsUtils.toString(this);
+            }
+        };
+    }
+
+    static GenericArrayType create(final Type componentType)
+    {
+        return new GenericArrayType()
+        {
+            public Type getGenericComponentType()
+            {
+                return componentType;
+            }
 
-        return (Class) actualType;
+            @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()
+        {
+            public Type[] getUpperBounds()
+            {
+                return upperBounds;
+            }
+
+            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",
+                isSuper ? toString(wt.getLowerBounds()) : 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 clazz
-     *            - the parameterized class
-     * @param typeVar
-     *            - the type variable in question.
-     * @return the index of the type variable in the classes type parameters.
-     */
-    private static int getTypeVariableIndex(Class clazz, TypeVariable typeVar)
-    {
-        // the label from the class (the T in List<T>, the K and V in Map<K,V>, etc)
-        String typeVarName = typeVar.getName();
-        int typeArgumentIndex = 0;
-        final TypeVariable[] typeParameters = clazz.getTypeParameters();
-        for (; typeArgumentIndex < typeParameters.length; typeArgumentIndex++)
+     *
+     * @param typeVariable - the type variable in question.
+     * @return the index of the type variable in it's 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))
-                break;
+                return typeArgumentIndex;
         }
-        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));
     }
 }