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