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