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