    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 * &lt;field&gt; 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$
    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<>();
    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 keyName = 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(keyName)) {
    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[] argList = new Arg[this.args.length];
    455
    456        for (int i = 0; i < this.args.length; i++) {
    457            argList[i] = this.getArg(key, i);
    458        }
    459
    460        return argList;
    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        Var v = getVarMap().get(mainKey);
    500        if (v != null) {
    501            value = v.getValue();
    502        }
    503
    504        return value;
    505    }
    506
    507    /**
    508     * The <code>Field</code>'s variables are returned as an
    509     * unmodifiable <code>Map</code>.
    510     * @return the Map of Variable's for a Field.
    511     */
    512    public Map<String, Var> getVars() {
    513        return Collections.unmodifiableMap(getVarMap());
    514    }
    515
    516    /**
    517     * Gets a unique key based on the property and indexedProperty fields.
    518     * @return a unique key for the field.
    519     */
    520    public String getKey() {
    521        if (this.key == null) {
    522            this.generateKey();
    523        }
    524
    525        return this.key;
    526    }
    527
    528    /**
    529     * Sets a unique key for the field.  This can be used to change
    530     * the key temporarily to have a unique key for an indexed field.
    531     * @param key a unique key for the field
    532     */
    533    public void setKey(String key) {
    534        this.key = key;
    535    }
    536
    537    /**
    538     * If there is a value specified for the indexedProperty field then
    539     * <code>true</code> will be returned.  Otherwise it will be
    540     * <code>false</code>.
    541     * @return Whether the Field is indexed.
    542     */
    543    public boolean isIndexed() {
    544        return (indexedListProperty != null && indexedListProperty.length() > 0);
    545    }
    546
    547    /**
    548     * Generate correct <code>key</code> value.
    549     */
    550    public void generateKey() {
    551        if (this.isIndexed()) {
    552            this.key = this.indexedListProperty + TOKEN_INDEXED + "." + this.property;
    553        } else {
    554            this.key = this.property;
    555        }
    556    }
    557
    558    /**
    559     * Replace constants with values in fields and process the depends field
    560     * to create the dependency <code>Map</code>.
    561     */
    562    void process(Map<String, String> globalConstants, Map<String, String> constants) {
    563        this.hMsgs.setFast(false);
    564        this.hVars.setFast(true);
    565
    566        this.generateKey();
    567
    568        // Process FormSet Constants
    569        for (Iterator<Entry<String, String>> i = constants.entrySet().iterator(); i.hasNext();) {
    570            Entry<String, String> entry = i.next();
    571            String key1 = entry.getKey();
    572            String key2 = TOKEN_START + key1 + TOKEN_END;
    573            String replaceValue = entry.getValue();
    574
    575            property = ValidatorUtils.replace(property, key2, replaceValue);
    576
    577            processVars(key2, replaceValue);
    578
    579            this.processMessageComponents(key2, replaceValue);
    580        }
    581
    582        // Process Global Constants
    583        for (Iterator<Entry<String, String>> i = globalConstants.entrySet().iterator(); i.hasNext();) {
    584            Entry<String, String> entry = i.next();
    585            String key1 = entry.getKey();
    586            String key2 = TOKEN_START + key1 + TOKEN_END;
    587            String replaceValue = entry.getValue();
    588
    589            property = ValidatorUtils.replace(property, key2, replaceValue);
    590
    591            processVars(key2, replaceValue);
    592
    593            this.processMessageComponents(key2, replaceValue);
    594        }
    595
    596        // Process Var Constant Replacement
    597        for (Iterator<String> i = getVarMap().keySet().iterator(); i.hasNext();) {
    598            String key1 = i.next();
    599            String key2 = TOKEN_START + TOKEN_VAR + key1 + TOKEN_END;
    600            Var var = this.getVar(key1);
    601            String replaceValue = var.getValue();
    602
    603            this.processMessageComponents(key2, replaceValue);
    604        }
    605
    606        hMsgs.setFast(true);
    607    }
    608
    609    /**
    610     * Replace the vars value with the key/value pairs passed in.
    611     */
    612    private void processVars(String key, String replaceValue) {
    613        Iterator<String> i = getVarMap().keySet().iterator();
    614        while (i.hasNext()) {
    615            String varKey = i.next();
    616            Var var = this.getVar(varKey);
    617
    618            var.setValue(ValidatorUtils.replace(var.getValue(), key, replaceValue));
    619        }
    620
    621    }
    622
    623    /**
    624     * Replace the args key value with the key/value pairs passed in.
    625     */
    626    private void processMessageComponents(String key, String replaceValue) {
    627        String varKey = TOKEN_START + TOKEN_VAR;
    628        // Process Messages
    629        if (key != null && !key.startsWith(varKey)) {
    630            for (Iterator<Msg> i = getMsgMap().values().iterator(); i.hasNext();) {
    631                Msg msg = i.next();
    632                msg.setKey(ValidatorUtils.replace(msg.getKey(), key, replaceValue));
    633            }
    634        }
    635
    636        this.processArg(key, replaceValue);
    637    }
    638
    639    /**
    640     * Replace the arg <code>Collection</code> key value with the key/value
    641     * pairs passed in.
    642     */
    643    private void processArg(String key, String replaceValue) {
    644        for (int i = 0; i < this.args.length; i++) {
    645
    646            Map<String, Arg> argMap = this.args[i];
    647            if (argMap == null) {
    648                continue;
    649            }
    650
    651            Iterator<Arg> iter = argMap.values().iterator();
    652            while (iter.hasNext()) {
    653                Arg arg = iter.next();
    654
    655                if (arg != null) {
    656                    arg.setKey(
    657                            ValidatorUtils.replace(arg.getKey(), key, replaceValue));
    658                }
    659            }
    660        }
    661    }
    662
    663    /**
    664     * Checks if the validator is listed as a dependency.
    665     * @param validatorName Name of the validator to check.
    666     * @return Whether the field is dependant on a validator.
    667     */
    668    public boolean isDependency(String validatorName) {
    669        return this.dependencyList.contains(validatorName);
    670    }
    671
    672    /**
    673     * Gets an unmodifiable <code>List</code> of the dependencies in the same
    674     * order they were defined in parameter passed to the setDepends() method.
    675     * @return A list of the Field's dependancies.
    676     */
    677    public List<String> getDependencyList() {
    678        return Collections.unmodifiableList(this.dependencyList);
    679    }
    680
    681    /**
    682     * Creates and returns a copy of this object.
    683     * @return A copy of the Field.
    684     */
    685    @Override
    686    public Object clone() {
    687        Field field = null;
    688        try {
    689            field = (Field) super.clone();
    690        } catch(CloneNotSupportedException e) {
    691            throw new RuntimeException(e.toString());
    692        }
    693
    694        @SuppressWarnings("unchecked") // empty array always OK; cannot check this at compile time
    695        final Map<String, Arg>[] tempMap = new Map[this.args.length];
    696        field.args = tempMap;
    697        for (int i = 0; i < this.args.length; i++) {
    698            if (this.args[i] == null) {
    699                continue;
    700            }
    701
    702            Map<String, Arg> argMap = new HashMap<>(this.args[i]);
    703            Iterator<Entry<String, Arg>> iter = argMap.entrySet().iterator();
    704            while (iter.hasNext()) {
    705                Entry<String, Arg> entry = iter.next();
    706                String validatorName = entry.getKey();
    707                Arg arg = entry.getValue();
    708                argMap.put(validatorName, (Arg) arg.clone());
    709            }
    710            field.args[i] = argMap;
    711        }
    712
    713        field.hVars = ValidatorUtils.copyFastHashMap(hVars);
    714        field.hMsgs = ValidatorUtils.copyFastHashMap(hMsgs);
    715
    716        return field;
    717    }
    718
    719    /**
    720     * Returns a string representation of the object.
    721     * @return A string representation of the object.
    722     */
    723    @Override
    724    public String toString() {
    725        StringBuilder results = new StringBuilder();
    726
    727        results.append("\t\tkey = " + key + "\n");
    728        results.append("\t\tproperty = " + property + "\n");
    729        results.append("\t\tindexedProperty = " + indexedProperty + "\n");
    730        results.append("\t\tindexedListProperty = " + indexedListProperty + "\n");
    731        results.append("\t\tdepends = " + depends + "\n");
    732        results.append("\t\tpage = " + page + "\n");
    733        results.append("\t\tfieldOrder = " + fieldOrder + "\n");
    734
    735        if (hVars != null) {
    736            results.append("\t\tVars:\n");
    737            for (Iterator<?> i = getVarMap().keySet().iterator(); i.hasNext();) {
    738                Object key1 = i.next();
    739                results.append("\t\t\t");
    740                results.append(key1);
    741                results.append("=");
    742                results.append(getVarMap().get(key1));
    743                results.append("\n");
    744            }
    745        }
    746
    747        return results.toString();
    748    }
    749
    750    /**
    751     * Returns an indexed property from the object we're validating.
    752     *
    753     * @param bean The bean to extract the indexed values from.
    754     * @throws ValidatorException If there's an error looking up the property
    755     * or, the property found is not indexed.
    756     */
    757    Object[] getIndexedProperty(Object bean) throws ValidatorException {
    758        Object indexProp = null;
    759
    760        try {
    761            indexProp =
    762                PropertyUtils.getProperty(bean, this.getIndexedListProperty());
    763
    764        } catch(IllegalAccessException|InvocationTargetException|NoSuchMethodException e) {
    765            throw new ValidatorException(e.getMessage());
    766        }
    767
    768        if (indexProp instanceof Collection) {
    769            return ((Collection<?>) indexProp).toArray();
    770
    771        } else if (indexProp.getClass().isArray()) {
    772            return (Object[]) indexProp;
    773
    774        } else {
    775            throw new ValidatorException(this.getKey() + " is not indexed");
    776        }
    777
    778    }
    779    /**
    780     * Returns the size of an indexed property from the object we're validating.
    781     *
    782     * @param bean The bean to extract the indexed values from.
    783     * @throws ValidatorException If there's an error looking up the property
    784     * or, the property found is not indexed.
    785     */
    786    private int getIndexedPropertySize(Object bean) throws ValidatorException {
    787        Object indexProp = null;
    788
    789        try {
    790            indexProp =
    791                PropertyUtils.getProperty(bean, this.getIndexedListProperty());
    792
    793        } catch(IllegalAccessException|InvocationTargetException|NoSuchMethodException e) {
    794            throw new ValidatorException(e.getMessage());
    795        }
    796
    797        if (indexProp == null) {
    798            return 0;
    799        } else if (indexProp instanceof Collection) {
    800            return ((Collection<?>)indexProp).size();
    801        } else if (indexProp.getClass().isArray()) {
    802            return ((Object[])indexProp).length;
    803        } else {
    804            throw new ValidatorException(this.getKey() + " is not indexed");
    805        }
    806
    807    }
    808
    809    /**
    810     * Executes the given ValidatorAction and all ValidatorActions that it
    811     * depends on.
    812     * @return true if the validation succeeded.
    813     */
    814    private boolean validateForRule(
    815        ValidatorAction va,
    816        ValidatorResults results,
    817        Map<String, ValidatorAction> actions,
    818        Map<String, Object> params,
    819        int pos)
    820        throws ValidatorException {
    821
    822        ValidatorResult result = results.getValidatorResult(this.getKey());
    823        if (result != null && result.containsAction(va.getName())) {
    824            return result.isValid(va.getName());
    825        }
    826
    827        if (!this.runDependentValidators(va, results, actions, params, pos)) {
    828            return false;
    829        }
    830
    831        return va.executeValidationMethod(this, params, results, pos);
    832    }
    833
    834    /**
    835     * Calls all of the validators that this validator depends on.
    836     * TODO ValidatorAction should know how to run its own dependencies.
    837     * @param va Run dependent validators for this action.
    838     * @param results
    839     * @param actions
    840     * @param pos
    841     * @return true if all of the dependent validations passed.
    842     * @throws ValidatorException If there's an error running a validator
    843     */
    844    private boolean runDependentValidators(
    845        ValidatorAction va,
    846        ValidatorResults results,
    847        Map<String, ValidatorAction> actions,
    848        Map<String, Object> params,
    849        int pos)
    850        throws ValidatorException {
    851
    852        List<String> dependentValidators = va.getDependencyList();
    853
    854        if (dependentValidators.isEmpty()) {
    855            return true;
    856        }
    857
    858        Iterator<String> iter = dependentValidators.iterator();
    859        while (iter.hasNext()) {
    860            String depend = iter.next();
    861
    862            ValidatorAction action = actions.get(depend);
    863            if (action == null) {
    864                this.handleMissingAction(depend);
    865            }
    866
    867            if (!this.validateForRule(action, results, actions, params, pos)) {
    868                return false;
    869            }
    870        }
    871
    872        return true;
    873    }
    874
    875    /**
    876     * Run the configured validations on this field.  Run all validations
    877     * in the depends clause over each item in turn, returning when the first
    878     * one fails.
    879     * @param params A Map of parameter class names to parameter values to pass
    880     * into validation methods.
    881     * @param actions A Map of validator names to ValidatorAction objects.
    882     * @return A ValidatorResults object containing validation messages for
    883     * this field.
    884     * @throws ValidatorException If an error occurs during validation.
    885     */
    886    public ValidatorResults validate(Map<String, Object> params, Map<String, ValidatorAction> actions)
    887        throws ValidatorException {
    888
    889        if (this.getDepends() == null) {
    890            return new ValidatorResults();
    891        }
    892
    893        ValidatorResults allResults = new ValidatorResults();
    894
    895        Object bean = params.get(Validator.BEAN_PARAM);
    896        int numberOfFieldsToValidate =
    897            this.isIndexed() ? this.getIndexedPropertySize(bean) : 1;
    898
    899        for (int fieldNumber = 0; fieldNumber < numberOfFieldsToValidate; fieldNumber++) {
    900
    901            ValidatorResults results = new ValidatorResults();
    902            synchronized(dependencyList) {
    903                Iterator<String> dependencies = this.dependencyList.iterator();
    904                while (dependencies.hasNext()) {
    905                    String depend = dependencies.next();
    906
    907                    ValidatorAction action = actions.get(depend);
    908                    if (action == null) {
    909                        this.handleMissingAction(depend);
    910                    }
    911
    912                    boolean good =
    913                        validateForRule(action, results, actions, params, fieldNumber);
    914
    915                    if (!good) {
    916                        allResults.merge(results);
    917                        return allResults;
    918                    }
    919                }
    920            }
    921            allResults.merge(results);
    922        }
    923
    924        return allResults;
    925    }
    926
    927    /**
    928     * Called when a validator name is used in a depends clause but there is
    929     * no know ValidatorAction configured for that name.
    930     * @param name The name of the validator in the depends list.
    931     * @throws ValidatorException
    932     */
    933    private void handleMissingAction(String name) throws ValidatorException {
    934        throw new ValidatorException("No ValidatorAction named " + name
    935                + " found for field " + this.getProperty());
    936    }
    937
    938    /**
    939     * Returns a Map of String Msg names to Msg objects.
    940     * @since Validator 1.2.0
    941     * @return A Map of the Field's messages.
    942     */
    943    @SuppressWarnings("unchecked") // FastHashMap does not support generics
    944    protected Map<String, Msg> getMsgMap() {
    945        return hMsgs;
    946    }
    947
    948    /**
    949     * Returns a Map of String Var names to Var objects.
    950     * @since Validator 1.2.0
    951     * @return A Map of the Field's variables.
    952     */
    953    @SuppressWarnings("unchecked") // FastHashMap does not support generics
    954    protected Map<String, Var> getVarMap() {
    955        return hVars;
    956    }
    957}
    958