001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.validator;
018
019import java.io.Serializable;
020import java.lang.reflect.InvocationTargetException;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.StringTokenizer;
029
030import org.apache.commons.beanutils.PropertyUtils;
031import org.apache.commons.collections.FastHashMap; // DEPRECATED
032import org.apache.commons.validator.util.ValidatorUtils;
033
034/**
035 * This contains the list of pluggable validators to run on a field and any
036 * message information and variables to perform the validations and generate
037 * error messages.  Instances of this class are configured with a
038 * <field> xml element.
039 * <p>
040 * The use of FastHashMap is deprecated and will be replaced in a future
041 * release.
042 * </p>
043 *
044 * @see org.apache.commons.validator.Form
045 */
046// TODO mutable non-private fields
047public class Field implements Cloneable, Serializable {
048
049    private static final long serialVersionUID = -8502647722530192185L;
050
051    /**
052     * This is the value that will be used as a key if the {@code Arg}
053     * name field has no value.
054     */
055    private static final String DEFAULT_ARG =
056            "org.apache.commons.validator.Field.DEFAULT";
057
058    /**
059     * This indicates an indexed property is being referenced.
060     */
061    public static final String TOKEN_INDEXED = "[]";
062
063    /**
064     * The start of a token.
065     */
066    protected static final String TOKEN_START = "${";
067
068    /**
069     * The end of a token.
070     */
071    protected static final String TOKEN_END = "}";
072
073    /**
074     * A Variable token.
075     */
076    protected static final String TOKEN_VAR = "var:";
077
078    /**
079     * The Field's property name.
080     */
081    protected String property;
082
083    /**
084     * The Field's indexed property name.
085     */
086    protected String indexedProperty;
087
088    /**
089     * The Field's indexed list property name.
090     */
091    protected String indexedListProperty;
092
093    /**
094     * The Field's unique key.
095     */
096    protected String key;
097
098    /**
099     * 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