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 * This contains a set of validation rules for a form/JavaBean. The information
030 * is contained in a list of {@code Field} objects. Instances of this class
031 * are configured with a <form> xml element.
032 * <p>
033 * The use of FastHashMap is deprecated and will be replaced in a future
034 * release.
035 * </p>
036 */
037//TODO mutable non-private fields
038public class Form implements Serializable {
039
040    private static final long serialVersionUID = 6445211789563796371L;
041
042    /** The name/key the set of validation rules is stored under. */
043    protected String name;
044
045    /**
046     * List of {@code Field}s. Used to maintain the order they were added
047     * in although individual {@code Field}s can be retrieved using {@link Map}
048     * of {@code Field}s.
049     */
050    protected List<Field> lFields = new ArrayList<>();
051
052    /**
053     * Map of {@code Field}s keyed on their property value.
054     *
055     * @deprecated   Subclasses should use getFieldMap() instead.
056     */
057    @Deprecated
058    protected FastHashMap hFields = new FastHashMap(); // <String, Field>
059
060    /**
061     * The name/key of the form which this form extends from.
062     *
063     * @since 1.2.0
064     */
065    protected String inherit;
066
067    /**
068     * Whether or not the this {@code Form} was processed for replacing
069     * variables in strings with their values.
070     */
071    private boolean processed;
072
073    /**
074     * Constructs a new instance.
075     */
076    public Form() {
077        // empty
078    }
079
080    /**
081     * Add a {@code Field} to the {@code Form}.
082     *
083     * @param f  The field
084     */
085    public void addField(final Field f) {
086        lFields.add(f);
087        getFieldMap().put(f.getKey(), f);
088    }
089
090    /**
091     * Returns true if this Form contains a Field with the given name.
092     *
093     * @param fieldName  The field name
094     * @return           True if this form contains the field by the given name
095     * @since 1.1
096     */
097    public boolean containsField(final String fieldName) {
098        return getFieldMap().containsKey(fieldName);
099    }
100
101    /**
102     * Gets the name/key of the parent set of validation rules.
103     *
104     * @return   The extends value
105     * @since 1.2.0
106     */
107    public String getExtends() {
108        return inherit;
109    }
110
111    /**
112     * Returns the Field with the given name or null if this Form has no such
113     * field.
114     *
115     * @param fieldName  The field name
116     * @return           The field value
117     * @since 1.1
118     */
119    public Field getField(final String fieldName) {
120        return getFieldMap().get(fieldName);
121    }
122
123    /**
124     * Returns a Map of String field keys to Field objects.
125     *
126     * @return   The fieldMap value
127     * @since 1.2.0
128     */
129    @SuppressWarnings("unchecked") // FastHashMap is not generic
130    protected Map<String, Field> getFieldMap() {
131        return hFields;
132    }
133
134    /**
135     * A {@code List} of {@code Field}s is returned as an unmodifiable
136     * {@code List}.
137     *
138     * @return   The fields value
139     */
140    public List<Field> getFields() {
141        return Collections.unmodifiableList(lFields);
142    }
143
144    /**
145     * Gets the name/key of the set of validation rules.
146     *
147     * @return   The name value
148     */
149    public String getName() {
150        return name;
151    }
152
153    /**
154     * Gets extends flag.
155     *
156     * @return   The extending value
157     * @since 1.2.0
158     */
159    public boolean isExtending() {
160        return inherit != null;
161    }
162
163    /**
164     * Whether or not the this {@code Form} was processed for replacing
165     * variables in strings with their values.
166     *
167     * @return   The processed value
168     * @since 1.2.0
169     */
170    public boolean isProcessed() {
171        return processed;
172    }
173
174    /**
175     * Merges the given form into this one. For any field in {@code depends}
176     * not present in this form, include it. {@code depends} has precedence
177     * in the way the fields are ordered.
178     *
179     * @param depends  The form we want to merge
180     * @since 1.2.0
181     */
182    protected void merge(final Form depends) {
183        final List<Field> templFields = new ArrayList<>();
184        @SuppressWarnings("unchecked") // FastHashMap is not generic
185        final Map<String, Field> temphFields = new FastHashMap();
186        for (final Field defaultField : depends.getFields()) {
187            if (defaultField != null) {
188                final String fieldKey = defaultField.getKey();
189                if (!containsField(fieldKey)) {
190                    templFields.add(defaultField);
191                    temphFields.put(fieldKey, defaultField);
192                } else {
193                    final Field old = getField(fieldKey);
194                    getFieldMap().remove(fieldKey);
195                    lFields.remove(old);
196                    templFields.add(old);
197                    temphFields.put(fieldKey, old);
198                }
199            }
200        }
201        lFields.addAll(0, templFields);
202        getFieldMap().putAll(temphFields);
203    }
204
205    /**
206     * Processes all of the {@code Form}'s {@code Field}s.
207     *
208     * @param globalConstants  A map of global constants
209     * @param constants        Local constants
210     * @param forms            Map of forms
211     * @since 1.2.0
212     */
213    protected void process(final Map<String, String> globalConstants, final Map<String, String> constants, final Map<String, Form> forms) {
214        if (isProcessed()) {
215            return;
216        }
217
218        int n = 0; //we want the fields from its parent first
219        if (isExtending()) {
220            final Form parent = forms.get(inherit);
221            if (parent != null) {
222                if (!parent.isProcessed()) {
223                    // we want to go all the way up the tree
224                    parent.process(globalConstants, constants, forms);
225                }
226                for (final Field f : parent.getFields()) {
227                    // we want to be able to override any fields we like
228                    if (getFieldMap().get(f.getKey()) == null) {
229                        lFields.add(n, f);
230                        getFieldMap().put(f.getKey(), f);
231                        n++;
232                    }
233                }
234            }
235        }
236        hFields.setFast(true);
237        // no need to reprocess parent's fields, we iterate from 'n'
238        for (final Iterator<Field> i = lFields.listIterator(n); i.hasNext(); ) {
239            final Field f = i.next();
240            f.process(globalConstants, constants);
241        }
242
243        processed = true;
244    }
245
246    /**
247     * Sets the name/key of the parent set of validation rules.
248     *
249     * @param inherit  The new extends value
250     * @since 1.2.0
251     */
252    public void setExtends(final String inherit) {
253        this.inherit = inherit;
254    }
255
256    /**
257     * Sets the name/key of the set of validation rules.
258     *
259     * @param name  The new name value
260     */
261    public void setName(final String name) {
262        this.name = name;
263    }
264
265    /**
266     * Returns a string representation of the object.
267     *
268     * @return string representation
269     */
270    @Override
271    public String toString() {
272        final StringBuilder results = new StringBuilder();
273
274        results.append("Form: ");
275        results.append(name);
276        results.append("\n");
277
278        for (final Field lField : lFields) {
279            results.append("\tField: \n");
280            results.append(lField);
281            results.append("\n");
282        }
283
284        return results.toString();
285    }
286
287    /**
288     * Validate all Fields in this Form on the given page and below.
289     *
290     * @param params               A Map of parameter class names to parameter
291     *      values to pass into validation methods.
292     * @param actions              A Map of validator names to ValidatorAction
293     *      objects.
294     * @param page                 Fields on pages higher than this will not be
295     *      validated.
296     * @return                     A ValidatorResults object containing all
297     *      validation messages.
298     * @throws ValidatorException
299     */
300    ValidatorResults validate(final Map<String, Object> params, final Map<String, ValidatorAction> actions, final int page)
301        throws ValidatorException {
302        return validate(params, actions, page, null);
303    }
304
305    /**
306     * Validate all Fields in this Form on the given page and below.
307     *
308     * @param params  A Map of parameter class names to parameter values to pass into validation methods.
309     * @param actions A Map of validator names to ValidatorAction objects.
310     * @param page    Fields on pages higher than this will not be validated.
311     * @return A ValidatorResults object containing all validation messages.
312     * @throws ValidatorException
313     * @since 1.2.0
314     */
315    ValidatorResults validate(final Map<String, Object> params, final Map<String, ValidatorAction> actions, final int page, final String fieldName)
316            throws ValidatorException {
317        final ValidatorResults results = new ValidatorResults();
318        params.put(Validator.VALIDATOR_RESULTS_PARAM, results);
319        // Only validate a single field if specified
320        if (fieldName != null) {
321            final Field field = getFieldMap().get(fieldName);
322            if (field == null) {
323                throw new ValidatorException("Unknown field %s in form %s", fieldName, getName());
324            }
325            params.put(Validator.FIELD_PARAM, field);
326            if (field.getPage() <= page) {
327                results.merge(field.validate(params, actions));
328            }
329        } else {
330            for (final Field field : lFields) {
331                params.put(Validator.FIELD_PARAM, field);
332                if (field.getPage() <= page) {
333                    results.merge(field.validate(params, actions));
334                }
335            }
336        }
337        return results;
338    }
339}