    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}