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