Commits

rainerh  committed 17e1062

Merge XWork tiger subproject into XWork core
o move unit tests over
o some fixes for GenericsObjectTypeDeterminer (ported from 1.2.1)
o NPE checkin DefaultConfiguration (ported from 1.2.1)

Issue Number: XW-435

git-svn-id: http://svn.opensymphony.com/svn/xwork/trunk@1199e221344d-f017-0410-9bd5-d282ab1896d7

  • Participants
  • Parent commits e1ac547

Comments (0)

Files changed (61)

File src/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java

         }
         else {
         	PackageConfig baseConfigPackageConfig = (PackageConfig) packageContexts.get(baseConfig.getPackageName());
-        	results = new TreeMap<String, ResultConfig>(baseConfigPackageConfig.getAllGlobalResults());
+            if ( baseConfigPackageConfig != null) {
+                results = new TreeMap<String, ResultConfig>(baseConfigPackageConfig.getAllGlobalResults());
+            }
+
         	results.putAll(baseConfig.getResults());
         }        
 

File src/java/com/opensymphony/xwork2/util/DefaultObjectTypeDeterminer.java

  * @see XWorkMapPropertyAccessor
  */
 public class DefaultObjectTypeDeterminer implements ObjectTypeDeterminer {
-    private static final Log LOG = LogFactory.getLog(DefaultObjectTypeDeterminer.class);
+    protected static final Log LOG = LogFactory.getLog(DefaultObjectTypeDeterminer.class);
 
     public static final String KEY_PREFIX = "Key_";
     public static final String ELEMENT_PREFIX = "Element_";

File src/java/com/opensymphony/xwork2/util/GenericsObjectTypeDeterminer.java

 package com.opensymphony.xwork2.util;
 
 import ognl.OgnlRuntime;
+import ognl.OgnlException;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
+import java.lang.reflect.Method;
+import java.lang.annotation.Annotation;
 import java.util.Map;
+import java.beans.IntrospectionException;
 
 /**
  * GenericsObjectTypeDeterminer
  *
  * @author Patrick Lightbody
  * @author Rainer Hermanns
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
  */
 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.
      * @see com.opensymphony.xwork2.util.ObjectTypeDeterminer#getKeyClass(Class, String)
      */
     public Class getKeyClass(Class parentClass, String property) {
+        Key annotation = getAnnotation(parentClass, property, Key.class);
 
-        Field field = OgnlRuntime.getField(parentClass, property);
-
-        if (field != null) {
-            Key annotation = field.getAnnotation(Key.class);
-            if (annotation != null) {
-                return annotation.value();
-            }
+        if (annotation != null) {
+            return annotation.value();
+        }
 
-            Class clazz = getClass(field, false);
+        Class clazz = getClass(parentClass, property, false);
 
-            if (clazz != null) {
-                return clazz;
-            }
+        if (clazz != null) {
+            return clazz;
         }
 
         return super.getKeyClass(parentClass, property);
      * @see com.opensymphony.xwork2.util.ObjectTypeDeterminer#getElementClass(Class, String, Object)
      */
     public Class getElementClass(Class parentClass, String property, Object key) {
+        Element annotation = getAnnotation(parentClass, property, Element.class);
 
-        Field field = OgnlRuntime.getField(parentClass, property);
-
-        if (field != null) {
-            Element annotation = field.getAnnotation(Element.class);
-            if (annotation != null) {
-                return annotation.value();
-            }
+        if (annotation != null) {
+            return annotation.value();
+        }
 
-            Class clazz = getClass(field, true);
+        Class clazz = getClass(parentClass, property, true);
 
-            if (clazz != null) {
-                return clazz;
-            }
+        if (clazz != null) {
+            return clazz;
         }
 
         return super.getElementClass(parentClass, property, key);
      * @see com.opensymphony.xwork2.util.ObjectTypeDeterminer#getKeyProperty(Class, String)
      */
     public String getKeyProperty(Class parentClass, String property) {
+        KeyProperty annotation = getAnnotation(parentClass, property, KeyProperty.class);
 
-        Field field = OgnlRuntime.getField(parentClass, property);
-
-        if (field != null) {
-            KeyProperty annotation = field.getAnnotation(KeyProperty.class);
-
-            if (annotation != null) {
-                return annotation.value();
-            }
+        if (annotation != null) {
+            return annotation.value();
         }
 
-
         return super.getKeyProperty(parentClass, property);
     }
 
     /**
      * 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.xwork2.util.ObjectTypeDeterminer#getKeyProperty(Class, String)
+     * @see ObjectTypeDeterminer#getKeyProperty(Class, String)
      */
     public boolean shouldCreateIfNew(Class parentClass,
                                      String property,
                                      String keyProperty,
                                      boolean isIndexAccessed) {
 
+        CreateIfNull annotation = getAnnotation(parentClass, property, CreateIfNull.class);
+
+        if (annotation != null) {
+            return annotation.value();
+        }
+
+        return super.shouldCreateIfNew(parentClass, property, target, keyProperty, isIndexAccessed);
+    }
+
+    /**
+     * 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;
         Field field = OgnlRuntime.getField(parentClass, property);
 
         if (field != null) {
-            CreateIfNull annotation = field.getAnnotation(CreateIfNull.class);
-            if (annotation != null) {
-                return annotation.value();
+            annotation = field.getAnnotation(annotationClass);
+        }
+        if (annotation == null) { // HINT: try with setter
+            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 (OgnlException ognle) {
+            ; // ignore
+        }
+        catch (IntrospectionException ie) {
+            ; // ignore
+        }
+        return null;
+    }
 
-        return super.shouldCreateIfNew(parentClass, property, target, keyProperty, isIndexAccessed);
+    /**
+     * 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);
+            }
+        }
+        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
+                }
+            }
+
+            // 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
+                }
+            }
 
-            return (Class) type.getActualTypeArguments()[index];
-        } else {
-            return null;
+            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;
     }
 }

File src/java/com/opensymphony/xwork2/util/InstantiatingNullHandler.java

 package com.opensymphony.xwork2.util;
 
 import com.opensymphony.xwork2.ObjectFactory;
-import com.opensymphony.xwork2.inject.Inject;
 
 import ognl.NullHandler;
 import ognl.Ognl;
  * For example, if a form element has a text field named <b>person.name</b> and the expression <i>person</i> evaluates
  * to null, then this class will be invoked. Because the <i>person</i> expression evaluates to a <i>Person</i> class, a
  * new Person is created and assigned to the null reference. Finally, the name is set on that object and the overall
- * effect is that the system automatically created a Person object for you, set it by calling setPerson() and then
- * finally called getPerson().setName() as you would typically expect.
+ * effect is that the system automatically created a Person object for you, set it by calling setUsers() and then
+ * finally called getUsers().setName() as you would typically expect.
  *
  * <!-- END SNIPPET: example>
  *

File src/java/com/opensymphony/xwork2/util/XWorkConverter.java

         return message;
     }
 
+
     public static XWorkConverter getInstance() {
-        if ( instance == null ) {
-            instance = new XWorkConverter();
-        }
+         if (instance == null) {
+             try {
+                 Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.opensymphony.xwork2.util.AnnotationXWorkConverter");
+                 instance = (XWorkConverter) clazz.newInstance();
+                 LOG.info("Detected AnnotationXWorkConverter, initializing it...");
+             } catch (ClassNotFoundException e) {
+                 // this is fine, just fall back to the default object type determiner
+             } catch (Exception e) {
+                 LOG.error("Exception when trying to create new AnnotationXWorkConverter", e);
+             }
+             if ( instance == null ) {
+                 instance = new XWorkConverter();
+             }
+         }
+
+         return instance;
+     }
 
-        return instance;
-    }
     
     @Inject
     public static void setInstance(XWorkConverter instance) {

File src/java/com/opensymphony/xwork2/validator/AnnotationValidationConfigurationBuilder.java

 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
+import java.text.SimpleDateFormat;
+import java.text.DateFormat;
+import java.text.ParseException;
 
 import com.opensymphony.xwork2.validator.annotations.*;
 
         return vCfg;
     }
 
+    private static Date parseDateString(String value) {
+
+        SimpleDateFormat d1 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, Locale.getDefault());
+        SimpleDateFormat d2 = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, Locale.getDefault());
+        SimpleDateFormat d3 = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());
+        SimpleDateFormat[] dfs = {d1, d2, d3};
+        DateFormat df = null;
+        for (int i = 0; i < dfs.length; i++) {
+            try {
+                Date check = dfs[i].parse(value);
+                df = dfs[i];
+                if (check != null) {
+                    return check;
+                }
+            }
+            catch (ParseException ignore) {
+            }
+        }
+        return null;
+
+    }
+
     private static ValidatorConfig processRequiredStringValidatorAnnotation(RequiredStringValidator v, String fieldName) {
         String validatorType = "requiredstring";
 
             params.put("fieldName", v.fieldName());
         }
         if ( v.min() != null && v.min().length() > 0) {
-            params.put("min", v.min());
+            params.put("min", parseDateString(v.min()));
         }
         if ( v.max() != null && v.max().length() > 0) {
-            params.put("max", v.max());
+            params.put("max", parseDateString(v.max()));
         }
 
         ValidatorFactory.lookupRegisteredValidatorType(validatorType);

File src/test/com/opensymphony/xwork2/AnnotatedTestBean.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.validator.annotations.IntRangeFieldValidator;
+import com.opensymphony.xwork2.validator.annotations.Validations;
+import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
+
+import java.util.Date;
+
+
+/**
+ * AnnotatedTestBean
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ * Created Aug 4, 2003 12:39:53 AM
+ */
+public class AnnotatedTestBean {
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private Date birth;
+    private String name;
+    private int count;
+
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public AnnotatedTestBean() {
+    }
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public void setBirth(Date birth) {
+        this.birth = birth;
+    }
+
+    public Date getBirth() {
+        return birth;
+    }
+
+    @Validations(
+            intRangeFields = {
+                @IntRangeFieldValidator(shortCircuit = true, min = "1", max="100", key="invalid.count", message = "Invalid Count!"),
+                @IntRangeFieldValidator(shortCircuit = true, min = "20", max="28", key="invalid.count.bad", message = "Smaller Invalid Count: ${count}")
+            }
+
+    )
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    @RequiredStringValidator(message = "You must enter a name.")
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

File src/test/com/opensymphony/xwork2/AnnotatedTestBean.properties

+#
+# Copyright (c) 2002-2006 by OpenSymphony
+# All rights reserved.
+#
+
+invalid.count=Count must be between ${min} and ${max}, current value is ${count}.

File src/test/com/opensymphony/xwork2/GenericsBean.java

+package com.opensymphony.xwork2;
+
+import java.util.List;
+
+/**
+ * <code>GenericsBean</code>
+ *
+ * @author <a href="mailto:hermanns@aixcept.de">Rainer Hermanns</a>
+ * @version $Id$
+ */
+public class GenericsBean {
+
+    private List<Double> doubles;
+
+    /**
+     * @return Returns the doubles.
+     */
+    public List<Double> getDoubles() {
+        return doubles;
+    }
+
+    /**
+     * @param doubles The doubles to set.
+     */
+    public void setDoubles(List<Double> doubles) {
+        this.doubles = doubles;
+    }
+}

File src/test/com/opensymphony/xwork2/ModelDrivenAnnotationAction.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2;
+
+/**
+ * ModelDrivenAnnotationAction
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ *         Created Apr 8, 2003 6:27:29 PM
+ */
+public class ModelDrivenAnnotationAction extends ActionSupport implements ModelDriven {
+
+    private String foo;
+    private AnnotatedTestBean model = new AnnotatedTestBean();
+
+
+    public void setFoo(String foo) {
+        this.foo = foo;
+    }
+
+    public String getFoo() {
+        return foo;
+    }
+
+    /**
+     * @return the model to be pushed onto the ValueStack after the Action itself
+     */
+    public Object getModel() {
+        return model;
+    }
+}

File src/test/com/opensymphony/xwork2/ModelDrivenAnnotationAction.properties

+#
+# Copyright (c) 2002-2006 by OpenSymphony
+# All rights reserved.
+#
+
+invalid.fieldvalue.birth=Invalid date for birth.

File src/test/com/opensymphony/xwork2/SimpleAnnotationAction.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.validator.annotations.*;
+import com.opensymphony.xwork2.ActionSupport;
+import com.opensymphony.xwork2.AnnotatedTestBean;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Properties;
+
+
+/**
+ * Simple Test Action for annotaton processing.
+ *
+ * @author Rainer Hermanns
+ * @version $Revision$
+ */
+@Validation()
+public class SimpleAnnotationAction extends ActionSupport {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    public static final String COMMAND_RETURN_CODE = "com.opensymphony.xwork2.SimpleAnnotationAction.CommandInvoked";
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private ArrayList someList = new ArrayList();
+    private Date date = new Date();
+    private Properties settings = new Properties();
+    private String blah;
+    private String name;
+    private AnnotatedTestBean bean = new AnnotatedTestBean();
+    private boolean throwException;
+    private int bar;
+    private int baz;
+    private int foo;
+    private double percentage;
+
+    private String aliasSource;
+    private String aliasDest;
+    
+    
+
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public SimpleAnnotationAction() {
+    }
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    @RequiredFieldValidator(type = ValidatorType.FIELD, message = "You must enter a value for bar.")
+    @IntRangeFieldValidator(type = ValidatorType.FIELD, min = "6", max = "10", message = "bar must be between ${min} and ${max}, current value is ${bar}.")
+    public void setBar(int bar) {
+        this.bar = bar;
+    }
+
+    public int getBar() {
+        return bar;
+    }
+
+    @IntRangeFieldValidator(min = "0", key = "baz.range", message = "Could not find baz.range!")
+    public void setBaz(int baz) {
+        this.baz = baz;
+    }
+
+    public int getBaz() {
+        return baz;
+    }
+
+    public double getPercentage() {
+        return percentage;
+    }
+
+    @DoubleRangeFieldValidator(minInclusive = "0.123", key = "baz.range", message = "Could not find percentage.range!")
+    public void setPercentage(double percentage) {
+        this.percentage = percentage;
+    }
+
+    public void setBean(AnnotatedTestBean bean) {
+        this.bean = bean;
+    }
+
+    public AnnotatedTestBean getBean() {
+        return bean;
+    }
+
+    public void setBlah(String blah) {
+        this.blah = blah;
+    }
+
+    public String getBlah() {
+        return blah;
+    }
+
+    public Boolean getBool(String b) {
+        return new Boolean(b);
+    }
+
+    public boolean[] getBools() {
+        boolean[] b = new boolean[] {true, false, false, true};
+
+        return b;
+    }
+
+    @DateRangeFieldValidator(min = "12/22/2002", max = "12/25/2002", message = "The date must be between 12-22-2002 and 12-25-2002.")
+    public void setDate(Date date) {
+        this.date = date;
+    }
+
+    public Date getDate() {
+        return date;
+    }
+
+    public void setFoo(int foo) {
+        this.foo = foo;
+    }
+
+    public int getFoo() {
+        return foo;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setSettings(Properties settings) {
+        this.settings = settings;
+    }
+
+    public Properties getSettings() {
+        return settings;
+    }
+
+
+    public String getAliasDest() {
+        return aliasDest;
+    }
+
+    public void setAliasDest(String aliasDest) {
+        this.aliasDest = aliasDest;
+    }
+
+    public String getAliasSource() {
+        return aliasSource;
+    }
+
+    public void setAliasSource(String aliasSource) {
+        this.aliasSource = aliasSource;
+    }
+
+    
+    public void setSomeList(ArrayList someList) {
+        this.someList = someList;
+    }
+
+    public ArrayList getSomeList() {
+        return someList;
+    }
+
+    public void setThrowException(boolean throwException) {
+        this.throwException = throwException;
+    }
+
+    public String commandMethod() throws Exception {
+        return COMMAND_RETURN_CODE;
+    }
+
+    public String exceptionMethod() throws Exception {
+        if (throwException) {
+            throw new Exception("We're supposed to throw this");
+        }
+
+        return "OK";
+    }
+
+    @Validations(
+            requiredFields =
+                    {@RequiredFieldValidator(type = ValidatorType.SIMPLE, fieldName = "customfield", message = "You must enter a value for field.")},
+            requiredStrings =
+                    {@RequiredStringValidator(type = ValidatorType.SIMPLE, fieldName = "stringisrequired", message = "You must enter a value for string.")},
+            emails =
+                    { @EmailValidator(type = ValidatorType.SIMPLE, fieldName = "emailaddress", message = "You must enter a value for email.")},
+            urls =
+                    { @UrlValidator(type = ValidatorType.SIMPLE, fieldName = "hreflocation", message = "You must enter a value for email.")},
+            stringLengthFields =
+                    {@StringLengthFieldValidator(type = ValidatorType.SIMPLE, trim = true, minLength="10" , maxLength = "12", fieldName = "needstringlength", message = "You must enter a stringlength.")},
+            intRangeFields =
+                    { @IntRangeFieldValidator(type = ValidatorType.SIMPLE, fieldName = "intfield", min = "6", max = "10", message = "bar must be between ${min} and ${max}, current value is ${bar}.")},
+            dateRangeFields =
+                    {@DateRangeFieldValidator(type = ValidatorType.SIMPLE, fieldName = "datefield", min = "-1", max = "99", message = "bar must be between ${min} and ${max}, current value is ${bar}.")},
+            expressions = {
+                @ExpressionValidator(expression = "foo &gt; 1", message = "Foo must be greater than Bar 1. Foo = ${foo}, Bar = ${bar}."),
+                @ExpressionValidator(expression = "foo &gt; 2", message = "Foo must be greater than Bar 2. Foo = ${foo}, Bar = ${bar}."),
+                @ExpressionValidator(expression = "foo &gt; 3", message = "Foo must be greater than Bar 3. Foo = ${foo}, Bar = ${bar}."),
+                @ExpressionValidator(expression = "foo &gt; 4", message = "Foo must be greater than Bar 4. Foo = ${foo}, Bar = ${bar}."),
+                @ExpressionValidator(expression = "foo &gt; 5", message = "Foo must be greater than Bar 5. Foo = ${foo}, Bar = ${bar}.")
+    }
+    )
+    public String execute() throws Exception {
+        if (foo == bar) {
+            return ERROR;
+        }
+
+        baz = foo + bar;
+
+        name = "HelloWorld";
+        settings.put("foo", "bar");
+        settings.put("black", "white");
+
+        someList.add("jack");
+        someList.add("bill");
+        someList.add("kerry");
+
+        return SUCCESS;
+    }
+}

File src/test/com/opensymphony/xwork2/SimpleAnnotationAction.properties

+#
+# Copyright (c) 2002-2006 by OpenSymphony
+# All rights reserved.
+#
+
+foo.range=Foo Range Message
+baz.range=${getText(fieldName)} must be greater than ${min}
+baz=Baz Field

File src/test/com/opensymphony/xwork2/SimpleAnnotationAction_de.properties

+#
+# Copyright (c) 2002-2006 by OpenSymphony
+# All rights reserved.
+#
+
+foo.range=I don''t know German

File src/test/com/opensymphony/xwork2/SimpleAnnotationAction_en.properties

+#
+# Copyright (c) 2002-2006 by OpenSymphony
+# All rights reserved.
+#
+

File src/test/com/opensymphony/xwork2/conversion/ConversionTestAction.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.conversion;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.util.XWorkBasicConverter;
+import com.opensymphony.xwork2.conversion.annotations.*;
+
+import java.util.List;
+import java.util.HashMap;
+import java.math.BigInteger;
+
+/**
+ * <code>ConversionTestAction</code>
+ *
+ * @author Rainer Hermanns
+ * @version $Id$
+ */
+@Conversion()
+public class ConversionTestAction implements Action {
+
+
+
+    private String convertInt;
+
+    private String convertDouble;
+
+    private List users = null;
+
+
+    private HashMap keyValues = null;
+
+
+    public String getConvertInt() {
+        return convertInt;
+    }
+
+    @TypeConversion(type = ConversionType.APPLICATION, converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+    public void setConvertInt( String convertInt ) {
+        this.convertInt = convertInt;
+    }
+
+    public String getConvertDouble() {
+        return convertDouble;
+    }
+
+    @TypeConversion(converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+    public void setConvertDouble( String convertDouble ) {
+        this.convertDouble = convertDouble;
+    }
+
+    public List getUsers() {
+        return users;
+    }
+
+    @TypeConversion(rule = ConversionRule.COLLECTION, converter = "java.lang.String")
+    public void setUsers( List users ) {
+        this.users = users;
+    }
+
+    public HashMap getKeyValues() {
+        return keyValues;
+    }
+
+    @TypeConversion(rule = ConversionRule.MAP, converter = "java.math.BigInteger")
+    public void setKeyValues( HashMap keyValues ) {
+        this.keyValues = keyValues;
+    }
+
+    /**
+     * Where the logic of the action is executed.
+     *
+     * @return a string representing the logical result of the execution.
+     *         See constants in this interface for a list of standard result values.
+     * @throws Exception thrown if a system level exception occurs.
+     *                   Application level exceptions should be handled by returning
+     *                   an error value, such as Action.ERROR.
+     */
+    @TypeConversion(type = ConversionType.APPLICATION, key = "java.util.Date", converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+    public String execute() throws Exception {
+        return SUCCESS;
+    }
+}

File src/test/com/opensymphony/xwork2/interceptor/annotations/AnnotatedAction.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.interceptor.annotations;
+
+import com.opensymphony.xwork2.Action;
+
+/**
+ * @author Zsolt Szasz, zsolt at lorecraft dot com
+ * @author Rainer Hermanns
+ */
+public class AnnotatedAction extends BaseAnnotatedAction {
+
+    @Before
+	public String before() {
+		log = log + "before";
+		return null;
+	}
+	
+	public String execute() {
+		log = log + "-execute";
+		return Action.SUCCESS;
+	}
+	
+	@BeforeResult
+	public void beforeResult() throws Exception {
+		log = log +"-beforeResult";
+	}
+	
+	@After
+	public void after() {
+		log = log + "-after";
+	}
+}

File src/test/com/opensymphony/xwork2/interceptor/annotations/AnnotationWorkflowInterceptorTest.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.interceptor.annotations;
+
+import java.util.Arrays;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.mock.MockResult;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.inject.ContainerBuilder;
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.ConfigurationManager;
+import com.opensymphony.xwork2.config.ConfigurationProvider;
+import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.PackageConfig;
+import com.opensymphony.xwork2.config.entities.InterceptorMapping;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+
+/**
+ * @author Zsolt Szasz, zsolt at lorecraft dot com
+ * @author Rainer Hermanns
+ */
+public class AnnotationWorkflowInterceptorTest extends XWorkTestCase {
+    private static final String ANNOTATED_ACTION = "annotatedAction";
+    private static final String SHORTCIRCUITED_ACTION = "shortCircuitedAction";
+    private final AnnotationWorkflowInterceptor annotationInterceptor = new AnnotationWorkflowInterceptor();
+
+    public void setUp() {
+        configurationManager = new ConfigurationManager();
+        configurationManager.addConfigurationProvider(new MockConfigurationProvider());
+        configurationManager.getConfiguration().reload(configurationManager.getConfigurationProviders());
+        container = configurationManager.getConfiguration().getContainer();
+        ObjectFactory.setObjectFactory(container.getInstance(ObjectFactory.class));
+
+        ValueStack stack = ValueStackFactory.getFactory().createValueStack();
+        ActionContext.setContext(new ActionContext(stack.getContext()));
+        
+    }
+
+    public void testInterceptsBeforeAndAfter() throws Exception {
+        ActionProxy proxy = container.getInstance(ActionProxyFactory.class).createActionProxy(configurationManager.getConfiguration(), "", ANNOTATED_ACTION, null);
+        assertEquals(Action.SUCCESS, proxy.execute());
+        AnnotatedAction action = (AnnotatedAction)proxy.getInvocation().getAction();
+        assertEquals("baseBefore-before-execute-beforeResult-after", action.log);
+    }
+
+    public void testInterceptsShortcircuitedAction() throws Exception {
+        ActionProxy proxy = container.getInstance(ActionProxyFactory.class).createActionProxy(configurationManager.getConfiguration(), "", SHORTCIRCUITED_ACTION, null);
+        assertEquals("shortcircuit", proxy.execute());
+        ShortcircuitedAction action = (ShortcircuitedAction)proxy.getInvocation().getAction();
+        assertEquals("baseBefore-before", action.log);
+    }
+
+    private class MockConfigurationProvider implements ConfigurationProvider {
+        private Configuration config;
+
+        public void init(Configuration configuration) throws ConfigurationException {
+            this.config = configuration;
+        }
+
+        public boolean needsReload() {
+            return false;
+        }
+
+        public void destroy() { }
+
+
+        public void register(ContainerBuilder builder, Properties props) throws ConfigurationException {
+            if (!builder.contains(ObjectFactory.class)) {
+                builder.factory(ObjectFactory.class);
+            }
+            if (!builder.contains(ActionProxyFactory.class)) {
+                builder.factory(ActionProxyFactory.class, DefaultActionProxyFactory.class);
+            }
+        }
+
+        public void loadPackages() throws ConfigurationException {
+            PackageConfig packageConfig = new PackageConfig();
+            config.addPackageConfig("default", packageConfig);
+
+            ActionConfig actionConfig = new ActionConfig(null, AnnotatedAction.class, null, null,
+                    Arrays.asList(new InterceptorMapping[]{ new InterceptorMapping("annotationInterceptor", annotationInterceptor) }));
+            packageConfig.addActionConfig(ANNOTATED_ACTION, actionConfig);
+            actionConfig.addResultConfig(new ResultConfig("success", MockResult.class.getName()));
+            actionConfig = new ActionConfig(null, ShortcircuitedAction.class, null, null,
+                    Arrays.asList(new InterceptorMapping[]{ new InterceptorMapping("annotationInterceptor", annotationInterceptor) }));
+            packageConfig.addActionConfig(SHORTCIRCUITED_ACTION, actionConfig);
+            actionConfig.addResultConfig(new ResultConfig("shortcircuit", MockResult.class.getName()));
+            config.addPackageConfig("defaultPackage", packageConfig);
+        }
+    }
+}

File src/test/com/opensymphony/xwork2/interceptor/annotations/BaseAnnotatedAction.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.interceptor.annotations;
+
+/**
+ * @author Zsolt Szasz, zsolt at lorecraft dot com
+ * @author Rainer Hermanns
+ */
+public class BaseAnnotatedAction {
+
+	protected String log = "";
+	
+	@Before
+	public String baseBefore() {
+		log = log + "baseBefore-";
+		return null;
+	}
+
+}

File src/test/com/opensymphony/xwork2/interceptor/annotations/ShortcircuitedAction.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.interceptor.annotations;
+
+import com.opensymphony.xwork2.Action;
+
+/**
+ * @author Zsolt Szasz, zsolt at lorecraft dot com
+ * @author Rainer Hermanns
+ */
+public class ShortcircuitedAction extends BaseAnnotatedAction {	
+	@Before
+	public String before() {
+		log = log + "before";
+		return "shortcircuit";
+	}
+	
+	public String execute() {
+		log = log + "-execute-";
+		return Action.SUCCESS;
+	}
+}

File src/test/com/opensymphony/xwork2/test/AnnotationDataAware.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.test;
+
+import com.opensymphony.xwork2.conversion.annotations.Conversion;
+import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
+import com.opensymphony.xwork2.util.Bar;
+import com.opensymphony.xwork2.validator.annotations.RequiredFieldValidator;
+import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
+import com.opensymphony.xwork2.validator.annotations.Validation;
+
+
+/**
+ * Implemented by SimpleAction3 and AnnotationTestBean2 to test class hierarchy traversal.
+ *
+ * @author Mark Woon
+ * @author Rainer Hermanns
+ */
+@Validation()
+@Conversion()
+public interface AnnotationDataAware {
+
+    void setBarObj(Bar b);
+
+    @TypeConversion(
+            converter = "com.opensymphony.xwork2.util.FooBarConverter"
+    )
+    Bar getBarObj();
+
+    @RequiredFieldValidator(message = "You must enter a value for data.")
+    @RequiredStringValidator(message = "You must enter a value for data.")
+    void setData(String data);
+
+    String getData();
+}

File src/test/com/opensymphony/xwork2/test/AnnotationDataAware2.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.test;
+
+import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
+import com.opensymphony.xwork2.test.AnnotationDataAware;
+
+
+/**
+ * Used to test hierarchy traversal for interfaces.
+ *
+ * @author Mark Woon
+ * @author Rainer Hermanns
+ */
+public interface AnnotationDataAware2 extends AnnotationDataAware {
+
+    @RequiredStringValidator(message = "You must enter a value for data.")
+    public void setBling(String bling);
+
+    public String getBling();
+}

File src/test/com/opensymphony/xwork2/test/AnnotationTestBean2.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.test;
+
+import com.opensymphony.xwork2.AnnotatedTestBean;
+import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
+import com.opensymphony.xwork2.conversion.annotations.Conversion;
+import com.opensymphony.xwork2.util.Bar;
+import com.opensymphony.xwork2.util.Cat;
+
+
+/**
+ * Extend TestBean to test class hierarchy traversal.
+ *
+ * @author Mark Woon
+ * @author Rainer Hermanns
+ */
+@Conversion()
+public class AnnotationTestBean2 extends AnnotatedTestBean implements AnnotationDataAware {
+
+    private Bar bar;
+    private String data;
+    private Cat cat;
+
+
+    public void setBarObj(Bar b) {
+        bar = b;
+    }
+
+    public Bar getBarObj() {
+        return bar;
+    }
+
+    public void setData(String data) {
+        this.data = data;
+    }
+
+    public String getData() {
+        return data;
+    }
+
+    public Cat getCat() {
+        return cat;
+    }
+
+    @TypeConversion(
+            key = "cat", converter = "com.opensymphony.xwork2.util.FooBarConverter"
+    )
+    public void setCat(Cat cat) {
+        this.cat = cat;
+    }
+}

File src/test/com/opensymphony/xwork2/test/AnnotationUser.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.test;
+
+import com.opensymphony.xwork2.validator.annotations.*;
+import com.opensymphony.xwork2.util.KeyProperty;
+import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
+import com.opensymphony.xwork2.conversion.annotations.ConversionRule;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Test bean.
+ *
+ * @author Mark Woon
+ * @author Rainer Hermanns
+ */
+@Validation(
+        validations = @Validations(
+                expressions = {
+                    @ExpressionValidator(expression = "email.startsWith('mark')", message = "Email does not start with mark"),
+                    @ExpressionValidator(expression = "email2.startsWith('mark')", message = "Email2 does not start with mark")
+                }
+        )
+)
+public class AnnotationUser implements AnnotationUserMarker {
+
+    private Collection collection;
+    private List list;
+    private Map map;
+    private String email;
+    private String email2;
+    private String name;
+
+
+    public void setCollection(Collection collection) {
+        this.collection = collection;
+    }
+
+    public Collection getCollection() {
+        return collection;
+    }
+
+    @EmailValidator(shortCircuit = true, message = "Not a valid e-mail.")
+    @FieldExpressionValidator(expression = "email.endsWith('mycompany.com')", message = "Email not from the right company.")
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    @EmailValidator(message = "Not a valid e-mail2.")
+    @FieldExpressionValidator(expression = "email2.endsWith('mycompany.com')", message = "Email2 not from the right company.")
+    public void setEmail2(String email) {
+        email2 = email;
+    }
+
+    public String getEmail2() {
+        return email2;
+    }
+
+    public void setList(List l) {
+        list = l;
+    }
+
+    @KeyProperty( value = "name")
+    @TypeConversion( converter = "java.lang.String", rule = ConversionRule.COLLECTION)
+    public List getList() {
+        return list;
+    }
+
+    @TypeConversion( converter = "java.lang.String", rule = ConversionRule.MAP)
+    public void setMap(Map m) {
+        map = m;
+    }
+
+    public Map getMap() {
+        return map;
+    }
+
+    @RequiredFieldValidator(key = "name.key", message = "You must enter a value for name.")
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

File src/test/com/opensymphony/xwork2/test/AnnotationUserMarker.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.test;
+
+import com.opensymphony.xwork2.validator.annotations.*;
+
+/**
+ * Marker interface to help test hierarchy traversal.
+ *
+ * @author Mark Woon
+ * @author Rainer Hermanns
+ */
+@Validation(
+        validations = @Validations(
+                requiredFields = {
+                    @RequiredFieldValidator(fieldName = "email", shortCircuit = true, message = "You must enter a value for email."),
+                    @RequiredFieldValidator(fieldName = "email2", shortCircuit = true, message = "You must enter a value for email2.")
+                },
+                expressions = {
+                        @ExpressionValidator(shortCircuit = true, expression = "email.equals(email2)", message = "Email not the same as email2" )
+                }
+        )
+)
+public interface AnnotationUserMarker {
+}

File src/test/com/opensymphony/xwork2/test/ModelDrivenAnnotationAction2.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.test;
+
+import com.opensymphony.xwork2.ModelDrivenAnnotationAction;
+
+
+/**
+ * Extend ModelDrivenAction to test class hierarchy traversal.
+ *
+ * @author Mark Woon
+ * @author Rainer Hermanns
+ */
+public class ModelDrivenAnnotationAction2 extends ModelDrivenAnnotationAction {
+
+    private AnnotationTestBean2 model = new AnnotationTestBean2();
+
+
+    /**
+     * @return the model to be pushed onto the ValueStack after the Action itself
+     */
+    public Object getModel() {
+        return model;
+    }
+}

File src/test/com/opensymphony/xwork2/test/SimpleAnnotationAction2.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.test;
+
+import com.opensymphony.xwork2.validator.annotations.RequiredFieldValidator;
+import com.opensymphony.xwork2.validator.annotations.IntRangeFieldValidator;
+import com.opensymphony.xwork2.SimpleAnnotationAction;
+
+/**
+ * SimpleAction2
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ *         Created Jun 14, 2003 9:51:12 PM
+ */
+public class SimpleAnnotationAction2 extends SimpleAnnotationAction {
+
+    private int count;
+
+    @RequiredFieldValidator(message = "You must enter a value for count.")
+    @IntRangeFieldValidator(min = "0", max = "5", message = "count must be between ${min} and ${max}, current value is ${count}.")
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    public int getCount() {
+        return count;
+    }
+}

File src/test/com/opensymphony/xwork2/test/SimpleAnnotationAction3.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.test;
+
+import com.opensymphony.xwork2.util.Bar;
+import com.opensymphony.xwork2.test.AnnotationDataAware;
+import com.opensymphony.xwork2.SimpleAnnotationAction;
+
+
+/**
+ * Extend SimpleAction to test class hierarchy traversal.
+ *
+ * @author Mark Woon
+ * @author Rainer Hermanns
+ */
+public class SimpleAnnotationAction3 extends SimpleAnnotationAction implements AnnotationDataAware {
+
+    private Bar bar;
+    private String data;
+
+
+    public void setBarObj(Bar b) {
+        bar = b;
+    }
+
+    public Bar getBarObj() {
+        return bar;
+    }
+
+    public void setData(String data) {
+        this.data = data;
+    }
+
+    public String getData() {
+        return data;
+    }
+}

File src/test/com/opensymphony/xwork2/util/AnnotatedCat.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
+import com.opensymphony.xwork2.conversion.annotations.Conversion;
+
+import java.util.List;
+
+
+/**
+ * @author <a href="mailto:plightbo@cisco.com">Pat Lightbody</a>
+ * @author $Author$
+ * @author Rainer Hermanns
+ * @version $Revision$
+ */
+@Conversion()
+public class AnnotatedCat {
+
+    public static final String SCIENTIFIC_NAME = "Feline";
+
+
+    Foo foo;
+    List kittens;
+    String name;
+
+
+    public void setFoo(Foo foo) {
+        this.foo = foo;
+    }
+
+    public Foo getFoo() {
+        return foo;
+    }
+
+    public void setKittens(List kittens) {
+        this.kittens = kittens;
+    }
+
+    @TypeConversion(
+            key = "kittens", converter = "com.opensymphony.xwork2.util.Cat"
+    )
+    public List getKittens() {
+        return kittens;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

File src/test/com/opensymphony/xwork2/util/AnnotationXWorkConverterTest.java

+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.test.ModelDrivenAnnotationAction2;
+import com.opensymphony.xwork2.test.AnnotationUser;
+import com.opensymphony.xwork2.config.ConfigurationManager;
+import ognl.OgnlException;
+import ognl.OgnlRuntime;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+
+/**
+ * @author $Author$
+ * @author Rainer Hermanns
+ * @version $Revision$
+ */
+public class AnnotationXWorkConverterTest extends XWorkTestCase {
+
+    ActionContext ac;
+    Map context;
+    XWorkConverter converter;
+
+//    public void testConversionToSetKeepsOriginalSetAndReplacesContents() {
+//        ValueStack stack = ValueStackFactory.getFactory().createValueStack();
+//
+//        Map stackContext = stack.getContext();
+//        stackContext.put(InstantiatingNullHandler.CREATE_NULL_OBJECTS, Boolean.TRUE);
+//        stackContext.put(XWorkMethodAccessor.DENY_METHOD_EXECUTION, Boolean.TRUE);
+//        stackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE);
+//
+//        String[] param = new String[] {"abc", "def", "ghi"};
+//        List paramList = Arrays.asList(param);
+//
+//        List originalList = new ArrayList();
+//        originalList.add("jkl");
+//        originalList.add("mno");
+//
+//        AnnotationUser user = new AnnotationUser();
+//        user.setList(originalList);
+//        stack.push(user);
+//
+//        stack.setValue("list", param);
+//
+//        List userList = user.getList();
+//        assertEquals(3,userList.size());
+//        assertEquals(paramList,userList);
+//        assertSame(originalList,userList);
+//    }
+
+    public void testArrayToNumberConversion() {
+        String[] value = new String[]{"12345"};
+        assertEquals(new Integer(12345), converter.convertValue(context, null, null, null, value, Integer.class));
+        assertEquals(new Long(12345), converter.convertValue(context, null, null, null, value, Long.class));
+        value[0] = "123.45";
+        assertEquals(new Float(123.45), converter.convertValue(context, null, null, null, value, Float.class));
+        assertEquals(new Double(123.45), converter.convertValue(context, null, null, null, value, Double.class));
+        value[0] = "1234567890123456789012345678901234567890";
+        assertEquals(new BigInteger(value[0]), converter.convertValue(context, null, null, null, value, BigInteger.class));
+        value[0] = "1234567890123456789.012345678901234567890";
+        assertEquals(new BigDecimal(value[0]), converter.convertValue(context, null, null, null, value, BigDecimal.class));
+    }
+
+    public void testDateConversion() throws ParseException {
+        java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
+        assertEquals(sqlDate, converter.convertValue(context, null, null, null, sqlDate, Date.class));
+
+        SimpleDateFormat format = new SimpleDateFormat("mm/dd/yyyy hh:mm:ss");
+        Date date = format.parse("01/10/2001 00:00:00");
+        String dateStr = (String) converter.convertValue(context, null, null, null, date, String.class);
+        Date date2 = (Date) converter.convertValue(context, null, null, null, dateStr, Date.class);
+        assertEquals(date, date2);
+    }
+
+    public void testFieldErrorMessageAddedForComplexProperty() {
+        SimpleAnnotationAction action = new SimpleAnnotationAction();
+        action.setBean(new AnnotatedTestBean());
+
+        ValueStack stack = ValueStackFactory.getFactory().createValueStack();
+        stack.push(action);
+
+        Map ognlStackContext = stack.getContext();
+        ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE);
+        ognlStackContext.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME, "bean.birth");
+
+        String[] value = new String[]{"invalid date"};
+        assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action.getBean(), null, "birth", value, Date.class));
+        stack.pop();
+
+        Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS);
+        assertNotNull(conversionErrors);
+        assertTrue(conversionErrors.size() == 1);
+        assertEquals(value, conversionErrors.get("bean.birth"));
+    }
+
+    public void testFieldErrorMessageAddedWhenConversionFails() {
+        SimpleAnnotationAction action = new SimpleAnnotationAction();
+        action.setDate(null);
+
+        ValueStack stack = ValueStackFactory.getFactory().createValueStack();
+        stack.push(action);
+
+        Map ognlStackContext = stack.getContext();
+        ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE);
+
+        String[] value = new String[]{"invalid date"};
+        assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action, null, "date", value, Date.class));
+        stack.pop();
+
+        Map conversionErrors = (Map) ognlStackContext.get(ActionContext.CONVERSION_ERRORS);
+        assertNotNull(conversionErrors);
+        assertEquals(1, conversionErrors.size());
+        assertNotNull(conversionErrors.get("date"));
+        assertEquals(value, conversionErrors.get("date"));
+    }
+
+    public void testFieldErrorMessageAddedWhenConversionFailsOnModelDriven() {
+        ModelDrivenAnnotationAction action = new ModelDrivenAnnotationAction();
+        ValueStack stack = ValueStackFactory.getFactory().createValueStack();
+        stack.push(action);
+        stack.push(action.getModel());
+
+        Map ognlStackContext = stack.getContext();
+        ognlStackContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE);
+
+        String[] value = new String[]{"invalid date"};
+        assertEquals("Conversion should have failed.", OgnlRuntime.NoConversionPossible, converter.convertValue(ognlStackContext, action, null, "birth", value, Date.class));
+        stack.pop();
+        stack.pop();
+
+        Map conversionErrors = (Map) ognlStackContext.get(ActionContext.CONVERSION_ERRORS);