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.util.ArrayList;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.commons.collections.FastHashMap; // DEPRECATED
027
028/**
029 * <p>
030 * This contains a set of validation rules for a form/JavaBean. The information
031 * is contained in a list of {@code Field} objects. Instances of this class
032 * are configured with a &lt;form&gt; xml element.
033 * </p>
034 * <p>
035 * The use of FastHashMap is deprecated and will be replaced in a future
036 * release.
037 * </p>
038 */
039//TODO mutable non-private fields
040public class Form implements Serializable {
041
042    private static final long serialVersionUID = 6445211789563796371L;
043
044    /** The name/key the set of validation rules is stored under. */
045    protected String name;
046
047    /**
048     * List of {@code Field}s. Used to maintain the order they were added
049     * in although individual {@code Field}s can be retrieved using {@link Map}
050     * of {@code Field}s.
051     */
052    protected List<Field> lFields = new ArrayList<>();
053
054    /**
055     * Map of {@code Field}s keyed on their property value.
056     *
057     * @deprecated   Subclasses should use getFieldMap() instead.
058     */
059    @Deprecated
060    protected FastHashMap hFields = new FastHashMap(); // <String, Field>
061
062    /**
063     * The name/key of the form which this form extends from.
064     *
065     * @since 1.2.0
066     */
067    protected String inherit;
068
069    /**
070     * Whether or not the this {@code Form} was processed for replacing
071     * variables in strings with their values.
072     */
073    private boolean processed;
074
075    /**
076     * Constructs a new instance.
077     */
078    public Form() {
079        // empty
080    }
081
082    /**
083     * Add a {@code Field} to the {@code Form}.
084     *
085     * @param f  The field
086     */
087    public void addField(final Field f) {
088        lFields.add(f);
089        getFieldMap().put(f.getKey(), f);
090    }
091
092    /**
093     * Returns true if this Form contains a Field with the given name.
094     *
095     * @param fieldName  The field name
096     * @return           True if this form contains the field by the given name
097     * @since 1.1
098     */
099    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}