1. opensymphony
  2. xwork

Commits

tm_jee  committed dddbd1a

XW-452
- declarative validation should be able to handle validation of list

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

  • Participants
  • Parent commits 58d5e14
  • Branches xwork_1-2

Comments (0)

Files changed (7)

File src/java/com/opensymphony/xwork/validator/ActionValidatorManager.java

View file
  • Ignore whitespace
      * @throws ValidationException if an error happens when validating the action.
      */
     void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException;
+    
+    
+    /**
+     * Validates an action through a series of <code>validators</code> with 
+     * the given <code>validatorContext</code>.
+     * 
+     * @param object
+     * @param validators
+     * @param validatorContext
+     * @throws ValidationException
+     */
+    void validate(Object object, List validators, ValidatorContext validatorContext) throws ValidationException;
 }

File src/java/com/opensymphony/xwork/validator/DefaultActionValidatorManager.java

View file
  • Ignore whitespace
  * @author Mark Woon
  * @author James House
  * @author Rainer Hermanns
+ * @author tmjee
  */
 public class DefaultActionValidatorManager implements ActionValidatorManager {
 
      */
     public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
         List validators = getValidators(object.getClass(), context);
-        Set shortcircuitedFields = null;
+        validate(object, validators, validatorContext);
+    }
+    
+    /**
+     * Validates an action through a series of <code>validators</code> with 
+     * the given <code>validatorContext</code>
+     * 
+     * @param object
+     * @param validators
+     * @param validatorContext
+     * @throws ValidationException
+     */
+    public void validate(Object object, List validators, ValidatorContext validatorContext) throws ValidationException {
+    	Set shortcircuitedFields = null;
 
         for (Iterator iterator = validators.iterator(); iterator.hasNext();) {
             final Validator validator = (Validator) iterator.next();

File src/java/com/opensymphony/xwork/validator/validators/CollectionFieldValidator.java

View file
  • Ignore whitespace
+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork.validator.validators;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import ognl.OgnlException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.opensymphony.xwork.XworkException;
+import com.opensymphony.xwork.util.OgnlUtil;
+import com.opensymphony.xwork.validator.ActionValidatorManagerFactory;
+import com.opensymphony.xwork.validator.FieldValidator;
+import com.opensymphony.xwork.validator.ShortCircuitableValidator;
+import com.opensymphony.xwork.validator.ValidationException;
+import com.opensymphony.xwork.validator.Validator;
+import com.opensymphony.xwork.validator.ValidatorConfig;
+import com.opensymphony.xwork.validator.ValidatorFactory;
+
+/**
+ * <!-- START SNIPPET: javadoc -->
+ * 
+ * Validate a property available in the object of a collection.
+ * 
+ * <!-- END SNIPPET: javadoc -->
+ * 
+ * 
+ * <!-- START SNIPPET: parameters -->
+ * 
+ * <ul>
+ * 	<li>property - the full property name that this validator should validate. 
+ * 							eg. if "persons" is a collection of Persons object with a property
+ *                             called name the property would be "persons.name" </li>
+ * 	<li>validatorRef - validator name of an existing validator (could not be "collection" validator)
+ * 							     eg. required, requiredstring, int etc.</li>
+ * 	<li>validatorParams - the parameters to be passed into the validator referenced
+ * 										by the "validatorParams" attribute above.</li>
+ * </ul>
+ * 
+ * <!-- END SNIPPET: parameters -->
+ * 
+ * 
+ * <!-- START SNIPPET: examples -->
+ * 
+ * public class MyAction extends ActionSupport {
+ * 	private List persons = new ArrayList();
+ *     ....
+ *     public List getPersons() { return this.persons; }
+ *     public void setPersons(List persons) { this.persons = persons; } 
+ * }
+ * 
+ * 
+ * public class Person {
+ *    private String name;
+ *    private Integer age;
+ *    
+ *    public String getName() { return name; }
+ *    public void setName(String name) { this.name = name; }
+ *    
+ *    public Integer getAge() { return age; }
+ *    public void setAge(Integer age) { this.age = age; }
+ * }
+ * 
+ * 
+ * <validators>
+ *    <field name="persons">
+ *        <field-validator type="collection">
+ *        		<param name="property">persons.name</param>
+ *        		<param name="validatorRef">requiredstring</param>
+ *             <param name="validatorParams['defaultMessage']">Must be String</param>
+ *             <param name="validatorParams['trim']">true</param>
+ *             <message></message>
+ *        </field-validator>
+ *        <field-validator type="collection">
+ *            <param name="property">persons.age</param>
+ *            <param name="validatorRef"></required</param>
+ *            <param name="validatorParams['defaultMessage']">Must be filled in</param>
+ *            <message></message>
+ *        </field-validator>
+ *        <field-validator type="collection">
+ *        		<param name="property">persons.age</param>
+ *             <param name="validatorRef">int</param>
+ *             <param name="validatorParams['defaultMessage']">Needs to be an integer</param>
+ *             <message></message>
+ *        </field-validator>
+ *    </field>
+ * </validators>
+ * 
+ * <!-- END SNIPPET: examples -->
+ * 
+ * @author tmjee
+ * @version $Date$ $Id$
+ */
+public class CollectionFieldValidator extends FieldValidatorSupport {
+	
+	private static final Log LOG = LogFactory.getLog(CollectionFieldValidator.class);
+
+	private String property;
+	public String getProperty() { return this.property; }
+	public void setProperty(String collection) { this.property = collection; }
+	
+	private String validatorRef;
+	public String getValidatorRef() { return this.validatorRef; }
+	public void setValidatorRef(String validatorRef) { this.validatorRef = validatorRef; }
+	
+	private Map validatorParams = new LinkedHashMap();
+	public void setValidatorParams(Map validatorParams) { this.validatorParams = validatorParams; }
+	public Map getValidatorParams() { return validatorParams; }
+	
+	
+	/**
+	 * Validate the <code>object</code>.
+	 * 
+	 * @see {@link Validator#validate(Object)}
+	 */
+	public void validate(Object object) throws ValidationException {
+		if (property == null || property.trim().length() <= 0) {
+			throw new XworkException("collection property cannot be null or empty, it is needed to specify a property that doesn't return back a Collection");
+		}
+		
+		// will throw IllegalArgumentException if validatorRef is bad (not registered etc)
+		ValidatorFactory.lookupRegisteredValidatorType(validatorRef);
+		Object obj = getFieldValue(getFieldName(), object);
+		if (obj != null) {
+			List result = new ArrayList();
+			PropertySpliter spliter = new PropertySpliter(property);
+			try {
+				String overallProperty = "";
+				populateValue(object, spliter.iterator(), result, overallProperty);
+			}
+			catch(Exception e) {
+				throw new XworkException(e.toString(), e);
+			}
+			
+			// validate 
+			Validator validator = ValidatorFactory.getValidator(new ValidatorConfig(validatorRef, validatorParams));
+			validator.setValidatorContext(getValidatorContext());
+			if (validatorParams.containsKey("defaultMessage")) {
+				validator.setDefaultMessage((String) validatorParams.get("defaultMessage"));
+			}
+			if (validatorParams.containsKey("messageKey")) {
+				validator.setDefaultMessage((String) validatorParams.get("messageKey"));
+			}
+			if (validatorParams.containsKey("shortCircuit") && (validator instanceof ShortCircuitableValidator))  {
+				((ShortCircuitableValidator)validator).setShortCircuit(validatorParams.get("shortCircuit").equals("true"));
+			}
+			
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("validatorRef ["+validatorRef+"] found to be referencing to validator ["+validator+"]");
+				LOG.debug("injecting parameters ["+validatorParams+"] into validator ["+validator+"]");
+			}
+				
+			for(Iterator i = result.iterator(); i.hasNext(); ) {
+				List validators = new ArrayList();
+				String overallPropertyName = (String) i.next();
+				if (validator instanceof FieldValidator)  {
+					((FieldValidator)validator).setFieldName(overallPropertyName);
+				}
+				validators.add(validator);
+				ActionValidatorManagerFactory.getInstance().validate(object, validators, getValidatorContext());
+			}
+		}
+		else {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("valued obtained from field name ["+getFieldName()+"] is null, skiping this validator");
+			}
+		}
+	}
+	
+	
+	/**
+	 * Populate <code>result</code> with a list of property name, eg.
+	 * if we have a list of Person object with a property called "name". If we have 
+	 * 2 persons in the list the result would be :-
+	 * 
+	 *  <ul>
+	 *  	<li>persons[0].name</li>
+	 *     <li>persons[1].name</li>
+	 *  </ul>
+	 * 
+	 * @param obj
+	 * @param iterator
+	 * @param result
+	 * @param overallPropertyName
+	 * @throws OgnlException
+	 * @throws CloneNotSupportedException
+	 */
+	protected void populateValue(Object obj, CloneableIterator iterator, List result, String overallPropertyName) throws OgnlException, CloneNotSupportedException {
+		if (iterator.hasNext()) {
+			String expression = (String) iterator.next();
+		
+			if (overallPropertyName.trim().length() <= 0) {
+				overallPropertyName = overallPropertyName + expression;
+			}
+			else {
+				overallPropertyName = overallPropertyName+"."+expression;
+			}
+		
+			Object val = OgnlUtil.getValue(expression, Collections.EMPTY_MAP, obj);
+			if (val instanceof Collection) {
+				if (! iterator.hasNext()) {
+					throw new XworkException("collection property ["+property+"] ends with a collection, it should end with an object not collection");
+				}
+			
+				int a=0;
+				for (Iterator i = ((Collection)val).iterator(); i.hasNext(); ) {
+					CloneableIterator ii = (CloneableIterator) iterator.clone();
+					// strip out [*] eg. [1], [2] etc.
+					if (overallPropertyName.endsWith("]")) {
+						int index = overallPropertyName.lastIndexOf("[");
+						overallPropertyName = overallPropertyName.substring(0, index);
+					}
+					overallPropertyName = overallPropertyName + "["+a+"]";
+					populateValue(i.next(), ii, result, overallPropertyName);
+					a = a + 1;
+				}
+			}
+			else {
+				if (! iterator.hasNext()) {
+					result.add(overallPropertyName);
+				}
+				else {
+					populateValue(val, iterator, result, overallPropertyName);
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Splits up the <code>properties</code> supplied, eg. if the <code>properties</code> 
+	 * is "persons.addresses.name", the {@link #iterator()} method would return a
+	 * {@link CloneableIterator} with the following entries
+	 * 
+	 * <ul>
+	 *     <li>persons</li>
+	 *     <li>addresses</li>
+	 *     <li>name</li>
+	 * </ul>
+	 * 
+	 * in order.
+	 * 
+	 * @author tmjee
+	 * @version $Date$ $Id$
+	 */
+	protected class PropertySpliter {
+		private String properties;
+		public PropertySpliter(String properties) {
+			this.properties = properties;
+		}
+		
+		/**
+		 * Return an iterator of the splited <code>properties</code>
+		 * @return
+		 */
+		CloneableIterator iterator() {
+			List propertyList = new ArrayList();
+			int tmpPrevIndex = 0;
+			int tmpIndex = properties.indexOf(".", 0);
+			while (tmpIndex > 0) {
+				String property = properties.substring(tmpPrevIndex, tmpIndex);
+				propertyList.add(property);
+				tmpPrevIndex = tmpIndex + 1;
+				tmpIndex = properties.indexOf(".", tmpIndex + 1);
+			}
+			propertyList.add(properties.substring(tmpPrevIndex));
+			return new CloneableIterator(propertyList); 
+		}
+	}
+	
+	/**
+	 * A cloneable iterator, when the iterator is cloned, its state is preserved, 
+	 * eg. when this iterator is iterated to its 2nd element and the iterator is 
+	 * cloned, the cloned iterator will start at its 3rd element as well when its
+	 * {{@link #next()} method is called.
+	 * 
+	 * @author tmjee
+	 * @version $Date$ $Id$
+	 */
+	protected class CloneableIterator implements Iterator, Cloneable {
+		private List delegate;
+		private ListIterator delegateIterator;
+		public int index = -1; // current element index in list
+		
+		/**
+		 * Create a {@link CloneableIterator} based on the <code>list</code>
+		 * supplied.
+		 * 
+		 * @param list
+		 */
+		public CloneableIterator(List list) {
+			this.delegate = list;
+			this.delegateIterator = list.listIterator();
+		}
+		
+		/**
+		 * An internal constructor used when cloning, its private so not to be 
+		 * seen by outer class.
+		 * 
+		 * @param list
+		 * @param index index of original iterator's current iterated element
+		 */
+		private CloneableIterator(List list, int index) {
+			this.delegate = list;
+			this.index = index;
+			this.delegateIterator = list.listIterator(index+1);
+		}
+		
+		/**
+		 * See if there's a next element pending.
+		 */
+		public boolean hasNext() {
+			return delegateIterator.hasNext();
+		}
+
+		/**
+		 * Go get the next element.
+		 */
+		public Object next() {
+			index = index + 1;
+			return delegateIterator.next();
+		}
+
+		/**
+		 * This operation is NOT SUPPORTED, will throw {@link UnsupportedOperationException}
+		 * if its invoked.
+		 */
+		public void remove() {
+			throw new UnsupportedOperationException("remove() is not supported");
+		}
+		
+		/**
+		 * Clone this iterator, when the iterator is cloned, its state is preserved, 
+		 * eg. when this iterator is iterated to its 2nd element and the iterator is 
+		 * cloned, the cloned iterator will start at its 3rd element as well when its
+		 * {{@link #next()} method is called.
+		 */
+		protected Object clone() throws CloneNotSupportedException {
+			return new CloneableIterator(delegate, index);
+		}
+	}
+}

File src/java/com/opensymphony/xwork/validator/validators/default.xml

View file
  • Ignore whitespace
     <validator name="conversion" class="com.opensymphony.xwork.validator.validators.ConversionErrorFieldValidator"/>
     <validator name="stringlength" class="com.opensymphony.xwork.validator.validators.StringLengthFieldValidator"/>
     <validator name="regex" class="com.opensymphony.xwork.validator.validators.RegexFieldValidator"/>
+    <validator name="collection" class="com.opensymphony.xwork.validator.validators.CollectionFieldValidator" />
 </validators>
 <!--  END SNIPPET: validators-default -->

File src/test/com/opensymphony/xwork/validator/DefaultActionValidatorManagerTest.java

View file
  • Ignore whitespace
             user.setEmail2("bad_email");
 
             ValidatorContext context = new GenericValidatorContext(user);
-            actionValidatorManager.validate(user, null, context);
+            actionValidatorManager.validate(user, (String) null, context);
             assertTrue(context.hasFieldErrors());
 
             // check field errors
             user.setEmail2("mark_bad_email_for_field_val@foo.com");
 
             ValidatorContext context = new GenericValidatorContext(user);
-            actionValidatorManager.validate(user, null, context);
+            actionValidatorManager.validate(user, (String) null, context);
             assertTrue(context.hasFieldErrors());
 
             // check field errors
         user.setEmail("tm_jee(at)yahoo.co.uk");
         
         ValidatorContext context = new GenericValidatorContext(user);
-        actionValidatorManager.validate(user, null, context);
+        actionValidatorManager.validate(user, (String) null, context);
     	
     	// check field level errors
         // shouldn't have any because action error prevents validation of anything else
             user.setEmail2("mark@mycompany.com");
 
             ValidatorContext context = new GenericValidatorContext(user);
-            actionValidatorManager.validate(user, null, context);
+            actionValidatorManager.validate(user, (String) null, context);
             assertFalse(context.hasErrors());
         } catch (ValidationException ex) {
             ex.printStackTrace();

File src/test/com/opensymphony/xwork/validator/StringValidatorTest.java

View file
  • Ignore whitespace
 		equidae.setHorse(null);
 		
 		DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport());
-		ActionValidatorManagerFactory.getInstance().validate(equidae, null, context);
+		ActionValidatorManagerFactory.getInstance().validate(equidae, (String) null, context);
 		
 		assertTrue(context.hasFieldErrors());
 	}
         ActionContext.getContext().getValueStack().push(equidae);
 
         DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport());
-        ActionValidatorManagerFactory.getInstance().validate(equidae, null, context);
+        ActionValidatorManagerFactory.getInstance().validate(equidae, (String) null, context);
 
         assertTrue(context.hasFieldErrors());
 
         equidae.setHorse("  ");
         ActionContext.getContext().getValueStack().push(equidae);
         context = new DelegatingValidatorContext(new ValidationAwareSupport());
-        ActionValidatorManagerFactory.getInstance().validate(equidae, null, context);
+        ActionValidatorManagerFactory.getInstance().validate(equidae, (String) null, context);
 
         assertTrue(context.hasFieldErrors());
         fieldErrors = context.getFieldErrors();
         ActionContext.getContext().getValueStack().push(equidae);
 
         DelegatingValidatorContext context = new DelegatingValidatorContext(new ValidationAwareSupport());
-        ActionValidatorManagerFactory.getInstance().validate(equidae, null, context);
+        ActionValidatorManagerFactory.getInstance().validate(equidae, (String) null, context);
         assertTrue(context.hasFieldErrors());
 
         Map fieldErrors = context.getFieldErrors();
         equidae.setDonkey("asdf  ");
         ActionContext.getContext().getValueStack().push(equidae);
         context = new DelegatingValidatorContext(new ValidationAwareSupport());
-        ActionValidatorManagerFactory.getInstance().validate(equidae, null, context);
+        ActionValidatorManagerFactory.getInstance().validate(equidae, (String) null, context);
         assertTrue(context.hasFieldErrors());
 
         fieldErrors = context.getFieldErrors();
         equidae.setDonkey("asdfasdf");
         ActionContext.getContext().getValueStack().push(equidae);
         context = new DelegatingValidatorContext(new ValidationAwareSupport());
-        ActionValidatorManagerFactory.getInstance().validate(equidae, null, context);
+        ActionValidatorManagerFactory.getInstance().validate(equidae, (String) null, context);
         assertTrue(context.hasFieldErrors());
 
         fieldErrors = context.getFieldErrors();
         equidae.setDonkey("asdfasdf   ");
         ActionContext.getContext().getValueStack().push(equidae);
         context = new DelegatingValidatorContext(new ValidationAwareSupport());
-        ActionValidatorManagerFactory.getInstance().validate(equidae, null, context);
+        ActionValidatorManagerFactory.getInstance().validate(equidae, (String) null, context);
         assertTrue(context.hasFieldErrors());
 
         fieldErrors = context.getFieldErrors();
         equidae.setDonkey("asdfasdfasdf");
         ActionContext.getContext().getValueStack().push(equidae);
         context = new DelegatingValidatorContext(new ValidationAwareSupport());
-        ActionValidatorManagerFactory.getInstance().validate(equidae, null, context);
+        ActionValidatorManagerFactory.getInstance().validate(equidae, (String) null, context);
         assertTrue(context.hasFieldErrors());
 
         fieldErrors = context.getFieldErrors();

File src/test/com/opensymphony/xwork/validator/validators/CollectionFieldValidatorTest.java

View file
  • Ignore whitespace
+/*
+ * Copyright (c) 2002-2006 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork.validator.validators;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import com.opensymphony.xwork.ActionSupport;
+import com.opensymphony.xwork.validator.DelegatingValidatorContext;
+import com.opensymphony.xwork.validator.validators.CollectionFieldValidator.PropertySpliter;
+
+import junit.framework.TestCase;
+
+/**
+ * @author tmjee
+ * @version $Date$ $Id$
+ */
+public class CollectionFieldValidatorTest extends TestCase {
+
+	public void testValidator1() throws Exception {
+		TestAction action = new TestAction();
+		
+		CollectionFieldValidator validator = new CollectionFieldValidator();
+		validator.setValidatorContext(new DelegatingValidatorContext(action));
+		validator.setFieldName("collection");
+		validator.setProperty("collection.element.element2s.element3s.element4s.name");
+		validator.setValidatorRef("requiredstring");
+		validator.setValidatorParams(new LinkedHashMap() {
+			private static final long serialVersionUID = 0L;
+			{put("trim", "true");
+			  put("defaultMessage", "error name required");}
+		});
+		validator.validate(action);
+		assertEquals(action.getFieldErrors().size(), 0);
+	}
+	
+	public void testValidator2() throws Exception {
+		TestAction action = new TestAction();
+		
+		CollectionFieldValidator validator = new CollectionFieldValidator();
+		validator.setValidatorContext(new DelegatingValidatorContext(action));
+		validator.setFieldName("collection");
+		validator.setProperty("collection.element.element2s.element3s.element4s.address");
+		validator.setValidatorRef("requiredstring");
+		validator.setValidatorParams(new LinkedHashMap() {
+			private static final long serialVersionUID = 0L;
+			{put("trim", "true");
+			  put("defaultMessage", "error address required");}
+		});
+		validator.validate(action);
+		assertEquals(action.getFieldErrors().size(), 18);
+		
+		
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[0].element4s[0].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[0].element4s[0].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[0].element4s[1].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[0].element4s[1].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[0].element4s[2].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[0].element4s[2].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[1].element4s[0].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[1].element4s[0].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[1].element4s[1].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[1].element4s[1].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[1].element4s[2].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[1].element4s[2].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[2].element4s[0].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[2].element4s[0].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[2].element4s[1].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[2].element4s[1].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[2].element4s[2].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[0].element3s[2].element4s[2].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[0].element4s[0].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[0].element4s[0].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[0].element4s[1].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[0].element4s[1].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[0].element4s[2].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[0].element4s[2].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[1].element4s[0].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[1].element4s[0].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[1].element4s[1].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[1].element4s[1].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[1].element4s[2].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[1].element4s[2].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[2].element4s[0].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[2].element4s[0].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[2].element4s[1].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[2].element4s[1].address")).size(), 1);
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[2].element4s[2].address")).get(0), "error address required");
+		assertEquals(((List)action.getFieldErrors().get("collection[0].element.element2s[1].element3s[2].element4s[2].address")).size(), 1);
+	}
+	
+	
+	public void testPropertySpliter() throws Exception {
+		CollectionFieldValidator validator = new CollectionFieldValidator();
+		PropertySpliter propertySpliter = validator.new PropertySpliter("aaa.bbb.ccc.ddd.eee");
+		CollectionFieldValidator.CloneableIterator i = propertySpliter.iterator();
+
+		String element = null;
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("aaa", element);
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("bbb", element);
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("ccc", element);
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("ddd", element);
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("eee", element);
+		
+		assertFalse(i.hasNext());
+	}
+	
+	public void testCloningPropertySpliter1() throws Exception {
+		CollectionFieldValidator validator = new CollectionFieldValidator();
+		PropertySpliter propertySpliter = validator.new PropertySpliter("aaa.bbb.ccc.ddd.eee");
+		CollectionFieldValidator.CloneableIterator i = propertySpliter.iterator();
+		i = (CollectionFieldValidator.CloneableIterator) i.clone();
+
+		String element = null;
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("aaa", element);
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("bbb", element);
+		
+		{
+			CollectionFieldValidator.CloneableIterator ii = (CollectionFieldValidator.CloneableIterator) i.clone();
+			
+			assertTrue(ii.hasNext());
+			element = (String) ii.next();
+			assertNotNull(element);
+			assertEquals("ccc", element);
+			
+			{
+				CollectionFieldValidator.CloneableIterator iii = (CollectionFieldValidator.CloneableIterator) ii.clone();
+				
+				assertTrue(iii.hasNext());
+				element = (String) iii.next();
+				assertNotNull(element);
+				assertEquals("ddd", element);
+				
+				assertTrue(iii.hasNext());
+				element = (String) iii.next();
+				assertNotNull(element);
+				assertEquals("eee", element);
+			}
+			
+			assertTrue(ii.hasNext());
+			element = (String) ii.next();
+			assertNotNull(element);
+			assertEquals("ddd", element);
+			
+			assertTrue(ii.hasNext());
+			element = (String) ii.next();
+			assertNotNull(element);
+			assertEquals("eee", element);
+		}
+		
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("ccc", element);
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("ddd", element);
+		
+		assertTrue(i.hasNext());
+		element = (String) i.next();
+		assertNotNull(element);
+		assertEquals("eee", element);
+		
+		assertFalse(i.hasNext());
+	}
+	
+	
+	public void testCloningPropertySpliter2() throws Exception {
+		CollectionFieldValidator validator = new CollectionFieldValidator();
+		CollectionFieldValidator.PropertySpliter spliter = validator.new PropertySpliter("element.element2s.element3s.element4s.name");
+		CollectionFieldValidator.CloneableIterator i = (CollectionFieldValidator.CloneableIterator) spliter.iterator();
+		
+		assertEquals("element", i.next());
+			CollectionFieldValidator.CloneableIterator ii1 = (CollectionFieldValidator.CloneableIterator) i.clone();
+			assertEquals("element2s", ii1.next());
+				CollectionFieldValidator.CloneableIterator iii1 = (CollectionFieldValidator.CloneableIterator) ii1.clone();
+				assertEquals("element3s", iii1.next());
+					CollectionFieldValidator.CloneableIterator iiii1 = (CollectionFieldValidator.CloneableIterator) iii1.clone();
+					assertEquals("element4s", iiii1.next());
+					assertEquals("name", iiii1.next());
+				assertEquals("element4s", iii1.next());
+				assertEquals("name", iii1.next());
+			assertEquals("element3s", ii1.next());
+			assertEquals("element4s", ii1.next());
+			assertEquals("name", ii1.next());
+		assertEquals("element2s", i.next());
+		assertEquals("element3s", i.next());
+			CollectionFieldValidator.CloneableIterator ii2 = (CollectionFieldValidator.CloneableIterator) i.clone();
+			assertEquals("element4s", ii2.next());
+			assertEquals("name", ii2.next());
+		assertEquals("element4s", i.next());
+		assertEquals("name", i.next());
+	}
+	
+	
+	public void testPopulateValue() throws Exception {
+		Element4.count = 0;
+		
+		List result = new ArrayList();
+		TestObject testObject = new TestObject();
+		CollectionFieldValidator validator = new CollectionFieldValidator();
+		CollectionFieldValidator.PropertySpliter spliter = validator.new PropertySpliter("element.element2s.element3s.element4s.name");
+		String e = "";
+		validator.populateValue(testObject, spliter.iterator(), result, e);
+		
+		/*
+		 *  TestObject
+		 *      + Element
+		 *             + Element2
+		 *                   + Element3
+		 *                   		+ Element4
+		 *                                + name (tmjee1)
+		 *                         + Element4
+		 *                                + name (phil1)
+		 *                         + Element4
+		 *                                + name (pat1)
+		 *                   + Element3
+		 *                         + Element4
+		 *                                + name (tmjee2)
+		 *                         + Element4
+		 *                                + name (phil2)
+		 *                         + Element4
+		 *                                + name (pat2)
+		 *                   + Element3
+		 *                         + Element4
+		 *                                + name (tmjee3)
+		 *                         + Element4
+		 *                                + name (phil3)
+		 *                         + Element4
+		 *                                + name (pat3)
+		 *             + Element2
+		 *                   + Element3
+		 *                         + Element4 
+		 *                               + name (tmjee4)
+		 *                         + Element4
+		 *                               + name (phil4)
+		 *                         + Element4
+		 *                               + name (pat4)
+		 *                   + Element3
+		 *                         + Element4
+		 *                               + name (tmjee5)
+		 *                         + Element4
+		 *                               + name (phil5)
+		 *                         + Element4
+		 *                               + name (pat5)
+		 *                   + Element3
+		 *                         + Element4
+		 *                               + name (tmjee6)
+		 *                         + Element4
+		 *                               + name (phil6)
+		 *                         + Element4
+		 *                               + name (pat6)
+		 */
+		assertEquals(result.size(), 18);
+		assertEquals("element.element2s[0].element3s[0].element4s[0].name", result.get(0).toString());
+		assertEquals("element.element2s[0].element3s[0].element4s[1].name", result.get(1).toString());
+		assertEquals("element.element2s[0].element3s[0].element4s[2].name", result.get(2).toString());
+		assertEquals("element.element2s[0].element3s[1].element4s[0].name", result.get(3).toString());
+		assertEquals("element.element2s[0].element3s[1].element4s[1].name", result.get(4).toString());
+		assertEquals("element.element2s[0].element3s[1].element4s[2].name", result.get(5).toString());
+		assertEquals("element.element2s[0].element3s[2].element4s[0].name", result.get(6).toString());
+		assertEquals("element.element2s[0].element3s[2].element4s[1].name", result.get(7).toString());
+		assertEquals("element.element2s[0].element3s[2].element4s[2].name", result.get(8).toString());
+		assertEquals("element.element2s[1].element3s[0].element4s[0].name", result.get(9).toString());
+		assertEquals("element.element2s[1].element3s[0].element4s[1].name", result.get(10).toString());
+		assertEquals("element.element2s[1].element3s[0].element4s[2].name", result.get(11).toString());
+		assertEquals("element.element2s[1].element3s[1].element4s[0].name", result.get(12).toString());
+		assertEquals("element.element2s[1].element3s[1].element4s[1].name", result.get(13).toString());
+		assertEquals("element.element2s[1].element3s[1].element4s[2].name", result.get(14).toString());
+		assertEquals("element.element2s[1].element3s[2].element4s[0].name", result.get(15).toString());
+		assertEquals("element.element2s[1].element3s[2].element4s[1].name", result.get(16).toString());
+		assertEquals("element.element2s[1].element3s[2].element4s[2].name", result.get(17).toString());
+	}
+	
+	
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	public static class TestAction extends ActionSupport {
+		private static final long serialVersionUID = 7815309144526028220L;
+		public List getCollection() {
+			List collection = new ArrayList();
+			collection.add(new TestObject());
+			return collection;
+		}
+	}
+	
+	
+	public static class TestObject {
+		private Element element;
+		
+		public TestObject() {
+			element = new Element();
+		}
+		
+		public Element getElement() { return this.element; }
+		public void setElement(Element element) { this.element = element; }
+	}
+	
+	public static class Element {
+		private List elements; 
+		
+		public Element() {
+			elements = new ArrayList();
+			elements.add(new Element2());
+			elements.add(new Element2());
+		}
+		
+		public List getElement2s() { return this.elements; }
+		public void setElement2s(List elements) { this.elements = elements; }
+	}
+	
+	
+	public static class Element2 {
+		private List elements;
+		
+		public Element2() {
+			elements = new ArrayList();
+			elements.add(new Element3());
+			elements.add(new Element3());
+			elements.add(new Element3());
+		}
+		
+		public List getElement3s() { return this.elements; }
+		public void setElement3s(List elements) { this.elements = elements; }
+	}
+	
+	public static class Element3 {
+		private List elements; 
+		
+		public Element3() {
+			elements = new ArrayList();
+			elements.add(new Element4("tmjee"));
+			elements.add(new Element4("phil"));
+			elements.add(new Element4("pat"));
+		}
+		
+		public List getElement4s() { return this.elements; }
+		public void setElement4s(List elements) { this.elements = elements; }
+	}
+	
+	public static class Element4 {
+		private static int count = 0;
+		private String name;
+		private String address;
+		
+		public Element4(String name) {
+			this.name = name+count;
+			count = count +1;
+		}
+		
+		public String getName() { return this.name; }
+		public void setName(String name) { this.name = name; }
+		
+		public String getAddress() { return this.address; }
+		public void setAddress(String address) { this.address = address; }
+	}
+}