You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2008/01/25 03:12:40 UTC

svn commit: r615099 - in /tapestry/tapestry5/trunk: tapestry-core/src/main/java/org/apache/tapestry/internal/services/ tapestry-core/src/test/java/org/apache/tapestry/integration/ tapestry-core/src/test/java/org/apache/tapestry/integration/app1/base/ t...

Author: hlship
Date: Thu Jan 24 18:12:31 2008
New Revision: 615099

URL: http://svn.apache.org/viewvc?rev=615099&view=rev
Log:
TAPESTRY-1518: Add support for JDK 1.5 Generics when defining pages and accessing bean properties

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/base/GenericEditor.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/TrackEditor.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/GenericBean.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolder.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolderBean.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/base/
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/base/GenericEditor.tml
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/GenericsUtils.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/BaseGenericBean.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/GenericUtilsTest.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/NonGenericBean.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/Pair.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringBean.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringLongPair.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PropertyConduitSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PropertyConduitSourceImplTest.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/ClassPropertyAdapterImpl.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/PropertyAdapterImpl.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/services/PropertyAccessImplTest.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PropertyConduitSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PropertyConduitSourceImpl.java?rev=615099&r1=615098&r2=615099&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PropertyConduitSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PropertyConduitSourceImpl.java Thu Jan 24 18:12:31 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newConcurrentMap;
 import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
 import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
+import org.apache.tapestry.ioc.internal.util.GenericsUtils;
 import org.apache.tapestry.ioc.services.*;
 import org.apache.tapestry.ioc.util.BodyBuilder;
 import org.apache.tapestry.services.ComponentLayer;
@@ -30,9 +31,51 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 public class PropertyConduitSourceImpl implements PropertyConduitSource, InvalidationListener
 {
+    private interface ReadInfo extends AnnotationProvider
+    {
+        /**
+         * The name of the method to invoke.
+         */
+        String getMethodName();
+
+        /**
+         * The return type of the method, or the type of the property.
+         */
+        Class getType();
+
+        /**
+         * True if an explicit cast to the return type is needed (typically because of generics).
+         */
+        boolean isCastRequired();
+    }
+
+
+    /**
+     * Result from writing the property navigation portion of the expression.  For getter methods, the navigation is all
+     * terms in the expression; for setter methods, the navigation is all but the last term.
+     */
+    private interface PropertyNavigationResult
+    {
+        /**
+         * The name of the variable holding the final step in the expression.
+         */
+        String getFinalStepVariable();
+
+        /**
+         * The type of the final step variable.
+         */
+        Class getFinalStepType();
+
+        /**
+         * The method read information for the final term in the navigation portion of the expression.
+         */
+        ReadInfo getFinalReadInfo();
+    }
+
     private static final String PARENS = "()";
 
     private final PropertyAccess _access;
@@ -53,6 +96,8 @@
                                                                              new Class[]{Object.class, Object.class},
                                                                              null);
 
+    private final Pattern SPLIT_AT_DOTS = Pattern.compile("\\.");
+
     public PropertyConduitSourceImpl(PropertyAccess access, @ComponentLayer ClassFactory classFactory)
     {
         _access = access;
@@ -95,9 +140,9 @@
     }
 
     /**
-     * Clears its cache when the component class loader is invalidated; this is because it will be
-     * common to generated conduits rooted in a component class (which will no longer be valid and
-     * must be released to the garbage collector).
+     * Clears its caches when the component class loader is invalidated; this is because it will be common to generate
+     * conduits rooted in a component class (which will no longer be valid and must be released to the garbage
+     * collector).
      */
     public void objectWasInvalidated()
     {
@@ -105,11 +150,11 @@
         _classToEffectiveClass.clear();
     }
 
+
     /**
-     * Builds a subclass of {@link BasePropertyConduit} that implements the get() and set() methods
-     * and overrides the constructor. In a worst-case race condition, we may build two (or more)
-     * conduits for the same rootClass/expression, and it will get sorted out when the conduit is
-     * stored into the cache.
+     * Builds a subclass of {@link BasePropertyConduit} that implements the get() and set() methods and overrides the
+     * constructor. In a worst-case race condition, we may build two (or more) conduits for the same
+     * rootClass/expression, and it will get sorted out when the conduit is stored into the cache.
      *
      * @param rootClass
      * @param expression
@@ -123,15 +168,15 @@
 
         classFab.addConstructor(new Class[]{Class.class, AnnotationProvider.class, String.class}, null, "super($$);");
 
-        String[] terms = expression.split("\\.");
+        String[] terms = SPLIT_AT_DOTS.split(expression);
 
-        final Method readMethod = buildGetter(rootClass, classFab, expression, terms);
+        final ReadInfo readInfo = buildGetter(rootClass, classFab, expression, terms);
         final Method writeMethod = buildSetter(rootClass, classFab, expression, terms);
 
         // A conduit is either readable or writable, otherwise there will already have been
         // an error about unknown method name or property name.
 
-        Class propertyType = readMethod != null ? readMethod.getReturnType() : writeMethod
+        Class propertyType = readInfo != null ? readInfo.getType() : writeMethod
                 .getParameterTypes()[0];
 
         String description = String.format("PropertyConduit[%s %s]", rootClass.getName(), expression);
@@ -140,10 +185,9 @@
 
         AnnotationProvider provider = new AnnotationProvider()
         {
-
             public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
             {
-                T result = readMethod == null ? null : readMethod.getAnnotation(annotationClass);
+                T result = readInfo == null ? null : readInfo.getAnnotation(annotationClass);
 
                 if (result == null && writeMethod != null) result = writeMethod.getAnnotation(annotationClass);
 
@@ -163,19 +207,60 @@
 
     }
 
-    private Method buildGetter(Class rootClass, ClassFab classFab, String expression, String[] terms)
+    private ReadInfo buildGetter(Class rootClass, ClassFab classFab, String expression, String[] terms)
     {
         BodyBuilder builder = new BodyBuilder();
+
         builder.begin();
 
+        PropertyNavigationResult result = writePropertyNavigationCode(builder, rootClass, expression, terms, false);
+
+
+        if (result == null)
+        {
+            builder.clear();
+            builder
+                    .addln("throw new RuntimeException(\"Expression %s for class %s is write-only.\");", expression,
+                           rootClass.getName());
+        }
+        else
+        {
+            builder.addln("return %s;", result.getFinalStepVariable());
+
+            builder.end();
+        }
+
+        classFab.addMethod(Modifier.PUBLIC, GET_SIGNATURE, builder.toString());
+
+
+        return result == null ? null : result.getFinalReadInfo();
+    }
+
+    /**
+     * Writes the code for navigation
+     *
+     * @param builder
+     * @param rootClass
+     * @param expression
+     * @param terms
+     * @param forSetter  if true, then the last term is not read since it will be updated
+     * @return
+     */
+    private PropertyNavigationResult writePropertyNavigationCode(BodyBuilder builder, Class rootClass,
+                                                                 String expression, String[] terms, boolean forSetter)
+    {
         builder.addln("%s root = (%<s) $1;", ClassFabUtils.toJavaClassName(rootClass));
         String previousStep = "root";
 
         Class activeType = rootClass;
-        Method result = null;
-        boolean writeOnly = false;
+        ReadInfo readInfo = null;
+
+        // For a setter method, the navigation stops with  the penultimate
+        // term in the expression (the final term is what gets updated).
 
-        for (int i = 0; i < terms.length; i++)
+        int lastIndex = forSetter ? terms.length - 1 : terms.length;
+
+        for (int i = 0; i < lastIndex; i++)
         {
             String thisStep = "step" + (i + 1);
             String term = terms[i];
@@ -183,45 +268,75 @@
             boolean nullable = term.endsWith("?");
             if (nullable) term = term.substring(0, term.length() - 1);
 
-            Method readMethod = readMethodForTerm(activeType, expression, term, (i < terms.length - 1));
+            // All the navigation terms in the expression must be readable properties.
+            // The only exception is the final term in a reader method.
 
-            if (readMethod == null)
-            {
-                writeOnly = true;
-                break;
-            }
+            boolean mustExist = forSetter || i < terms.length - 1;
+
+            readInfo = readInfoForTerm(activeType, expression, term, mustExist);
+
+            // Means the property for this step exists but is write only, which is a problem!
+            // This can only happen for getter methods, we return null to indicate that
+            // the expression is write-only.
+
+            if (readInfo == null) return null;
 
             // If a primitive type, convert to wrapper type
 
-            Class termType = ClassFabUtils.getWrapperType(readMethod.getReturnType());
+            Class termType = readInfo.getType();
+            Class wrappedType = ClassFabUtils.getWrapperType(termType);
+
+            String termJavaName = ClassFabUtils.toJavaClassName(wrappedType);
+            builder.add("%s %s = ", termJavaName, thisStep);
 
-            // $w is harmless for non-wrapper types.
+            // Casts are needed for primitives, and for the case where
+            // generics are involved.
 
-            builder.addln("%s %s = ($w) %s.%s();", ClassFabUtils.toJavaClassName(termType), thisStep, previousStep,
-                          readMethod.getName());
+            if (termType.isPrimitive())
+            {
+                builder.add(" ($w) ");
+            }
+            else if (readInfo.isCastRequired())
+            {
+                builder.add(" (%s) ", termJavaName);
+            }
+
+            builder.addln("%s.%s();", previousStep, readInfo.getMethodName());
+
+            if (nullable)
+            {
+                builder.add("if (%s == null) return", thisStep);
 
-            if (nullable) builder.addln("if (%s == null) return null;", thisStep);
+                if (!forSetter) builder.add(" null");
 
-            activeType = termType;
-            result = readMethod;
+                builder.addln(";");
+            }
+
+            activeType = wrappedType;
             previousStep = thisStep;
         }
 
-        builder.addln("return %s;", previousStep);
-
-        builder.end();
+        final String finalStepVariable = previousStep;
+        final Class finalStepType = activeType;
+        final ReadInfo finalReadInfo = readInfo;
 
-        if (writeOnly)
+        return new PropertyNavigationResult()
         {
-            builder.clear();
-            builder
-                    .addln("throw new java.lang.RuntimeException(\"Expression %s for class %s is write-only.\");",
-                           expression, rootClass.getName());
-        }
+            public String getFinalStepVariable()
+            {
+                return finalStepVariable;
+            }
 
-        classFab.addMethod(Modifier.PUBLIC, GET_SIGNATURE, builder.toString());
+            public Class getFinalStepType()
+            {
+                return finalStepType;
+            }
 
-        return result;
+            public ReadInfo getFinalReadInfo()
+            {
+                return finalReadInfo;
+            }
+        };
     }
 
     private Method buildSetter(Class rootClass, ClassFab classFab, String expression, String[] terms)
@@ -229,46 +344,19 @@
         BodyBuilder builder = new BodyBuilder();
         builder.begin();
 
-        builder.addln("%s root = (%<s) $1;", ClassFabUtils.toJavaClassName(rootClass));
-        String previousStep = "root";
+        PropertyNavigationResult result = writePropertyNavigationCode(builder, rootClass, expression, terms, true);
 
-        Class activeType = rootClass;
-
-        for (int i = 0; i < terms.length - 1; i++)
-        {
-            String thisStep = "step" + (i + 1);
-            String term = terms[i];
-
-            boolean nullable = term.endsWith("?");
-            if (nullable) term = term.substring(0, term.length() - 1);
-
-            Method readMethod = readMethodForTerm(activeType, expression, term, true);
-
-            // If a primitive type, convert to wrapper type
-
-            Class termType = ClassFabUtils.getWrapperType(readMethod.getReturnType());
-
-            // $w is harmless for non-wrapper types.
-
-            builder.addln("%s %s = ($w) %s.%s();", ClassFabUtils.toJavaClassName(termType), thisStep, previousStep,
-                          readMethod.getName());
-
-            if (nullable) builder.addln("if (%s == null) return;", thisStep);
-
-            activeType = termType;
-            previousStep = thisStep;
-        }
+        // Because we pass true for the forSetter parameter, we know that the expression for the leading
+        // terms is a chain of readable expressions.  But is the final term writable?
 
-        // When writing, the last step is different.
-
-        Method writeMethod = writeMethodForTerm(activeType, expression, terms[terms.length - 1]);
+        Method writeMethod = writeMethodForTerm(result.getFinalStepType(), expression, terms[terms.length - 1]);
 
         if (writeMethod == null)
         {
             builder.clear();
             builder
-                    .addln("throw new java.lang.RuntimeException(\"Expression %s for class %s is read-only.\");",
-                           expression, rootClass.getName());
+                    .addln("throw new RuntimeException(\"Expression %s for class %s is read-only.\");", expression,
+                           rootClass.getName());
             classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder.toString());
 
             return null;
@@ -278,9 +366,13 @@
 
         Class wrapperType = ClassFabUtils.getWrapperType(parameterType);
 
+        // Cast the parameter from Object to the expected type for the method.
+
         builder.addln("%s value = (%<s) $2;", ClassFabUtils.toJavaClassName(wrapperType));
 
-        builder.add("%s.%s(value", previousStep, writeMethod.getName());
+        // Invoke the method, possibly converting a wrapper type to a primitive type along the way.
+
+        builder.add("%s.%s(value", result.getFinalStepVariable(), writeMethod.getName());
 
         if (parameterType != wrapperType)
             builder.add(".%s()", ClassFabUtils.getUnwrapMethodName(parameterType.getName()));
@@ -307,39 +399,89 @@
         return adapter.getWriteMethod();
     }
 
-    private Method readMethodForTerm(Class activeType, String expression, String term, boolean mustExist)
+    private ReadInfo readInfoForTerm(Class activeType, String expression, String term, boolean mustExist)
     {
         if (term.endsWith(PARENS))
         {
-            Method method = null;
             String methodName = term.substring(0, term.length() - PARENS.length());
 
             try
             {
-                method = activeType.getMethod(methodName);
+                final Method method = activeType.getMethod(methodName);
+
+                if (method.getReturnType().equals(void.class))
+                    throw new RuntimeException(ServicesMessages.methodIsVoid(term, activeType, expression));
+
+
+                final Class genericType = GenericsUtils.extractGenericReturnType(activeType, method);
+
+                return new ReadInfo()
+                {
+                    public String getMethodName()
+                    {
+                        return method.getName();
+                    }
+
+                    public Class getType()
+                    {
+                        return genericType;
+                    }
+
+                    public boolean isCastRequired()
+                    {
+                        return genericType != method.getReturnType();
+                    }
+
+                    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+                    {
+                        return method.getAnnotation(annotationClass);
+                    }
+                };
+
             }
             catch (NoSuchMethodException ex)
             {
                 throw new RuntimeException(ServicesMessages.methodNotFound(term, activeType, expression), ex);
             }
 
-            if (method.getReturnType().equals(void.class))
-                throw new RuntimeException(ServicesMessages.methodIsVoid(term, activeType, expression));
-
-            return method;
         }
 
+        // Otherwise, just a property name.
+
         ClassPropertyAdapter classAdapter = _access.getAdapter(activeType);
-        PropertyAdapter adapter = classAdapter.getPropertyAdapter(term);
+        final PropertyAdapter adapter = classAdapter.getPropertyAdapter(term);
 
         if (adapter == null) throw new RuntimeException(
                 ServicesMessages.noSuchProperty(activeType, term, expression, classAdapter.getPropertyNames()));
 
-        Method m = adapter.getReadMethod();
+        if (!adapter.isRead())
+        {
+            if (mustExist) throw new RuntimeException(ServicesMessages.writeOnlyProperty(term, activeType, expression));
+
+            return null;
+        }
+
+        return new ReadInfo()
+        {
+            public String getMethodName()
+            {
+                return adapter.getReadMethod().getName();
+            }
+
+            public Class getType()
+            {
+                return adapter.getType();
+            }
 
-        if (m == null && mustExist)
-            throw new RuntimeException(ServicesMessages.writeOnlyProperty(term, activeType, expression));
+            public boolean isCastRequired()
+            {
+                return adapter.isCastRequired();
+            }
 
-        return m;
+            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+            {
+                return adapter.getAnnotation(annotationClass);
+            }
+        };
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?rev=615099&r1=615098&r2=615099&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java Thu Jan 24 18:12:31 2008
@@ -1498,4 +1498,17 @@
         // The rendered &nbsp; becomes just a blank string.
         assertTextSeries("//tr[1]/td[%d]", 1, "7", "view", "1", "");
     }
+
+    /**
+     * TAPESTRY-1518
+     */
+    @Test
+    public void generic_page_type()
+    {
+        start("Generic Page Class Demo");
+
+        assertTextPresent("Editor for org.apache.tapestry.integration.app1.data.Track");
+
+        assertSourcePresent("<label for=\"title\" id=\"title:label\">Title</label>");
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/base/GenericEditor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/base/GenericEditor.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/base/GenericEditor.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/base/GenericEditor.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,65 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.integration.app1.base;
+
+import org.apache.tapestry.PropertyConduit;
+import org.apache.tapestry.annotations.Component;
+import org.apache.tapestry.annotations.Persist;
+import org.apache.tapestry.annotations.Retain;
+import org.apache.tapestry.corelib.components.BeanEditForm;
+import org.apache.tapestry.ioc.annotations.Inject;
+import org.apache.tapestry.services.PropertyConduitSource;
+
+/**
+ * For testing TAPESTRY-1518.
+ */
+public class GenericEditor<T>
+{
+    @Persist
+    private T _bean;
+
+    @Component(parameters = {"object=bean"})
+    private BeanEditForm _form;
+
+    @Inject
+    private PropertyConduitSource _conduit;
+
+    @Retain
+    private String _beanType;
+
+    {
+        // Use getClass(), not GenericEditor.class, to determine the correct type for the bean.
+        // Otherwise, it would be Object.
+
+        PropertyConduit conduit = _conduit.create(getClass(), "bean");
+
+        _beanType = conduit.getPropertyType().getName();
+    }
+
+    public String getBeanType()
+    {
+        return _beanType;
+    }
+
+    public T getBean()
+    {
+        return _bean;
+    }
+
+    public void setBean(T bean)
+    {
+        _bean = bean;
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java?rev=615099&r1=615098&r2=615099&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java Thu Jan 24 18:12:31 2008
@@ -208,7 +208,10 @@
 
             new Item("ExceptionEventDemo", "Exception Event Demo", "handling component event exceptions"),
 
-            new Item("AddedGridColumnsDemo", "Added Grid Columns Demo", "programatically adding grid columns"));
+            new Item("AddedGridColumnsDemo", "Added Grid Columns Demo", "programatically adding grid columns"),
+
+            new Item("TrackEditor", "Generic Page Class Demo",
+                     "demo use of generics with component classes and, particularily, with property types"));
 
     static
     {

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/TrackEditor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/TrackEditor.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/TrackEditor.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/TrackEditor.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,22 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.integration.app1.pages;
+
+import org.apache.tapestry.integration.app1.base.GenericEditor;
+import org.apache.tapestry.integration.app1.data.Track;
+
+public class TrackEditor extends GenericEditor<Track>
+{
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/GenericBean.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/GenericBean.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/GenericBean.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/GenericBean.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,30 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.services;
+
+public class GenericBean<T>
+{
+    private T _value;
+
+    public T getValue()
+    {
+        return _value;
+    }
+
+    public void setValue(T value)
+    {
+        _value = value;
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PropertyConduitSourceImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PropertyConduitSourceImplTest.java?rev=615099&r1=615098&r2=615099&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PropertyConduitSourceImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PropertyConduitSourceImplTest.java Thu Jan 24 18:12:31 2008
@@ -83,8 +83,8 @@
     }
 
     /**
-     * Or call this the "Hibernate" case; Hibernate creates sub-classes of entity classes in its own
-     * class loader to do all sorts of proxying. This trips up Javassist.
+     * Or call this the "Hibernate" case; Hibernate creates sub-classes of entity classes in its own class loader to do
+     * all sorts of proxying. This trips up Javassist.
      */
     @Test
     public void handle_beans_from_unexpected_classloader() throws Exception
@@ -110,6 +110,22 @@
         PropertyConduit conduit = _source.create(proxyClass, "firstName");
 
         assertEquals(conduit.get(simple), "Howard");
+    }
+
+    @Test
+    public void generics()
+    {
+        String string = "surprise";
+        StringHolder stringHolder = new StringHolder();
+        stringHolder.put(string);
+        StringHolderBean bean = new StringHolderBean();
+        bean.setValue(stringHolder);
+
+        PropertyConduit conduit = _source.create(StringHolderBean.class, "value.get()");
+
+        assertSame(conduit.get(bean), string);
+
+        assertSame(conduit.getPropertyType(), String.class);
     }
 
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolder.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolder.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolder.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,21 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.services;
+
+import org.apache.tapestry.internal.util.Holder;
+
+public class StringHolder extends Holder<String>
+{
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolderBean.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolderBean.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolderBean.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/StringHolderBean.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,19 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.services;
+
+public class StringHolderBean extends GenericBean<StringHolder>
+{
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/base/GenericEditor.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/base/GenericEditor.tml?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/base/GenericEditor.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/base/GenericEditor.tml Thu Jan 24 18:12:31 2008
@@ -0,0 +1,7 @@
+<t:border xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+
+    <h1>Editor for ${beanType}</h1>
+
+    <form t:id="form"/>
+
+</t:border>

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/ClassPropertyAdapterImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/ClassPropertyAdapterImpl.java?rev=615099&r1=615098&r2=615099&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/ClassPropertyAdapterImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/ClassPropertyAdapterImpl.java Thu Jan 24 18:12:31 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,11 +15,13 @@
 package org.apache.tapestry.ioc.internal.services;
 
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap;
+import org.apache.tapestry.ioc.internal.util.GenericsUtils;
 import org.apache.tapestry.ioc.internal.util.InternalUtils;
 import org.apache.tapestry.ioc.services.ClassPropertyAdapter;
 import org.apache.tapestry.ioc.services.PropertyAdapter;
 
 import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Map;
 
@@ -40,7 +42,12 @@
 
             if (pd.getPropertyType() == null) continue;
 
-            PropertyAdapter pa = new PropertyAdapterImpl(pd);
+            Method readMethod = pd.getReadMethod();
+
+            Class propertyType = readMethod == null ? pd.getPropertyType() : GenericsUtils.extractGenericReturnType(
+                    beanType, readMethod);
+
+            PropertyAdapter pa = new PropertyAdapterImpl(pd.getName(), propertyType, readMethod, pd.getWriteMethod());
 
             _adapters.put(pa.getName(), pa);
         }
@@ -83,8 +90,7 @@
     {
         PropertyAdapter pa = _adapters.get(name);
 
-        if (pa == null)
-            throw new IllegalArgumentException(ServiceMessages.noSuchProperty(_beanType, name));
+        if (pa == null) throw new IllegalArgumentException(ServiceMessages.noSuchProperty(_beanType, name));
 
         return pa;
     }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/PropertyAdapterImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/PropertyAdapterImpl.java?rev=615099&r1=615098&r2=615099&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/PropertyAdapterImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/services/PropertyAdapterImpl.java Thu Jan 24 18:12:31 2008
@@ -1,4 +1,4 @@
-// Copyright 2006 The Apache Software Foundation
+// Copyright 2006, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
 import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
 import org.apache.tapestry.ioc.services.PropertyAdapter;
 
-import java.beans.PropertyDescriptor;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -33,6 +32,8 @@
 
     private final Class _type;
 
+    private final boolean _castRequired;
+
     public PropertyAdapterImpl(String name, Class type, Method readMethod, Method writeMethod)
     {
         _name = notBlank(name, "name");
@@ -40,12 +41,8 @@
 
         _readMethod = readMethod;
         _writeMethod = writeMethod;
-    }
 
-    public PropertyAdapterImpl(PropertyDescriptor descriptor)
-    {
-        this(descriptor.getName(), descriptor.getPropertyType(), descriptor.getReadMethod(),
-             descriptor.getWriteMethod());
+        _castRequired = readMethod != null && readMethod.getReturnType() != type;
     }
 
     public String getName()
@@ -133,5 +130,10 @@
         if (result == null && _writeMethod != null) result = _writeMethod.getAnnotation(annotationClass);
 
         return result;
+    }
+
+    public boolean isCastRequired()
+    {
+        return _castRequired;
     }
 }

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/GenericsUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/GenericsUtils.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/GenericsUtils.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/GenericsUtils.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,91 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.ioc.internal.util;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+/**
+ * Static methods related to the use of JDK 1.5 generics.
+ */
+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 type   base type for evaluation
+     * @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)
+     */
+    public static Class extractGenericReturnType(Class type, Method method)
+    {
+        Class defaultType = method.getReturnType();
+
+        Type genericType = method.getGenericReturnType();
+
+        // We can only handle the case where you "lock down" a generic type to a specific type.
+
+        if (genericType instanceof TypeVariable)
+        {
+
+            // 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.
+
+            Type superType = type.getGenericSuperclass();
+
+            if (superType instanceof ParameterizedType)
+            {
+                ParameterizedType superPType = (ParameterizedType) superType;
+
+                TypeVariable tv = (TypeVariable) genericType;
+
+                String name = tv.getName();
+
+                TypeVariable[] typeVariables = tv.getGenericDeclaration().getTypeParameters();
+
+                for (int i = 0; i < typeVariables.length; i++)
+                {
+                    TypeVariable stv = typeVariables[i];
+
+                    // 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 (stv.getName().equals(name))
+                    {
+                        Type actualType = superPType.getActualTypeArguments()[i];
+
+                        if (actualType instanceof Class) return (Class) actualType;
+
+                        break;
+                    }
+                }
+
+            }
+        }
+
+
+        return defaultType;
+
+        // P.S. I wrote this and I barely understand it.  Fortunately, I have tests ...
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java?rev=615099&r1=615098&r2=615099&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java Thu Jan 24 18:12:31 2008
@@ -1,4 +1,4 @@
-// Copyright 2006 The Apache Software Foundation
+// Copyright 2006, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -19,8 +19,8 @@
 import java.lang.reflect.Method;
 
 /**
- * Provides access to a single property within a class. Acts as an {@link AnnotationProvider}; when searching
- * for annotations, the read method (if present) is checked first, followed by the write method.
+ * Provides access to a single property within a class. Acts as an {@link AnnotationProvider}; when searching for
+ * annotations, the read method (if present) is checked first, followed by the write method.
  *
  * @see org.apache.tapestry.ioc.services.ClassPropertyAdapter
  */
@@ -60,8 +60,8 @@
     Object get(Object instance);
 
     /**
-     * Updates the property value. The provided value must not be null if the property type is
-     * primitive, and must otherwise be of the proper type.
+     * Updates the property value. The provided value must not be null if the property type is primitive, and must
+     * otherwise be of the proper type.
      *
      * @param instance to update
      * @param value    new value for the property
@@ -73,4 +73,11 @@
      * Returns the type of the property.
      */
     Class getType();
+
+    /**
+     * Returns true if the return type of the read method is not the same as the property type. This can occur when the
+     * property has been defined using generics, in which case, the method's type may be Object when the property type
+     * is something more specific. This method is primarily used when generating runtime code related to the property.
+     */
+    boolean isCastRequired();
 }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/services/PropertyAccessImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/services/PropertyAccessImplTest.java?rev=615099&r1=615098&r2=615099&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/services/PropertyAccessImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/services/PropertyAccessImplTest.java Thu Jan 24 18:12:31 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
 import org.apache.tapestry.ioc.Registry;
 import org.apache.tapestry.ioc.annotations.Scope;
 import org.apache.tapestry.ioc.internal.IOCInternalTestCase;
+import org.apache.tapestry.ioc.internal.util.Pair;
+import org.apache.tapestry.ioc.internal.util.StringLongPair;
 import org.apache.tapestry.ioc.services.ClassPropertyAdapter;
 import org.apache.tapestry.ioc.services.PropertyAccess;
 import org.apache.tapestry.ioc.services.PropertyAdapter;
@@ -332,6 +334,7 @@
 
         assertTrue(pa.isRead());
         assertFalse(pa.isUpdate());
+        assertFalse(pa.isCastRequired());
 
         assertNull(pa.getWriteMethod());
         assertEquals(pa.getReadMethod(), findMethod(Bean.class, "getReadOnly"));
@@ -447,5 +450,33 @@
         PropertyAdapter pa = _access.getAdapter(AnnotatedBean.class).getPropertyAdapter("readOnly");
 
         assertNull(pa.getAnnotation(Scope.class));
+    }
+
+    @Test
+    public void using_generics()
+    {
+        ClassPropertyAdapter cpa1 = _access.getAdapter(StringLongPair.class);
+
+        PropertyAdapter pa1 = cpa1.getPropertyAdapter("key");
+        assertSame(pa1.getType(), String.class);
+        assertTrue(pa1.isCastRequired());
+
+        PropertyAdapter pa2 = cpa1.getPropertyAdapter("value");
+        assertSame(pa2.getType(), Long.class);
+        assertTrue(pa2.isCastRequired());
+
+        // On the base class, which defines the generic parameter type variables,
+        // the properties just look like Object.
+
+        ClassPropertyAdapter cpa2 = _access.getAdapter(Pair.class);
+
+        pa1 = cpa2.getPropertyAdapter("key");
+        assertSame(pa1.getType(), Object.class);
+        assertFalse(pa1.isCastRequired());
+
+        pa2 = cpa2.getPropertyAdapter("value");
+        assertSame(pa2.getType(), Object.class);
+        assertFalse(pa2.isCastRequired());
+
     }
 }

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/BaseGenericBean.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/BaseGenericBean.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/BaseGenericBean.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/BaseGenericBean.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,30 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.ioc.internal.util;
+
+public class BaseGenericBean<T>
+{
+    private T _value;
+
+    public T getValue()
+    {
+        return _value;
+    }
+
+    public void setValue(T value)
+    {
+        _value = value;
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/GenericUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/GenericUtilsTest.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/GenericUtilsTest.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/GenericUtilsTest.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,61 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.ioc.internal.util;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+public class GenericUtilsTest extends Assert
+{
+    protected Method find(Class clazz, String name)
+    {
+        for (Method m : clazz.getMethods())
+        {
+            if (m.getName().equalsIgnoreCase(name)) return m;
+        }
+
+        throw new IllegalArgumentException(
+                String.format("Could not locate a public method named '%s' in %s.", name, clazz));
+
+    }
+
+    @Test
+    public void generic_return_type_of_non_generic_type()
+    {
+        Method m = find(NonGenericBean.class, "getvalue");
+
+        assertSame(GenericsUtils.extractGenericReturnType(NonGenericBean.class, m), String.class);
+    }
+
+    @Test
+    public void generic_return_type_of_parameterized_bean()
+    {
+        Method m = find(StringBean.class, "getvalue");
+
+        assertSame(GenericsUtils.extractGenericReturnType(StringBean.class, m), String.class);
+    }
+
+    @Test
+    public void generic_bean_with_multiple_parameters()
+    {
+        Method getKey = find(StringLongPair.class, "getkey");
+        Method getValue = find(StringLongPair.class, "getvalue");
+
+        assertSame(GenericsUtils.extractGenericReturnType(StringLongPair.class, getKey), String.class);
+        assertSame(GenericsUtils.extractGenericReturnType(StringLongPair.class, getValue), Long.class);
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/NonGenericBean.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/NonGenericBean.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/NonGenericBean.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/NonGenericBean.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,30 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.ioc.internal.util;
+
+public class NonGenericBean
+{
+    private String _value;
+
+    public String getValue()
+    {
+        return _value;
+    }
+
+    public void setValue(String value)
+    {
+        _value = value;
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/Pair.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/Pair.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/Pair.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/Pair.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,42 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.ioc.internal.util;
+
+public class Pair<K, V>
+{
+    private K _key;
+
+    private V _value;
+
+    public K getKey()
+    {
+        return _key;
+    }
+
+    public void setKey(K key)
+    {
+        _key = key;
+    }
+
+    public V getValue()
+    {
+        return _value;
+    }
+
+    public void setValue(V value)
+    {
+        _value = value;
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringBean.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringBean.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringBean.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringBean.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,20 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.ioc.internal.util;
+
+public class StringBean extends BaseGenericBean<String>
+{
+
+}

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringLongPair.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringLongPair.java?rev=615099&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringLongPair.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry/ioc/internal/util/StringLongPair.java Thu Jan 24 18:12:31 2008
@@ -0,0 +1,19 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.ioc.internal.util;
+
+public class StringLongPair extends Pair<String, Long>
+{
+}