001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.validator;
018    
019    import java.io.Serializable;
020    import java.lang.reflect.InvocationTargetException;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.Collections;
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.StringTokenizer;
029    
030    import org.apache.commons.beanutils.PropertyUtils;
031    import org.apache.commons.collections.FastHashMap; // DEPRECATED
032    import org.apache.commons.validator.util.ValidatorUtils;
033    
034    /**
035     * This contains the list of pluggable validators to run on a field and any 
036     * message information and variables to perform the validations and generate 
037     * error messages.  Instances of this class are configured with a 
038     * <field> xml element.
039     * <p>
040     * The use of FastHashMap is deprecated and will be replaced in a future
041     * release.
042     * </p>
043     *
044     * @version $Revision: 591548 $ $Date: 2007-11-03 04:43:41 +0100 (Sa, 03. Nov 2007) $
045     * @see org.apache.commons.validator.Form
046     */
047    public class Field implements Cloneable, Serializable {
048    
049        /**
050         * This is the value that will be used as a key if the <code>Arg</code>
051         * name field has no value.
052         */
053        private static final String DEFAULT_ARG =
054                "org.apache.commons.validator.Field.DEFAULT";
055    
056        /**
057         * This indicates an indexed property is being referenced.
058         */
059        public static final String TOKEN_INDEXED = "[]";
060    
061        /**
062         * The start of a token.
063         */
064        protected static final String TOKEN_START = "${";
065    
066        /**
067         * The end of a token.
068         */
069        protected static final String TOKEN_END = "}";
070    
071        /**
072         * A Vriable token.
073         */
074        protected static final String TOKEN_VAR = "var:";
075    
076        /**
077         * The Field's property name.
078         */
079        protected String property = null;
080    
081        /**
082         * The Field's indexed property name.
083         */
084        protected String indexedProperty = null;
085    
086        /**
087         * The Field's indexed list property name.
088         */
089        protected String indexedListProperty = null;
090    
091        /**
092         * The Field's unique key.
093         */
094        protected String key = null;
095    
096        /**
097         * A comma separated list of validator's this field depends on.
098         */
099        protected String depends = null;
100    
101        /**
102         * The Page Number
103         */
104        protected int page = 0;
105        
106        /**
107         * The flag that indicates whether scripting should be generated
108         * by the client for client-side validation.
109         * @since Validator 1.4
110         */
111        protected boolean clientValidation = true;
112        
113        /**
114         * The order of the Field in the Form.
115         */
116        protected int fieldOrder = 0;
117    
118        /**
119         * Internal representation of this.depends String as a List.  This List 
120         * gets updated whenever setDepends() gets called.  This List is 
121         * synchronized so a call to setDepends() (which clears the List) won't 
122         * interfere with a call to isDependency().
123         */
124        private List dependencyList = Collections.synchronizedList(new ArrayList());
125    
126        /**
127         * @deprecated Subclasses should use getVarMap() instead. 
128         */
129        protected FastHashMap hVars = new FastHashMap();
130    
131        /**
132         * @deprecated Subclasses should use getMsgMap() instead.
133         */
134        protected FastHashMap hMsgs = new FastHashMap();
135    
136        /**
137         * Holds Maps of arguments.  args[0] returns the Map for the first 
138         * replacement argument.  Start with a 0 length array so that it will
139         * only grow to the size of the highest argument position.
140         * @since Validator 1.1
141         */
142        protected Map[] args = new Map[0];
143    
144        /**
145         * Gets the page value that the Field is associated with for
146         * validation.
147         * @return The page number.
148         */
149        public int getPage() {
150            return this.page;
151        }
152    
153        /**
154         * Sets the page value that the Field is associated with for
155         * validation.
156         * @param page The page number.
157         */
158        public void setPage(int page) {
159            this.page = page;
160        }
161    
162        /**
163         * Gets the position of the <code>Field</code> in the validation list.
164         * @return The field position.
165         */
166        public int getFieldOrder() {
167            return this.fieldOrder;
168        }
169    
170        /**
171         * Sets the position of the <code>Field</code> in the validation list.
172         * @param fieldOrder The field position.
173         */
174        public void setFieldOrder(int fieldOrder) {
175            this.fieldOrder = fieldOrder;
176        }
177    
178        /**
179         * Gets the property name of the field.
180         * @return The field's property name.
181         */
182        public String getProperty() {
183            return this.property;
184        }
185    
186        /**
187         * Sets the property name of the field.
188         * @param property The field's property name.
189         */
190        public void setProperty(String property) {
191            this.property = property;
192        }
193    
194        /**
195         * Gets the indexed property name of the field.  This
196         * is the method name that can take an <code>int</code> as
197         * a parameter for indexed property value retrieval.
198         * @return The field's indexed property name.
199         */
200        public String getIndexedProperty() {
201            return this.indexedProperty;
202        }
203    
204        /**
205         * Sets the indexed property name of the field.
206         * @param indexedProperty The field's indexed property name.
207         */
208        public void setIndexedProperty(String indexedProperty) {
209            this.indexedProperty = indexedProperty;
210        }
211    
212        /**
213         * Gets the indexed property name of the field.  This
214         * is the method name that will return an array or a
215         * <code>Collection</code> used to retrieve the
216         * list and then loop through the list performing the specified
217         * validations.
218         * @return The field's indexed List property name.
219         */
220        public String getIndexedListProperty() {
221            return this.indexedListProperty;
222        }
223    
224        /**
225         * Sets the indexed property name of the field.
226         * @param indexedListProperty The field's indexed List property name.
227         */
228        public void setIndexedListProperty(String indexedListProperty) {
229            this.indexedListProperty = indexedListProperty;
230        }
231    
232        /**
233         * Gets the validation rules for this field as a comma separated list.
234         * @return A comma separated list of validator names.
235         */
236        public String getDepends() {
237            return this.depends;
238        }
239    
240        /**
241         * Sets the validation rules for this field as a comma separated list.
242         * @param depends A comma separated list of validator names.
243         */
244        public void setDepends(String depends) {
245            this.depends = depends;
246    
247            this.dependencyList.clear();
248    
249            StringTokenizer st = new StringTokenizer(depends, ",");
250            while (st.hasMoreTokens()) {
251                String depend = st.nextToken().trim();
252    
253                if (depend != null && depend.length() > 0) {
254                    this.dependencyList.add(depend);
255                }
256            }
257        }
258    
259        /**
260         * Add a <code>Msg</code> to the <code>Field</code>.
261         * @param msg A validation message.
262         */
263        public void addMsg(Msg msg) {
264            hMsgs.put(msg.getName(), msg);
265        }
266    
267        /**
268         * Retrieve a message value.
269         * @param key Validation key.
270         * @return A validation message for a specified validator.
271         */
272        public String getMsg(String key) {
273            Msg msg = getMessage(key);
274            return (msg == null) ? null : msg.getKey();
275        }
276    
277        /**
278         * Retrieve a message object.
279         * @since Validator 1.1.4
280         * @param key Validation key.
281         * @return A validation message for a specified validator.
282         */
283        public Msg getMessage(String key) {
284            return (Msg) hMsgs.get(key);
285        }
286    
287        /**
288         * The <code>Field</code>'s messages are returned as an
289         * unmodifiable <code>Map</code>.
290         * @since Validator 1.1.4
291         * @return Map of validation messages for the field.
292         */
293        public Map getMessages() {
294            return Collections.unmodifiableMap(hMsgs);
295        }
296    
297        /**
298         * Determines whether client-side scripting should be generated
299         * for this field. The default is <code>true</code>
300         * @return <code>true</code> for scripting; otherwise false
301         * @see #setClientValidation(boolean)
302         * @since Validator 1.4
303         */
304        public boolean isClientValidation() {
305            return this.clientValidation;
306        }
307    
308        /**
309         * Sets the flag that determines whether client-side scripting should 
310         * be generated for this field. 
311         * @param clientValidation the scripting flag
312         * @see #isClientValidation()
313         * @since Validator 1.4
314         */
315        public void setClientValidation(boolean clientValidation) {
316            this.clientValidation = clientValidation;
317        }
318    
319        /**
320         * Add an <code>Arg</code> to the replacement argument list.
321         * @since Validator 1.1
322         * @param arg Validation message's argument.
323         */
324        public void addArg(Arg arg) {
325            // TODO this first if check can go away after arg0, etc. are removed from dtd
326            if (arg == null || arg.getKey() == null || arg.getKey().length() == 0) {
327                return;
328            }
329    
330            determineArgPosition(arg);
331            ensureArgsCapacity(arg);
332    
333            Map argMap = this.args[arg.getPosition()];
334            if (argMap == null) {
335                argMap = new HashMap();
336                this.args[arg.getPosition()] = argMap;
337            }
338    
339            if (arg.getName() == null) {
340                argMap.put(DEFAULT_ARG, arg);
341            } else {
342                argMap.put(arg.getName(), arg);
343            }
344    
345        }
346    
347        /**
348         * Calculate the position of the Arg
349         */
350        private void determineArgPosition(Arg arg) {
351            
352            int position = arg.getPosition();
353    
354            // position has been explicity set
355            if (position >= 0) {
356                return;
357            }
358    
359            // first arg to be added
360            if (args == null || args.length == 0) {
361                arg.setPosition(0);
362                return;
363            }
364    
365            // determine the position of the last argument with
366            // the same name or the last default argument
367            String key = arg.getName() == null ? DEFAULT_ARG : arg.getName();
368            int lastPosition = -1;
369            int lastDefault  = -1;
370            for (int i = 0; i < args.length; i++) {
371                if (args[i] != null && args[i].containsKey(key)) {
372                    lastPosition = i;
373                }
374                if (args[i] != null && args[i].containsKey(DEFAULT_ARG)) {
375                    lastDefault = i;
376                }
377            }
378    
379            if (lastPosition < 0) { 
380                lastPosition = lastDefault;
381            }
382    
383            // allocate the next position
384            arg.setPosition(++lastPosition);
385    
386        }
387    
388        /**
389         * Ensures that the args array can hold the given arg.  Resizes the array as
390         * necessary.
391         * @param arg Determine if the args array is long enough to store this arg's
392         * position.
393         */
394        private void ensureArgsCapacity(Arg arg) {
395            if (arg.getPosition() >= this.args.length) {
396                Map[] newArgs = new Map[arg.getPosition() + 1];
397                System.arraycopy(this.args, 0, newArgs, 0, this.args.length);
398                this.args = newArgs;
399            }
400        }
401    
402        /**
403         * Gets the default <code>Arg</code> object at the given position.
404         * @param position Validation message argument's position.
405         * @return The default Arg or null if not found.
406         * @since Validator 1.1
407         */
408        public Arg getArg(int position) {
409            return this.getArg(DEFAULT_ARG, position);
410        }
411    
412        /**
413         * Gets the <code>Arg</code> object at the given position.  If the key
414         * finds a <code>null</code> value then the default value will be 
415         * retrieved.
416         * @param key The name the Arg is stored under.  If not found, the default 
417         * Arg for the given position (if any) will be retrieved.
418         * @param position The Arg number to find.
419         * @return The Arg with the given name and position or null if not found.
420         * @since Validator 1.1
421         */
422        public Arg getArg(String key, int position) {
423            if ((position >= this.args.length) || (this.args[position] == null)) {
424                return null;
425            }
426    
427            Arg arg = (Arg) args[position].get(key);
428    
429            // Didn't find default arg so exit, otherwise we would get into 
430            // infinite recursion
431            if ((arg == null) && key.equals(DEFAULT_ARG)) {
432                return null;
433            }
434    
435            return (arg == null) ? this.getArg(position) : arg;
436        }
437        
438        /**
439         * Retrieves the Args for the given validator name.
440         * @param key The validator's args to retrieve.
441         * @return An Arg[] sorted by the Args' positions (i.e. the Arg at index 0
442         * has a position of 0). 
443         * @since Validator 1.1.1
444         */
445        public Arg[] getArgs(String key){
446            Arg[] args = new Arg[this.args.length];
447            
448            for (int i = 0; i < this.args.length; i++) {
449                args[i] = this.getArg(key, i);
450            }
451            
452            return args;
453        }
454    
455        /**
456         * Add a <code>Var</code> to the <code>Field</code>.
457         * @param v The Validator Argument.
458         */
459        public void addVar(Var v) {
460            this.hVars.put(v.getName(), v);
461        }
462    
463        /**
464         * Add a <code>Var</code>, based on the values passed in, to the
465         * <code>Field</code>.
466         * @param name Name of the validation.
467         * @param value The Argument's value.
468         * @param jsType The Javascript type.
469         */
470        public void addVar(String name, String value, String jsType) {
471            this.addVar(new Var(name, value, jsType));
472        }
473    
474        /**
475         * Retrieve a variable.
476         * @param mainKey The Variable's key
477         * @return the Variable
478         */
479        public Var getVar(String mainKey) {
480            return (Var) hVars.get(mainKey);
481        }
482    
483        /**
484         * Retrieve a variable's value.
485         * @param mainKey The Variable's key
486         * @return the Variable's value
487         */
488        public String getVarValue(String mainKey) {
489            String value = null;
490    
491            Object o = hVars.get(mainKey);
492            if (o != null && o instanceof Var) {
493                Var v = (Var) o;
494                value = v.getValue();
495            }
496    
497            return value;
498        }
499    
500        /**
501         * The <code>Field</code>'s variables are returned as an
502         * unmodifiable <code>Map</code>.
503         * @return the Map of Variable's for a Field.
504         */
505        public Map getVars() {
506            return Collections.unmodifiableMap(hVars);
507        }
508    
509        /**
510         * Gets a unique key based on the property and indexedProperty fields.
511         * @return a unique key for the field.
512         */
513        public String getKey() {
514            if (this.key == null) {
515                this.generateKey();
516            }
517    
518            return this.key;
519        }
520    
521        /**
522         * Sets a unique key for the field.  This can be used to change
523         * the key temporarily to have a unique key for an indexed field.
524         * @param key a unique key for the field
525         */
526        public void setKey(String key) {
527            this.key = key;
528        }
529    
530        /**
531         * If there is a value specified for the indexedProperty field then
532         * <code>true</code> will be returned.  Otherwise it will be 
533         * <code>false</code>.
534         * @return Whether the Field is indexed.
535         */
536        public boolean isIndexed() {
537            return ((indexedListProperty != null && indexedListProperty.length() > 0));
538        }
539    
540        /**
541         * Generate correct <code>key</code> value.
542         */
543        public void generateKey() {
544            if (this.isIndexed()) {
545                this.key = this.indexedListProperty + TOKEN_INDEXED + "." + this.property;
546            } else {
547                this.key = this.property;
548            }
549        }
550    
551        /**
552         * Replace constants with values in fields and process the depends field
553         * to create the dependency <code>Map</code>.
554         */
555        void process(Map globalConstants, Map constants) {
556            this.hMsgs.setFast(false);
557            this.hVars.setFast(true);
558    
559            this.generateKey();
560    
561            // Process FormSet Constants
562            for (Iterator i = constants.keySet().iterator(); i.hasNext();) {
563                String key = (String) i.next();
564                String key2 = TOKEN_START + key + TOKEN_END;
565                String replaceValue = (String) constants.get(key);
566    
567                property = ValidatorUtils.replace(property, key2, replaceValue);
568    
569                processVars(key2, replaceValue);
570    
571                this.processMessageComponents(key2, replaceValue);
572            }
573    
574            // Process Global Constants
575            for (Iterator i = globalConstants.keySet().iterator(); i.hasNext();) {
576                String key = (String) i.next();
577                String key2 = TOKEN_START + key + TOKEN_END;
578                String replaceValue = (String) globalConstants.get(key);
579    
580                property = ValidatorUtils.replace(property, key2, replaceValue);
581    
582                processVars(key2, replaceValue);
583    
584                this.processMessageComponents(key2, replaceValue);
585            }
586    
587            // Process Var Constant Replacement
588            for (Iterator i = hVars.keySet().iterator(); i.hasNext();) {
589                String key = (String) i.next();
590                String key2 = TOKEN_START + TOKEN_VAR + key + TOKEN_END;
591                Var var = this.getVar(key);
592                String replaceValue = var.getValue();
593    
594                this.processMessageComponents(key2, replaceValue);
595            }
596    
597            hMsgs.setFast(true);
598        }
599    
600        /**
601         * Replace the vars value with the key/value pairs passed in.
602         */
603        private void processVars(String key, String replaceValue) {
604            Iterator i = this.hVars.keySet().iterator();
605            while (i.hasNext()) {
606                String varKey = (String) i.next();
607                Var var = this.getVar(varKey);
608    
609                var.setValue(ValidatorUtils.replace(var.getValue(), key, replaceValue));
610            }
611    
612        }
613    
614        /**
615         * Replace the args key value with the key/value pairs passed in.
616         */
617        private void processMessageComponents(String key, String replaceValue) {
618            String varKey = TOKEN_START + TOKEN_VAR;
619            // Process Messages
620            if (key != null && !key.startsWith(varKey)) {
621                for (Iterator i = hMsgs.values().iterator(); i.hasNext();) {
622                    Msg msg = (Msg) i.next();
623                    msg.setKey(ValidatorUtils.replace(msg.getKey(), key, replaceValue));
624                }
625            }
626    
627            this.processArg(key, replaceValue);
628        }
629    
630        /**
631         * Replace the arg <code>Collection</code> key value with the key/value 
632         * pairs passed in.
633         */
634        private void processArg(String key, String replaceValue) {
635            for (int i = 0; i < this.args.length; i++) {
636    
637                Map argMap = this.args[i];
638                if (argMap == null) {
639                    continue;
640                }
641    
642                Iterator iter = argMap.values().iterator();
643                while (iter.hasNext()) {
644                    Arg arg = (Arg) iter.next();
645    
646                    if (arg != null) {
647                        arg.setKey(
648                                ValidatorUtils.replace(arg.getKey(), key, replaceValue));
649                    }
650                }
651            }
652        }
653    
654        /**
655         * Checks if the validator is listed as a dependency.
656         * @param validatorName Name of the validator to check.
657         * @return Whether the field is dependant on a validator.
658         */
659        public boolean isDependency(String validatorName) {
660            return this.dependencyList.contains(validatorName);
661        }
662    
663        /**
664         * Gets an unmodifiable <code>List</code> of the dependencies in the same 
665         * order they were defined in parameter passed to the setDepends() method.
666         * @return A list of the Field's dependancies.
667         */
668        public List getDependencyList() {
669            return Collections.unmodifiableList(this.dependencyList);
670        }
671    
672        /**
673         * Creates and returns a copy of this object.
674         * @return A copy of the Field.
675         */
676        public Object clone() {
677            Field field = null;
678            try {
679                field = (Field) super.clone();
680            } catch(CloneNotSupportedException e) {
681                throw new RuntimeException(e.toString());
682            }
683    
684            field.args = new Map[this.args.length];
685            for (int i = 0; i < this.args.length; i++) {
686                if (this.args[i] == null) {
687                    continue;
688                }
689    
690                Map argMap = new HashMap(this.args[i]);
691                Iterator iter = argMap.keySet().iterator();
692                while (iter.hasNext()) {
693                    String validatorName = (String) iter.next();
694                    Arg arg = (Arg) argMap.get(validatorName);
695                    argMap.put(validatorName, arg.clone());
696                }
697                field.args[i] = argMap;
698            }
699    
700            field.hVars = ValidatorUtils.copyFastHashMap(hVars);
701            field.hMsgs = ValidatorUtils.copyFastHashMap(hMsgs);
702    
703            return field;
704        }
705    
706        /**
707         * Returns a string representation of the object.
708         * @return A string representation of the object.
709         */
710        public String toString() {
711            StringBuffer results = new StringBuffer();
712    
713            results.append("\t\tkey = " + key + "\n");
714            results.append("\t\tproperty = " + property + "\n");
715            results.append("\t\tindexedProperty = " + indexedProperty + "\n");
716            results.append("\t\tindexedListProperty = " + indexedListProperty + "\n");
717            results.append("\t\tdepends = " + depends + "\n");
718            results.append("\t\tpage = " + page + "\n");
719            results.append("\t\tfieldOrder = " + fieldOrder + "\n");
720    
721            if (hVars != null) {
722                results.append("\t\tVars:\n");
723                for (Iterator i = hVars.keySet().iterator(); i.hasNext();) {
724                    Object key = i.next();
725                    results.append("\t\t\t");
726                    results.append(key);
727                    results.append("=");
728                    results.append(hVars.get(key));
729                    results.append("\n");
730                }
731            }
732    
733            return results.toString();
734        }
735        
736        /**
737         * Returns an indexed property from the object we're validating.
738         *
739         * @param bean The bean to extract the indexed values from.
740         * @throws ValidatorException If there's an error looking up the property 
741         * or, the property found is not indexed.
742         */
743        Object[] getIndexedProperty(Object bean) throws ValidatorException {
744            Object indexedProperty = null;
745    
746            try {
747                indexedProperty =
748                    PropertyUtils.getProperty(bean, this.getIndexedListProperty());
749    
750            } catch(IllegalAccessException e) {
751                throw new ValidatorException(e.getMessage());
752            } catch(InvocationTargetException e) {
753                throw new ValidatorException(e.getMessage());
754            } catch(NoSuchMethodException e) {
755                throw new ValidatorException(e.getMessage());
756            }
757    
758            if (indexedProperty instanceof Collection) {
759                return ((Collection) indexedProperty).toArray();
760    
761            } else if (indexedProperty.getClass().isArray()) {
762                return (Object[]) indexedProperty;
763    
764            } else {
765                throw new ValidatorException(this.getKey() + " is not indexed");
766            }
767    
768        }
769        /**
770         * Returns the size of an indexed property from the object we're validating.
771         *
772         * @param bean The bean to extract the indexed values from.
773         * @throws ValidatorException If there's an error looking up the property 
774         * or, the property found is not indexed.
775         */
776        private int getIndexedPropertySize(Object bean) throws ValidatorException {
777            Object indexedProperty = null;
778    
779            try {
780                indexedProperty =
781                    PropertyUtils.getProperty(bean, this.getIndexedListProperty());
782    
783            } catch(IllegalAccessException e) {
784                throw new ValidatorException(e.getMessage());
785            } catch(InvocationTargetException e) {
786                throw new ValidatorException(e.getMessage());
787            } catch(NoSuchMethodException e) {
788                throw new ValidatorException(e.getMessage());
789            }
790    
791            if (indexedProperty == null) {
792                return 0;
793            } else if (indexedProperty instanceof Collection) {
794                return ((Collection)indexedProperty).size();
795            } else if (indexedProperty.getClass().isArray()) {
796                return ((Object[])indexedProperty).length;
797            } else {
798                throw new ValidatorException(this.getKey() + " is not indexed");
799            }
800    
801        }
802        
803        /**
804         * Executes the given ValidatorAction and all ValidatorActions that it 
805         * depends on.
806         * @return true if the validation succeeded.
807         */
808        private boolean validateForRule(
809            ValidatorAction va,
810            ValidatorResults results,
811            Map actions,
812            Map params,
813            int pos)
814            throws ValidatorException {
815    
816            ValidatorResult result = results.getValidatorResult(this.getKey());
817            if (result != null && result.containsAction(va.getName())) {
818                return result.isValid(va.getName());
819            }
820    
821            if (!this.runDependentValidators(va, results, actions, params, pos)) {
822                return false;
823            }
824    
825            return va.executeValidationMethod(this, params, results, pos);
826        }
827    
828        /**
829         * Calls all of the validators that this validator depends on.
830         * TODO ValidatorAction should know how to run its own dependencies.
831         * @param va Run dependent validators for this action.
832         * @param results
833         * @param actions
834         * @param pos
835         * @return true if all of the dependent validations passed.
836         * @throws ValidatorException If there's an error running a validator
837         */
838        private boolean runDependentValidators(
839            ValidatorAction va,
840            ValidatorResults results,
841            Map actions,
842            Map params,
843            int pos)
844            throws ValidatorException {
845    
846            List dependentValidators = va.getDependencyList();
847    
848            if (dependentValidators.isEmpty()) {
849                return true;
850            }
851    
852            Iterator iter = dependentValidators.iterator();
853            while (iter.hasNext()) {
854                String depend = (String) iter.next();
855    
856                ValidatorAction action = (ValidatorAction) actions.get(depend);
857                if (action == null) {
858                    this.handleMissingAction(depend);
859                }
860    
861                if (!this.validateForRule(action, results, actions, params, pos)) {
862                    return false;
863                }
864            }
865    
866            return true;
867        }
868    
869        /**
870         * Run the configured validations on this field.  Run all validations 
871         * in the depends clause over each item in turn, returning when the first 
872         * one fails.
873         * @param params A Map of parameter class names to parameter values to pass
874         * into validation methods.
875         * @param actions A Map of validator names to ValidatorAction objects.
876         * @return A ValidatorResults object containing validation messages for 
877         * this field.
878         * @throws ValidatorException If an error occurs during validation.
879         */
880        public ValidatorResults validate(Map params, Map actions)
881            throws ValidatorException {
882            
883            if (this.getDepends() == null) {
884                return new ValidatorResults();
885            }
886    
887            ValidatorResults allResults = new ValidatorResults();
888    
889            Object bean = params.get(Validator.BEAN_PARAM);
890            int numberOfFieldsToValidate =
891                this.isIndexed() ? this.getIndexedPropertySize(bean) : 1;
892    
893            for (int fieldNumber = 0; fieldNumber < numberOfFieldsToValidate; fieldNumber++) {
894                
895                Iterator dependencies = this.dependencyList.iterator();
896                ValidatorResults results = new ValidatorResults();
897                while (dependencies.hasNext()) {
898                    String depend = (String) dependencies.next();
899    
900                    ValidatorAction action = (ValidatorAction) actions.get(depend);
901                    if (action == null) {
902                        this.handleMissingAction(depend);
903                    }
904    
905                    boolean good =
906                        validateForRule(action, results, actions, params, fieldNumber);
907    
908                    if (!good) {
909                        allResults.merge(results);
910                        return allResults;
911                    }
912                }
913                allResults.merge(results);
914            }
915            
916            return allResults;
917        }
918        
919        /**
920         * Called when a validator name is used in a depends clause but there is
921         * no know ValidatorAction configured for that name.
922         * @param name The name of the validator in the depends list.
923         * @throws ValidatorException
924         */
925        private void handleMissingAction(String name) throws ValidatorException {
926            throw new ValidatorException("No ValidatorAction named " + name
927                    + " found for field " + this.getProperty());
928        }
929    
930        /**
931         * Returns a Map of String Msg names to Msg objects.
932         * @since Validator 1.2.0
933         * @return A Map of the Field's messages.
934         */
935        protected Map getMsgMap() {
936            return hMsgs;
937        }
938    
939        /**
940         * Returns a Map of String Var names to Var objects.
941         * @since Validator 1.2.0
942         * @return A Map of the Field's variables.
943         */
944        protected Map getVarMap() {
945            return hVars;
946        }
947    }
948