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