Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
CollectionResourcesBase |
|
| 3.4545454545454546;3.455 |
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 | 0 | private transient Log log = |
54 | 0 | 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 | 0 | super(name); |
66 | 0 | this.base = base; |
67 | 0 | } |
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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | this.defaultLocale = defaultLocale; |
111 | 0 | } |
112 | ||
113 | /** | |
114 | * Return the default locale. | |
115 | * @return The default Locale. | |
116 | */ | |
117 | public Locale getDefaultLocale() { | |
118 | 0 | return defaultLocale; |
119 | } | |
120 | ||
121 | /** | |
122 | * @see org.apache.commons.resources.impl.ResourcesBase#getKeys() | |
123 | */ | |
124 | public Iterator getKeys() { | |
125 | ||
126 | 0 | synchronized (maps) { |
127 | ||
128 | 0 | Set results = new HashSet(); |
129 | 0 | Iterator locales = maps.keySet().iterator(); |
130 | 0 | while (locales.hasNext()) { |
131 | 0 | Locale locale = (Locale) locales.next(); |
132 | 0 | Map map = (Map) maps.get(locale); |
133 | 0 | results.addAll(map.keySet()); |
134 | 0 | } |
135 | 0 | return (results.iterator()); |
136 | ||
137 | 0 | } |
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 | 0 | if (getLog().isTraceEnabled()) { |
167 | 0 | getLog().trace("Retrieving message for key '" + key + "' and locale '" |
168 | + locale + "'"); | |
169 | } | |
170 | ||
171 | 0 | if (locale == null) { |
172 | 0 | locale = defaultLocale; |
173 | } | |
174 | ||
175 | // Prepare local variables we will need | |
176 | 0 | List list = getLocaleList(locale); |
177 | 0 | int n = list.size(); |
178 | ||
179 | // Search through the Locale hierarchy for this resource key | |
180 | 0 | for (int i = 0; i < n; i++) { |
181 | 0 | Map map = getLocaleMap((Locale) list.get(i)); |
182 | 0 | if (map.containsKey(key)) { |
183 | 0 | Object object = map.get(key); |
184 | 0 | if (getLog().isTraceEnabled()) { |
185 | 0 | getLog().trace("Retrieved object for key '" + key + |
186 | "' and locale '" + locale + | |
187 | "' is '" + object + "'"); | |
188 | } | |
189 | 0 | return object; |
190 | } | |
191 | } | |
192 | ||
193 | 0 | if (getLog().isTraceEnabled()) { |
194 | 0 | 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 | 0 | if (isReturnNull()) { |
200 | 0 | return (null); |
201 | } else { | |
202 | 0 | 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 | 0 | synchronized (lists) { |
222 | 0 | lists.clear(); |
223 | 0 | } |
224 | 0 | synchronized (maps) { |
225 | 0 | maps.clear(); |
226 | 0 | } |
227 | ||
228 | 0 | } |
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 | 0 | synchronized (lists) { |
260 | ||
261 | // Optimized lookup of any previously cached Map for this Locale | |
262 | 0 | List list = (List) lists.get(locale); |
263 | 0 | if (list != null) { |
264 | 0 | return (list); |
265 | } | |
266 | ||
267 | // Calculate, cache, and return the list for this Locale | |
268 | 0 | list = new ArrayList(); |
269 | 0 | String language = locale.getLanguage(); |
270 | 0 | int languageLength = language.length(); |
271 | 0 | String country = locale.getCountry(); |
272 | 0 | int countryLength = country.length(); |
273 | 0 | String variant = locale.getVariant(); |
274 | 0 | int variantLength = variant.length(); |
275 | ||
276 | 0 | list.add(locale); |
277 | 0 | if (variantLength > 0) { |
278 | 0 | list.add(new Locale(language, country, "")); |
279 | } | |
280 | 0 | if ((countryLength > 0) && (languageLength > 0)) { |
281 | 0 | list.add(new Locale(language, "", "")); |
282 | } | |
283 | 0 | if ((languageLength > 0) || (countryLength > 0)) { |
284 | 0 | list.add(new Locale("", "", "")); |
285 | } | |
286 | 0 | lists.put(locale, list); |
287 | 0 | return (list); |
288 | ||
289 | 0 | } |
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 | 0 | synchronized (maps) { |
306 | ||
307 | // Optimized lookup of any previously cached Map for this Locale | |
308 | 0 | Map map = (Map) maps.get(locale); |
309 | 0 | if (map != null) { |
310 | 0 | return (map); |
311 | } | |
312 | ||
313 | // Calculate, cache, and return the map for this Locale | |
314 | 0 | map = getLocaleMap(base, locale); |
315 | 0 | maps.put(locale, map); |
316 | 0 | return (map); |
317 | ||
318 | 0 | } |
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 | 0 | if (locale == null) { |
364 | 0 | locale = defaultLocale; |
365 | } | |
366 | 0 | String language = locale.getLanguage(); |
367 | 0 | if (language == null) { |
368 | 0 | language = ""; |
369 | } | |
370 | 0 | String country = locale.getCountry(); |
371 | 0 | if (country == null) { |
372 | 0 | country = ""; |
373 | } | |
374 | 0 | if ((language.length() < 1) && (country.length() < 1)) { |
375 | 0 | return (""); |
376 | } | |
377 | 0 | StringBuffer sb = new StringBuffer(); |
378 | 0 | if (language.length() > 0) { |
379 | 0 | sb.append('_'); |
380 | 0 | sb.append(language.toLowerCase()); |
381 | } | |
382 | 0 | if (country.length() > 0) { |
383 | 0 | sb.append('_'); |
384 | 0 | sb.append(country.toUpperCase()); |
385 | } | |
386 | 0 | String variant = locale.getVariant(); |
387 | 0 | if ((variant != null) && (variant.length() > 0)) { |
388 | 0 | sb.append('_'); |
389 | 0 | sb.append(variant); |
390 | } | |
391 | 0 | 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 | 0 | if (log == null) { |
407 | 0 | log = LogFactory.getLog(CollectionResourcesBase.class); |
408 | } | |
409 | 0 | return log; |
410 | } | |
411 | ||
412 | } |