Revision: 353
Author:   tfenne
Date:     2006-07-31 20:08:42 -0700 (Mon, 31 Jul 2006)
ViewCVS:  http://svn.sourceforge.net/stripes/?rev=353&view=rev

Log Message:
-----------
Change to the PropertyExpressionEvaluation to support the use of object 
instances when inferring type information if no luck is had inferring it from 
the declarations.

Modified Paths:
--------------
    
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
    trunk/tests/src/net/sourceforge/stripes/controller/BasicBindingTests.java
    trunk/tests/src/net/sourceforge/stripes/controller/MapBindingTests.java
    trunk/tests/src/net/sourceforge/stripes/test/TestActionBean.java
    trunk/tests/src/net/sourceforge/stripes/test/TestBean.java
Modified: 
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
       2006-07-31 22:49:45 UTC (rev 352)
+++ 
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
       2006-08-01 03:08:42 UTC (rev 353)
@@ -42,7 +42,6 @@
     private PropertyExpression expression;
     private Object bean;
     private NodeEvaluation root, leaf;
-    private boolean typeInformationValid;
 
     /**
      * Constructs a new PropertyExpressionEvaluation for the expression and 
bean supplied.
@@ -145,33 +144,144 @@
             if (type instanceof Class) {
                 Class clazz = (Class) type;
                 String property = current.getNode().getStringValue();
-                PropertyDescriptor pd = 
ReflectUtil.getPropertyDescriptor(clazz, property);
+                type = getBeanPropertyType(clazz, property);
 
-                if (pd != null) {
-                    if (pd.getReadMethod() != null) {
-                        type = pd.getReadMethod().getGenericReturnType();
-                    }
-                    else {
-                        type = 
pd.getWriteMethod().getGenericParameterTypes()[0];
-                    }
+                if (type != null) {
                     current.setValueType(type);
                     current.setType(NodeType.BeanProperty);
                 }
                 else {
-                    Field field = ReflectUtil.getField(clazz, property);
-                    if (field == null) { break; }
-                    else  { type = field.getGenericType(); }
-                    current.setValueType(type);
-                    current.setType(NodeType.BeanProperty);
+                    type = getTypeViaInstances(current);
+                    if (type == null) {
+
+                    }
+
                 }
             }
         }
+    }
 
-        // Finally figure out what to store on the leaf node!
-        this.typeInformationValid = (this.leaf.getValueType() != null);
+    /**
+     * Fetches the type of a property with the given name on the Class of the 
specified type.
+     * Uses the methods first to fetch the generic type if a 
PropertyDescriptor can be found,
+     * otherwise looks for a public field and returns its generic type.
+     *
+     * @param beanClass the class of the JavaBean containing the property
+     * @param property the name of the property
+     * @return the Type if it can be determined, or null otherwise
+     */
+    protected Type getBeanPropertyType(Class beanClass, String property) {
+        PropertyDescriptor pd = ReflectUtil.getPropertyDescriptor(beanClass, 
property);
+        if (pd != null) {
+            if (pd.getReadMethod() != null) {
+                return pd.getReadMethod().getGenericReturnType();
+            }
+            else {
+                return pd.getWriteMethod().getGenericParameterTypes()[0];
+            }
+        }
+        else {
+            Field field = ReflectUtil.getField(beanClass, property);
+            if (field == null) {
+                return null;
+            }
+            else  {
+                return field.getGenericType();
+            }
+        }
     }
 
     /**
+     * <p>Determines the type of the supplied node and sets appropriate 
information on the node.
+     * The type is discovered by fetching (and instantiating if necessary) all 
prior values
+     * in the expression to determine the actual type of the prior node.  The 
prior node is
+     * then examined to determine the type of the node provided.</p>
+     *
+     * <p>After this method executes either 1) all necessary type information 
will be set on the
+     * node and the appropriate type object returned or 2) an exception will 
be thrown.</p>
+     *
+     * @param end the node to instantate up to and determine the type of
+     * @return the Type of the node if possible
+     * @throws NoSuchPropertyException if the previous node is a JavaBean 
(i.e. non-collection)
+     *         node and does not contain a property with the corresponding name
+     * @throws EvaluationException if the previous node is a List or Map and 
does not contain
+     *         enough information to determine the type
+     */
+    protected Type getTypeViaInstances(NodeEvaluation end)
+        throws EvaluationException, NoSuchPropertyException {
+        Object previous;
+        Object value = this.bean;
+
+        // First loop through and get to the pre-cursor node using the type 
info we have
+        for (NodeEvaluation node = this.root; node != end; node = 
node.getNext()) {
+            PropertyAccessor accessor = node.getType().getPropertyAccessor();
+            previous = value;
+            value = accessor.getValue(node, previous);
+
+            if (value == null) {
+                value = getDefaultValue(node);
+            }
+        }
+
+        // Then determine how to fish for the next property in line
+        previous = value;
+        if (value instanceof Map) {
+            value = ((Map) value).get(end.getNode().getTypedValue());
+            if (value != null) {
+                end.setType(NodeType.MapEntry);
+                end.setValueType(value.getClass());
+                end.setKeyType(end.getNode().getTypedValue().getClass());
+                return value.getClass();
+            }
+            else {
+                throw new EvaluationException("Not enough type information 
available to " +
+                    "evaluate expression. Expression: '" + expression + "'. 
Type information ran " +
+                    "out at node '" + end.getNode().getStringValue() + "', 
which represents a Map " +
+                    "entry. Please ensure that either the getter for the Map 
contains appropriate " +
+                    "generic type information or that it contains a value with 
the key type " +
+                    end.getNode().getTypedValue().getClass().getName() + " and 
value " +
+                    end.getNode().getStringValue());
+            }
+        }
+        else if (value instanceof List) {
+            List list = (List) value;
+            if (end.getNode().getTypedValue() instanceof Integer) {
+                Integer index = (Integer) end.getNode().getTypedValue();
+                if (index < list.size()) {
+                    value = list.get(index);
+                    if (value != null) {
+                        end.setType(NodeType.ListEntry);
+                        end.setValueType(value.getClass());
+                        end.setKeyType(Integer.class);
+                        return value.getClass();
+                    }
+                }
+            }
+
+            throw new EvaluationException("Not enough type information 
available to " +
+                "evaluate expression. Expression: '" + expression + "'. Type 
information ran " +
+                "out at node '" + end.getNode().getStringValue() + "', which 
represents a List " +
+                "entry. Please ensure that either the getter for the List 
contains appropriate " +
+                "generic type information or that the index is numeric and a 
value exists at " +
+                "the supplied index (" + end.getNode().getStringValue() + 
").");
+        }
+        else {
+            Type type = getBeanPropertyType(value.getClass(), 
end.getNode().getStringValue());
+            if (type != null) {
+                end.setType(NodeType.BeanProperty);
+                end.setValueType(type);
+                return type;
+            }
+            else {
+                throw new NoSuchPropertyException("Bean class " + 
previous.getClass().getName() +
+                    " does not contain a property called '" + 
end.getNode().getStringValue() +
+                    "'. As a result the following expression could not be 
evaluated: " +
+                    this.expression);
+            }
+        }
+    }
+
+    /**
      * Attempts to convert the [EMAIL PROTECTED] Type} object into a Class 
object. Currently will extract the
      * raw type from a [EMAIL PROTECTED] ParameterizedType} and the 
appropriate bound from a
      * [EMAIL PROTECTED] WildcardType}. If the result after these operations 
is a Class object it will
@@ -250,12 +360,7 @@
      * @return the Class of object that can be set/get with this evaluation or 
null
      */
     public Class getType() {
-        if (this.typeInformationValid) {
-            return convertToClass(this.leaf.getValueType(), this.leaf);
-        }
-        else {
-            return null;
-        }
+        return convertToClass(this.leaf.getValueType(), this.leaf);
     }
 
     /**
@@ -272,35 +377,30 @@
      *         point at a non scalar property
      */
     public Class getScalarType() {
-        if (this.typeInformationValid) {
-            Type type = this.leaf.getValueType();
-            Class clazz = convertToClass(type, this.leaf);
+        Type type = this.leaf.getValueType();
+        Class clazz = convertToClass(type, this.leaf);
 
-            if (clazz.isArray()) {
-                return clazz.getComponentType();
+        if (clazz.isArray()) {
+            return clazz.getComponentType();
+        }
+        else if (Collection.class.isAssignableFrom(clazz)) {
+            if (type instanceof ParameterizedType) {
+                return convertToClass(((ParameterizedType) 
type).getActualTypeArguments()[0], this.leaf);
             }
-            else if (Collection.class.isAssignableFrom(clazz)) {
-                if (type instanceof ParameterizedType) {
-                    return convertToClass(((ParameterizedType) 
type).getActualTypeArguments()[0], this.leaf);
-                }
-                else {
-                    return String.class;
-                }
+            else {
+                return String.class;
             }
-            else if (Map.class.isAssignableFrom(clazz)) {
-                if (type instanceof ParameterizedType) {
-                    return convertToClass(((ParameterizedType) 
type).getActualTypeArguments()[1], this.leaf);
-                }
-                else {
-                    return String.class;
-                }
+        }
+        else if (Map.class.isAssignableFrom(clazz)) {
+            if (type instanceof ParameterizedType) {
+                return convertToClass(((ParameterizedType) 
type).getActualTypeArguments()[1], this.leaf);
             }
             else {
-                return clazz;
+                return String.class;
             }
         }
         else {
-            return null;
+            return clazz;
         }
     }
 

Modified: 
trunk/tests/src/net/sourceforge/stripes/controller/BasicBindingTests.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/controller/BasicBindingTests.java   
2006-07-31 22:49:45 UTC (rev 352)
+++ trunk/tests/src/net/sourceforge/stripes/controller/BasicBindingTests.java   
2006-08-01 03:08:42 UTC (rev 353)
@@ -243,4 +243,16 @@
         Assert.assertEquals(colors[1], TestActionBean.Color.Green);
         Assert.assertEquals(colors[2], TestActionBean.Color.Blue);
     }
+
+    @Test(groups="fast")
+    public void testBindingToSubclassOfDeclaredType() throws Exception {
+        MockRoundtrip trip = getRoundtrip();
+        trip.addParameter("item.id", "1000000");
+        trip.execute();
+
+        TestActionBean bean = trip.getActionBean(TestActionBean.class);
+        TestActionBean.PropertyLess item = bean.getItem();
+        Assert.assertEquals(item.getClass(), TestActionBean.Item.class);
+        Assert.assertEquals( ((TestActionBean.Item) item).getId(), new 
Long(1000000l));
+    }
 }

Modified: 
trunk/tests/src/net/sourceforge/stripes/controller/MapBindingTests.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/controller/MapBindingTests.java     
2006-07-31 22:49:45 UTC (rev 352)
+++ trunk/tests/src/net/sourceforge/stripes/controller/MapBindingTests.java     
2006-08-01 03:08:42 UTC (rev 353)
@@ -1,18 +1,21 @@
 package net.sourceforge.stripes.controller;
 
 import net.sourceforge.stripes.StripesTestFixture;
-import net.sourceforge.stripes.test.TestBean;
 import net.sourceforge.stripes.action.ActionBean;
 import net.sourceforge.stripes.action.ActionBeanContext;
+import net.sourceforge.stripes.action.Before;
 import net.sourceforge.stripes.action.Resolution;
 import net.sourceforge.stripes.mock.MockRoundtrip;
 import net.sourceforge.stripes.mock.MockServletContext;
+import net.sourceforge.stripes.test.TestBean;
+import net.sourceforge.stripes.test.TestEnum;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.Map;
-import java.util.Date;
-import java.util.Calendar;
 
 /**
  * Tests all reasonable variations of binding involving Maps. String keys, 
numeric keys,
@@ -60,6 +63,11 @@
     public Map<Date, Date> getMapDateDate() { return mapDateDate; }
     public void setMapDateDate(Map<Date, Date> mapDateDate) { this.mapDateDate 
= mapDateDate; }
 
+    /** A map completely lacking in type information!!. */
+    private Map typelessMap;
+    public Map getTypelessMap() { return typelessMap; }
+    public void setTypelessMap(Map typelessMap) { this.typelessMap = 
typelessMap; }
+
     /** Helper method to create a roundtrip with the TestActionBean class. */
     protected MockRoundtrip getRoundtrip() {
         MockServletContext context = StripesTestFixture.getServletContext();
@@ -230,4 +238,26 @@
         MapBindingTests bean = trip.getActionBean(MapBindingTests.class);
         Assert.assertNotNull(bean.getMapDateDate().get(key));
     }
+
+    @Before(LifecycleStage.BindingAndValidation)
+    public void populateTypelessMap() {
+        this.typelessMap = new HashMap();
+        this.typelessMap.put(1, new TestBean());
+        this.typelessMap.put(2l, new TestBean());
+        this.typelessMap.put("foo", new TestBean());
+    }
+
+    @Test(groups="fast")
+    public void bindThroughTypelessMap() throws Exception {
+        MockRoundtrip trip = getRoundtrip();
+        trip.addParameter("typelessMap[1].longProperty", "1234");
+        trip.addParameter("typelessMap[2l].nestedBean.longProperty", "4321");
+        trip.addParameter("typelessMap['foo'].enumProperty", "Sixth");
+        trip.execute();
+
+        MapBindingTests bean = trip.getActionBean(MapBindingTests.class);
+        Assert.assertEquals( ((TestBean) 
bean.getTypelessMap().get(1)).getLongProperty(), new Long(1234));
+        Assert.assertEquals( ((TestBean) 
bean.getTypelessMap().get(2l)).getNestedBean().getLongProperty(), new 
Long(4321));
+        Assert.assertEquals( ((TestBean) 
bean.getTypelessMap().get("foo")).getEnumProperty(), TestEnum.Sixth);
+    }
 }

Modified: trunk/tests/src/net/sourceforge/stripes/test/TestActionBean.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/test/TestActionBean.java    
2006-07-31 22:49:45 UTC (rev 352)
+++ trunk/tests/src/net/sourceforge/stripes/test/TestActionBean.java    
2006-08-01 03:08:42 UTC (rev 353)
@@ -21,6 +21,14 @@
 public class TestActionBean implements ActionBean {
     public enum Color {Red, Green, Blue, Yellow, Orange, Black, White }
 
+    /** Pair of static classes used to check type finding via instances intead 
of type inference. */
+    public static class PropertyLess { }
+    public static class Item extends PropertyLess {
+        private Long id;
+        public Long getId() { return id; }
+        public void setId(Long id) { this.id = id; }
+    }
+
     private ActionBeanContext context;
     private List<Long> listOfLongs;
     private Set<String> setOfStrings;
@@ -35,6 +43,7 @@
     private String setOnlyString;
     public Long publicLong;
     public Color[] colors;
+    private PropertyLess item = new Item();
 
     /** A pretty ordinary list of longs, to test lists of primitive/simply 
objects. */
     public List<Long> getListOfLongs() { return listOfLongs; }
@@ -85,6 +94,10 @@
     public Color[] getColors() { return colors; }
     public void setColors(Color[] colors) { this.colors = colors; }
 
+    /** Return type is a property-less class, but returns an instance of a 
subclass with an 'id' property. */
+    public PropertyLess getItem() { return item; }
+    public void setItem(PropertyLess item) { this.item = item; }
+
     ///////////////////////////////////////////////////////////////////////////
     // Dummied up ActionBean methods that aren't really used for much.
     ///////////////////////////////////////////////////////////////////////////

Modified: trunk/tests/src/net/sourceforge/stripes/test/TestBean.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/test/TestBean.java  2006-07-31 
22:49:45 UTC (rev 352)
+++ trunk/tests/src/net/sourceforge/stripes/test/TestBean.java  2006-08-01 
03:08:42 UTC (rev 353)
@@ -10,7 +10,8 @@
  *
  * @author Tim Fennell
  */
-public class TestBean {
+public class
+        TestBean {
     private String stringProperty;
     private int intProperty;
     private Long longProperty;


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.


-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to