You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by bu...@apache.org on 2003/01/24 15:12:15 UTC

DO NOT REPLY [Bug 16394] New: - Enhance the IndexedListProperty to handle nested lists.

DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG 
RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT
<http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16394>.
ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND 
INSERTED IN THE BUG DATABASE.

http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16394

Enhance the IndexedListProperty to handle nested lists.

           Summary: Enhance the IndexedListProperty to handle nested lists.
           Product: Commons
           Version: 1.0 Final
          Platform: All
        OS/Version: All
            Status: NEW
          Severity: Enhancement
          Priority: Other
         Component: Validator
        AssignedTo: commons-dev@jakarta.apache.org
        ReportedBy: peteroldershaw@comcast.net


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;

    }

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>