View Javadoc

1   /*
2    * $Id: CollectionResourcesBase.java 354330 2005-12-06 06:05:19Z niallp $
3    * $Revision: 354330 $
4    * $Date: 2005-12-06 06:05:19 +0000 (Tue, 06 Dec 2005) $
5    *
6    * ====================================================================
7    *
8    *  Copyright 2003-2005 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   *
22   */
23  
24  package org.apache.commons.resources.impl;
25  
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.commons.resources.ResourcesException;
38  import org.apache.commons.resources.ResourcesKeyException;
39  
40  /**
41   * <p>Abstract base classes for 
42   * {@link org.apache.commons.resources.Resources} implementations that
43   * store their name-value mappings for each supported <code>Locale</code>
44   * in a URL-accessible resource file with a common base URL.  Subclasses
45   * need only override <code>loadLocale()</code> to manage the details of
46   * loading the name-value mappings for a particular Locale.</p>
47   */
48  public abstract class CollectionResourcesBase extends ResourcesBase {
49  
50      /**
51       * <p>The logging instance for this class.</p>
52       */
53      private transient Log log =
54          LogFactory.getLog(CollectionResourcesBase.class);
55  
56      /**
57       * <p>Create a new {@link org.apache.commons.resources.Resources} instance with the specified
58       * logical name and base URL.</p>
59       *
60       * @param name Logical name of the new instance
61       * @param base Base URL of the resource files that contain the per-Locale
62       *  name-value mappings for this {@link org.apache.commons.resources.Resources} instance
63       */
64      public CollectionResourcesBase(String name, String base) {
65          super(name);
66          this.base = base;
67      }
68  
69  
70      // ----------------------------------------------------- Instance Variables
71  
72  
73      /**
74       * <p>The base URL for the per-Locale resources files containing the
75       * name-value mappings for this {@link org.apache.commons.resources.Resources} instance.</p>
76       */
77      private String base = null;
78  
79  
80      /**
81       * <p>The default <code>Locale</code> to use when no <code>Locale</code>
82       * is specified by the caller.</p>
83       */
84      private Locale defaultLocale = Locale.getDefault();
85  
86  
87      /**
88       * <p>The previously calculated <code>Locale</code> lists returned
89       * by <code>getLocaleList()</code>, keyed by <code>Locale</code>.</p>
90       */
91      private Map lists = new HashMap();
92  
93  
94      /**
95       * <p>The previously calculated name-value mappings <code>Map</code>s
96       * returned by <code>getLocaleMap()</code>, keyed by <code>Locale</code>.
97       * </p>
98       */
99      private Map maps = new HashMap();
100 
101 
102     // ------------------------------------------------------------- Properties
103 
104 
105     /**
106      * Set the default locale.
107      * @param defaultLocale The default Locale.
108      */
109     public void setDefaultLocale(Locale defaultLocale) {
110         this.defaultLocale = defaultLocale;
111     }
112 
113     /**
114      * Return the default locale.
115      * @return The default Locale.
116      */
117     public Locale getDefaultLocale() {
118         return defaultLocale;
119     }
120 
121     /**
122      * @see org.apache.commons.resources.impl.ResourcesBase#getKeys()
123      */
124     public Iterator getKeys() {
125 
126         synchronized (maps) {
127 
128             Set results = new HashSet();
129             Iterator locales = maps.keySet().iterator();
130             while (locales.hasNext()) {
131                 Locale locale = (Locale) locales.next();
132                 Map map = (Map) maps.get(locale);
133                 results.addAll(map.keySet());
134             }
135             return (results.iterator());
136 
137         }
138 
139 
140 
141 
142     }
143 
144 
145     // ---------------------------------------------- Content Retrieval Methods
146 
147 
148     /**
149      * <p>Return the content for the specified <code>key</code> as an
150      * Object, localized based on the specified <code>locale</code>.
151      * </p>
152      *
153      * @param key Identifier for the requested content
154      * @param locale Locale with which to localize retrieval,
155      *  or <code>null</code> for the default Locale
156      * @return The content for the specified key.
157      *
158      * @exception ResourcesException if an error occurs retrieving or
159      *  returning the requested content
160      * @exception ResourcesKeyException if the no value for the specified
161      *  key was found, and <code>isReturnNull()</code> returns
162      *  <code>false</code>
163      */
164     public Object getObject(String key, Locale locale) {
165 
166         if (getLog().isTraceEnabled()) {
167             getLog().trace("Retrieving message for key '" + key + "' and locale '"
168                       + locale + "'");
169         }
170 
171         if (locale == null) {
172             locale = defaultLocale;
173         }
174 
175         // Prepare local variables we will need
176         List list = getLocaleList(locale);
177         int n = list.size();
178 
179         // Search through the Locale hierarchy for this resource key
180         for (int i = 0; i < n; i++) {
181             Map map = getLocaleMap((Locale) list.get(i));
182             if (map.containsKey(key)) {
183                 Object object  = map.get(key);
184                 if (getLog().isTraceEnabled()) {
185                     getLog().trace("Retrieved object for key '" + key + 
186                                    "' and locale '" + locale +
187                                    "' is '" + object + "'");
188                 }
189                 return object;
190             }
191         }
192 
193         if (getLog().isTraceEnabled()) {
194             getLog().trace("No message found for key '" + key + 
195                            "' and locale '" + locale + "'");
196         }
197 
198         // No value for this key was located in the entire hierarchy
199         if (isReturnNull()) {
200             return (null);
201         } else {
202             throw new ResourcesKeyException(key);
203         }
204 
205     }
206 
207 
208     // ------------------------------------------------------ Lifecycle Methods
209 
210 
211     /**
212      * <p>This method must be called when the manager of this resource
213      * decides that it's no longer needed.  After this method is called,
214      * no further calls to any of the <code>getXxx()</code> methods are
215      * allowed.</p>
216      *
217      * @exception ResourcesException if an error occurs during finalization
218      */
219     public void destroy() {
220 
221         synchronized (lists) {
222             lists.clear();
223         }
224         synchronized (maps) {
225             maps.clear();
226         }
227 
228     }
229 
230 
231     // ------------------------------------------------------ Protected Methods
232 
233 
234     /**
235      * <p>Return a <code>List</code> of Locales that should be searched
236      * when locating resources for the specified Locale.  The returned
237      * list will start with the specified Locale itself, followed by Locales
238      * that do not specify variant, country, or language modifiers.  For
239      * example, if you pass in a Locale for the <code>en_US_POSIX</code>
240      * combination, the returned list will have Locale instances for
241      * the following country/language/variant combinations:</p>
242      * <ul>
243      * <li><code>en_US_POSIX</code></li>
244      * <li><code>en_US</code></li>
245      * <li><code>en</code></li>
246      * <li>(zero-length country, language, and variant)</li>
247      * </ul>
248      *
249      * <p>The search order calculated by this method makes it easy for
250      * {@link org.apache.commons.resources.Resources} implementations to implement hierarchical search
251      * strategies similar to that employed by the standard Java class
252      * <code>java.util.ResourceBundle</code>.</p>
253      *
254      * @param locale Locale on which to base the list calculation
255      * @return A List of locales.
256      */
257     protected List getLocaleList(Locale locale) {
258 
259         synchronized (lists) {
260 
261             // Optimized lookup of any previously cached Map for this Locale
262             List list = (List) lists.get(locale);
263             if (list != null) {
264                 return (list);
265             }
266 
267             // Calculate, cache, and return the list for this Locale
268             list = new ArrayList();
269             String language = locale.getLanguage();
270             int languageLength = language.length();
271             String country = locale.getCountry();
272             int countryLength = country.length();
273             String variant = locale.getVariant();
274             int variantLength = variant.length();
275 
276             list.add(locale);
277             if (variantLength > 0) {
278                 list.add(new Locale(language, country, ""));
279             }
280             if ((countryLength > 0) && (languageLength > 0)) {
281                 list.add(new Locale(language, "", ""));
282             }
283             if ((languageLength > 0) || (countryLength > 0)) {
284                 list.add(new Locale("", "", ""));
285             }
286             lists.put(locale, list);
287             return (list);
288 
289         }
290 
291     }
292 
293 
294     /**
295      * <p>Return the <code>Map</code> to be used to resolve name-value
296      * mappings for the specified <code>Locale</code>.  Caching is utilized
297      * to ensure that a call to <code>getLocaleMap(base,locale)</code>
298      * occurs only once per <code>Locale</code>.</p>
299      *
300      * @param locale Locale for which to return a name-value mappings Map
301      * @return A name-value Map for the specified locale.
302      */
303     protected Map getLocaleMap(Locale locale) {
304 
305         synchronized (maps) {
306 
307             // Optimized lookup of any previously cached Map for this Locale
308             Map map = (Map) maps.get(locale);
309             if (map != null) {
310                 return (map);
311             }
312 
313             // Calculate, cache, and return the map for this Locale
314             map = getLocaleMap(base, locale);
315             maps.put(locale, map);
316             return (map);
317 
318         }
319 
320     }
321 
322 
323     /**
324      * <p>Return a <code>Map</code> containing the name-value mappings for
325      * the specified base URL and requested <code>Locale</code>, if there
326      * are any.  If there are no defined mappings for the specified
327      * <code>Locale</code>, return an empty <code>Map</code> instead.</p>
328      *
329      * <p>Concrete subclasses must override this method to perform the
330      * appropriate lookup.  A typical implementation will construct an
331      * absolute URL based on the specified base URL and <code>Locale</code>,
332      * retrieve the specified resource file (if any), and parse it into
333      * a <code>Map</code> structure.</p>
334      *
335      * <p>Caching of previously retrieved <code>Map</code>s (if any) should
336      * be performed by callers of this method.  Therefore, this method should
337      * always attempt to retrieve the specified resource and load it
338      * appropriately.</p>
339      *
340      * @param baseUrl Base URL of the resource files for this 
341      * {@link org.apache.commons.resources.Resources} instance
342      * @param locale <code>Locale</code> for which name-value mappings
343      *  are requested
344      * @return A name-value Map for the specified URL and locale.
345      */
346     protected abstract Map getLocaleMap(String baseUrl, Locale locale);
347 
348 
349     /**
350      * <p>Return the <code>Locale</code>-specific suffix for the specified
351      * <code>Locale</code>.  If the specified <code>Locale</code> has
352      * zero-length language and country components, the returned suffix
353      * will also have zero length.  Otherwise, it will contain an
354      * underscore character followed by the non-zero-length language,
355      * country, and variant properties (separated by underscore characters).
356      *
357      * @param locale <code>Locale</code> for which a suffix string
358      *  is requested
359      * @return The locale specific suffix.
360      */
361     protected String getLocaleSuffix(Locale locale) {
362 
363         if (locale == null) {
364             locale = defaultLocale;
365         }
366         String language = locale.getLanguage();
367         if (language == null) {
368             language = "";
369         }
370         String country = locale.getCountry();
371         if (country == null) {
372             country = "";
373         }
374         if ((language.length() < 1) && (country.length() < 1)) {
375             return ("");
376         }
377         StringBuffer sb = new StringBuffer();
378         if (language.length() > 0) {
379             sb.append('_');
380             sb.append(language.toLowerCase());
381         }
382         if (country.length() > 0) {
383             sb.append('_');
384             sb.append(country.toUpperCase());
385         }
386         String variant = locale.getVariant();
387         if ((variant != null) && (variant.length() > 0)) {
388             sb.append('_');
389             sb.append(variant);
390         }
391         return (sb.toString());
392 
393     }
394 
395     /**
396      * Accessor method for Log instance.
397      *
398      * The Log instance variable is transient and
399      * accessing it through this method ensures it
400      * is re-initialized when this instance is
401      * de-serialized.
402      *
403      * @return The Log instance.
404      */
405     private Log getLog() {
406         if (log == null) {
407             log =  LogFactory.getLog(CollectionResourcesBase.class);
408         }
409         return log;
410     }
411 
412 }