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