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 }