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