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    *      https://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.Locale;
23  import java.util.Map;
24  import java.util.Map.Entry;
25  import java.util.Objects;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  /**
31   * Holds a set of {@code Form}s stored associated with a {@link Locale}
32   * based on the country, language, and variant specified. Instances of this
33   * class are configured with a <formset> xml element.
34   */
35  public class FormSet implements Serializable {
36  
37      private static final long serialVersionUID = -8936513232763306055L;
38  
39      /**
40       * This is the type of {@code FormSet}s where no locale is specified.
41       */
42      protected static final int GLOBAL_FORMSET = 1;
43  
44      /**
45       * This is the type of {@code FormSet}s where only language locale is
46       * specified.
47       */
48      protected static final int LANGUAGE_FORMSET = 2;
49  
50      /**
51       * This is the type of {@code FormSet}s where only language and country
52       * locale are specified.
53       */
54      protected static final int COUNTRY_FORMSET = 3;
55  
56      /**
57       * This is the type of {@code FormSet}s where full locale has been set.
58       */
59      protected static final int VARIANT_FORMSET = 4;
60  
61      /** Logging */
62      private transient Log log = LogFactory.getLog(FormSet.class);
63  
64      /**
65       * Whether or not the this {@code FormSet} was processed for replacing
66       * variables in strings with their values.
67       */
68      private boolean processed;
69  
70      /** Language component of {@link Locale} (required). */
71      private String language;
72  
73      /** Country component of {@link Locale} (optional). */
74      private String country;
75  
76      /** Variant component of {@link Locale} (optional). */
77      private String variant;
78  
79      /**
80       * A {@link Map} of {@code Form}s using the name field of the
81       * {@code Form} as the key.
82       */
83      private final Map<String, Form> forms = new HashMap<>();
84  
85      /**
86       * A {@link Map} of {@code Constant}s using the name field of the
87       * {@code Constant} as the key.
88       */
89      private final Map<String, String> constants = new HashMap<>();
90  
91      /**
92       * Flag indicating if this formSet has been merged with its parent (higher
93       * rank in Locale hierarchy).
94       */
95      private volatile boolean merged;
96  
97      /**
98       * Constructs a new instance.
99       */
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 }