commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Niall Pemberton (JIRA)" <j...@apache.org>
Subject [jira] Updated: (VALIDATOR-186) Enhance the IndexedListProperty to handle nested lists.
Date Thu, 23 Nov 2006 09:07:09 GMT
     [ http://issues.apache.org/jira/browse/VALIDATOR-186?page=all ]

Niall Pemberton updated VALIDATOR-186:
--------------------------------------

    Fix Version/s: Validator2
      Description: 
Allowing lists to be validated helps a great deal but the code that is supplied 
with the commons-validator 1.0 could be simplified and enhanced.  There are two
features that it would be nice to have:

1) When the IndexedListProperty is used it would be nice if all the fields in 
the list were validated, i.e. the validation did not stop at the first error.  
This would need a different loop mechanism and the key to be set to the fully 
qualified path, e.g list[0].value and not list[].value.

2) It would be very nice if the IndexedListProperty could be nested, e.g. the 
size for all the doors on all the rooms must be greater than n.  I am not 
sure of the syntax in the configuration file that makes sense but offer 
list1.list2.list3. as a suggestion.  The PropertyUtils could then be called 
on the bean using the "list1" property to get the first list and then called 
on each of the returned objects using the "list2" property, etc.  So, if you 
had two rooms, the first with one door, the second with two and the 
validation.xml Field looked like:

<field 
	property="size"
	indexedListProperty="rooms.doors"
	depends="min">
	<arg0 key="error.door.size"/>
	<var>
		<var-name>min</var-name>
		<var-value>${n}</var-value>
	</var>		   
</field>             

The validator waould in effect be called for:

rooms[0].doors[0].size
rooms[1].doors[0].size
rooms[1].doors[1].size

The code below provides an example of how this could be done.  It uses the 
IndexedProperty to pass information down a reentrant stack because I did not 
want to change the parameters to the Validator.validate method but that would 
obviously be safer.  Also, by putting this code snipit in place, all the other 
isIndexed code could be removed so you don't have to loop twice (as it does in 
version 1.0).


Thanks...Peter

//////////////////////////////////////////////////////////////////////////////
// These snippits are out of the Validator class.  There is a one line
// change to validate, one new method and one tidied up method.
// The code has been tested and works but you may not like the way it 
// changes the BEAN_KEY and uses the IndexProperty.
//////////////////////////////////////////////////////////////////////////////

    /**
     * Performs validations based on the configured resources.
     * Needed as we cannot access (override) the private methods.
     *
     * @return	The <code>Map</code> returned uses the property
     *		of the <code>Field</code> for the key and the value
     *		is the number of error the field had.
     */
    public ValidatorResults validate() throws ValidatorException
    {
        ValidatorResults results = new ValidatorResults();
        Locale locale = null;

        if (hResources.containsKey(LOCALE_KEY))
        {
            locale = (Locale)hResources.get(LOCALE_KEY);
        }
        hResources.put(VALIDATOR_KEY, this);

        if (locale == null)
        {
            locale = Locale.getDefault();
        }

        Form form = null;
        if (resources == null)
        {
            throw new ValidatorException("Resources not defined for Validator");
        }
        if ((form = resources.get(locale, formName)) != null)
        {
            for (Iterator i = form.getFields().iterator(); i.hasNext(); )
            {
                Field field = (Field)i.next();
                if (field.getPage() <= page)
                {
		    // *************************************************
                    // This is the only line that changed in this method.
		    // *************************************************
                    validateFieldNested(field, results);
		    // *************************************************
                }
            }
        }
        return results;
    }

    /**
     * Validate field nested.  This method handles nested list validation
     * of the form IndexedListProperty = list1.list2.list3.  It gets all the
     * instances in list1 off the BEAN_KEY (root bean) using the PropertyUtils
     * then gets all the list2 entries of all the list1 objects, then all the
     * list3 objects of the list2 objects, etc.  The result is that the
     * validateField method is called on all the list3 objects with the property
     * as it was but the BEAN_KEY set to the current list3 object and the
     * Field key set to the fully qualified key (e.g. list1[0].list2[0].list3
[0]).
     *
     * @param field         See Validator.validateField
     * @param allResults    See Validator.validateField
     */
    private void validateFieldNested (Field field, ValidatorResults allResults)
        throws ValidatorException
    {
        // Does it have an IndexedList property?
        String indexedList = field.getIndexedListProperty();
        if (null != indexedList && 0 < indexedList.length())
        {
            // Is it nested?
            String nestName = indexedList;
            String restName = null;
            int nestOffset = indexedList.indexOf(".");
            if (-1 != nestOffset)
            {
                nestName = indexedList.substring(0, nestOffset);
                restName = indexedList.substring(nestOffset + 1);
            }

            // Build the field object based on the nesting.
            Field indexedField = (Field)field.clone();
            indexedField.setIndexedListProperty(restName);

            // The keyBase is a local copy of the IndexedProperty so that
            // the appropriate instance ([n]) can be added inside the
            // loop.  The IndexedProperty is not currently used by the
            // validator framework and so we use it to pass the revised
            // nesting level through the reenterant code.
            String keyBase = field.getIndexedProperty();
            if (null == keyBase)
            {
                keyBase = nestName;
            }
            else
            {
                keyBase += "." + nestName;
            }

            // Save the current BEAN_KEY, the object that the validator
            // is working on, so we can reset it as we pop out of the
            // reenterant loop.
            Object oldBean = hResources.get(BEAN_KEY);

            // Call for each of the objects at this level
            Object[] list = getIndexedList(oldBean, nestName);
            for (int i = 0; i < list.length; i++)
            {
                // Set the BEAN_KEY to the current object
                hResources.put(BEAN_KEY, list[i]);

                // Use the indexedProperty to pass the current level into
                // the next level so that it does not have to do a load of
                // parsing and replacement when it builds the key.  The key
                // is used to make the field error (if any) unique.
                indexedField.setIndexedProperty(keyBase + "[" + i + "]");
                indexedField.setKey(indexedField.getIndexedProperty() + "."
                + indexedField.getProperty());

                // Call ourselves to handle the next (if any) level of
                // nesting.
                validateFieldNested(indexedField, allResults);
            }
            // Put the old BEAN_KEY back as we pop the reentrancy stack.
            hResources.put(BEAN_KEY, oldBean);
            return;
        }
        // If it is not nested (actually IndexedList) use the normal method.
        validateField(field, allResults);
    }

    /**
     * Get an array of instances for the supplied property, whether it is a
     * collection or an array.  This is just a tidy up of an existing method
     * in the Validator class.
     *
     * @param bean          The bean that contains the list or array.
     * @param property      The name of the property that will return a list
     *                      or array on the supplied bean.
     * @return Object[]     An array of objects that were retrieved from the
     *                      supplied property.
     */
    private Object[] getIndexedList(Object bean, String property)
    {
        Object oIndexed;
        try
        {
            oIndexed = PropertyUtils.getProperty(bean, property);
        }
        catch (Exception e)
        {
            log.error("in validateFieldNested", e);
            return null;
        }

        Object indexedList[] = new Object[0];

        if (oIndexed instanceof Collection)
        {
            indexedList = ((Collection)oIndexed).toArray();
        }
        else if (oIndexed.getClass().isArray())
        {
            indexedList = (Object[]) oIndexed;
        }
        return indexedList;

    }

  was:
Allowing lists to be validated helps a great deal but the code that is supplied 
with the commons-validator 1.0 could be simplified and enhanced.  There are two
features that it would be nice to have:

1) When the IndexedListProperty is used it would be nice if all the fields in 
the list were validated, i.e. the validation did not stop at the first error.  
This would need a different loop mechanism and the key to be set to the fully 
qualified path, e.g list[0].value and not list[].value.

2) It would be very nice if the IndexedListProperty could be nested, e.g. the 
size for all the doors on all the rooms must be greater than n.  I am not 
sure of the syntax in the configuration file that makes sense but offer 
list1.list2.list3. as a suggestion.  The PropertyUtils could then be called 
on the bean using the "list1" property to get the first list and then called 
on each of the returned objects using the "list2" property, etc.  So, if you 
had two rooms, the first with one door, the second with two and the 
validation.xml Field looked like:

<field 
	property="size"
	indexedListProperty="rooms.doors"
	depends="min">
	<arg0 key="error.door.size"/>
	<var>
		<var-name>min</var-name>
		<var-value>${n}</var-value>
	</var>		   
</field>             

The validator waould in effect be called for:

rooms[0].doors[0].size
rooms[1].doors[0].size
rooms[1].doors[1].size

The code below provides an example of how this could be done.  It uses the 
IndexedProperty to pass information down a reentrant stack because I did not 
want to change the parameters to the Validator.validate method but that would 
obviously be safer.  Also, by putting this code snipit in place, all the other 
isIndexed code could be removed so you don't have to loop twice (as it does in 
version 1.0).


ThanksÂ…Peter

//////////////////////////////////////////////////////////////////////////////
// These snippits are out of the Validator class.  There is a one line
// change to validate, one new method and one tidied up method.
// The code has been tested and works but you may not like the way it 
// changes the BEAN_KEY and uses the IndexProperty.
//////////////////////////////////////////////////////////////////////////////

    /**
     * Performs validations based on the configured resources.
     * Needed as we cannot access (override) the private methods.
     *
     * @return	The <code>Map</code> returned uses the property
     *		of the <code>Field</code> for the key and the value
     *		is the number of error the field had.
     */
    public ValidatorResults validate() throws ValidatorException
    {
        ValidatorResults results = new ValidatorResults();
        Locale locale = null;

        if (hResources.containsKey(LOCALE_KEY))
        {
            locale = (Locale)hResources.get(LOCALE_KEY);
        }
        hResources.put(VALIDATOR_KEY, this);

        if (locale == null)
        {
            locale = Locale.getDefault();
        }

        Form form = null;
        if (resources == null)
        {
            throw new ValidatorException("Resources not defined for Validator");
        }
        if ((form = resources.get(locale, formName)) != null)
        {
            for (Iterator i = form.getFields().iterator(); i.hasNext(); )
            {
                Field field = (Field)i.next();
                if (field.getPage() <= page)
                {
		    // *************************************************
                    // This is the only line that changed in this method.
		    // *************************************************
                    validateFieldNested(field, results);
		    // *************************************************
                }
            }
        }
        return results;
    }

    /**
     * Validate field nested.  This method handles nested list validation
     * of the form IndexedListProperty = list1.list2.list3.  It gets all the
     * instances in list1 off the BEAN_KEY (root bean) using the PropertyUtils
     * then gets all the list2 entries of all the list1 objects, then all the
     * list3 objects of the list2 objects, etc.  The result is that the
     * validateField method is called on all the list3 objects with the property
     * as it was but the BEAN_KEY set to the current list3 object and the
     * Field key set to the fully qualified key (e.g. list1[0].list2[0].list3
[0]).
     *
     * @param field         See Validator.validateField
     * @param allResults    See Validator.validateField
     */
    private void validateFieldNested (Field field, ValidatorResults allResults)
        throws ValidatorException
    {
        // Does it have an IndexedList property?
        String indexedList = field.getIndexedListProperty();
        if (null != indexedList && 0 < indexedList.length())
        {
            // Is it nested?
            String nestName = indexedList;
            String restName = null;
            int nestOffset = indexedList.indexOf(".");
            if (-1 != nestOffset)
            {
                nestName = indexedList.substring(0, nestOffset);
                restName = indexedList.substring(nestOffset + 1);
            }

            // Build the field object based on the nesting.
            Field indexedField = (Field)field.clone();
            indexedField.setIndexedListProperty(restName);

            // The keyBase is a local copy of the IndexedProperty so that
            // the appropriate instance ([n]) can be added inside the
            // loop.  The IndexedProperty is not currently used by the
            // validator framework and so we use it to pass the revised
            // nesting level through the reenterant code.
            String keyBase = field.getIndexedProperty();
            if (null == keyBase)
            {
                keyBase = nestName;
            }
            else
            {
                keyBase += "." + nestName;
            }

            // Save the current BEAN_KEY, the object that the validator
            // is working on, so we can reset it as we pop out of the
            // reenterant loop.
            Object oldBean = hResources.get(BEAN_KEY);

            // Call for each of the objects at this level
            Object[] list = getIndexedList(oldBean, nestName);
            for (int i = 0; i < list.length; i++)
            {
                // Set the BEAN_KEY to the current object
                hResources.put(BEAN_KEY, list[i]);

                // Use the indexedProperty to pass the current level into
                // the next level so that it does not have to do a load of
                // parsing and replacement when it builds the key.  The key
                // is used to make the field error (if any) unique.
                indexedField.setIndexedProperty(keyBase + "[" + i + "]");
                indexedField.setKey(indexedField.getIndexedProperty() + "."
                + indexedField.getProperty());

                // Call ourselves to handle the next (if any) level of
                // nesting.
                validateFieldNested(indexedField, allResults);
            }
            // Put the old BEAN_KEY back as we pop the reentrancy stack.
            hResources.put(BEAN_KEY, oldBean);
            return;
        }
        // If it is not nested (actually IndexedList) use the normal method.
        validateField(field, allResults);
    }

    /**
     * Get an array of instances for the supplied property, whether it is a
     * collection or an array.  This is just a tidy up of an existing method
     * in the Validator class.
     *
     * @param bean          The bean that contains the list or array.
     * @param property      The name of the property that will return a list
     *                      or array on the supplied bean.
     * @return Object[]     An array of objects that were retrieved from the
     *                      supplied property.
     */
    private Object[] getIndexedList(Object bean, String property)
    {
        Object oIndexed;
        try
        {
            oIndexed = PropertyUtils.getProperty(bean, property);
        }
        catch (Exception e)
        {
            log.error("in validateFieldNested", e);
            return null;
        }

        Object indexedList[] = new Object[0];

        if (oIndexed instanceof Collection)
        {
            indexedList = ((Collection)oIndexed).toArray();
        }
        else if (oIndexed.getClass().isArray())
        {
            indexedList = (Object[]) oIndexed;
        }
        return indexedList;

    }

      Bugzilla Id:   (was: 16394)
          Summary: Enhance the IndexedListProperty to handle nested lists.  (was: [validator]
Enhance the IndexedListProperty to handle nested lists.)

> Enhance the IndexedListProperty to handle nested lists.
> -------------------------------------------------------
>
>                 Key: VALIDATOR-186
>                 URL: http://issues.apache.org/jira/browse/VALIDATOR-186
>             Project: Commons Validator
>          Issue Type: Improvement
>          Components: Framework
>    Affects Versions: Nightly Builds
>         Environment: Operating System: All
> Platform: All
>            Reporter: Peter Oldershaw
>            Priority: Minor
>             Fix For: Validator2
>
>
> Allowing lists to be validated helps a great deal but the code that is supplied 
> with the commons-validator 1.0 could be simplified and enhanced.  There are two
> features that it would be nice to have:
> 1) When the IndexedListProperty is used it would be nice if all the fields in 
> the list were validated, i.e. the validation did not stop at the first error.  
> This would need a different loop mechanism and the key to be set to the fully 
> qualified path, e.g list[0].value and not list[].value.
> 2) It would be very nice if the IndexedListProperty could be nested, e.g. the 
> size for all the doors on all the rooms must be greater than n.  I am not 
> sure of the syntax in the configuration file that makes sense but offer 
> list1.list2.list3. as a suggestion.  The PropertyUtils could then be called 
> on the bean using the "list1" property to get the first list and then called 
> on each of the returned objects using the "list2" property, etc.  So, if you 
> had two rooms, the first with one door, the second with two and the 
> validation.xml Field looked like:
> <field 
> 	property="size"
> 	indexedListProperty="rooms.doors"
> 	depends="min">
> 	<arg0 key="error.door.size"/>
> 	<var>
> 		<var-name>min</var-name>
> 		<var-value>${n}</var-value>
> 	</var>		   
> </field>             
> The validator waould in effect be called for:
> rooms[0].doors[0].size
> rooms[1].doors[0].size
> rooms[1].doors[1].size
> The code below provides an example of how this could be done.  It uses the 
> IndexedProperty to pass information down a reentrant stack because I did not 
> want to change the parameters to the Validator.validate method but that would 
> obviously be safer.  Also, by putting this code snipit in place, all the other 
> isIndexed code could be removed so you don't have to loop twice (as it does in 
> version 1.0).
> Thanks...Peter
> //////////////////////////////////////////////////////////////////////////////
> // These snippits are out of the Validator class.  There is a one line
> // change to validate, one new method and one tidied up method.
> // The code has been tested and works but you may not like the way it 
> // changes the BEAN_KEY and uses the IndexProperty.
> //////////////////////////////////////////////////////////////////////////////
>     /**
>      * Performs validations based on the configured resources.
>      * Needed as we cannot access (override) the private methods.
>      *
>      * @return	The <code>Map</code> returned uses the property
>      *		of the <code>Field</code> for the key and the value
>      *		is the number of error the field had.
>      */
>     public ValidatorResults validate() throws ValidatorException
>     {
>         ValidatorResults results = new ValidatorResults();
>         Locale locale = null;
>         if (hResources.containsKey(LOCALE_KEY))
>         {
>             locale = (Locale)hResources.get(LOCALE_KEY);
>         }
>         hResources.put(VALIDATOR_KEY, this);
>         if (locale == null)
>         {
>             locale = Locale.getDefault();
>         }
>         Form form = null;
>         if (resources == null)
>         {
>             throw new ValidatorException("Resources not defined for Validator");
>         }
>         if ((form = resources.get(locale, formName)) != null)
>         {
>             for (Iterator i = form.getFields().iterator(); i.hasNext(); )
>             {
>                 Field field = (Field)i.next();
>                 if (field.getPage() <= page)
>                 {
> 		    // *************************************************
>                     // This is the only line that changed in this method.
> 		    // *************************************************
>                     validateFieldNested(field, results);
> 		    // *************************************************
>                 }
>             }
>         }
>         return results;
>     }
>     /**
>      * Validate field nested.  This method handles nested list validation
>      * of the form IndexedListProperty = list1.list2.list3.  It gets all the
>      * instances in list1 off the BEAN_KEY (root bean) using the PropertyUtils
>      * then gets all the list2 entries of all the list1 objects, then all the
>      * list3 objects of the list2 objects, etc.  The result is that the
>      * validateField method is called on all the list3 objects with the property
>      * as it was but the BEAN_KEY set to the current list3 object and the
>      * Field key set to the fully qualified key (e.g. list1[0].list2[0].list3
> [0]).
>      *
>      * @param field         See Validator.validateField
>      * @param allResults    See Validator.validateField
>      */
>     private void validateFieldNested (Field field, ValidatorResults allResults)
>         throws ValidatorException
>     {
>         // Does it have an IndexedList property?
>         String indexedList = field.getIndexedListProperty();
>         if (null != indexedList && 0 < indexedList.length())
>         {
>             // Is it nested?
>             String nestName = indexedList;
>             String restName = null;
>             int nestOffset = indexedList.indexOf(".");
>             if (-1 != nestOffset)
>             {
>                 nestName = indexedList.substring(0, nestOffset);
>                 restName = indexedList.substring(nestOffset + 1);
>             }
>             // Build the field object based on the nesting.
>             Field indexedField = (Field)field.clone();
>             indexedField.setIndexedListProperty(restName);
>             // The keyBase is a local copy of the IndexedProperty so that
>             // the appropriate instance ([n]) can be added inside the
>             // loop.  The IndexedProperty is not currently used by the
>             // validator framework and so we use it to pass the revised
>             // nesting level through the reenterant code.
>             String keyBase = field.getIndexedProperty();
>             if (null == keyBase)
>             {
>                 keyBase = nestName;
>             }
>             else
>             {
>                 keyBase += "." + nestName;
>             }
>             // Save the current BEAN_KEY, the object that the validator
>             // is working on, so we can reset it as we pop out of the
>             // reenterant loop.
>             Object oldBean = hResources.get(BEAN_KEY);
>             // Call for each of the objects at this level
>             Object[] list = getIndexedList(oldBean, nestName);
>             for (int i = 0; i < list.length; i++)
>             {
>                 // Set the BEAN_KEY to the current object
>                 hResources.put(BEAN_KEY, list[i]);
>                 // Use the indexedProperty to pass the current level into
>                 // the next level so that it does not have to do a load of
>                 // parsing and replacement when it builds the key.  The key
>                 // is used to make the field error (if any) unique.
>                 indexedField.setIndexedProperty(keyBase + "[" + i + "]");
>                 indexedField.setKey(indexedField.getIndexedProperty() + "."
>                 + indexedField.getProperty());
>                 // Call ourselves to handle the next (if any) level of
>                 // nesting.
>                 validateFieldNested(indexedField, allResults);
>             }
>             // Put the old BEAN_KEY back as we pop the reentrancy stack.
>             hResources.put(BEAN_KEY, oldBean);
>             return;
>         }
>         // If it is not nested (actually IndexedList) use the normal method.
>         validateField(field, allResults);
>     }
>     /**
>      * Get an array of instances for the supplied property, whether it is a
>      * collection or an array.  This is just a tidy up of an existing method
>      * in the Validator class.
>      *
>      * @param bean          The bean that contains the list or array.
>      * @param property      The name of the property that will return a list
>      *                      or array on the supplied bean.
>      * @return Object[]     An array of objects that were retrieved from the
>      *                      supplied property.
>      */
>     private Object[] getIndexedList(Object bean, String property)
>     {
>         Object oIndexed;
>         try
>         {
>             oIndexed = PropertyUtils.getProperty(bean, property);
>         }
>         catch (Exception e)
>         {
>             log.error("in validateFieldNested", e);
>             return null;
>         }
>         Object indexedList[] = new Object[0];
>         if (oIndexed instanceof Collection)
>         {
>             indexedList = ((Collection)oIndexed).toArray();
>         }
>         else if (oIndexed.getClass().isArray())
>         {
>             indexedList = (Object[]) oIndexed;
>         }
>         return indexedList;
>     }

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://issues.apache.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

       

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message