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