Revision: 617
          http://stripes.svn.sourceforge.net/stripes/?rev=617&view=rev
Author:   mongus
Date:     2007-11-06 11:00:11 -0800 (Tue, 06 Nov 2007)

Log Message:
-----------
patch provided by Alan Burlison to fix STS-426 along with updated unit tests

Modified Paths:
--------------
    
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
    trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTests.java
    
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTests2.java
    
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTestsBaseClass.java

Added Paths:
-----------
    trunk/tests/src/net/sourceforge/stripes/test/TestGenericBean.java

Modified: 
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
       2007-10-11 02:10:42 UTC (rev 616)
+++ 
trunk/stripes/src/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java
       2007-11-06 19:00:11 UTC (rev 617)
@@ -25,7 +25,9 @@
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
 import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -136,8 +138,7 @@
             // Else if it's parameterized and it's a List or Map, get the next 
type
             if (type instanceof ParameterizedType) {
                 ParameterizedType ptype = (ParameterizedType) type;
-                // TODO: convert to using convertToClass() here
-                Type rawType = ptype.getRawType();
+                Type rawType = convertToClass(type, current);
 
                 if (rawType instanceof Class) {
                     Class rawClass = (Class) rawType;
@@ -163,7 +164,7 @@
                     }
                 }
                 else {
-                    // Raw type is not a class?  What on earth do we do now?
+                    // XXX Raw type is not a class?  What on earth do we do 
now?
                     break;
                 }
             }
@@ -175,6 +176,7 @@
                 String property = current.getNode().getStringValue();
                 type = getBeanPropertyType(clazz, property);
 
+                // XXX What do we do if type is a generic type?
                 if (type != null) {
                     current.setValueType(type);
                     current.setType(NodeType.BeanProperty);
@@ -182,9 +184,8 @@
                 else {
                     type = getTypeViaInstances(current);
                     if (type == null) {
-
+                        // XXX What do we do now?
                     }
-
                 }
             }
         }
@@ -392,40 +393,78 @@
      * @param type the TypeVariable to try and find a more concrete type for
      * @return the actual type argument for the type variable if possible, or 
null
      */
-    protected Type getTypeVariableValue(NodeEvaluation evaluation, 
TypeVariable type) {
-        // First try to locate the last node that is a bonafide Class and not 
some other Type
+    protected Type getTypeVariableValue(NodeEvaluation evaluation, 
TypeVariable typeVar) {
+
+        // Type map from TypeVariables to the corresponding Type.
+        List<HashMap<TypeVariable, Type>> typemap = new 
ArrayList<HashMap<TypeVariable, Type>>();
+
+        // Scan the evaluation chain for the first class or any parameterized 
types.
         Class lastBean = this.bean.getClass();
-        for (NodeEvaluation n = evaluation.getPrevious(); n != null; 
n=n.getPrevious()) {
-            if (n.getValueType() instanceof Class) {
+        for (NodeEvaluation n = evaluation.getPrevious(); n != null; n = 
n.getPrevious()) {
+            Type type = n.getValueType();
+
+            // Bean class found?  Stop searching.
+            if (type instanceof Class) {
                 lastBean = (Class) n.getValueType();
                 break;
+
+            // Parameterized type?  Add to the typemap and keep going.
+            } else if (type instanceof ParameterizedType) {
+                addTypeMappings(typemap, (ParameterizedType) type);
             }
         }
 
-        // Now if the super type is parameterized try and loop through the 
type variables
-        // and type arguments in tandem matching a concrete parameter to the 
type variable
-        for (Class beanClass=lastBean; beanClass != null; 
beanClass=beanClass.getSuperclass()) {
-            Type stype = beanClass.getGenericSuperclass();
+        // Add the bean class and all its superclasses to the typemap.
+        for (Class c = lastBean; c != null; c = c.getSuperclass()) {
+            Type t = c.getGenericSuperclass();
+            if (t instanceof ParameterizedType) {
+                addTypeMappings(typemap, (ParameterizedType) t);
+            }
+        }
 
-            if (stype instanceof ParameterizedType) {
-                ParameterizedType ptype = (ParameterizedType) stype;
-                Type sclass = ptype.getRawType();
+        // Now traverse the typemap list, mapping the TypeVariable.
+        Type type = null;
+        for (int i = typemap.size() - 1; i >= 0; i--) {
 
-                if (sclass instanceof Class) {
-                    Class parent = (Class) sclass;
-                    TypeVariable[] variables = parent.getTypeParameters();
-                    Type[] arguments = ptype.getActualTypeArguments();
+            // Map the type variable to a type.
+            if ((type = typemap.get(i).get(typeVar)) != null) {
 
-                    for (int i=0; i<variables.length && i<arguments.length; 
++i) {
-                        if (variables[i] == type) {
-                            return arguments[i];
-                        }
-                    }
+                // Reached a real class?  Done.
+                if (type instanceof Class) {
+                    break;
+
+                // Another TypeVariable? Keep going.
+                } else if (type instanceof TypeVariable) {
+                    typeVar = (TypeVariable) type;
                 }
             }
         }
+        return type;
+    }
 
-        return null;
+   /**
+    * Build a map of TypeVariables to Types.  We have to traverse the class 
hierarchy
+    * from subclass to superclass, but the mapping of TypeVariables to Types 
has to start
+    * from the place were the type variable was originally defined 
(superclass) and map
+    * to the place where the type is bound to an actual class (subclass).  We 
therefore
+    * need to build up the mapings sub to super and then traverse super to sub.
+    *
+    * @param paramType parameterized type to add to the map.
+    */
+    private void addTypeMappings(List<HashMap<TypeVariable, Type>> typemap,
+      ParameterizedType paramType) {
+        Type rawType = paramType.getRawType();
+        if (rawType instanceof Class) {
+            Class rawClass = (Class) rawType;
+            TypeVariable[] vars = rawClass.getTypeParameters();
+            Type[] args = paramType.getActualTypeArguments();
+            HashMap<TypeVariable, Type> entry =
+              new HashMap<TypeVariable, Type>(vars.length);
+            for (int i = 0;  i < vars.length && i < args.length; ++i) {
+                entry.put(vars[i], args[i]);
+            }
+            typemap.add(entry);
+        }
     }
 
     /**
@@ -630,5 +669,4 @@
             }
         }
     }
-
 }

Modified: 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTests.java
===================================================================
--- 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTests.java    
    2007-10-11 02:10:42 UTC (rev 616)
+++ 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTests.java    
    2007-11-06 19:00:11 UTC (rev 617)
@@ -19,7 +19,7 @@
  * @author Tim Fennell
  */
 public class GenericsBindingTests
-     extends GenericsBindingTestsBaseClass<TestBean,Double,Boolean,Long, Date>
+     extends GenericsBindingTestsBaseClass<TestBean,Double,Boolean,Long,Date>
   implements ActionBean {
 
     // Stuff necessary to implement ActionBean!
@@ -49,6 +49,20 @@
     }
 
     @Test(groups="fast")
+    public void testGenericBean() throws Exception {
+        MockRoundtrip trip = getRoundtrip();
+        trip.addParameter("genericBean.genericA", "123.4");
+        trip.addParameter("genericBean.genericB", "true");
+        trip.execute();
+
+        GenericsBindingTests bean = 
trip.getActionBean(GenericsBindingTests.class);
+        Assert.assertNotNull(bean.getGenericBean().getGenericA());
+        Assert.assertEquals(bean.getGenericBean().getGenericA(), new 
Double(123.4));
+        Assert.assertNotNull(bean.getGenericBean().getGenericB());
+        Assert.assertEquals(bean.getGenericBean().getGenericB(), Boolean.TRUE);
+    }
+
+    @Test(groups="fast")
     public void testTypeVariableLists() throws Exception {
         MockRoundtrip trip = getRoundtrip();
         trip.addParameter("list[0]", "true");

Modified: 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTests2.java
===================================================================
--- 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTests2.java   
    2007-10-11 02:10:42 UTC (rev 616)
+++ 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTests2.java   
    2007-11-06 19:00:11 UTC (rev 617)
@@ -1,37 +1,129 @@
 package net.sourceforge.stripes.controller;
 
+import java.util.Calendar;
+import java.util.Date;
+import net.sourceforge.stripes.StripesTestFixture;
+import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.action.ActionBeanContext;
+import net.sourceforge.stripes.action.DefaultHandler;
+import net.sourceforge.stripes.action.RedirectResolution;
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.mock.MockRoundtrip;
+import net.sourceforge.stripes.test.TestBean;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 /**
  * Basically a mirror of GenericsBindingTests except that in this case the 
type variable/
- * parameter information is pushed further up the hierarchy by the fact that 
we just extend
- * GenericsBindingTests.  So this test ensures that our type inference using 
type variables
- * works when the information is not directly in this class, but further up 
the hierarchy.
+ * parameter information is pushed further up the hierarchy.  So this test 
ensures that
+ * our type inference using type variables works when the information is not 
directly in
+ * this class, but further up the hierarchy.
  *
  * @author Tim Fennell
  */
-public class GenericsBindingTests2 extends GenericsBindingTests {
-    @Override
+class Class1<A,B,C,D,E> extends GenericsBindingTestsBaseClass<A,B,C,D,E> { }
+
+class Class2<D,E,B,A,C> extends Class1<D,E,B,A,C> { }
+
+class Class3<Y,W,Z,V,X> extends Class2<Y,W,Z,V,X> { }
+
+class Class4<Z,Y,X,W,V> extends Class3<Z,Y,X,W,V> { }
+
+public class GenericsBindingTests2
+     extends Class4<TestBean,Double,Boolean,Long,Date>
+  implements ActionBean {
+
+    // Stuff necessary to implement ActionBean!
+    private ActionBeanContext context;
+    public ActionBeanContext getContext() { return context; }
+    public void setContext(ActionBeanContext context) { this.context = 
context; }
+    @DefaultHandler public Resolution execute() { return new 
RedirectResolution("/somewhere.jsp"); }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Test and Support Methods
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** Makes a roundtrip using the current instances' type. */
+    protected MockRoundtrip getRoundtrip() {
+        return new MockRoundtrip(StripesTestFixture.getServletContext(), 
GenericsBindingTests2.class);
+    }
+
     @Test(groups="fast")
     public void testSimpleTypeVariable() throws Exception {
-        super.testSimpleTypeVariable();
+        MockRoundtrip trip = getRoundtrip();
+        trip.addParameter("number", "123.4");
+        trip.execute();
+
+        GenericsBindingTests2 bean = 
trip.getActionBean(GenericsBindingTests2.class);
+        Assert.assertNotNull(bean.getNumber());
+        Assert.assertEquals(bean.getNumber(), new Double(123.4));
     }
 
-    @Override
     @Test(groups="fast")
+    public void testGenericBean() throws Exception {
+        MockRoundtrip trip = getRoundtrip();
+        trip.addParameter("genericBean.genericA", "123.4");
+        trip.addParameter("genericBean.genericB", "true");
+        trip.execute();
+
+        GenericsBindingTests2 bean = 
trip.getActionBean(GenericsBindingTests2.class);
+        Assert.assertNotNull(bean.getGenericBean().getGenericA());
+        Assert.assertEquals(bean.getGenericBean().getGenericA(), new 
Double(123.4));
+        Assert.assertNotNull(bean.getGenericBean().getGenericB());
+        Assert.assertEquals(bean.getGenericBean().getGenericB(), Boolean.TRUE);
+    }
+
+    @Test(groups="fast")
     public void testTypeVariableLists() throws Exception {
-        super.testTypeVariableLists();
+        MockRoundtrip trip = getRoundtrip();
+        trip.addParameter("list[0]", "true");
+        trip.addParameter("list[1]", "false");
+        trip.addParameter("list[2]", "yes");
+        trip.execute();
+
+        GenericsBindingTests2 bean = 
trip.getActionBean(GenericsBindingTests2.class);
+        Assert.assertNotNull(bean.getList());
+        Assert.assertEquals(bean.getList().get(0), Boolean.TRUE);
+        Assert.assertEquals(bean.getList().get(1), Boolean.FALSE);
+        Assert.assertEquals(bean.getList().get(2), Boolean.TRUE);
     }
 
-    @Override
     @Test(groups="fast")
     public void testTypeVariableMaps() throws Exception {
-        super.testTypeVariableMaps();
+        MockRoundtrip trip = getRoundtrip();
+        trip.addParameter("map[10]", "1/1/2010");
+        trip.addParameter("map[20]", "1/1/2020");
+        trip.addParameter("map[30]", "1/1/2030");
+        trip.execute();
+
+        GenericsBindingTests2 bean = 
trip.getActionBean(GenericsBindingTests2.class);
+        Assert.assertNotNull(bean.getMap());
+        Assert.assertEquals(bean.getMap().get(10l), makeDate(2010,1,1));
+        Assert.assertEquals(bean.getMap().get(20l), makeDate(2020,1,1));
+        Assert.assertEquals(bean.getMap().get(30l), makeDate(2030,1,1));
     }
 
-    @Override
     @Test(groups="fast")
     public void testTypeVariableNestedProperties() throws Exception {
-        super.testTypeVariableNestedProperties();
+        MockRoundtrip trip = getRoundtrip();
+        trip.addParameter("bean.longProperty", "1234");
+        trip.addParameter("bean.stringProperty", "foobar");
+        trip.execute();
+
+        GenericsBindingTests2 bean = 
trip.getActionBean(GenericsBindingTests2.class);
+        Assert.assertNotNull(bean.getBean());
+        Assert.assertEquals(bean.getBean().getLongProperty(), new Long(1234));
+        Assert.assertEquals(bean.getBean().getStringProperty(), "foobar");
     }
+
+    /**
+     * Helper method to manufacture dates without time components. Months are 
1 based unlike
+     * the retarded Calendar API that uses 1 based everything else and 0 based 
months. Sigh.
+     */
+    private Date makeDate(int year, int month, int day) {
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(year, month-1, day);
+        return cal.getTime();
+    }
 }

Modified: 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTestsBaseClass.java
===================================================================
--- 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTestsBaseClass.java
       2007-10-11 02:10:42 UTC (rev 616)
+++ 
trunk/tests/src/net/sourceforge/stripes/controller/GenericsBindingTestsBaseClass.java
       2007-11-06 19:00:11 UTC (rev 617)
@@ -2,16 +2,18 @@
 
 import java.util.List;
 import java.util.Map;
+import net.sourceforge.stripes.test.TestGenericBean;
 
 /**
  * A simple base class that is littered with Type parameters at the class 
level. Contains
  * no tests in and of itself, but it is necessary to be a public class in 
order for
  * [EMAIL PROTECTED] GenericsBindingTests} to extend it and have the methods 
be accessible.
  *
- * @author Tim Fennell 
+ * @author Tim Fennell
  */
 public class GenericsBindingTestsBaseClass<JB,N,E,K,V> {
     JB bean;
+    TestGenericBean<N,E> genericBean;
     N number;
     List<? extends E> list;
     Map<K,V> map;
@@ -19,6 +21,9 @@
     public JB getBean() { return bean; }
     public void setBean(JB bean) { this.bean = bean; }
 
+    public TestGenericBean<N,E> getGenericBean() { return genericBean; }
+    public void setGenericBean(TestGenericBean<N,E> genericBean) { 
this.genericBean = genericBean; }
+
     public N getNumber() { return number; }
     public void setNumber(N number) { this.number = number; }
 

Added: trunk/tests/src/net/sourceforge/stripes/test/TestGenericBean.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/test/TestGenericBean.java           
                (rev 0)
+++ trunk/tests/src/net/sourceforge/stripes/test/TestGenericBean.java   
2007-11-06 19:00:11 UTC (rev 617)
@@ -0,0 +1,40 @@
+package net.sourceforge.stripes.test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A JavaBean that is a generic type.
+ *
+ * @author Alan Burlison
+ *
+ * XXX If this class is not the top level in the inheritance hierarchy,
+ * Stripes cannot bind its properties.  e.g. if instead of:
+ *     public class TestGenericBean<A,B> { ... }
+ * we have
+ *     class Class1<A,B> { ... }
+ *     class Class2<B,A> extends Class1<B,A> {}
+ *     class Class3<X,Y> extends Class2<X,Y> {}
+ *     public class TestGenericBean<A,B> extends Class3<A,B> {}
+ * Stripes will fail to bind the properties of Class1 correctly.  See STS-XXX
+ */
+public class TestGenericBean<A,B> {
+    private A genericA;
+    private B genericB;
+
+    public A getGenericA() {
+        return genericA;
+    }
+    public void setGenericA(A genericA) {
+        this.genericA = genericA;
+    }
+
+    public B getGenericB() {
+        return genericB;
+    }
+
+    public void setGenericB(B genericB) {
+        this.genericB = genericB;
+    }
+}
\ No newline at end of file


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

-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to