Commits

Anonymous committed fb74fd2

GenericsObjectTypeDeterminer fixes
o should fix most of the reported stuff, but nested generics still not working...
Issue Number: XW-407

git-svn-id: http://svn.opensymphony.com/svn/xwork/branches/xwork_1-2@1107e221344d-f017-0410-9bd5-d282ab1896d7

Comments (0)

Files changed (3)

tiger/src/java/com/opensymphony/xwork/util/GenericsObjectTypeDeterminer.java

 
 import java.beans.IntrospectionException;
 import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
+import java.lang.reflect.*;
 import java.util.Map;
 
 import ognl.OgnlException;
 import ognl.OgnlRuntime;
 
 /**
- * GenericsObjectTypeDeterminer use the following algorithm for determining the types: looks for 
+ * GenericsObjectTypeDeterminer use the following algorithm for determining the types: looks for
  * a field annotation, than for the corresponding setter annotation, and for special cases falls
  * back to generic types.
  *
  */
 public class GenericsObjectTypeDeterminer extends DefaultObjectTypeDeterminer {
 
+
     /**
      * Determines the key class by looking for the value of @Key annotation for the given class.
      * If no annotation is found, the key class is determined by using the generic parametrics.
             return annotation.value();
         }
 
-        Class clazz = getClass(OgnlRuntime.getField(parentClass, property), false);
+        Class clazz = getClass(parentClass, property, false);
 
         if (clazz != null) {
             return clazz;
             return annotation.value();
         }
 
-        Class clazz = getClass(OgnlRuntime.getField(parentClass, property), true);
+        Class clazz = getClass(parentClass, property, true);
 
         if (clazz != null) {
             return clazz;
      */
     public String getKeyProperty(Class parentClass, String property) {
         KeyProperty annotation = getAnnotation(parentClass, property, KeyProperty.class);
-        
+
         if (annotation != null) {
             return annotation.value();
         }
     /**
      * Determines the createIfNull property for a Collection or Map by getting it from the @CreateIfNull annotation.
      *
-     * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for.
-     * @param property    the property of the Map or Collection for the given parent class
+     * @param parentClass     the Class which contains as a property the Map or Collection we are finding the key for.
+     * @param property        the property of the Map or Collection for the given parent class
      * @param target
      * @param keyProperty
      * @param isIndexAccessed <tt>true</tt>, if the collection or map is accessed via index, <tt>false</tt> otherwise.
-     * @see com.opensymphony.xwork.util.ObjectTypeDeterminer#getKeyProperty(Class, String)
+     * @see ObjectTypeDeterminer#getKeyProperty(Class, String)
      */
     public boolean shouldCreateIfNew(Class parentClass,
                                      String property,
     }
 
     /**
-     * Retrieves an annotation for the specified field of setter.
-     * 
-     * @param <T> the annotation type to be retrieved
-     * @param parentClass the class
-     * @param property the property
-     * @param annotationClass the annotation 
-     * @return the field or setter annotation or <code>null</code> if not found
+     * Retrieves an annotation for the specified property of field, setter or getter.
+     *
+     * @param <T>             the annotation type to be retrieved
+     * @param parentClass     the class
+     * @param property        the property
+     * @param annotationClass the annotation
+     * @return the field or setter/getter annotation or <code>null</code> if not found
      */
     protected <T extends Annotation> T getAnnotation(Class parentClass, String property, Class<T> annotationClass) {
         T annotation = null;
             annotation = field.getAnnotation(annotationClass);
         }
         if (annotation == null) { // HINT: try with setter
-            try {
-                Method setter = OgnlRuntime.getSetMethod(null, parentClass, property);
-                
-                if (setter != null) {
-                    annotation = setter.getAnnotation(annotationClass);                    
-                }
-            }
-            catch(OgnlException ognle) {
-                ; // ignore
+            annotation = getAnnotationFromSetter(parentClass, property, annotationClass);
+        }
+        if (annotation == null) { // HINT: try with getter
+            annotation = getAnnotationFromGetter(parentClass, property, annotationClass);
+        }
+
+        return annotation;
+    }
+
+    /**
+     * Retrieves an annotation for the specified field of getter.
+     *
+     * @param parentClass     the Class which contains as a property the Map or Collection we are finding the key for.
+     * @param property        the property of the Map or Collection for the given parent class
+     * @param annotationClass The annotation
+     * @return concrete Annotation instance or <tt>null</tt> if none could be retrieved.
+     */
+    private <T extends Annotation>T getAnnotationFromGetter(Class parentClass, String property, Class<T> annotationClass) {
+        try {
+            Method getter = OgnlRuntime.getGetMethod(null, parentClass, property);
+
+            if (getter != null) {
+                return getter.getAnnotation(annotationClass);
             }
-            catch(IntrospectionException ie) {
-                ; // ignore
+        }
+        catch (OgnlException ognle) {
+            ; // ignore
+        }
+        catch (IntrospectionException ie) {
+            ; // ignore
+        }
+        return null;
+    }
+
+    /**
+     * Retrieves an annotation for the specified field of setter.
+     *
+     * @param parentClass     the Class which contains as a property the Map or Collection we are finding the key for.
+     * @param property        the property of the Map or Collection for the given parent class
+     * @param annotationClass The annotation
+     * @return concrete Annotation instance or <tt>null</tt> if none could be retrieved.
+     */
+    private <T extends Annotation>T getAnnotationFromSetter(Class parentClass, String property, Class<T> annotationClass) {
+        try {
+            Method setter = OgnlRuntime.getSetMethod(null, parentClass, property);
+
+            if (setter != null) {
+                return setter.getAnnotation(annotationClass);
             }
         }
-        
-        return annotation;
+        catch (OgnlException ognle) {
+            ; // ignore
+        }
+        catch (IntrospectionException ie) {
+            ; // ignore
+        }
+        return null;
     }
-    
+
     /**
      * Returns the class for the given field via generic type check.
      *
-     * @param field The field to check for generic types.
-     * @param element <tt>true</tt> for indexed types and Maps.
+     * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for.
+     * @param property    the property of the Map or Collection for the given parent class
+     * @param element     <tt>true</tt> for indexed types and Maps.
      * @return Class of the specified field.
      */
-    private Class getClass(Field field, boolean element) {
-        Type genericType = field.getGenericType();
+    private Class getClass(Class parentClass, String property, boolean element) {
+
+
+        try {
 
-        if (genericType instanceof ParameterizedType) {
+            Field field = OgnlRuntime.getField(parentClass, property);
 
-            int index = (element && Map.class.isAssignableFrom(field.getType())) ? 1 : 0;
-            ParameterizedType type = (ParameterizedType) genericType;
+            Type genericType = null;
+
+            // Check fields first
+            if (field != null) {
+                genericType = field.getGenericType();
+            }
+
+            // Try to get ParameterType from setter method
+            if (genericType == null || !(genericType instanceof ParameterizedType)) {
+                try {
+                    Method setter = OgnlRuntime.getSetMethod(null, parentClass, property);
+                    genericType = setter.getGenericParameterTypes()[0];
+                }
+                catch (OgnlException ognle) {
+                    ; // ignore
+                }
+                catch (IntrospectionException ie) {
+                    ; // ignore
+                }
+            }
 
-            return (Class) type.getActualTypeArguments()[index];
-        } else {
-            return null;
+            // Try to get ReturnType from getter method
+            if (genericType == null || !(genericType instanceof ParameterizedType)) {
+                try {
+                    Method getter = OgnlRuntime.getGetMethod(null, parentClass, property);
+                    genericType = getter.getGenericReturnType();
+                }
+                catch (OgnlException ognle) {
+                    ; // ignore
+                }
+                catch (IntrospectionException ie) {
+                    ; // ignore
+                }
+            }
+
+            if (genericType instanceof ParameterizedType) {
+
+
+                ParameterizedType type = (ParameterizedType) genericType;
+
+                int index = (element && type.getRawType().toString().contains(Map.class.getName())) ? 1 : 0;
+
+                Type resultType = type.getActualTypeArguments()[index];
+
+                if ( resultType instanceof ParameterizedType) {
+                    return resultType.getClass();
+                }
+                return (Class) resultType;
+
+            }
+        } catch (Exception e) {
+            if ( LOG.isDebugEnabled()) {
+                LOG.debug("Error while retrieving generic property class for property=" + property, e);
+            }
         }
+        return null;
     }
 }

tiger/src/test/com/opensymphony/xwork/GenericsBean.java

 package com.opensymphony.xwork;
 
+import com.opensymphony.xwork.util.Element;
+
 import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
 
 /**
  * <code>GenericsBean</code>
  */
 public class GenericsBean {
 
-    private List<Double> doubles;
+    private List<Double> blubb;
+    private List<Double> getterList;
+    private Map<Double, Integer> genericMap = new HashMap<Double, Integer>();
+    private Map<Double, List<Integer>> extendedMap = new HashMap<Double, List<Integer>>();
 
     /**
      * @return Returns the doubles.
      */
     public List<Double> getDoubles() {
-        return doubles;
+        return blubb;
     }
 
     /**
      * @param doubles The doubles to set.
      */
     public void setDoubles(List<Double> doubles) {
-        this.doubles = doubles;
+        this.blubb = doubles;
+    }
+
+    public Map<Double, Integer> getGenericMap() {
+        return genericMap;
+    }
+
+    public void setGenericMap(Map<Double, Integer> genericMap) {
+        this.genericMap = genericMap;
+    }
+
+    public List<Double> getGetterList() {
+        if ( getterList == null ) {
+            getterList = new ArrayList<Double>(1);
+            getterList.add(42.42);
+        }
+        return getterList;
+    }
+
+    public Map<Double, List<Integer>> getExtendedMap() {
+        return extendedMap;
+    }
+
+    public void setExtendedMap(Map<Double, List<Integer>> extendedMap) {
+        this.extendedMap = extendedMap;
     }
 }

tiger/src/test/com/opensymphony/xwork/util/AnnotationXWorkConverterTest.java

         }, Set.class));
     }
 
-    // TODO: Fixme... This test does not work with GenericsObjectDeterminer!
     public void testStringToCollectionConversion() {
         OgnlValueStack stack = new OgnlValueStack();
         Map stackContext = stack.getContext();
         assertNotNull(bar);
     }
 
-    public void testGenericProperties() {
+    public void testGenericPropertiesFromField() {
         GenericsBean gb = new GenericsBean();
         OgnlValueStack stack = new OgnlValueStack();
         stack.push(gb);
 
-        String[] value = new String[] {"123.12", "123.45"};
-        stack.setValue("doubles", value);
-        assertEquals(2, gb.getDoubles().size());
-        assertEquals(Double.class, gb.getDoubles().get(0).getClass());
-        assertEquals(new Double(123.12), gb.getDoubles().get(0));
-        assertEquals(new Double(123.45), gb.getDoubles().get(1));
+        stack.setValue("genericMap[123.12]", "66");
+        stack.setValue("genericMap[456.12]", "42");
+
+        assertEquals(2, gb.getGenericMap().size());
+        assertEquals(Integer.class, stack.findValue("genericMap.get(123.12).class"));
+        assertEquals(Integer.class, stack.findValue("genericMap.get(456.12).class"));
+        assertEquals(66, stack.findValue("genericMap.get(123.12)"));
+        assertEquals(42, stack.findValue("genericMap.get(456.12)"));
+        assertEquals(true, stack.findValue("genericMap.containsValue(66)"));
+        assertEquals(true, stack.findValue("genericMap.containsValue(42)"));
+        assertEquals(true, stack.findValue("genericMap.containsKey(123.12)"));
+        assertEquals(true, stack.findValue("genericMap.containsKey(456.12)"));
+    }
+
+    public void testGenericPropertiesFromSetter() {
+        GenericsBean gb = new GenericsBean();
+        OgnlValueStack stack = new OgnlValueStack();
+        stack.push(gb);
+
+        stack.setValue("genericMap[123.12]", "66");
+        stack.setValue("genericMap[456.12]", "42");
+
+        assertEquals(2, gb.getGenericMap().size());
+        assertEquals(Integer.class, stack.findValue("genericMap.get(123.12).class"));
+        assertEquals(Integer.class, stack.findValue("genericMap.get(456.12).class"));
+        assertEquals(66, stack.findValue("genericMap.get(123.12)"));
+        assertEquals(42, stack.findValue("genericMap.get(456.12)"));
+        assertEquals(true, stack.findValue("genericMap.containsValue(66)"));
+        assertEquals(true, stack.findValue("genericMap.containsValue(42)"));
+        assertEquals(true, stack.findValue("genericMap.containsKey(123.12)"));
+        assertEquals(true, stack.findValue("genericMap.containsKey(456.12)"));
+    }
+
+    public void testGenericPropertiesFromGetter() {
+        GenericsBean gb = new GenericsBean();
+        OgnlValueStack stack = new OgnlValueStack();
+        stack.push(gb);
+
+        assertEquals(1, gb.getGetterList().size());
+        assertEquals(Double.class, stack.findValue("getterList.get(0).class"));
+        assertEquals(new Double(42.42), stack.findValue("getterList.get(0)"));
+        assertEquals(new Double(42.42), gb.getGetterList().get(0));
+
+    }
+
+
+    public void no_testGenericPropertiesWithNestedGenerics() {
+        GenericsBean gb = new GenericsBean();
+        OgnlValueStack stack = new OgnlValueStack();
+        stack.push(gb);
+
+        stack.setValue("extendedMap[123.12]", new String[] {"1", "2", "3", "4"});
+        stack.setValue("extendedMap[456.12]", new String[] {"5", "6", "7", "8", "9"});
+
+        System.out.println("gb.getExtendedMap(): " + gb.getExtendedMap());
+
+        assertEquals(2, gb.getExtendedMap().size());
+        assertEquals(4, stack.findValue("extendedMap.get(123.12).size"));
+        assertEquals(5, stack.findValue("extendedMap.get(456.12).size"));
+        assertEquals(List.class, stack.findValue("extendedMap.get(123.12).class"));
+        assertEquals(List.class, stack.findValue("extendedMap.get(456.12).class"));
     }
 
     public static class Foo1 {