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