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