    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 &lt;form&gt; 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}