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}