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