    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.Collections;
    021import java.util.HashMap;
    022import java.util.Map;
    023import java.util.Map.Entry;
    024
    025import org.apache.commons.logging.Log;
    026import org.apache.commons.logging.LogFactory;
    027
    028/**
    029 * Holds a set of <code>Form</code>s stored associated with a <code>Locale</code>
    030 * based on the country, language, and variant specified. Instances of this
    031 * class are configured with a &lt;formset&gt; xml element.
    032 */
    033public class FormSet implements Serializable {
    034
    035    private static final long serialVersionUID = -8936513232763306055L;
    036
    037    /**
    038     * This is the type of <code>FormSet</code>s where no locale is specified.
    039     */
    040    protected final static int GLOBAL_FORMSET = 1;
    041
    042    /**
    043     * This is the type of <code>FormSet</code>s where only language locale is
    044     * specified.
    045     */
    046    protected final static int LANGUAGE_FORMSET = 2;
    047
    048    /**
    049     * This is the type of <code>FormSet</code>s where only language and country
    050     * locale are specified.
    051     */
    052    protected final static int COUNTRY_FORMSET = 3;
    053
    054    /**
    055     * This is the type of <code>FormSet</code>s where full locale has been set.
    056     */
    057    protected final static int VARIANT_FORMSET = 4;
    058
    059    /** Logging */
    060    private transient Log log = LogFactory.getLog(FormSet.class);
    061
    062    /**
    063     * Whether or not the this <code>FormSet</code> was processed for replacing
    064     * variables in strings with their values.
    065     */
    066    private boolean processed;
    067
    068    /** Language component of <code>Locale</code> (required). */
    069    private String language;
    070
    071    /** Country component of <code>Locale</code> (optional). */
    072    private String country;
    073
    074    /** Variant component of <code>Locale</code> (optional). */
    075    private String variant;
    076
    077    /**
    078     * A <code>Map</code> of <code>Form</code>s using the name field of the
    079     * <code>Form</code> as the key.
    080     */
    081    private final Map<String, Form> forms = new HashMap<>();
    082
    083    /**
    084     * A <code>Map</code> of <code>Constant</code>s using the name field of the
    085     * <code>Constant</code> as the key.
    086     */
    087    private final Map<String, String> constants = new HashMap<>();
    088
    089    /**
    090     * Flag indicating if this formSet has been merged with its parent (higher
    091     * rank in Locale hierarchy).
    092     */
    093    private boolean merged;
    094
    095    /**
    096     * Add a <code>Constant</code> to the locale level.
    097     *
    098     * @param name   The constant name
    099     * @param value  The constant value
    100     */
    101    public void addConstant(final String name, final String value) {
    102        if (constants.containsKey(name)) {
    103            getLog().error("Constant '" + name + "' already exists in FormSet[" + this.displayKey() + "] - ignoring.");
    104        } else {
    105            constants.put(name, value);
    106        }
    107    }
    108
    109    /**
    110     * Add a <code>Form</code> to the <code>FormSet</code>.
    111     *
    112     * @param f  The form
    113     */
    114    public void addForm(final Form f) {
    115
    116        final String formName = f.getName();
    117        if (forms.containsKey(formName)) {
    118            getLog().error("Form '" + formName + "' already exists in FormSet[" + this.displayKey() + "] - ignoring.");
    119
    120        } else {
    121            forms.put(f.getName(), f);
    122        }
    123
    124    }
    125
    126    /**
    127     * Returns a string representation of the object's key.
    128     *
    129     * @return   A string representation of the key
    130     */
    131    public String displayKey() {
    132        final StringBuilder results = new StringBuilder();
    133        if (language != null && !language.isEmpty()) {
    134            results.append("language=");
    135            results.append(language);
    136        }
    137        if (country != null && !country.isEmpty()) {
    138            if (results.length() > 0) {
    139                results.append(", ");
    140            }
    141            results.append("country=");
    142            results.append(country);
    143        }
    144        if (variant != null && !variant.isEmpty()) {
    145            if (results.length() > 0) {
    146                results.append(", ");
    147            }
    148            results.append("variant=");
    149            results.append(variant);
    150        }
    151        if (results.length() == 0) {
    152            results.append("default");
    153        }
    154
    155        return results.toString();
    156    }
    157
    158    /**
    159     * Gets the equivalent of the country component of <code>Locale</code>.
    160     *
    161     * @return   The country value
    162     */
    163    public String getCountry() {
    164        return country;
    165    }
    166
    167    /**
    168     * Retrieve a <code>Form</code> based on the form name.
    169     *
    170     * @param formName  The form name
    171     * @return          The form
    172     */
    173    public Form getForm(final String formName) {
    174        return this.forms.get(formName);
    175    }
    176
    177    /**
    178     * A <code>Map</code> of <code>Form</code>s is returned as an unmodifiable
    179     * <code>Map</code> with the key based on the form name.
    180     *
    181     * @return   The forms map
    182     */
    183    public Map<String, Form> getForms() {
    184        return Collections.unmodifiableMap(forms);
    185    }
    186
    187    /**
    188     * Gets the equivalent of the language component of <code>Locale</code>.
    189     *
    190     * @return   The language value
    191     */
    192    public String getLanguage() {
    193        return language;
    194    }
    195
    196    /**
    197     * Accessor method for Log instance.
    198     *
    199     * The Log instance variable is transient and
    200     * accessing it through this method ensures it
    201     * is re-initialized when this instance is
    202     * de-serialized.
    203     *
    204     * @return The Log instance.
    205     */
    206    private Log getLog() {
    207        if (log == null) {
    208            log = LogFactory.getLog(FormSet.class);
    209        }
    210        return log;
    211    }
    212
    213    /**
    214     * Returns the type of <code>FormSet</code>:<code>GLOBAL_FORMSET</code>,
    215     * <code>LANGUAGE_FORMSET</code>,<code>COUNTRY_FORMSET</code> or <code>VARIANT_FORMSET</code>
    216     * .
    217     *
    218     * @return                       The type value
    219     * @since 1.2.0
    220     * @throws NullPointerException  if there is inconsistency in the locale
    221     *      definition (not sure about this)
    222     */
    223    protected int getType() {
    224        if (getVariant() != null) {
    225            if (getLanguage() == null || getCountry() == null) {
    226                throw new NullPointerException("When variant is specified, country and language must be specified.");
    227            }
    228            return VARIANT_FORMSET;
    229        }
    230        if (getCountry() != null) {
    231            if (getLanguage() == null) {
    232                throw new NullPointerException("When country is specified, language must be specified.");
    233            }
    234            return COUNTRY_FORMSET;
    235        }
    236        if (getLanguage() != null) {
    237            return LANGUAGE_FORMSET;
    238        }
    239        return GLOBAL_FORMSET;
    240    }
    241
    242    /**
    243     * Gets the equivalent of the variant component of <code>Locale</code>.
    244     *
    245     * @return   The variant value
    246     */
    247    public String getVariant() {
    248        return variant;
    249    }
    250
    251    /**
    252     * Has this formSet been merged?
    253     *
    254     * @return   true if it has been merged
    255     * @since 1.2.0
    256     */
    257    protected boolean isMerged() {
    258        return merged;
    259    }
    260
    261    /**
    262     * Whether or not the this <code>FormSet</code> was processed for replacing
    263     * variables in strings with their values.
    264     *
    265     * @return   The processed value
    266     */
    267    public boolean isProcessed() {
    268        return processed;
    269    }
    270
    271    /**
    272     * Merges the given <code>FormSet</code> into this one. If any of <code>depends</code>
    273     * s <code>Forms</code> are not in this <code>FormSet</code> then, include
    274     * them, else merge both <code>Forms</code>. Theoretically we should only
    275     * merge a "parent" formSet.
    276     *
    277     * @param depends  FormSet to be merged
    278     * @since 1.2.0
    279     */
    280    protected void merge(final FormSet depends) {
    281        if (depends != null) {
    282            final Map<String, Form> pForms = getForms();
    283            final Map<String, Form> dForms = depends.getForms();
    284            for (final Entry<String, Form> entry : dForms.entrySet()) {
    285                final String key = entry.getKey();
    286                final Form pForm = pForms.get(key);
    287                if (pForm != null) { // merge, but principal 'rules', don't overwrite
    288                    // anything
    289                    pForm.merge(entry.getValue());
    290                } else { // just add
    291                    addForm(entry.getValue());
    292                }
    293            }
    294        }
    295        merged = true;
    296    }
    297
    298    /**
    299     * Processes all of the <code>Form</code>s.
    300     *
    301     * @param globalConstants  Global constants
    302     */
    303    synchronized void process(final Map<String, String> globalConstants) {
    304        for (final Form f : forms.values()) {
    305            f.process(globalConstants, constants, forms);
    306        }
    307
    308        processed = true;
    309    }
    310
    311    /**
    312     * Sets the equivalent of the country component of <code>Locale</code>.
    313     *
    314     * @param country  The new country value
    315     */
    316    public void setCountry(final String country) {
    317        this.country = country;
    318    }
    319
    320    /**
    321     * Sets the equivalent of the language component of <code>Locale</code>.
    322     *
    323     * @param language  The new language value
    324     */
    325    public void setLanguage(final String language) {
    326        this.language = language;
    327    }
    328
    329    /**
    330     * Sets the equivalent of the variant component of <code>Locale</code>.
    331     *
    332     * @param variant  The new variant value
    333     */
    334    public void setVariant(final String variant) {
    335        this.variant = variant;
    336    }
    337
    338    /**
    339     * Returns a string representation of the object.
    340     *
    341     * @return   A string representation
    342     */
    343    @Override
    344    public String toString() {
    345        final StringBuilder results = new StringBuilder();
    346
    347        results.append("FormSet: language=");
    348        results.append(language);
    349        results.append("  country=");
    350        results.append(country);
    351        results.append("  variant=");
    352        results.append(variant);
    353        results.append("\n");
    354
    355        for (final Object name : getForms().values()) {
    356            results.append("   ");
    357            results.append(name);
    358            results.append("\n");
    359        }
    360
    361        return results.toString();
    362    }
    363}