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 *      http://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</code>
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 Vriable 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 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} 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