View Javadoc
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    *      https://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.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.collections.FastHashMap; // DEPRECATED
27  
28  /**
29   * <p>
30   * This contains a set of validation rules for a form/JavaBean. The information
31   * is contained in a list of {@code Field} objects. Instances of this class
32   * are configured with a &lt;form&gt; xml element.
33   * </p>
34   * <p>
35   * The use of FastHashMap is deprecated and will be replaced in a future
36   * release.
37   * </p>
38   */
39  //TODO mutable non-private fields
40  public class Form implements Serializable {
41  
42      private static final long serialVersionUID = 6445211789563796371L;
43  
44      /** The name/key the set of validation rules is stored under. */
45      protected String name;
46  
47      /**
48       * List of {@code Field}s. Used to maintain the order they were added
49       * in although individual {@code Field}s can be retrieved using {@link Map}
50       * of {@code Field}s.
51       */
52      protected List<Field> lFields = new ArrayList<>();
53  
54      /**
55       * Map of {@code Field}s keyed on their property value.
56       *
57       * @deprecated   Subclasses should use getFieldMap() instead.
58       */
59      @Deprecated
60      protected FastHashMap hFields = new FastHashMap(); // <String, Field>
61  
62      /**
63       * The name/key of the form which this form extends from.
64       *
65       * @since 1.2.0
66       */
67      protected String inherit;
68  
69      /**
70       * Whether or not the this {@code Form} was processed for replacing
71       * variables in strings with their values.
72       */
73      private boolean processed;
74  
75      /**
76       * Constructs a new instance.
77       */
78      public Form() {
79          // empty
80      }
81  
82      /**
83       * Add a {@code Field} to the {@code Form}.
84       *
85       * @param f  The field
86       */
87      public void addField(final Field f) {
88          lFields.add(f);
89          getFieldMap().put(f.getKey(), f);
90      }
91  
92      /**
93       * Returns true if this Form contains a Field with the given name.
94       *
95       * @param fieldName  The field name
96       * @return           True if this form contains the field by the given name
97       * @since 1.1
98       */
99      public boolean containsField(final String fieldName) {
100         return getFieldMap().containsKey(fieldName);
101     }
102 
103     /**
104      * Gets the name/key of the parent set of validation rules.
105      *
106      * @return   The extends value
107      * @since 1.2.0
108      */
109     public String getExtends() {
110         return inherit;
111     }
112 
113     /**
114      * Returns the Field with the given name or null if this Form has no such
115      * field.
116      *
117      * @param fieldName  The field name
118      * @return           The field value
119      * @since 1.1
120      */
121     public Field getField(final String fieldName) {
122         return getFieldMap().get(fieldName);
123     }
124 
125     /**
126      * Returns a Map of String field keys to Field objects.
127      *
128      * @return   The fieldMap value
129      * @since 1.2.0
130      */
131     @SuppressWarnings("unchecked") // FastHashMap is not generic
132     protected Map<String, Field> getFieldMap() {
133         return hFields;
134     }
135 
136     /**
137      * A {@code List} of {@code Field}s is returned as an unmodifiable
138      * {@code List}.
139      *
140      * @return   The fields value
141      */
142     public List<Field> getFields() {
143         return Collections.unmodifiableList(lFields);
144     }
145 
146     /**
147      * Gets the name/key of the set of validation rules.
148      *
149      * @return   The name value
150      */
151     public String getName() {
152         return name;
153     }
154 
155     /**
156      * Gets extends flag.
157      *
158      * @return   The extending value
159      * @since 1.2.0
160      */
161     public boolean isExtending() {
162         return inherit != null;
163     }
164 
165     /**
166      * Whether or not the this {@code Form} was processed for replacing
167      * variables in strings with their values.
168      *
169      * @return   The processed value
170      * @since 1.2.0
171      */
172     public boolean isProcessed() {
173         return processed;
174     }
175 
176     /**
177      * Merges the given form into this one. For any field in {@code depends}
178      * not present in this form, include it. {@code depends} has precedence
179      * in the way the fields are ordered.
180      *
181      * @param depends  the form we want to merge
182      * @since 1.2.0
183      */
184     protected void merge(final Form depends) {
185         final List<Field> templFields = new ArrayList<>();
186         @SuppressWarnings("unchecked") // FastHashMap is not generic
187         final Map<String, Field> temphFields = new FastHashMap();
188         for (final Field defaultField : depends.getFields()) {
189             if (defaultField != null) {
190                 final String fieldKey = defaultField.getKey();
191                 if (!containsField(fieldKey)) {
192                     templFields.add(defaultField);
193                     temphFields.put(fieldKey, defaultField);
194                 } else {
195                     final Field old = getField(fieldKey);
196                     getFieldMap().remove(fieldKey);
197                     lFields.remove(old);
198                     templFields.add(old);
199                     temphFields.put(fieldKey, old);
200                 }
201             }
202         }
203         lFields.addAll(0, templFields);
204         getFieldMap().putAll(temphFields);
205     }
206 
207     /**
208      * Processes all of the {@code Form}'s {@code Field}s.
209      *
210      * @param globalConstants  A map of global constants
211      * @param constants        Local constants
212      * @param forms            Map of forms
213      * @since 1.2.0
214      */
215     protected void process(final Map<String, String> globalConstants, final Map<String, String> constants, final Map<String, Form> forms) {
216         if (isProcessed()) {
217             return;
218         }
219 
220         int n = 0; //we want the fields from its parent first
221         if (isExtending()) {
222             final Form parent = forms.get(inherit);
223             if (parent != null) {
224                 if (!parent.isProcessed()) {
225                     // we want to go all the way up the tree
226                     parent.process(constants, globalConstants, forms);
227                 }
228                 for (final Field f : parent.getFields()) {
229                     // we want to be able to override any fields we like
230                     if (getFieldMap().get(f.getKey()) == null) {
231                         lFields.add(n, f);
232                         getFieldMap().put(f.getKey(), f);
233                         n++;
234                     }
235                 }
236             }
237         }
238         hFields.setFast(true);
239         // no need to reprocess parent's fields, we iterate from 'n'
240         for (final Iterator<Field> i = lFields.listIterator(n); i.hasNext(); ) {
241             final Field f = i.next();
242             f.process(globalConstants, constants);
243         }
244 
245         processed = true;
246     }
247 
248     /**
249      * Sets the name/key of the parent set of validation rules.
250      *
251      * @param inherit  The new extends value
252      * @since 1.2.0
253      */
254     public void setExtends(final String inherit) {
255         this.inherit = inherit;
256     }
257 
258     /**
259      * Sets the name/key of the set of validation rules.
260      *
261      * @param name  The new name value
262      */
263     public void setName(final String name) {
264         this.name = name;
265     }
266 
267     /**
268      * Returns a string representation of the object.
269      *
270      * @return string representation
271      */
272     @Override
273     public String toString() {
274         final StringBuilder results = new StringBuilder();
275 
276         results.append("Form: ");
277         results.append(name);
278         results.append("\n");
279 
280         for (final Field lField : lFields) {
281             results.append("\tField: \n");
282             results.append(lField);
283             results.append("\n");
284         }
285 
286         return results.toString();
287     }
288 
289     /**
290      * Validate all Fields in this Form on the given page and below.
291      *
292      * @param params               A Map of parameter class names to parameter
293      *      values to pass into validation methods.
294      * @param actions              A Map of validator names to ValidatorAction
295      *      objects.
296      * @param page                 Fields on pages higher than this will not be
297      *      validated.
298      * @return                     A ValidatorResults object containing all
299      *      validation messages.
300      * @throws ValidatorException
301      */
302     ValidatorResults validate(final Map<String, Object> params, final Map<String, ValidatorAction> actions, final int page)
303         throws ValidatorException {
304         return validate(params, actions, page, null);
305     }
306 
307     /**
308      * Validate all Fields in this Form on the given page and below.
309      *
310      * @param params               A Map of parameter class names to parameter
311      *      values to pass into validation methods.
312      * @param actions              A Map of validator names to ValidatorAction
313      *      objects.
314      * @param page                 Fields on pages higher than this will not be
315      *      validated.
316      * @return                     A ValidatorResults object containing all
317      *      validation messages.
318      * @throws ValidatorException
319      * @since 1.2.0
320      */
321     ValidatorResults validate(final Map<String, Object> params, final Map<String, ValidatorAction> actions, final int page, final String fieldName)
322             throws ValidatorException {
323         final ValidatorResults results = new ValidatorResults();
324         params.put(Validator.VALIDATOR_RESULTS_PARAM, results);
325 
326         // Only validate a single field if specified
327         if (fieldName != null) {
328             final Field field = getFieldMap().get(fieldName);
329 
330             if (field == null) {
331                 throw new ValidatorException("Unknown field " + fieldName + " in form " + getName());
332             }
333             params.put(Validator.FIELD_PARAM, field);
334 
335             if (field.getPage() <= page) {
336                 results.merge(field.validate(params, actions));
337             }
338         } else {
339             for (final Field field : lFields) {
340 
341                 params.put(Validator.FIELD_PARAM, field);
342 
343                 if (field.getPage() <= page) {
344                     results.merge(field.validate(params, actions));
345                 }
346             }
347         }
348 
349         return results;
350     }
351 }