You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2010/08/18 02:24:34 UTC

svn commit: r986533 - in /tapestry/tapestry5/trunk: tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ser...

Author: hlship
Date: Wed Aug 18 00:24:33 2010
New Revision: 986533

URL: http://svn.apache.org/viewvc?rev=986533&view=rev
Log:
TAP5-818: Tapestry should properly support JDK 1.5 Generics when reading and updating properties and property expressions

Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java

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=986533&r1=986532&r2=986533&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 Wed Aug 18 00:24:33 2010
@@ -14,31 +14,6 @@
 
 package org.apache.tapestry5.internal.services;
 
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.DECIMAL;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.DEREF;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.FALSE;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.IDENTIFIER;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.INTEGER;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.INVOKE;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.LIST;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.NOT;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.NULL;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.RANGEOP;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.SAFEDEREF;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.STRING;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.THIS;
-import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.TRUE;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
 import org.antlr.runtime.ANTLRInputStream;
 import org.antlr.runtime.CommonTokenStream;
 import org.antlr.runtime.tree.Tree;
@@ -52,14 +27,7 @@ import org.apache.tapestry5.ioc.internal
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.GenericsUtils;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.ioc.services.ClassFab;
-import org.apache.tapestry5.ioc.services.ClassFabUtils;
-import org.apache.tapestry5.ioc.services.ClassFactory;
-import org.apache.tapestry5.ioc.services.ClassPropertyAdapter;
-import org.apache.tapestry5.ioc.services.MethodSignature;
-import org.apache.tapestry5.ioc.services.PropertyAccess;
-import org.apache.tapestry5.ioc.services.PropertyAdapter;
-import org.apache.tapestry5.ioc.services.TypeCoercer;
+import org.apache.tapestry5.ioc.services.*;
 import org.apache.tapestry5.ioc.util.AvailableValues;
 import org.apache.tapestry5.ioc.util.BodyBuilder;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
@@ -67,6 +35,20 @@ import org.apache.tapestry5.services.Com
 import org.apache.tapestry5.services.InvalidationListener;
 import org.apache.tapestry5.services.PropertyConduitSource;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.*;
+
 public class PropertyConduitSourceImpl implements PropertyConduitSource, InvalidationListener
 {
     private static final MethodSignature GET_SIGNATURE = new MethodSignature(Object.class, "get", new Class[]
@@ -170,6 +152,12 @@ public class PropertyConduitSourceImpl i
          * Returns true if the term is actually a public field.
          */
         boolean isField();
+
+        /**
+         * Returns the Field if the term is a public field.
+         */
+        Field getField();
+
     }
 
     /**
@@ -196,7 +184,7 @@ public class PropertyConduitSourceImpl i
 
     private class GeneratedTerm
     {
-        final Class type;
+        final Type type;
 
         final String termReference;
 
@@ -206,7 +194,7 @@ public class PropertyConduitSourceImpl i
          * @param termReference
          *            name of variable, or a constant value
          */
-        private GeneratedTerm(Class type, String termReference)
+        private GeneratedTerm(Type type, String termReference)
         {
             this.type = type;
             this.termReference = termReference;
@@ -391,10 +379,9 @@ public class PropertyConduitSourceImpl i
 
             builder.addln("%s root = (%<s) $1;", ClassFabUtils.toJavaClassName(rootType));
 
-            builder
-                    .addln(
-                            "if (root == null) throw new NullPointerException(\"Root object of property expression '%s' is null.\");",
-                            expression);
+            builder.addln(
+                    "if (root == null) throw new NullPointerException(\"Root object of property expression '%s' is null.\");",
+                    expression);
 
             builder.addln("return root;");
 
@@ -418,7 +405,7 @@ public class PropertyConduitSourceImpl i
             navBuilder.begin();
 
             String previousReference = "$1";
-            Class activeType = rootType;
+            Type activeType = rootType;
 
             Tree node = tree;
 
@@ -438,13 +425,14 @@ public class PropertyConduitSourceImpl i
             navBuilder.addln("return %s;", previousReference);
 
             navBuilder.end();
+            Class activeClass = GenericsUtils.asClass(activeType);
 
-            MethodSignature sig = new MethodSignature(activeType, "navigate", new Class[]
+            MethodSignature sig = new MethodSignature(activeClass, "navigate", new Class[]
             { rootType }, null);
 
             classFab.addMethod(Modifier.PRIVATE, sig, navBuilder.toString());
 
-            createGetterAndSetter(activeType, sig, node);
+            createGetterAndSetter(activeClass, sig, node);
         }
 
         private void createGetterAndSetter(Class activeType, MethodSignature navigateMethod, Tree node)
@@ -646,7 +634,7 @@ public class PropertyConduitSourceImpl i
                                 rootName);
 
                         previousReference = generated.termReference;
-                        activeType = generated.type;
+                        activeType = GenericsUtils.asClass(generated.type);
 
                         node = node.getChild(1);
 
@@ -659,7 +647,7 @@ public class PropertyConduitSourceImpl i
                                 NullHandling.IGNORE);
 
                         previousReference = generated.termReference;
-                        activeType = generated.type;
+                        activeType = GenericsUtils.asClass(generated.type);
 
                         node = null;
 
@@ -739,8 +727,8 @@ public class PropertyConduitSourceImpl i
 
         private void createNoOpSetter()
         {
-            createNoOp(classFab, SET_SIGNATURE, "Expression '%s' for class %s is read-only.", expression, rootType
-                    .getName());
+            createNoOp(classFab, SET_SIGNATURE, "Expression '%s' for class %s is read-only.", expression,
+                    rootType.getName());
         }
 
         private void createGetter(MethodSignature navigateMethod, Tree node, ExpressionTermInfo info)
@@ -749,8 +737,8 @@ public class PropertyConduitSourceImpl i
 
             if (method == null && !info.isField())
             {
-                createNoOp(classFab, GET_SIGNATURE, "Expression %s for class %s is write-only.", expression, rootType
-                        .getName());
+                createNoOp(classFab, GET_SIGNATURE, "Expression %s for class %s is write-only.", expression,
+                        rootType.getName());
                 return;
             }
 
@@ -839,7 +827,7 @@ public class PropertyConduitSourceImpl i
                 GeneratedTerm generatedTerm = subexpression(bodyBuilder, node.getChild(i + childOffset), rootName);
                 String currentReference = generatedTerm.termReference;
 
-                Class actualType = generatedTerm.type;
+                Class actualType = GenericsUtils.asClass(generatedTerm.type);
 
                 Class parameterType = parameterTypes[i];
 
@@ -849,13 +837,13 @@ public class PropertyConduitSourceImpl i
                 {
                     String coerced = nextVariableName(parameterType);
 
-                    String call = String.format("coerce(($w) %s, %s)", currentReference, addInjection(Class.class,
-                            parameterType));
+                    String call = String.format("coerce(($w) %s, %s)", currentReference,
+                            addInjection(Class.class, parameterType));
 
                     String parameterTypeName = ClassFabUtils.toJavaClassName(parameterType);
 
-                    bodyBuilder.addln("%s %s = %s;", parameterTypeName, coerced, ClassFabUtils.castReference(call,
-                            parameterTypeName));
+                    bodyBuilder.addln("%s %s = %s;", parameterTypeName, coerced,
+                            ClassFabUtils.castReference(call, parameterTypeName));
 
                     currentReference = coerced;
                 }
@@ -882,7 +870,7 @@ public class PropertyConduitSourceImpl i
          * Extends the navigate method for a node, which will be a DEREF or
          * SAFEDERF.
          */
-        private GeneratedTerm processDerefNode(BodyBuilder builder, Class activeType, Tree node,
+        private GeneratedTerm processDerefNode(BodyBuilder builder, Type activeType, Tree node,
                 String previousVariableName, String rootName)
         {
             // The first child is the term.
@@ -922,26 +910,39 @@ public class PropertyConduitSourceImpl i
             return InternalUtils.lastTerm(type.getName());
         }
 
-        private GeneratedTerm addAccessForMember(BodyBuilder builder, Class activeType, Tree term,
+        private GeneratedTerm addAccessForMember(BodyBuilder builder, Type activeType, Tree term,
                 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(activeType, term);
+            ExpressionTermInfo info = infoForMember(activeClass, term);
 
             Method method = info.getReadMethod();
 
             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(), activeType.getName()));
+                        "Property '%s' of class %s is not readable (it has no read accessor method).",
+                        info.getDescription(), activeClass.getName()));
 
-            // If a primitive type, convert to wrapper type
+            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);
+            }
+
+            Class termClass = GenericsUtils.asClass(termType);
 
-            Class termType = info.getType();
-            final Class wrappedType = ClassFabUtils.getWrapperType(termType);
+            final Class wrappedType = ClassFabUtils.getWrapperType(termClass);
 
             String wrapperTypeName = ClassFabUtils.toJavaClassName(wrappedType);
 
@@ -955,11 +956,11 @@ public class PropertyConduitSourceImpl i
             // Casts are needed for primitives, and for the case where
             // generics are involved.
 
-            if (termType.isPrimitive())
+            if (termClass.isPrimitive())
             {
                 builder.add(" ($w) ");
             }
-            else if (info.isCastRequired())
+            else if (info.isCastRequired() || info.getType() != termClass)
             {
                 builder.add(" (%s) ", wrapperTypeName);
             }
@@ -982,7 +983,7 @@ public class PropertyConduitSourceImpl i
                     break;
             }
 
-            return new GeneratedTerm(wrappedType, variableName);
+            return new GeneratedTerm(wrappedType == termClass ? termType : wrappedType, variableName);
         }
 
         private void assertNodeType(Tree node, int... expected)
@@ -1005,9 +1006,9 @@ public class PropertyConduitSourceImpl i
             for (int i = 0; i < expected.length; i++)
                 tokenNames.add(PropertyExpressionParser.tokenNames[expected[i]]);
 
-            String message = String.format("Node %s was type %s, but was expected to be (one of) %s.", node
-                    .toStringTree(), PropertyExpressionParser.tokenNames[node.getType()], InternalUtils
-                    .joinSorted(tokenNames));
+            String message = String.format("Node %s was type %s, but was expected to be (one of) %s.",
+                    node.toStringTree(), PropertyExpressionParser.tokenNames[node.getType()],
+                    InternalUtils.joinSorted(tokenNames));
 
             return new RuntimeException(message);
         }
@@ -1082,6 +1083,11 @@ public class PropertyConduitSourceImpl i
                 {
                     return adapter.isField();
                 }
+
+                public Field getField()
+                {
+                    return adapter.getField();
+                }
             };
         }
 
@@ -1142,12 +1148,17 @@ public class PropertyConduitSourceImpl i
                     {
                         return false;
                     }
+
+                    public Field getField()
+                    {
+                        return null;
+                    }
                 };
             }
             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,
+                        activeType.getName()));
             }
         }
 
@@ -1375,8 +1386,8 @@ public class PropertyConduitSourceImpl i
         }
         catch (Exception ex)
         {
-            throw new RuntimeException(String.format("Error parsing property expression '%s': %s.", expression, ex
-                    .getMessage()), ex);
+            throw new RuntimeException(String.format("Error parsing property expression '%s': %s.", expression,
+                    ex.getMessage()), ex);
         }
     }
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java?rev=986533&r1=986532&r2=986533&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java Wed Aug 18 00:24:33 2010
@@ -22,6 +22,7 @@ import org.apache.tapestry5.integration.
 import org.apache.tapestry5.internal.InternalPropertyConduit;
 import org.apache.tapestry5.internal.bindings.PropBindingFactoryTest;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
+import org.apache.tapestry5.internal.util.Holder;
 import org.apache.tapestry5.internal.util.IntegerRange;
 import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl;
 import org.apache.tapestry5.ioc.services.ClassFab;
@@ -231,6 +232,228 @@ public class PropertyConduitSourceImplTe
         assertSame(conduit.getPropertyType(), String.class);
     }
 
+    public static class One<A, B>
+    {
+        A a;
+        B b;
+
+        public A getA()
+        {
+            return a;
+        }
+
+        public void setA(A a)
+        {
+            this.a = a;
+        }
+
+        public B getB()
+        {
+            return b;
+        }
+
+        public void setB(B b)
+        {
+            this.b = b;
+        }
+    }
+
+    public static class Two<B> extends One<String, B>
+    {
+        String s;
+        B b2;
+
+        public String getS()
+        {
+            return s;
+        }
+
+        public void setS(String s)
+        {
+            this.s = s;
+        }
+
+        public B getB2()
+        {
+            return b2;
+        }
+
+        public void setB2(B b2)
+        {
+            this.b2 = b2;
+        }
+    }
+
+    public static class Three extends Two<Long>
+    {
+        Long x;
+
+        public Long getX()
+        {
+            return x;
+        }
+
+        public void setX(Long x)
+        {
+            this.x = x;
+        }
+    }
+
+    public static class WithParameters<C, T>
+    {
+        private C type1Property; // method access
+        public C type1Field; // field access
+        private T type2Property; // method access
+        public T type2Field; // field access
+
+        private T[] type2ArrayProperty;
+        public T[] type2ArrayField;
+
+        public C getType1Property()
+        {
+            return type1Property;
+        }
+
+        public void setType1Property(C type1Property)
+        {
+            this.type1Property = type1Property;
+        }
+
+        public T getType2Property()
+        {
+            return type2Property;
+        }
+
+        public void setType2Property(T type2Property)
+        {
+            this.type2Property = type2Property;
+        }
+
+        public T[] getType2ArrayProperty()
+        {
+            return type2ArrayProperty;
+        }
+
+        public void setType2ArrayProperty(T[] type2ArrayProperty)
+        {
+            this.type2ArrayProperty = type2ArrayProperty;
+        }
+    }
+
+    public static class RealizedParameters extends WithParameters<Holder<SimpleBean>, Long>
+    {
+    }
+
+    public static class WithGenericProperties
+    {
+        public Holder<SimpleBean> holder = new Holder<SimpleBean>();
+    }
+
+    public static interface GenericInterface<A, B>
+    {
+        A genericA();
+
+        B genericB();
+    }
+
+    public static class WithRealizedGenericInterface implements GenericInterface<String, Long>
+    {
+        String a;
+        Long b;
+
+        public String genericA()
+        {
+            return a;
+        }
+
+        public Long genericB()
+        {
+            return b;
+        }
+    }
+
+    @Test
+    public void generic_properties()
+    {
+        final WithGenericProperties bean = new WithGenericProperties();
+        final String first = "John";
+        final String last = "Doe";
+        final SimpleBean simple = new SimpleBean();
+        simple.setLastName(last);
+        simple.setAge(2);
+        simple.setFirstName(first);
+        bean.holder.put(simple);
+
+        PropertyConduit conduit = source.create(WithGenericProperties.class, "holder.get().firstName");
+        assertSame(conduit.get(bean), first);
+    }
+
+    @Test
+    public void generic_parameterized_base_with_properties()
+    {
+        final String first = "John";
+        final String last = "Doe";
+        final SimpleBean simple = new SimpleBean();
+        simple.setAge(2);
+        simple.setFirstName(first);
+        simple.setLastName(last);
+
+        final RealizedParameters bean = new RealizedParameters();
+        final Holder<SimpleBean> holder = new Holder<SimpleBean>();
+        holder.put(simple);
+        bean.setType1Property(holder);
+        bean.setType2Property(1234L);
+        bean.type1Field = holder;
+        bean.type2Field = 5678L;
+        bean.type2ArrayField = new Long[]
+        { 123L, 456L };
+
+        PropertyConduit conduit = source.create(RealizedParameters.class, "type1property.get().firstName");
+        assertSame(conduit.get(bean), first);
+        conduit.set(bean, "Change");
+        assertSame(conduit.get(bean), "Change");
+        conduit.set(bean, first);
+
+        conduit = source.create(RealizedParameters.class, "type1field.get().firstName");
+        assertSame(conduit.get(bean), first);
+
+        conduit = source.create(RealizedParameters.class, "type2field");
+        assertEquals(conduit.get(bean), bean.type2Field);
+
+        conduit = source.create(RealizedParameters.class, "type2property");
+        assertEquals(conduit.get(bean), bean.getType2Property());
+
+        conduit = source.create(RealizedParameters.class, "type2ArrayField");
+        assertEquals(conduit.get(bean), bean.type2ArrayField);
+
+    }
+
+    @Test
+    public void generic_interface()
+    {
+        final WithRealizedGenericInterface bean = new WithRealizedGenericInterface();
+        bean.a = "Hello";
+        bean.b = 12345L;
+
+        PropertyConduit conduit = source.create(WithRealizedGenericInterface.class, "genericA()");
+        assertSame(conduit.get(bean), "Hello");
+        conduit = source.create(WithRealizedGenericInterface.class, "genericB()");
+        assertEquals(conduit.get(bean), 12345L);
+    }
+
+    @Test
+    public void generic_nested()
+    {
+        Three bean = new Three();
+        bean.setA("hello");
+        bean.setB(123L);
+        bean.setB2(1235L);
+        bean.setX(54321L);
+
+        PropertyConduit conduit = source.create(Three.class, "a");
+        assertSame(conduit.get(bean), "hello");
+    }
+
     @Test
     public void null_root_object()
     {
@@ -380,21 +603,23 @@ public class PropertyConduitSourceImplTe
 
         assertListsEquals(l, new Long(1), new Double(2.0), "Bart");
     }
-    
+
     @Test
     public void arrays_as_method_argument()
     {
         PropertyConduit conduit = source.create(EchoBean.class, "echoArray(storedArray)");
         EchoBean bean = new EchoBean();
 
-        bean.setStoredArray(new Number[][]{ new Integer[] {1, 2}, new Double[] {3.0, 4.0}});
+        bean.setStoredArray(new Number[][]
+        { new Integer[]
+        { 1, 2 }, new Double[]
+        { 3.0, 4.0 } });
 
         Number[][] array = (Number[][]) conduit.get(bean);
 
         assertArraysEqual(array[0], 1, 2);
         assertArraysEqual(array[1], 3.0, 4.0);
     }
-    
 
     @Test
     public void not_operator()
@@ -485,7 +710,9 @@ public class PropertyConduitSourceImplTe
         assertEquals(falseConduit.get(bedrock), "Barney");
     }
 
-    /** TAP5-747 */
+    /**
+     * TAP5-747
+     */
     @Test
     public void dereference_result_of_method_invocation()
     {

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java?rev=986533&r1=986532&r2=986533&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.java Wed Aug 18 00:24:33 2010
@@ -230,6 +230,11 @@ public class PropertyAdapterImpl impleme
         return field != null;
     }
 
+    public Field getField()
+    {
+        return field;
+    }
+
     public Class getDeclaringClass()
     {
         return declaringClass;

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=986533&r1=986532&r2=986533&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 Wed Aug 18 00:24:33 2010
@@ -14,11 +14,8 @@
 
 package org.apache.tapestry5.ioc.internal.util;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
+import java.lang.reflect.*;
+import java.util.LinkedList;
 
 /**
  * Static methods related to the use of JDK 1.5 generics.
@@ -40,7 +37,9 @@ public class GenericsUtils
      */
     public static Class extractGenericReturnType(Class containingClassType, Method method)
     {
-        return extractGenericType(containingClassType, method.getReturnType(), method.getGenericReturnType());
+        return extractActualTypeAsClass(containingClassType, method.getDeclaringClass(), method.getGenericReturnType(),
+                method.getReturnType());
+
     }
 
     /**
@@ -57,57 +56,164 @@ public class GenericsUtils
      */
     public static Class extractGenericFieldType(Class containingClassType, Field field)
     {
-        return extractGenericType(containingClassType, field.getType(), field.getGenericType());
+        return extractActualTypeAsClass(containingClassType, field.getDeclaringClass(), field.getGenericType(),
+                field.getType());
     }
 
-    private static Class extractGenericType(Class containingClassType, Type defaultType, Type genericType)
+    /**
+     * @param owner
+     *            - type that owns the field
+     * @param field
+     *            - field that is generic
+     * @return Type
+     */
+    public static Type extractActualType(Type owner, Field field)
     {
-        // We can only handle the case where you "lock down" a generic type to a specific type.
+        return extractActualType(owner, field.getDeclaringClass(), field.getGenericType(), field.getType());
+    }
 
-        if (genericType instanceof TypeVariable)
-        {
+    /**
+     * @param owner
+     *            - type that owns the field
+     * @param method
+     *            - method with generic return type
+     * @return Type
+     */
+    public static Type extractActualType(Type owner, Method method)
+    {
+        return extractActualType(owner, method.getDeclaringClass(), method.getGenericReturnType(),
+                method.getReturnType());
+    }
 
-            // An odd name for the method that gives you access to the type parameters
-            // used when implementing this class. When you say Bean<String>, the first
-            // type variable of the generic superclass is class String.
+    /**
+     * 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)
+     * @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.
+     * @since 5.2.?
+     */
+    private static Type extractActualType(final Type containingType, final Class declaringClass, final Type type,
+            final Class defaultType)
+    {
 
-            Type superType = containingClassType.getGenericSuperclass();
+        if (type instanceof ParameterizedType) { return type; }
+        if (!(type instanceof TypeVariable))
+            return defaultType;
+
+        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())); }
+
+        // First, check to see if we are operating on a parameterized type already.
+        Type extractedType = type;
+        if (containingType instanceof ParameterizedType)
+        {
+            final int i = getTypeVariableIndex(asClass(containingType), typeVariable);
+            extractedType = ((ParameterizedType) containingType).getActualTypeArguments()[i];
+            if (extractedType instanceof Class || extractedType instanceof ParameterizedType) { return extractedType; }
+        }
 
-            if (superType instanceof ParameterizedType)
-            {
-                ParameterizedType superPType = (ParameterizedType) superType;
+        // 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
+
+        final LinkedList<Type> classStack = new LinkedList<Type>();
+        Type cur = containingType;
+        while (cur != null && !asClass(cur).equals(declaringClass))
+        {
+            classStack.add(0, cur);
+            cur = asClass(cur).getSuperclass();
+        }
 
-                TypeVariable tv = (TypeVariable) genericType;
+        int typeArgumentIndex = getTypeVariableIndex(declaringClass, (TypeVariable) extractedType);
 
-                String name = tv.getName();
+        for (Type descendant : classStack)
+        {
+            final Class descendantClass = asClass(descendant);
+            final ParameterizedType parameterizedType = (ParameterizedType) descendantClass.getGenericSuperclass();
 
-                TypeVariable[] typeVariables = tv.getGenericDeclaration().getTypeParameters();
+            extractedType = parameterizedType.getActualTypeArguments()[typeArgumentIndex];
 
-                for (int i = 0; i < typeVariables.length; i++)
-                {
-                    TypeVariable stv = typeVariables[i];
+            if (extractedType instanceof Class || extractedType instanceof ParameterizedType) { return extractedType; }
 
-                    // We're trying to match the name of the type variable that is used as the return type
-                    // of the method. With that name, we find the corresponding index in the
-                    // type declarations. With the index, we check superPType for the Class instance
-                    // that defines it. Generics has lots of other options that we simply can't handle.
+            if (extractedType instanceof TypeVariable)
+            {
+                typeArgumentIndex = getTypeVariableIndex(descendantClass, (TypeVariable) extractedType);
+            }
+            else
+            {
+                // I don't know what else this could be?
+                break;
+            }
+        }
 
-                    if (stv.getName().equals(name))
-                    {
-                        Type actualType = superPType.getActualTypeArguments()[i];
+        return defaultType;
+    }
 
-                        if (actualType instanceof Class)
-                            return (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)
+     */
+    private static Class extractActualTypeAsClass(Class containingClassType, Class<?> declaringClass, Type type,
+            Class<?> defaultType)
+    {
+        final Type actualType = extractActualType(containingClassType, declaringClass, type, defaultType);
 
-                        break;
-                    }
-                }
+        return asClass(actualType);
+    }
 
+    public static Class asClass(Type actualType)
+    {
+        if (actualType instanceof ParameterizedType)
+        {
+            final Type rawType = ((ParameterizedType) actualType).getRawType();
+            if (rawType 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;
             }
         }
 
-        return (Class) defaultType;
+        return (Class) actualType;
+    }
 
-        // P.S. I wrote this and I barely understand it. Fortunately, I have tests ...
+    /**
+     * 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++)
+        {
+            if (typeParameters[typeArgumentIndex].getName().equals(typeVarName))
+                break;
+        }
+        return typeArgumentIndex;
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java?rev=986533&r1=986532&r2=986533&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java Wed Aug 18 00:24:33 2010
@@ -16,13 +16,14 @@ package org.apache.tapestry5.ioc.service
 
 import org.apache.tapestry5.ioc.AnnotationProvider;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 
 /**
  * Provides access to a single property within a class. Acts as an {@link org.apache.tapestry5.ioc.AnnotationProvider};
  * when searching for annotations, the read method (if present) is checked first, followed by the write method, followed
  * by the underlying field (when the property name matches the field name).
- * <p>
+ * <p/>
  * Starting in release 5.2, this property may actually be a public field.
  * 
  * @see org.apache.tapestry5.ioc.services.ClassPropertyAdapter
@@ -110,6 +111,13 @@ public interface PropertyAdapter extends
     boolean isField();
 
     /**
+     * Returns the field if the property is a public field or null if the property is accessed via the read method.
+     * 
+     * @since 5.2
+     */
+    Field getField();
+
+    /**
      * The class in which the property (or public field) is defined.
      * 
      * @since 5.2