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