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