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