Field.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.validator;

  18. import java.io.Serializable;
  19. import java.lang.reflect.InvocationTargetException;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Map.Entry;
  27. import java.util.StringTokenizer;

  28. import org.apache.commons.beanutils.PropertyUtils;
  29. import org.apache.commons.collections.FastHashMap; // DEPRECATED
  30. import org.apache.commons.validator.util.ValidatorUtils;

  31. /**
  32.  * This contains the list of pluggable validators to run on a field and any
  33.  * message information and variables to perform the validations and generate
  34.  * error messages.  Instances of this class are configured with a
  35.  * <field> xml element.
  36.  * <p>
  37.  * The use of FastHashMap is deprecated and will be replaced in a future
  38.  * release.
  39.  * </p>
  40.  *
  41.  * @see org.apache.commons.validator.Form
  42.  */
  43. // TODO mutable non-private fields
  44. public class Field implements Cloneable, Serializable {

  45.     private static final long serialVersionUID = -8502647722530192185L;

  46.     /**
  47.      * This is the value that will be used as a key if the <code>Arg</code>
  48.      * name field has no value.
  49.      */
  50.     private static final String DEFAULT_ARG =
  51.             "org.apache.commons.validator.Field.DEFAULT";

  52.     /**
  53.      * This indicates an indexed property is being referenced.
  54.      */
  55.     public static final String TOKEN_INDEXED = "[]";

  56.     /**
  57.      * The start of a token.
  58.      */
  59.     protected static final String TOKEN_START = "${";

  60.     /**
  61.      * The end of a token.
  62.      */
  63.     protected static final String TOKEN_END = "}";

  64.     /**
  65.      * A Vriable token.
  66.      */
  67.     protected static final String TOKEN_VAR = "var:";

  68.     /**
  69.      * The Field's property name.
  70.      */
  71.     protected String property;

  72.     /**
  73.      * The Field's indexed property name.
  74.      */
  75.     protected String indexedProperty;

  76.     /**
  77.      * The Field's indexed list property name.
  78.      */
  79.     protected String indexedListProperty;

  80.     /**
  81.      * The Field's unique key.
  82.      */
  83.     protected String key;

  84.     /**
  85.      * A comma separated list of validator's this field depends on.
  86.      */
  87.     protected String depends;

  88.     /**
  89.      * The Page Number
  90.      */
  91.     protected int page;

  92.     /**
  93.      * The flag that indicates whether scripting should be generated
  94.      * by the client for client-side validation.
  95.      * @since 1.4
  96.      */
  97.     protected boolean clientValidation = true;

  98.     /**
  99.      * The order of the Field in the Form.
  100.      */
  101.     protected int fieldOrder;

  102.     /**
  103.      * Internal representation of this.depends String as a List.  This List
  104.      * gets updated whenever setDepends() gets called.  This List is
  105.      * synchronized so a call to setDepends() (which clears the List) won't
  106.      * interfere with a call to isDependency().
  107.      */
  108.     private final List<String> dependencyList = Collections.synchronizedList(new ArrayList<>());

  109.     /**
  110.      * @deprecated Subclasses should use getVarMap() instead.
  111.      */
  112.     @Deprecated
  113.     protected FastHashMap hVars = new FastHashMap(); // <String, Var>

  114.     /**
  115.      * @deprecated Subclasses should use getMsgMap() instead.
  116.      */
  117.     @Deprecated
  118.     protected FastHashMap hMsgs = new FastHashMap(); // <String, Msg>

  119.     /**
  120.      * Holds Maps of arguments.  args[0] returns the Map for the first
  121.      * replacement argument.  Start with a 0 length array so that it will
  122.      * only grow to the size of the highest argument position.
  123.      * @since 1.1
  124.      */
  125.     @SuppressWarnings("unchecked") // cannot instantiate generic array, so have to assume this is OK
  126.     protected Map<String, Arg>[] args = new Map[0];

  127.     /**
  128.      * Add an <code>Arg</code> to the replacement argument list.
  129.      * @since 1.1
  130.      * @param arg Validation message's argument.
  131.      */
  132.     public void addArg(final Arg arg) {
  133.         // TODO this first if check can go away after arg0, etc. are removed from dtd
  134.         if (arg == null || arg.getKey() == null || arg.getKey().isEmpty()) {
  135.             return;
  136.         }

  137.         determineArgPosition(arg);
  138.         ensureArgsCapacity(arg);

  139.         Map<String, Arg> argMap = this.args[arg.getPosition()];
  140.         if (argMap == null) {
  141.             argMap = new HashMap<>();
  142.             this.args[arg.getPosition()] = argMap;
  143.         }

  144.         if (arg.getName() == null) {
  145.             argMap.put(DEFAULT_ARG, arg);
  146.         } else {
  147.             argMap.put(arg.getName(), arg);
  148.         }

  149.     }

  150.     /**
  151.      * Add a <code>Msg</code> to the <code>Field</code>.
  152.      * @param msg A validation message.
  153.      */
  154.     public void addMsg(final Msg msg) {
  155.         getMsgMap().put(msg.getName(), msg);
  156.     }

  157.     /**
  158.      * Add a <code>Var</code>, based on the values passed in, to the
  159.      * <code>Field</code>.
  160.      * @param name Name of the validation.
  161.      * @param value The Argument's value.
  162.      * @param jsType The JavaScript type.
  163.      */
  164.     public void addVar(final String name, final String value, final String jsType) {
  165.         this.addVar(new Var(name, value, jsType));
  166.     }

  167.     /**
  168.      * Add a <code>Var</code> to the <code>Field</code>.
  169.      * @param v The Validator Argument.
  170.      */
  171.     public void addVar(final Var v) {
  172.         this.getVarMap().put(v.getName(), v);
  173.     }

  174.     /**
  175.      * Creates and returns a copy of this object.
  176.      * @return A copy of the Field.
  177.      */
  178.     @Override
  179.     public Object clone() {
  180.         Field field = null;
  181.         try {
  182.             field = (Field) super.clone();
  183.         } catch (final CloneNotSupportedException e) {
  184.             throw new UnsupportedOperationException(e.toString(), e);
  185.         }

  186.         @SuppressWarnings("unchecked") // empty array always OK; cannot check this at compile time
  187.         final Map<String, Arg>[] tempMap = new Map[this.args.length];
  188.         field.args = tempMap;
  189.         for (int i = 0; i < this.args.length; i++) {
  190.             if (this.args[i] == null) {
  191.                 continue;
  192.             }

  193.             final Map<String, Arg> argMap = new HashMap<>(this.args[i]);
  194.             argMap.forEach((validatorName, arg) -> argMap.put(validatorName, (Arg) arg.clone()));
  195.             field.args[i] = argMap;
  196.         }

  197.         field.hVars = ValidatorUtils.copyFastHashMap(hVars);
  198.         field.hMsgs = ValidatorUtils.copyFastHashMap(hMsgs);

  199.         return field;
  200.     }

  201.     /**
  202.      * Calculate the position of the Arg
  203.      */
  204.     private void determineArgPosition(final Arg arg) {

  205.         final int position = arg.getPosition();

  206.         // position has been explicity set
  207.         if (position >= 0) {
  208.             return;
  209.         }

  210.         // first arg to be added
  211.         if (args == null || args.length == 0) {
  212.             arg.setPosition(0);
  213.             return;
  214.         }

  215.         // determine the position of the last argument with
  216.         // the same name or the last default argument
  217.         final String keyName = arg.getName() == null ? DEFAULT_ARG : arg.getName();
  218.         int lastPosition = -1;
  219.         int lastDefault = -1;
  220.         for (int i = 0; i < args.length; i++) {
  221.             if (args[i] != null && args[i].containsKey(keyName)) {
  222.                 lastPosition = i;
  223.             }
  224.             if (args[i] != null && args[i].containsKey(DEFAULT_ARG)) {
  225.                 lastDefault = i;
  226.             }
  227.         }

  228.         if (lastPosition < 0) {
  229.             lastPosition = lastDefault;
  230.         }

  231.         // allocate the next position
  232.         arg.setPosition(++lastPosition);

  233.     }

  234.     /**
  235.      * Ensures that the args array can hold the given arg.  Resizes the array as
  236.      * necessary.
  237.      * @param arg Determine if the args array is long enough to store this arg's
  238.      * position.
  239.      */
  240.     private void ensureArgsCapacity(final Arg arg) {
  241.         if (arg.getPosition() >= this.args.length) {
  242.             @SuppressWarnings("unchecked") // cannot check this at compile time, but it is OK
  243.             final
  244.             Map<String, Arg>[] newArgs = new Map[arg.getPosition() + 1];
  245.             System.arraycopy(this.args, 0, newArgs, 0, this.args.length);
  246.             this.args = newArgs;
  247.         }
  248.     }

  249.     /**
  250.      * Generate correct <code>key</code> value.
  251.      */
  252.     public void generateKey() {
  253.         if (this.isIndexed()) {
  254.             this.key = this.indexedListProperty + TOKEN_INDEXED + "." + this.property;
  255.         } else {
  256.             this.key = this.property;
  257.         }
  258.     }

  259.     /**
  260.      * Gets the default <code>Arg</code> object at the given position.
  261.      * @param position Validation message argument's position.
  262.      * @return The default Arg or null if not found.
  263.      * @since 1.1
  264.      */
  265.     public Arg getArg(final int position) {
  266.         return this.getArg(DEFAULT_ARG, position);
  267.     }

  268.     /**
  269.      * Gets the <code>Arg</code> object at the given position.  If the key
  270.      * finds a {@code null} value then the default value will be
  271.      * retrieved.
  272.      * @param key The name the Arg is stored under.  If not found, the default
  273.      * Arg for the given position (if any) will be retrieved.
  274.      * @param position The Arg number to find.
  275.      * @return The Arg with the given name and position or null if not found.
  276.      * @since 1.1
  277.      */
  278.     public Arg getArg(final String key, final int position) {
  279.         if (position >= this.args.length || this.args[position] == null) {
  280.             return null;
  281.         }

  282.         final Arg arg = args[position].get(key);

  283.         // Didn't find default arg so exit, otherwise we would get into
  284.         // infinite recursion
  285.         if (arg == null && key.equals(DEFAULT_ARG)) {
  286.             return null;
  287.         }

  288.         return arg == null ? this.getArg(position) : arg;
  289.     }

  290.     /**
  291.      * Retrieves the Args for the given validator name.
  292.      * @param key The validator's args to retrieve.
  293.      * @return An Arg[] sorted by the Args' positions (i.e. the Arg at index 0
  294.      * has a position of 0).
  295.      * @since 1.1.1
  296.      */
  297.     public Arg[] getArgs(final String key) {
  298.         final Arg[] argList = new Arg[this.args.length];

  299.         for (int i = 0; i < this.args.length; i++) {
  300.             argList[i] = this.getArg(key, i);
  301.         }

  302.         return argList;
  303.     }

  304.     /**
  305.      * Gets an unmodifiable <code>List</code> of the dependencies in the same
  306.      * order they were defined in parameter passed to the setDepends() method.
  307.      * @return A list of the Field's dependancies.
  308.      */
  309.     public List<String> getDependencyList() {
  310.         return Collections.unmodifiableList(this.dependencyList);
  311.     }

  312.     /**
  313.      * Gets the validation rules for this field as a comma separated list.
  314.      * @return A comma separated list of validator names.
  315.      */
  316.     public String getDepends() {
  317.         return this.depends;
  318.     }

  319.     /**
  320.      * Gets the position of the <code>Field</code> in the validation list.
  321.      * @return The field position.
  322.      */
  323.     public int getFieldOrder() {
  324.         return this.fieldOrder;
  325.     }

  326.     /**
  327.      * Gets the indexed property name of the field.  This
  328.      * is the method name that will return an array or a
  329.      * <code>Collection</code> used to retrieve the
  330.      * list and then loop through the list performing the specified
  331.      * validations.
  332.      * @return The field's indexed List property name.
  333.      */
  334.     public String getIndexedListProperty() {
  335.         return this.indexedListProperty;
  336.     }

  337.     /**
  338.      * Gets the indexed property name of the field.  This
  339.      * is the method name that can take an <code>int</code> as
  340.      * a parameter for indexed property value retrieval.
  341.      * @return The field's indexed property name.
  342.      */
  343.     public String getIndexedProperty() {
  344.         return this.indexedProperty;
  345.     }

  346.     /**
  347.      * Returns an indexed property from the object we're validating.
  348.      *
  349.      * @param bean The bean to extract the indexed values from.
  350.      * @throws ValidatorException If there's an error looking up the property
  351.      * or, the property found is not indexed.
  352.      */
  353.     Object[] getIndexedProperty(final Object bean) throws ValidatorException {
  354.         Object indexProp = null;

  355.         try {
  356.             indexProp = PropertyUtils.getProperty(bean, this.getIndexedListProperty());

  357.         } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
  358.             throw new ValidatorException(e.getMessage());
  359.         }

  360.         if (indexProp instanceof Collection) {
  361.             return ((Collection<?>) indexProp).toArray();

  362.         }
  363.         if (indexProp.getClass().isArray()) {
  364.             return (Object[]) indexProp;

  365.         }
  366.         throw new ValidatorException(this.getKey() + " is not indexed");

  367.     }

  368.     /**
  369.      * Returns the size of an indexed property from the object we're validating.
  370.      *
  371.      * @param bean The bean to extract the indexed values from.
  372.      * @throws ValidatorException If there's an error looking up the property
  373.      * or, the property found is not indexed.
  374.      */
  375.     private int getIndexedPropertySize(final Object bean) throws ValidatorException {
  376.         Object indexProp = null;

  377.         try {
  378.             indexProp = PropertyUtils.getProperty(bean, this.getIndexedListProperty());

  379.         } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
  380.             throw new ValidatorException(e.getMessage());
  381.         }

  382.         if (indexProp == null) {
  383.             return 0;
  384.         }
  385.         if (indexProp instanceof Collection) {
  386.             return ((Collection<?>) indexProp).size();
  387.         }
  388.         if (indexProp.getClass().isArray()) {
  389.             return ((Object[]) indexProp).length;
  390.         }
  391.         throw new ValidatorException(this.getKey() + " is not indexed");

  392.     }

  393.     /**
  394.      * Gets a unique key based on the property and indexedProperty fields.
  395.      * @return a unique key for the field.
  396.      */
  397.     public String getKey() {
  398.         if (this.key == null) {
  399.             this.generateKey();
  400.         }

  401.         return this.key;
  402.     }

  403.     /**
  404.      * Retrieve a message object.
  405.      * @since 1.1.4
  406.      * @param key Validation key.
  407.      * @return A validation message for a specified validator.
  408.      */
  409.     public Msg getMessage(final String key) {
  410.         return getMsgMap().get(key);
  411.     }

  412.     /**
  413.      * The <code>Field</code>'s messages are returned as an
  414.      * unmodifiable <code>Map</code>.
  415.      * @since 1.1.4
  416.      * @return Map of validation messages for the field.
  417.      */
  418.     public Map<String, Msg> getMessages() {
  419.         return Collections.unmodifiableMap(getMsgMap());
  420.     }

  421.     /**
  422.      * Retrieve a message value.
  423.      * @param key Validation key.
  424.      * @return A validation message for a specified validator.
  425.      */
  426.     public String getMsg(final String key) {
  427.         final Msg msg = getMessage(key);
  428.         return msg == null ? null : msg.getKey();
  429.     }

  430.     /**
  431.      * Returns a Map of String Msg names to Msg objects.
  432.      * @since 1.2.0
  433.      * @return A Map of the Field's messages.
  434.      */
  435.     @SuppressWarnings("unchecked") // FastHashMap does not support generics
  436.     protected Map<String, Msg> getMsgMap() {
  437.         return hMsgs;
  438.     }

  439.     /**
  440.      * Gets the page value that the Field is associated with for
  441.      * validation.
  442.      * @return The page number.
  443.      */
  444.     public int getPage() {
  445.         return this.page;
  446.     }

  447.     /**
  448.      * Gets the property name of the field.
  449.      * @return The field's property name.
  450.      */
  451.     public String getProperty() {
  452.         return this.property;
  453.     }

  454.     /**
  455.      * Retrieve a variable.
  456.      * @param mainKey The Variable's key
  457.      * @return the Variable
  458.      */
  459.     public Var getVar(final String mainKey) {
  460.         return getVarMap().get(mainKey);
  461.     }

  462.     /**
  463.      * Returns a Map of String Var names to Var objects.
  464.      * @since 1.2.0
  465.      * @return A Map of the Field's variables.
  466.      */
  467.     @SuppressWarnings("unchecked") // FastHashMap does not support generics
  468.     protected Map<String, Var> getVarMap() {
  469.         return hVars;
  470.     }

  471.     /**
  472.      * The <code>Field</code>'s variables are returned as an
  473.      * unmodifiable <code>Map</code>.
  474.      * @return the Map of Variable's for a Field.
  475.      */
  476.     public Map<String, Var> getVars() {
  477.         return Collections.unmodifiableMap(getVarMap());
  478.     }

  479.     /**
  480.      * Retrieve a variable's value.
  481.      * @param mainKey The Variable's key
  482.      * @return the Variable's value
  483.      */
  484.     public String getVarValue(final String mainKey) {
  485.         String value = null;

  486.         final Var v = getVarMap().get(mainKey);
  487.         if (v != null) {
  488.             value = v.getValue();
  489.         }

  490.         return value;
  491.     }

  492.     /**
  493.      * Called when a validator name is used in a depends clause but there is
  494.      * no know ValidatorAction configured for that name.
  495.      * @param name The name of the validator in the depends list.
  496.      * @throws ValidatorException
  497.      */
  498.     private void handleMissingAction(final String name) throws ValidatorException {
  499.         throw new ValidatorException("No ValidatorAction named " + name
  500.                 + " found for field " + this.getProperty());
  501.     }

  502.     /**
  503.      * Determines whether client-side scripting should be generated
  504.      * for this field. The default is {@code true}
  505.      * @return {@code true} for scripting; otherwise false
  506.      * @see #setClientValidation(boolean)
  507.      * @since 1.4
  508.      */
  509.     public boolean isClientValidation() {
  510.         return this.clientValidation;
  511.     }

  512.     /**
  513.      * Checks if the validator is listed as a dependency.
  514.      * @param validatorName Name of the validator to check.
  515.      * @return Whether the field is dependant on a validator.
  516.      */
  517.     public boolean isDependency(final String validatorName) {
  518.         return this.dependencyList.contains(validatorName);
  519.     }

  520.     /**
  521.      * If there is a value specified for the indexedProperty field then
  522.      * {@code true} will be returned.  Otherwise it will be
  523.      * {@code false}.
  524.      * @return Whether the Field is indexed.
  525.      */
  526.     public boolean isIndexed() {
  527.         return indexedListProperty != null && !indexedListProperty.isEmpty();
  528.     }

  529.     /**
  530.      * Replace constants with values in fields and process the depends field
  531.      * to create the dependency <code>Map</code>.
  532.      */
  533.     void process(final Map<String, String> globalConstants, final Map<String, String> constants) {
  534.         this.hMsgs.setFast(false);
  535.         this.hVars.setFast(true);

  536.         this.generateKey();

  537.         // Process FormSet Constants
  538.         for (final Entry<String, String> entry : constants.entrySet()) {
  539.             final String key1 = entry.getKey();
  540.             final String key2 = TOKEN_START + key1 + TOKEN_END;
  541.             final String replaceValue = entry.getValue();

  542.             property = ValidatorUtils.replace(property, key2, replaceValue);

  543.             processVars(key2, replaceValue);

  544.             this.processMessageComponents(key2, replaceValue);
  545.         }

  546.         // Process Global Constants
  547.         for (final Entry<String, String> entry : globalConstants.entrySet()) {
  548.             final String key1 = entry.getKey();
  549.             final String key2 = TOKEN_START + key1 + TOKEN_END;
  550.             final String replaceValue = entry.getValue();

  551.             property = ValidatorUtils.replace(property, key2, replaceValue);

  552.             processVars(key2, replaceValue);

  553.             this.processMessageComponents(key2, replaceValue);
  554.         }

  555.         // Process Var Constant Replacement
  556.         for (final String key1 : getVarMap().keySet()) {
  557.             final String key2 = TOKEN_START + TOKEN_VAR + key1 + TOKEN_END;
  558.             final Var var = this.getVar(key1);
  559.             final String replaceValue = var.getValue();

  560.             this.processMessageComponents(key2, replaceValue);
  561.         }

  562.         hMsgs.setFast(true);
  563.     }

  564.     /**
  565.      * Replace the arg <code>Collection</code> key value with the key/value
  566.      * pairs passed in.
  567.      */
  568.     private void processArg(final String key, final String replaceValue) {
  569.         for (final Map<String, Arg> argMap : this.args) {
  570.             if (argMap == null) {
  571.                 continue;
  572.             }
  573.             for (final Arg arg : argMap.values()) {
  574.                 if (arg != null) {
  575.                     arg.setKey(ValidatorUtils.replace(arg.getKey(), key, replaceValue));
  576.                 }
  577.             }
  578.         }
  579.     }

  580.     /**
  581.      * Replace the args key value with the key/value pairs passed in.
  582.      */
  583.     private void processMessageComponents(final String key, final String replaceValue) {
  584.         final String varKey = TOKEN_START + TOKEN_VAR;
  585.         // Process Messages
  586.         if (key != null && !key.startsWith(varKey)) {
  587.             for (final Msg msg : getMsgMap().values()) {
  588.                 msg.setKey(ValidatorUtils.replace(msg.getKey(), key, replaceValue));
  589.             }
  590.         }

  591.         this.processArg(key, replaceValue);
  592.     }

  593.     /**
  594.      * Replace the vars value with the key/value pairs passed in.
  595.      */
  596.     private void processVars(final String key, final String replaceValue) {
  597.         for (final String varKey : getVarMap().keySet()) {
  598.             final Var var = this.getVar(varKey);
  599.             var.setValue(ValidatorUtils.replace(var.getValue(), key, replaceValue));
  600.         }

  601.     }

  602.     /**
  603.      * Calls all of the validators that this validator depends on.
  604.      * TODO ValidatorAction should know how to run its own dependencies.
  605.      * @param va Run dependent validators for this action.
  606.      * @param results
  607.      * @param actions
  608.      * @param pos
  609.      * @return true if all of the dependent validations passed.
  610.      * @throws ValidatorException If there's an error running a validator
  611.      */
  612.     private boolean runDependentValidators(
  613.         final ValidatorAction va,
  614.         final ValidatorResults results,
  615.         final Map<String, ValidatorAction> actions,
  616.         final Map<String, Object> params,
  617.         final int pos)
  618.         throws ValidatorException {

  619.         final List<String> dependentValidators = va.getDependencyList();

  620.         if (dependentValidators.isEmpty()) {
  621.             return true;
  622.         }

  623.         for (final String depend : dependentValidators) {
  624.             final ValidatorAction action = actions.get(depend);
  625.             if (action == null) {
  626.                 this.handleMissingAction(depend);
  627.             }

  628.             if (!this.validateForRule(action, results, actions, params, pos)) {
  629.                 return false;
  630.             }
  631.         }

  632.         return true;
  633.     }

  634.     /**
  635.      * Sets the flag that determines whether client-side scripting should
  636.      * be generated for this field.
  637.      * @param clientValidation the scripting flag
  638.      * @see #isClientValidation()
  639.      * @since 1.4
  640.      */
  641.     public void setClientValidation(final boolean clientValidation) {
  642.         this.clientValidation = clientValidation;
  643.     }

  644.     /**
  645.      * Sets the validation rules for this field as a comma separated list.
  646.      * @param depends A comma separated list of validator names.
  647.      */
  648.     public void setDepends(final String depends) {
  649.         this.depends = depends;

  650.         this.dependencyList.clear();

  651.         final StringTokenizer st = new StringTokenizer(depends, ",");
  652.         while (st.hasMoreTokens()) {
  653.             final String depend = st.nextToken().trim();

  654.             if (depend != null && !depend.isEmpty()) {
  655.                 this.dependencyList.add(depend);
  656.             }
  657.         }
  658.     }

  659.     /**
  660.      * Sets the position of the <code>Field</code> in the validation list.
  661.      * @param fieldOrder The field position.
  662.      */
  663.     public void setFieldOrder(final int fieldOrder) {
  664.         this.fieldOrder = fieldOrder;
  665.     }

  666.     /**
  667.      * Sets the indexed property name of the field.
  668.      * @param indexedListProperty The field's indexed List property name.
  669.      */
  670.     public void setIndexedListProperty(final String indexedListProperty) {
  671.         this.indexedListProperty = indexedListProperty;
  672.     }
  673.     /**
  674.      * Sets the indexed property name of the field.
  675.      * @param indexedProperty The field's indexed property name.
  676.      */
  677.     public void setIndexedProperty(final String indexedProperty) {
  678.         this.indexedProperty = indexedProperty;
  679.     }

  680.     /**
  681.      * Sets a unique key for the field.  This can be used to change
  682.      * the key temporarily to have a unique key for an indexed field.
  683.      * @param key a unique key for the field
  684.      */
  685.     public void setKey(final String key) {
  686.         this.key = key;
  687.     }

  688.     /**
  689.      * Sets the page value that the Field is associated with for
  690.      * validation.
  691.      * @param page The page number.
  692.      */
  693.     public void setPage(final int page) {
  694.         this.page = page;
  695.     }

  696.     /**
  697.      * Sets the property name of the field.
  698.      * @param property The field's property name.
  699.      */
  700.     public void setProperty(final String property) {
  701.         this.property = property;
  702.     }

  703.     /**
  704.      * Returns a string representation of the object.
  705.      * @return A string representation of the object.
  706.      */
  707.     @Override
  708.     public String toString() {
  709.         final StringBuilder results = new StringBuilder();

  710.         results.append("\t\tkey = " + key + "\n");
  711.         results.append("\t\tproperty = " + property + "\n");
  712.         results.append("\t\tindexedProperty = " + indexedProperty + "\n");
  713.         results.append("\t\tindexedListProperty = " + indexedListProperty + "\n");
  714.         results.append("\t\tdepends = " + depends + "\n");
  715.         results.append("\t\tpage = " + page + "\n");
  716.         results.append("\t\tfieldOrder = " + fieldOrder + "\n");

  717.         if (hVars != null) {
  718.             results.append("\t\tVars:\n");
  719.             for (final Object key1 : getVarMap().keySet()) {
  720.                 results.append("\t\t\t");
  721.                 results.append(key1);
  722.                 results.append("=");
  723.                 results.append(getVarMap().get(key1));
  724.                 results.append("\n");
  725.             }
  726.         }

  727.         return results.toString();
  728.     }

  729.     /**
  730.      * Run the configured validations on this field.  Run all validations
  731.      * in the depends clause over each item in turn, returning when the first
  732.      * one fails.
  733.      * @param params A Map of parameter class names to parameter values to pass
  734.      * into validation methods.
  735.      * @param actions A Map of validator names to ValidatorAction objects.
  736.      * @return A ValidatorResults object containing validation messages for
  737.      * this field.
  738.      * @throws ValidatorException If an error occurs during validation.
  739.      */
  740.     public ValidatorResults validate(final Map<String, Object> params, final Map<String, ValidatorAction> actions)
  741.             throws ValidatorException {

  742.         if (this.getDepends() == null) {
  743.             return new ValidatorResults();
  744.         }

  745.         final ValidatorResults allResults = new ValidatorResults();

  746.         final Object bean = params.get(Validator.BEAN_PARAM);
  747.         final int numberOfFieldsToValidate = this.isIndexed() ? this.getIndexedPropertySize(bean) : 1;

  748.         for (int fieldNumber = 0; fieldNumber < numberOfFieldsToValidate; fieldNumber++) {

  749.             final ValidatorResults results = new ValidatorResults();
  750.             synchronized (dependencyList) {
  751.                 for (final String depend : this.dependencyList) {

  752.                     final ValidatorAction action = actions.get(depend);
  753.                     if (action == null) {
  754.                         this.handleMissingAction(depend);
  755.                     }

  756.                     final boolean good = validateForRule(action, results, actions, params, fieldNumber);

  757.                     if (!good) {
  758.                         allResults.merge(results);
  759.                         return allResults;
  760.                     }
  761.                 }
  762.             }
  763.             allResults.merge(results);
  764.         }

  765.         return allResults;
  766.     }

  767.     /**
  768.      * Executes the given ValidatorAction and all ValidatorActions that it
  769.      * depends on.
  770.      * @return true if the validation succeeded.
  771.      */
  772.     private boolean validateForRule(
  773.         final ValidatorAction va,
  774.         final ValidatorResults results,
  775.         final Map<String, ValidatorAction> actions,
  776.         final Map<String, Object> params,
  777.         final int pos)
  778.         throws ValidatorException {

  779.         final ValidatorResult result = results.getValidatorResult(this.getKey());
  780.         if (result != null && result.containsAction(va.getName())) {
  781.             return result.isValid(va.getName());
  782.         }

  783.         if (!this.runDependentValidators(va, results, actions, params, pos)) {
  784.             return false;
  785.         }

  786.         return va.executeValidationMethod(this, params, results, pos);
  787.     }
  788. }