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