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