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 2008/06/19 03:55:08 UTC

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

Author: hlship
Date: Wed Jun 18 18:55:08 2008
New Revision: 669357

URL: http://svn.apache.org/viewvc?rev=669357&view=rev
Log:
TAPESTRY-2450: Unlike reflective access (via PropertyAdapter), PropertyConduit does not make field annotations visible
TAPESTRY-2404: PropertyConduitSource could build a shared method to "navigate" to the final property

Added:
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AccessableObjectAnnotationProvider.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java
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/integration/app1/services/AppModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SimpleBean.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/PropertyAdapterImpl.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=669357&r1=669356&r2=669357&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 Jun 18 18:55:08 2008
@@ -18,9 +18,8 @@
 import org.apache.tapestry5.internal.events.InvalidationListener;
 import org.apache.tapestry5.internal.util.MultiKey;
 import org.apache.tapestry5.ioc.AnnotationProvider;
-import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newConcurrentMap;
-import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
-import static org.apache.tapestry5.ioc.internal.util.Defense.notNull;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.Defense;
 import org.apache.tapestry5.ioc.internal.util.GenericsUtils;
 import org.apache.tapestry5.ioc.services.*;
 import org.apache.tapestry5.ioc.util.BodyBuilder;
@@ -35,12 +34,17 @@
 
 public class PropertyConduitSourceImpl implements PropertyConduitSource, InvalidationListener
 {
-    private interface ReadInfo extends AnnotationProvider
+    private interface ExpressionTermInfo extends AnnotationProvider
     {
         /**
-         * The name of the method to invoke.
+         * The name of the method to invoke to read the property value, or null.
          */
-        String getMethodName();
+        String getReadMethodName();
+
+        /**
+         * The name of the method to invoke to write the property value, or null.
+         */
+        String getWriteMethodName();
 
         /**
          * The return type of the method, or the type of the property.
@@ -54,40 +58,18 @@
     }
 
 
-    /**
-     * 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;
 
     private final ClassFactory classFactory;
 
-    private final Map<Class, Class> classToEffectiveClass = newConcurrentMap();
+    private final Map<Class, Class> classToEffectiveClass = CollectionFactory.newConcurrentMap();
 
     /**
      * Keyed on combination of root class and expression.
      */
-    private final Map<MultiKey, PropertyConduit> cache = newConcurrentMap();
+    private final Map<MultiKey, PropertyConduit> cache = CollectionFactory.newConcurrentMap();
 
     private static final MethodSignature GET_SIGNATURE = new MethodSignature(Object.class, "get",
                                                                              new Class[] { Object.class }, null);
@@ -106,8 +88,8 @@
 
     public PropertyConduit create(Class rootClass, String expression)
     {
-        notNull(rootClass, "rootClass");
-        notBlank(expression, "expression");
+        Defense.notNull(rootClass, "rootClass");
+        Defense.notBlank(expression, "expression");
 
         Class effectiveClass = toEffectiveClass(rootClass);
 
@@ -171,85 +153,123 @@
 
         String[] terms = SPLIT_AT_DOTS.split(expression);
 
-        final ReadInfo readInfo = buildGetter(rootClass, classFab, expression, terms);
-        final Method writeMethod = buildSetter(rootClass, classFab, expression, terms);
+        MethodSignature navigate = createNavigationMethod(rootClass, classFab, expression, terms);
+
+        String lastTerm = terms[terms.length - 1];
 
-        // A conduit is either readable or writable, otherwise there will already have been
-        // an error about unknown method name or property name.
+        ExpressionTermInfo termInfo = infoForTerm(navigate.getReturnType(), expression, lastTerm);
 
-        Class propertyType = readInfo != null ? readInfo.getType() : writeMethod
-                .getParameterTypes()[0];
+        createAccessors(rootClass, expression, classFab, navigate, termInfo);
 
         String description = String.format("PropertyConduit[%s %s]", rootClass.getName(), expression);
 
         Class conduitClass = classFab.createClass();
 
-        AnnotationProvider provider = new AnnotationProvider()
-        {
-            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
-            {
-                T result = readInfo == null ? null : readInfo.getAnnotation(annotationClass);
-
-                if (result == null && writeMethod != null) result = writeMethod.getAnnotation(annotationClass);
-
-                return result;
-            }
-
-        };
-
         try
         {
-            return (PropertyConduit) conduitClass.getConstructors()[0].newInstance(propertyType, provider, description);
+            return (PropertyConduit) conduitClass.getConstructors()[0].newInstance(termInfo.getType(), termInfo,
+                                                                                   description);
         }
         catch (Exception ex)
         {
             throw new RuntimeException(ex);
         }
+    }
 
+    private void createAccessors(Class rootClass, String expression, ClassFab classFab, MethodSignature navigateMethod,
+                                 ExpressionTermInfo termInfo)
+    {
+        createGetter(rootClass, expression, classFab, navigateMethod, termInfo);
+        createSetter(rootClass, expression, classFab, navigateMethod, termInfo);
     }
 
-    private ReadInfo buildGetter(Class rootClass, ClassFab classFab, String expression, String[] terms)
+    private void createSetter(Class rootClass, String expression, ClassFab classFab, MethodSignature navigateMethod,
+                              ExpressionTermInfo termInfo)
     {
-        BodyBuilder builder = new BodyBuilder();
+        String methodName = termInfo.getWriteMethodName();
 
-        builder.begin();
+        if (methodName == null)
+        {
+            createNoOp(classFab, SET_SIGNATURE, "Expression %s for class %s is read-only.", expression,
+                       rootClass.getName());
+            return;
+        }
 
-        PropertyNavigationResult result = writePropertyNavigationCode(builder, rootClass, expression, terms, false);
+        BodyBuilder builder = new BodyBuilder().begin();
 
+        builder.addln("%s target = %s($1);",
+                      ClassFabUtils.toJavaClassName(navigateMethod.getReturnType()),
+                      navigateMethod.getName());
 
-        if (result == null)
+        // I.e. due to ?. operator
+
+        builder.addln("if (target == null) return;");
+
+        String propertyTypeName = ClassFabUtils.toJavaClassName(termInfo.getType());
+
+        builder.addln("target.%s(%s);", methodName, ClassFabUtils.castReference("$2", propertyTypeName));
+
+        builder.end();
+
+        classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder.toString());
+    }
+
+    private void createGetter(Class rootClass, String expression, ClassFab classFab, MethodSignature navigateMethod,
+                              ExpressionTermInfo termInfo)
+    {
+        String methodName = termInfo.getReadMethodName();
+
+        if (methodName == null)
         {
-            builder.clear();
-            builder
-                    .addln("throw new RuntimeException(\"Expression %s for class %s is write-only.\");", expression,
-                           rootClass.getName());
+            createNoOp(classFab, GET_SIGNATURE, "Expression %s for class %s is write-only.", expression,
+                       rootClass.getName());
+            return;
         }
-        else
-        {
-            builder.addln("return %s;", result.getFinalStepVariable());
 
-            builder.end();
-        }
+        BodyBuilder builder = new BodyBuilder().begin();
+
+        builder.addln("%s target = %s($1);", ClassFabUtils.toJavaClassName(navigateMethod.getReturnType()),
+                      navigateMethod.getName());
+
+        // I.e. due to ?. operator
+
+        builder.addln("if (target == null) return null;");
+
+        builder.addln("return ($w) target.%s();", methodName);
+
+        builder.end();
 
         classFab.addMethod(Modifier.PUBLIC, GET_SIGNATURE, builder.toString());
+    }
+
+
+    private void createNoOp(ClassFab classFab, MethodSignature signature, String format, Object... values)
+    {
+        String message = String.format(format, values);
 
+        String body = String.format("throw new RuntimeException(\"%s\");", message);
 
-        return result == null ? null : result.getFinalReadInfo();
+        classFab.addMethod(Modifier.PUBLIC, signature, body);
     }
 
+
     /**
-     * Writes the code for navigation
+     * Builds a method that navigates from the root object upto, but not including, the final property. For simple
+     * properties, the generated method is effectively a big cast.  Otherwise, the generated method returns the object
+     * that contains the final property (the final term).       The generated method may return null if an intermediate
+     * term is null (and evaluated using the "?." safe dereferencing operator).
      *
-     * @param builder
      * @param rootClass
+     * @param classFab
      * @param expression
-     * @param terms
-     * @param forSetter  if true, then the last term is not read since it will be updated
-     * @return
+     * @param terms      the expression divided into individual terms
+     * @return signature of the added method
      */
-    private PropertyNavigationResult writePropertyNavigationCode(BodyBuilder builder, Class rootClass,
-                                                                 String expression, String[] terms, boolean forSetter)
+    private MethodSignature createNavigationMethod(Class rootClass, ClassFab classFab, String expression,
+                                                   String[] terms)
     {
+        BodyBuilder builder = new BodyBuilder().begin();
+
         builder.addln("%s root = (%<s) $1;", ClassFabUtils.toJavaClassName(rootClass));
         String previousStep = "root";
 
@@ -258,37 +278,27 @@
                 expression);
 
         Class activeType = rootClass;
-        ReadInfo readInfo = null;
-
-        // For a setter method, the navigation stops with  the penultimate
-        // term in the expression (the final term is what gets updated).
+        ExpressionTermInfo expressionTermInfo = null;
 
-        int lastIndex = forSetter ? terms.length - 1 : terms.length;
-
-        for (int i = 0; i < lastIndex; i++)
+        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);
 
-            // All the navigation terms in the expression must be readable properties.
-            // The only exception is the final term in a reader method.
-
-            boolean mustExist = forSetter || i < terms.length - 1;
+            if (nullable) term = term.substring(0, term.length() - 1);
 
-            readInfo = readInfoForTerm(activeType, expression, term, mustExist);
+            expressionTermInfo = infoForTerm(activeType, expression, term);
 
-            // 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.
+            String methodName = expressionTermInfo.getReadMethodName();
 
-            if (readInfo == null) return null;
+            if (methodName == null)
+                throw new RuntimeException(ServicesMessages.writeOnlyProperty(term, activeType, expression));
 
             // If a primitive type, convert to wrapper type
 
-            Class termType = readInfo.getType();
+            Class termType = expressionTermInfo.getType();
             Class wrappedType = ClassFabUtils.getWrapperType(termType);
 
             String termJavaName = ClassFabUtils.toJavaClassName(wrappedType);
@@ -301,113 +311,41 @@
             {
                 builder.add(" ($w) ");
             }
-            else if (readInfo.isCastRequired())
+            else if (expressionTermInfo.isCastRequired())
             {
                 builder.add(" (%s) ", termJavaName);
             }
 
-            builder.addln("%s.%s();", previousStep, readInfo.getMethodName());
+            builder.addln("%s.%s();", previousStep, expressionTermInfo.getReadMethodName());
 
             if (nullable)
             {
-                builder.add("if (%s == null) return", thisStep);
-
-                if (!forSetter) builder.add(" null");
-
-                builder.addln(";");
+                builder.add("if (%s == null) return null;", thisStep);
             }
             else
             {
                 // Perform a null check on intermediate terms.
-                if (i < lastIndex - 1)
-                {
-                    builder.addln("if (%s == null) throw new NullPointerException(%s.nullTerm(\"%s\", \"%s\", root));",
-                                  thisStep, getClass().getName(), term, expression);
-                }
+                builder.addln("if (%s == null) %s.nullTerm(\"%s\", \"%s\", root);",
+                              thisStep, getClass().getName(), term, expression);
             }
 
             activeType = wrappedType;
             previousStep = thisStep;
         }
 
-        final String finalStepVariable = previousStep;
-        final Class finalStepType = activeType;
-        final ReadInfo finalReadInfo = readInfo;
-
-        return new PropertyNavigationResult()
-        {
-            public String getFinalStepVariable()
-            {
-                return finalStepVariable;
-            }
-
-            public Class getFinalStepType()
-            {
-                return finalStepType;
-            }
-
-            public ReadInfo getFinalReadInfo()
-            {
-                return finalReadInfo;
-            }
-        };
-    }
-
-    private Method buildSetter(Class rootClass, ClassFab classFab, String expression, String[] terms)
-    {
-        BodyBuilder builder = new BodyBuilder();
-        builder.begin();
-
-        PropertyNavigationResult result = writePropertyNavigationCode(builder, rootClass, expression, terms, true);
-
-        // 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?
-
-        Method writeMethod = writeMethodForTerm(result.getFinalStepType(), expression, terms[terms.length - 1]);
-
-        if (writeMethod == null)
-        {
-            builder.clear();
-            builder
-                    .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;
-        }
-
-        Class propertyType = writeMethod.getParameterTypes()[0];
-        String propertyTypeName = ClassFabUtils.toJavaClassName(propertyType);
-
-        // Cast the parameter from Object to the expected type for the method.
-
-        builder.addln("%s value = %s;", propertyTypeName, ClassFabUtils.castReference("$2", propertyTypeName));
-
-        // Invoke the method.
-
-        builder.addln("%s.%s(value);", result.getFinalStepVariable(), writeMethod.getName());
+        builder.addln("return %s;", previousStep);
 
         builder.end();
 
-        classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder.toString());
-
-        return writeMethod;
-    }
-
-    private Method writeMethodForTerm(Class activeType, String expression, String term)
-    {
-        if (term.endsWith(PARENS)) return null;
+        MethodSignature sig = new MethodSignature(activeType, "navigate", new Class[] { Object.class }, null);
 
-        ClassPropertyAdapter classAdapter = access.getAdapter(activeType);
-        PropertyAdapter adapter = classAdapter.getPropertyAdapter(term);
-
-        if (adapter == null) throw new RuntimeException(
-                ServicesMessages.noSuchProperty(activeType, term, expression, classAdapter.getPropertyNames()));
+        classFab.addMethod(Modifier.PRIVATE, sig, builder.toString());
 
-        return adapter.getWriteMethod();
+        return sig;
     }
 
-    private ReadInfo readInfoForTerm(Class activeType, String expression, String term, boolean mustExist)
+
+    private ExpressionTermInfo infoForTerm(Class activeType, String expression, String term)
     {
         if (term.endsWith(PARENS))
         {
@@ -420,16 +358,20 @@
                 if (method.getReturnType().equals(void.class))
                     throw new RuntimeException(ServicesMessages.methodIsVoid(term, activeType, expression));
 
-
                 final Class genericType = GenericsUtils.extractGenericReturnType(activeType, method);
 
-                return new ReadInfo()
+                return new ExpressionTermInfo()
                 {
-                    public String getMethodName()
+                    public String getReadMethodName()
                     {
                         return method.getName();
                     }
 
+                    public String getWriteMethodName()
+                    {
+                        return null;
+                    }
+
                     public Class getType()
                     {
                         return genericType;
@@ -462,18 +404,21 @@
         if (adapter == null) throw new RuntimeException(
                 ServicesMessages.noSuchProperty(activeType, term, expression, classAdapter.getPropertyNames()));
 
-        if (!adapter.isRead())
+        return new ExpressionTermInfo()
         {
-            if (mustExist) throw new RuntimeException(ServicesMessages.writeOnlyProperty(term, activeType, expression));
+            public String getReadMethodName()
+            {
+                return name(adapter.getReadMethod());
+            }
 
-            return null;
-        }
+            public String getWriteMethodName()
+            {
+                return name(adapter.getWriteMethod());
+            }
 
-        return new ReadInfo()
-        {
-            public String getMethodName()
+            private String name(Method m)
             {
-                return adapter.getReadMethod().getName();
+                return m == null ? null : m.getName();
             }
 
             public Class getType()
@@ -505,9 +450,14 @@
         throw new NoSuchMethodException(ServicesMessages.noSuchMethod(activeType, methodName));
     }
 
-    public static String nullTerm(String term, String expression, Object root)
+    /**
+     * May be invoked from the fabricated PropertyConduit instances.
+     */
+    public static void nullTerm(String term, String expression, Object root)
     {
-        return String.format("Property '%s' (within property expression '%s', of %s) is null.",
-                             term, expression, root);
+        String message = String.format("Property '%s' (within property expression '%s', of %s) is null.",
+                                       term, expression, root);
+
+        throw new NullPointerException(message);
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java?rev=669357&r1=669356&r2=669357&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java Wed Jun 18 18:55:08 2008
@@ -92,7 +92,7 @@
                 {
                     long elapsed = System.nanoTime() - startTime;
 
-                    log.info(String.format("Request time: %5.2f s (%s)", elapsed * 10E-9d, request.getPath()));
+                    log.info(String.format("Request time: %5.2f s -- %s", elapsed * 10E-9d, request.getPath()));
                 }
             }
         };

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=669357&r1=669356&r2=669357&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 Jun 18 18:55:08 2008
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.PropertyConduit;
+import org.apache.tapestry5.beaneditor.Validate;
 import org.apache.tapestry5.internal.bindings.PropBindingFactoryTest;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
 import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl;
@@ -190,4 +191,16 @@
         assertNull(conduit.get(bean));
     }
 
+    @Test
+    public void field_annotations_are_visible()
+    {
+        PropertyConduit conduit = source.create(CompositeBean.class, "simple.firstName");
+
+        Validate annotation = conduit.getAnnotation(Validate.class);
+
+        assertNotNull(annotation);
+
+        assertEquals(annotation.value(), "required");
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SimpleBean.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SimpleBean.java?rev=669357&r1=669356&r2=669357&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SimpleBean.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SimpleBean.java Wed Jun 18 18:55:08 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.
@@ -14,10 +14,12 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.beaneditor.Validate;
 import org.apache.tapestry5.beaneditor.Width;
 
 public class SimpleBean
 {
+    @Validate("required")
     private String firstName;
 
     private String lastName;

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AccessableObjectAnnotationProvider.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AccessableObjectAnnotationProvider.java?rev=669357&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AccessableObjectAnnotationProvider.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AccessableObjectAnnotationProvider.java Wed Jun 18 18:55:08 2008
@@ -0,0 +1,45 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.ioc.internal.services;
+
+import org.apache.tapestry5.ioc.AnnotationProvider;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+
+/**
+ * Provides access to annotations of an accessable object such as a {@link java.lang.reflect.Method} or {@link
+ * java.lang.reflect.Field}.
+ */
+public class AccessableObjectAnnotationProvider implements AnnotationProvider
+{
+    private final AccessibleObject object;
+
+    public AccessableObjectAnnotationProvider(AccessibleObject object)
+    {
+        this.object = object;
+    }
+
+    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+    {
+        return object.getAnnotation(annotationClass);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("AnnotationProvider[%s]", object);
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java?rev=669357&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java Wed Jun 18 18:55:08 2008
@@ -0,0 +1,58 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.ioc.internal.services;
+
+import org.apache.tapestry5.ioc.AnnotationProvider;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+/**
+ * Chain of command for {@link org.apache.tapestry5.ioc.AnnotationProvider}.
+ */
+public class AnnotationProviderChain implements AnnotationProvider
+{
+    private final AnnotationProvider[] providers;
+
+    public AnnotationProviderChain(AnnotationProvider[] providers)
+    {
+        this.providers = providers;
+    }
+
+    /**
+     * Creates an AnnotationProvider from the list of providers.  Returns either an {@link AnnotationProviderChain} or
+     * the sole element in the list.
+     */
+    public static AnnotationProvider create(List<AnnotationProvider> providers)
+    {
+        int size = providers.size();
+
+        if (size == 1) return providers.get(0);
+
+        return new AnnotationProviderChain(providers.toArray(new AnnotationProvider[providers.size()]));
+    }
+
+    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+    {
+        for (AnnotationProvider p : providers)
+        {
+            T result = p.getAnnotation(annotationClass);
+
+            if (result != null) return result;
+        }
+
+        return null;
+    }
+}

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=669357&r1=669356&r2=669357&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 Jun 18 18:55:08 2008
@@ -14,6 +14,8 @@
 
 package org.apache.tapestry5.ioc.internal.services;
 
+import org.apache.tapestry5.ioc.AnnotationProvider;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.services.ClassPropertyAdapter;
 import org.apache.tapestry5.ioc.services.PropertyAdapter;
 
@@ -21,6 +23,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.List;
 
 public class PropertyAdapterImpl implements PropertyAdapter
 {
@@ -36,15 +39,7 @@
 
     private final boolean castRequired;
 
-    /**
-     * Have we tried to resolve from the property name to the field yet?
-     */
-    private boolean fieldCheckedFor;
-    /**
-     * The field from the containing type that matches this property name (may be null if not found, or not checked for
-     * yet).
-     */
-    private Field field;
+    private AnnotationProvider annotationProvider;
 
     PropertyAdapterImpl(ClassPropertyAdapter classAdapter, String name, Class type, Method readMethod,
                         Method writeMethod)
@@ -139,27 +134,24 @@
 
     public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
     {
-        T result = readMethod != null ? readMethod.getAnnotation(annotationClass) : null;
-
-        if (result == null && writeMethod != null) result = writeMethod.getAnnotation(annotationClass);
-
-        if (result == null) result = getAnnotationFromField(annotationClass);
-
-        return result;
+        return getAnnnotationProvider().getAnnotation(annotationClass);
     }
 
-    private <T extends Annotation> T getAnnotationFromField(Class<T> annotationClass)
+    /**
+     * Creates (as needed) the annotation provider for this property.
+     */
+    private synchronized AnnotationProvider getAnnnotationProvider()
     {
-        Field field = getField();
+        if (annotationProvider == null)
+        {
+            List<AnnotationProvider> providers = CollectionFactory.newList();
 
-        return field == null ? null : field.getAnnotation(annotationClass);
-    }
+            if (readMethod != null)
+                providers.add(new AccessableObjectAnnotationProvider(readMethod));
 
+            if (writeMethod != null)
+                providers.add(new AccessableObjectAnnotationProvider(writeMethod));
 
-    private synchronized Field getField()
-    {
-        if (!fieldCheckedFor)
-        {
             // There's an assumption here, that the fields match the property name (we ignore case
             // which leads to a manageable ambiguity) and that the field and the getter/setter
             // are in the same class (i.e., that we don't have a getter exposing a protected field inherted
@@ -174,7 +166,8 @@
                 {
                     if (f.getName().equalsIgnoreCase(name))
                     {
-                        field = f;
+                        providers.add(new AccessableObjectAnnotationProvider(f));
+
                         break out;
                     }
                 }
@@ -182,11 +175,10 @@
                 cursor = cursor.getSuperclass();
             }
 
-
-            fieldCheckedFor = true;
+            annotationProvider = AnnotationProviderChain.create(providers);
         }
 
-        return field;
+        return annotationProvider;
     }
 
     public boolean isCastRequired()