    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}