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