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 2010/05/03 23:52:38 UTC

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

Author: hlship
Date: Mon May  3 21:52:37 2010
New Revision: 940643

URL: http://svn.apache.org/viewvc?rev=940643&view=rev
Log:
TAP5-1105: BeanModelSource should recognize public field as properties, but doesn't

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

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BeanModelSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BeanModelSourceImpl.java?rev=940643&r1=940642&r2=940643&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BeanModelSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/BeanModelSourceImpl.java Mon May  3 21:52:37 2010
@@ -4,7 +4,7 @@
 // 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
+// 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,
@@ -99,9 +99,11 @@ public class BeanModelSourceImpl impleme
 
             Method readMethod = pa.getReadMethod();
 
-            Location location = classFactory.getMethodLocation(readMethod);
+            Location location = readMethod == null ? null : classFactory.getMethodLocation(readMethod);
 
-            properties.add(new PropertyOrder(name, computeDepth(readMethod), location.getLine()));
+            int line = location == null ? -1 : location.getLine();
+
+            properties.add(new PropertyOrder(name, computeDepth(readMethod), line));
         }
 
         Collections.sort(properties);
@@ -153,16 +155,14 @@ public class BeanModelSourceImpl impleme
         return create(beanClass, true, messages);
     }
 
-    public <T> BeanModel<T> create(Class<T> beanClass, boolean filterReadOnlyProperties,
-            Messages messages)
+    public <T> BeanModel<T> create(Class<T> beanClass, boolean filterReadOnlyProperties, Messages messages)
     {
         Defense.notNull(beanClass, "beanClass");
         Defense.notNull(messages, "messages");
 
         ClassPropertyAdapter adapter = propertyAccess.getAdapter(beanClass);
 
-        BeanModel<T> model = new BeanModelImpl<T>(beanClass, propertyConduitSource, typeCoercer,
-                messages, locator);
+        BeanModel<T> model = new BeanModelImpl<T>(beanClass, propertyConduitSource, typeCoercer, messages, locator);
 
         for (final String propertyName : adapter.getPropertyNames())
         {

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=940643&r1=940642&r2=940643&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 Mon May  3 21:52:37 2010
@@ -914,13 +914,13 @@ public class PropertyConduitSourceImpl i
             if (type.isArray())
             {
                 Class<?> componentType = type.getComponentType();
-                
+
                 while (componentType.isArray())
                 {
                     componentType = componentType.getComponentType();
                 }
-                
-                return InternalUtils.lastTerm(componentType.getName())+"_array";
+
+                return InternalUtils.lastTerm(componentType.getName()) + "_array";
             }
             return InternalUtils.lastTerm(type.getName());
         }
@@ -1032,19 +1032,7 @@ public class PropertyConduitSourceImpl i
 
             if (adapter == null)
             {
-                ExpressionTermInfo fieldInfo = infoForPublicField(activeType, propertyName);
-
-                if (fieldInfo != null)
-                    return fieldInfo;
-
-                Set<String> names = CollectionFactory.newSet();
-
-                names.addAll(classAdapter.getPropertyNames());
-
-                for (Field f : activeType.getFields())
-                {
-                    names.add(f.getName());
-                }
+                List<String> names = classAdapter.getPropertyNames();
 
                 throw new UnknownValueException(String.format(
                         "Class %s does not contain a property (or public field) named '%s'.", activeType.getName(),
@@ -1054,60 +1042,6 @@ public class PropertyConduitSourceImpl i
             return createExpressionTermInfoForProperty(adapter);
         }
 
-        private ExpressionTermInfo infoForPublicField(Class activeType, String fieldName)
-        {
-            // Iterate over all public fields of the type (or its super classes)
-
-            for (final Field field : activeType.getFields())
-            {
-                if (field.getName().equalsIgnoreCase(fieldName)) { return new ExpressionTermInfo()
-                {
-                    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
-                    {
-                        return field.getAnnotation(annotationClass);
-                    }
-
-                    public boolean isCastRequired()
-                    {
-                        return false;
-                    }
-
-                    public Method getWriteMethod()
-                    {
-                        return null;
-                    }
-
-                    public Class getType()
-                    {
-                        return field.getType();
-                    }
-
-                    public Method getReadMethod()
-                    {
-                        // TODO Auto-generated method stub
-                        return null;
-                    }
-
-                    public String getPropertyName()
-                    {
-                        return field.getName();
-                    }
-
-                    public String getDescription()
-                    {
-                        return "field " + field.getName();
-                    }
-
-                    public boolean isField()
-                    {
-                        return true;
-                    }
-                }; }
-            }
-
-            return null;
-        }
-
         private ExpressionTermInfo createExpressionTermInfoForProperty(final PropertyAdapter adapter)
         {
             return new ExpressionTermInfo()
@@ -1149,7 +1083,7 @@ public class PropertyConduitSourceImpl i
 
                 public boolean isField()
                 {
-                    return false;
+                    return adapter.isField();
                 }
             };
         }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassPropertyAdapterImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassPropertyAdapterImpl.java?rev=940643&r1=940642&r2=940643&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassPropertyAdapterImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassPropertyAdapterImpl.java Mon May  3 21:52:37 2010
@@ -1,10 +1,10 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2010 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
+// 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,
@@ -21,6 +21,7 @@ import org.apache.tapestry5.ioc.services
 import org.apache.tapestry5.ioc.services.PropertyAdapter;
 
 import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Map;
@@ -40,18 +41,34 @@ public class ClassPropertyAdapterImpl im
             // Indexed properties will have a null propertyType (and a non-null
             // indexedPropertyType). We ignore indexed properties.
 
-            if (pd.getPropertyType() == null) continue;
+            if (pd.getPropertyType() == null)
+                continue;
 
             Method readMethod = pd.getReadMethod();
 
             Class propertyType = readMethod == null ? pd.getPropertyType() : GenericsUtils.extractGenericReturnType(
                     beanType, readMethod);
 
-            PropertyAdapter pa = new PropertyAdapterImpl(this, pd.getName(), propertyType, readMethod,
-                                                         pd.getWriteMethod());
+            PropertyAdapter pa = new PropertyAdapterImpl(this, pd.getName(), propertyType, readMethod, pd
+                    .getWriteMethod());
 
             adapters.put(pa.getName(), pa);
         }
+
+        // Now, add any public fields that do not conflict
+
+        for (Field f : beanType.getFields())
+        {
+            String name = f.getName();
+
+            if (!adapters.containsKey(name))
+            {
+                Class propertyType = GenericsUtils.extractGenericFieldType(beanType, f);
+                PropertyAdapter pa = new PropertyAdapterImpl(this, name, propertyType, f);
+
+                adapters.put(name, pa);
+            }
+        }
     }
 
     public Class getBeanType()
@@ -91,7 +108,8 @@ public class ClassPropertyAdapterImpl im
     {
         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/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=940643&r1=940642&r2=940643&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 Mon May  3 21:52:37 2010
@@ -1,10 +1,10 @@
-// Copyright 2006, 2008 The Apache Software Foundation
+// Copyright 2006, 2008, 2010 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
+// 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,
@@ -41,8 +41,10 @@ public class PropertyAdapterImpl impleme
 
     private AnnotationProvider annotationProvider;
 
+    private final Field field;
+
     PropertyAdapterImpl(ClassPropertyAdapter classAdapter, String name, Class type, Method readMethod,
-                        Method writeMethod)
+            Method writeMethod)
     {
         this.classAdapter = classAdapter;
         this.name = name;
@@ -52,6 +54,22 @@ public class PropertyAdapterImpl impleme
         this.writeMethod = writeMethod;
 
         castRequired = readMethod != null && readMethod.getReturnType() != type;
+
+        field = null;
+    }
+
+    PropertyAdapterImpl(ClassPropertyAdapter classAdapter, String name, Class type, Field field)
+    {
+        this.classAdapter = classAdapter;
+        this.name = name;
+        this.type = type;
+
+        this.field = field;
+
+        castRequired = field.getType() != type;
+
+        readMethod = null;
+        writeMethod = null;
     }
 
     public String getName()
@@ -76,24 +94,27 @@ public class PropertyAdapterImpl impleme
 
     public boolean isRead()
     {
-        return readMethod != null;
+        return field != null || readMethod != null;
     }
 
     public boolean isUpdate()
     {
-        return writeMethod != null;
+        return field != null || writeMethod != null;
     }
 
     public Object get(Object instance)
     {
-        if (readMethod == null)
+        if (field == null && readMethod == null)
             throw new UnsupportedOperationException(ServiceMessages.readNotSupported(instance, name));
 
         Throwable fail;
 
         try
         {
-            return readMethod.invoke(instance);
+            if (field == null)
+                return readMethod.invoke(instance);
+            else
+                return field.get(instance);
         }
         catch (InvocationTargetException ex)
         {
@@ -109,14 +130,17 @@ public class PropertyAdapterImpl impleme
 
     public void set(Object instance, Object value)
     {
-        if (writeMethod == null)
+        if (field == null && writeMethod == null)
             throw new UnsupportedOperationException(ServiceMessages.writeNotSupported(instance, name));
 
         Throwable fail;
 
         try
         {
-            writeMethod.invoke(instance, value);
+            if (field == null)
+                writeMethod.invoke(instance, value);
+            else
+                field.set(instance, value);
 
             return;
         }
@@ -159,8 +183,7 @@ public class PropertyAdapterImpl impleme
 
             Class cursor = getBeanType();
 
-            out:
-            while (cursor != null)
+            out: while (cursor != null)
             {
                 for (Field f : cursor.getDeclaredFields())
                 {
@@ -195,4 +218,10 @@ public class PropertyAdapterImpl impleme
     {
         return classAdapter.getBeanType();
     }
+
+    public boolean isField()
+    {
+        return field != null;
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java?rev=940643&r1=940642&r2=940643&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java Mon May  3 21:52:37 2010
@@ -1,10 +1,10 @@
-// Copyright 2008 The Apache Software Foundation
+// Copyright 2008, 2010 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
+// 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,
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.ioc.internal.util;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
@@ -22,34 +23,55 @@ import java.lang.reflect.TypeVariable;
 /**
  * Static methods related to the use of JDK 1.5 generics.
  */
+@SuppressWarnings("unchecked")
 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
+     * 
+     * @param containingClassType
+     *            class containing the method, used to reason about generics
+     * @param method
+     *            method (possibly from a base class of type) to extract
      * @return the generic type if it may be determined, or the raw type (that is, with type erasure, most often
      *         Object)
      */
-    public static Class extractGenericReturnType(Class type, Method method)
+    public static Class extractGenericReturnType(Class containingClassType, Method method)
     {
-        Class defaultType = method.getReturnType();
+        return extractGenericType(containingClassType, method.getReturnType(), method.getGenericReturnType());
+    }
 
-        Type genericType = method.getGenericReturnType();
+    /**
+     * Analyzes the field in the context of a particular concrete implementation of the class to establish
+     * the generic type of a (public) field. This works when the field type is defined as a class
+     * generic parameter.
+     * 
+     * @param containingClassType
+     *            class containing the method, used to reason about generics
+     * @param field
+     *            public field to extract type from
+     * @return the generic type if it may be determined, or the raw type (that is, with type erasure, most often
+     * @since 5.2.0
+     */
+    public static Class extractGenericFieldType(Class containingClassType, Field field)
+    {
+        return extractGenericType(containingClassType, field.getType(), field.getGenericType());
+    }
 
+    private static Class extractGenericType(Class containingClassType, Type defaultType, Type genericType)
+    {
         // 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
+            // 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();
+            Type superType = containingClassType.getGenericSuperclass();
 
             if (superType instanceof ParameterizedType)
             {
@@ -66,15 +88,16 @@ public class GenericsUtils
                     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
+                    // 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;
+                        if (actualType instanceof Class)
+                            return (Class) actualType;
 
                         break;
                     }
@@ -83,9 +106,8 @@ public class GenericsUtils
             }
         }
 
+        return (Class) defaultType;
 
-        return defaultType;
-
-        // P.S. I wrote this and I barely understand it.  Fortunately, I have tests ...
+        // 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/tapestry5/ioc/services/PropertyAccess.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAccess.java?rev=940643&r1=940642&r2=940643&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAccess.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAccess.java Mon May  3 21:52:37 2010
@@ -1,10 +1,10 @@
-// Copyright 2006 The Apache Software Foundation
+// Copyright 2006, 2010 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
+// 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,
@@ -17,24 +17,30 @@ package org.apache.tapestry5.ioc.service
 /**
  * A wrapper around the JavaBean Introspector that allows more manageable access to JavaBean properties of objects.
  * <p/>
- * <p/>
- * Only provides access to <em>simple</em> properties.  Indexed properties are ignored.
+ * Only provides access to <em>simple</em> properties. Indexed properties are ignored.
+ * <p>
+ * Starting in Tapestry 5.2, public fields can now be accessed as if they were properly JavaBean properties. Where there
+ * is a name conflict, the true property will be favored over the field access.
  */
 public interface PropertyAccess
 {
     /**
      * Reads the value of a property.
-     *
-     * @throws UnsupportedOperationException if the property is write only
-     * @throws IllegalArgumentException      if property does not exist
+     * 
+     * @throws UnsupportedOperationException
+     *             if the property is write only
+     * @throws IllegalArgumentException
+     *             if property does not exist
      */
     Object get(Object instance, String propertyName);
 
     /**
      * Updates the value of a property.
-     *
-     * @throws UnsupportedOperationException if the property is read only
-     * @throws IllegalArgumentException      if property does not exist
+     * 
+     * @throws UnsupportedOperationException
+     *             if the property is read only
+     * @throws IllegalArgumentException
+     *             if property does not exist
      */
     void set(Object instance, String propertyName, Object value);
 

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java?rev=940643&r1=940642&r2=940643&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java Mon May  3 21:52:37 2010
@@ -1,10 +1,10 @@
-// Copyright 2006, 2008 The Apache Software Foundation
+// Copyright 2006, 2008, 2010 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
+// 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,
@@ -22,51 +22,59 @@ import java.lang.reflect.Method;
  * Provides access to a single property within a class. Acts as an {@link org.apache.tapestry5.ioc.AnnotationProvider};
  * when searching for annotations, the read method (if present) is checked first, followed by the write method, followed
  * by the underlying field (when the property name matches the field name).
- *
+ * <p>
+ * Starting in release 5.2, this property may actually be a public field.
+ * 
  * @see org.apache.tapestry5.ioc.services.ClassPropertyAdapter
  */
+@SuppressWarnings("unchecked")
 public interface PropertyAdapter extends AnnotationProvider
 {
     /**
-     * Returns the name of the property.
+     * Returns the name of the property (or public field).
      */
     String getName();
 
     /**
-     * Returns true if the property is readable (i.e., has a getter method).
+     * Returns true if the property is readable (i.e., has a getter method or is a public field).
      */
     boolean isRead();
 
     /**
-     * Returns the method used to read the property, or null if the property is not readable.
+     * Returns the method used to read the property, or null if the property is not readable (or is a public field).
      */
     public Method getReadMethod();
 
     /**
-     * Returns true if the property is writeable (i.e., has a setter method).
+     * Returns true if the property is writeable (i.e., has a setter method or is a public field).
      */
     boolean isUpdate();
 
     /**
-     * Returns the method used to update the property, or null if the property is not writeable.
+     * Returns the method used to update the property, or null if the property is not writeable (or a public field).
      */
     public Method getWriteMethod();
 
     /**
      * Reads the property value.
-     *
-     * @param instance to read from
-     * @throws UnsupportedOperationException if the property is write only
+     * 
+     * @param instance
+     *            to read from
+     * @throws UnsupportedOperationException
+     *             if the property is write only
      */
     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.
-     *
-     * @param instance to update
-     * @param value    new value for the property
-     * @throws UnsupportedOperationException if the property is read only
+     * 
+     * @param instance
+     *            to update
+     * @param value
+     *            new value for the property
+     * @throws UnsupportedOperationException
+     *             if the property is read only
      */
     void set(Object instance, Object value);
 
@@ -82,7 +90,6 @@ public interface PropertyAdapter extends
      */
     boolean isCastRequired();
 
-
     /**
      * Returns the {@link org.apache.tapestry5.ioc.services.ClassPropertyAdapter} that provides access to other
      * properties defined by the same class.
@@ -90,8 +97,15 @@ public interface PropertyAdapter extends
     ClassPropertyAdapter getClassAdapter();
 
     /**
-     * Returns the type of bean to which this property belongs.  This is the same as {@link
-     * org.apache.tapestry5.ioc.services.ClassPropertyAdapter#getBeanType()}.
+     * Returns the type of bean to which this property belongs. This is the same as
+     * {@link org.apache.tapestry5.ioc.services.ClassPropertyAdapter#getBeanType()}.
      */
     Class getBeanType();
+
+    /**
+     * Returns true if the property is actually a public field.
+     * 
+     * @since 5.2
+     */
+    boolean isField();
 }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/services/PropertyAccessImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/services/PropertyAccessImplTest.java?rev=940643&r1=940642&r2=940643&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/services/PropertyAccessImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/services/PropertyAccessImplTest.java Mon May  3 21:52:37 2010
@@ -4,7 +4,7 @@
 // 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
+// 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,
@@ -145,7 +145,8 @@ public class PropertyAccessImplTest exte
 
     }
 
-    public static class ScalaBean {
+    public static class ScalaBean
+    {
         private String value;
 
         public String getValue()
@@ -169,7 +170,8 @@ public class PropertyAccessImplTest exte
         }
     }
 
-    public static class ScalaClass {
+    public static class ScalaClass
+    {
         private String value;
 
         public String value()
@@ -198,6 +200,37 @@ public class PropertyAccessImplTest exte
         }
     }
 
+    public static class PublicFieldBean
+    {
+        public String value;
+    }
+
+    public static class ShadowedPublicFieldBean
+    {
+        private String _value;
+
+        public String value;
+
+        public String getValue()
+        {
+            return _value;
+        }
+
+        public void setValue(String value)
+        {
+            _value = value;
+        }
+    }
+
+    public static abstract class GenericBean<T>
+    {
+        public T value;
+    }
+
+    public static class GenericStringBean extends GenericBean<String>
+    {
+    }
+
     @Test
     public void simple_read_access()
     {
@@ -259,8 +292,8 @@ public class PropertyAccessImplTest exte
         }
         catch (IllegalArgumentException ex)
         {
-            assertEquals(ex.getMessage(),
-                         "Class " + CLASS_NAME + "$Bean does not " + "contain a property named 'zaphod'.");
+            assertEquals(ex.getMessage(), "Class " + CLASS_NAME + "$Bean does not "
+                    + "contain a property named 'zaphod'.");
         }
     }
 
@@ -276,8 +309,8 @@ public class PropertyAccessImplTest exte
         }
         catch (UnsupportedOperationException ex)
         {
-            assertEquals(ex.getMessage(),
-                         "Class " + CLASS_NAME + "$Bean does not provide an mutator ('setter') method for property 'class'.");
+            assertEquals(ex.getMessage(), "Class " + CLASS_NAME
+                    + "$Bean does not provide an mutator ('setter') method for property 'class'.");
         }
     }
 
@@ -293,8 +326,8 @@ public class PropertyAccessImplTest exte
         }
         catch (UnsupportedOperationException ex)
         {
-            assertEquals(ex.getMessage(),
-                         "Class " + CLASS_NAME + "$Bean does not provide an accessor ('getter') method for property 'writeOnly'.");
+            assertEquals(ex.getMessage(), "Class " + CLASS_NAME
+                    + "$Bean does not provide an accessor ('getter') method for property 'writeOnly'.");
         }
     }
 
@@ -326,8 +359,7 @@ public class PropertyAccessImplTest exte
         }
         catch (RuntimeException ex)
         {
-            assertEquals(ex.getMessage(),
-                         "Error updating property 'failure' of PropertyUtilsExceptionBean: setFailure");
+            assertEquals(ex.getMessage(), "Error updating property 'failure' of PropertyUtilsExceptionBean: setFailure");
         }
     }
 
@@ -370,8 +402,8 @@ public class PropertyAccessImplTest exte
     {
         ClassPropertyAdapter cpa = access.getAdapter(Bean.class);
 
-        assertEquals(cpa.toString(),
-                     "<ClassPropertyAdaptor " + CLASS_NAME + "$Bean : class, readOnly, value, writeOnly>");
+        assertEquals(cpa.toString(), "<ClassPropertyAdaptor " + CLASS_NAME
+                + "$Bean : class, readOnly, value, writeOnly>");
     }
 
     @Test
@@ -464,8 +496,7 @@ public class PropertyAccessImplTest exte
     @Test
     public void get_annotation_when_annotation_not_present()
     {
-        PropertyAdapter pa = access.getAdapter(AnnotatedBean.class)
-                .getPropertyAdapter("readWrite");
+        PropertyAdapter pa = access.getAdapter(AnnotatedBean.class).getPropertyAdapter("readWrite");
 
         assertNull(pa.getAnnotation(Scope.class));
     }
@@ -563,7 +594,8 @@ public class PropertyAccessImplTest exte
     }
 
     @Test
-    public void get_scala_properties_with_bean_accessors() {
+    public void get_scala_properties_with_bean_accessors()
+    {
         PropertyAdapter pa = access.getAdapter(ScalaBean.class).getPropertyAdapter("value");
 
         // even thought scala accessors are present the java bean ones should be the ones used by Tapestry
@@ -572,10 +604,59 @@ public class PropertyAccessImplTest exte
     }
 
     @Test
-    public void get_scala_properties() {
+    public void get_scala_properties()
+    {
         PropertyAdapter pa = access.getAdapter(ScalaClass.class).getPropertyAdapter("value");
 
         assertEquals(pa.getReadMethod().getName(), "value");
         assertEquals(pa.getWriteMethod().getName(), "value_$eq");
     }
+
+    @Test
+    public void access_to_public_field()
+    {
+        PropertyAdapter pa = access.getAdapter(PublicFieldBean.class).getPropertyAdapter("value");
+
+        assertTrue(pa.isField());
+        assertTrue(pa.isRead());
+        assertTrue(pa.isUpdate());
+
+        PublicFieldBean bean = new PublicFieldBean();
+
+        pa.set(bean, "fred");
+
+        assertEquals(bean.value, "fred");
+
+        bean.value = "barney";
+
+        assertEquals(pa.get(bean), "barney");
+    }
+
+    @Test
+    public void property_is_favored_over_public_field()
+    {
+        PropertyAdapter pa = access.getAdapter(ShadowedPublicFieldBean.class).getPropertyAdapter("value");
+
+        assertFalse(pa.isField());
+
+        ShadowedPublicFieldBean bean = new ShadowedPublicFieldBean();
+
+        pa.set(bean, "fred");
+
+        assertNull(bean.value);
+
+        bean.value = "barney";
+        bean.setValue("wilma");
+
+        assertEquals(pa.get(bean), "wilma");
+    }
+
+    @Test
+    public void generic_field_is_recognized()
+    {
+        PropertyAdapter pa = access.getAdapter(GenericStringBean.class).getPropertyAdapter("value");
+
+        assertTrue(pa.isCastRequired());
+        assertEquals(pa.getType(), String.class);
+    }
 }