View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.validator;
18  
19  import java.io.Serializable;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.Map;
23  import java.util.Map.Entry;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  /**
29   * Holds a set of <code>Form</code>s stored associated with a <code>Locale</code>
30   * based on the country, language, and variant specified. Instances of this
31   * class are configured with a &lt;formset&gt; xml element.
32   */
33  public class FormSet implements Serializable {
34  
35      private static final long serialVersionUID = -8936513232763306055L;
36  
37      /**
38       * This is the type of <code>FormSet</code>s where no locale is specified.
39       */
40      protected final static int GLOBAL_FORMSET = 1;
41  
42      /**
43       * This is the type of <code>FormSet</code>s where only language locale is
44       * specified.
45       */
46      protected final static int LANGUAGE_FORMSET = 2;
47  
48      /**
49       * This is the type of <code>FormSet</code>s where only language and country
50       * locale are specified.
51       */
52      protected final static int COUNTRY_FORMSET = 3;
53  
54      /**
55       * This is the type of <code>FormSet</code>s where full locale has been set.
56       */
57      protected final static int VARIANT_FORMSET = 4;
58  
59      /** Logging */
60      private transient Log log = LogFactory.getLog(FormSet.class);
61  
62      /**
63       * Whether or not the this <code>FormSet</code> was processed for replacing
64       * variables in strings with their values.
65       */
66      private boolean processed;
67  
68      /** Language component of <code>Locale</code> (required). */
69      private String language;
70  
71      /** Country component of <code>Locale</code> (optional). */
72      private String country;
73  
74      /** Variant component of <code>Locale</code> (optional). */
75      private String variant;
76  
77      /**
78       * A <code>Map</code> of <code>Form</code>s using the name field of the
79       * <code>Form</code> as the key.
80       */
81      private final Map<String, Form> forms = new HashMap<>();
82  
83      /**
84       * A <code>Map</code> of <code>Constant</code>s using the name field of the
85       * <code>Constant</code> as the key.
86       */
87      private final Map<String, String> constants = new HashMap<>();
88  
89      /**
90       * Flag indicating if this formSet has been merged with its parent (higher
91       * rank in Locale hierarchy).
92       */
93      private boolean merged;
94  
95      /**
96       * Add a <code>Constant</code> to the locale level.
97       *
98       * @param name   The constant name
99       * @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 }