KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > context > support > ReloadableResourceBundleMessageSource


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.context.support;
18
19 import java.io.File JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.io.InputStream JavaDoc;
22 import java.io.InputStreamReader JavaDoc;
23 import java.text.MessageFormat JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Locale JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Properties JavaDoc;
31
32 import org.springframework.context.ResourceLoaderAware;
33 import org.springframework.core.io.DefaultResourceLoader;
34 import org.springframework.core.io.Resource;
35 import org.springframework.core.io.ResourceLoader;
36 import org.springframework.util.Assert;
37 import org.springframework.util.DefaultPropertiesPersister;
38 import org.springframework.util.PropertiesPersister;
39 import org.springframework.util.StringUtils;
40
41 /**
42  * {@link org.springframework.context.MessageSource} implementation that
43  * accesses resource bundles using specified basenames. This class uses
44  * {@link java.util.Properties} instances as its custom data structure for
45  * messages, loading them via a {@link org.springframework.util.PropertiesPersister}
46  * strategy: The default strategy is capable of loading properties files
47  * with a specific character encoding, if desired.
48  *
49  * <p>In contrast to {@link ResourceBundleMessageSource}, this class supports
50  * reloading of properties files through the {@link #setCacheSeconds "cacheSeconds"}
51  * setting, and also through programmatically clearing the properties cache.
52  * Since application servers typically cache all files loaded from the classpath,
53  * it is necessary to store resources somewhere else (for example, in the
54  * "WEB-INF" directory of a web app). Otherwise changes of files in the
55  * classpath will <i>not</i> be reflected in the application.
56  *
57  * <p>Note that the base names set as {@link #setBasenames "basenames"} property
58  * are treated in a slightly different fashion than the "basenames" property of
59  * {@link ResourceBundleMessageSource}. It follows the basic ResourceBundle rule of not
60  * specifying file extension or language codes, but can refer to any Spring resource
61  * location (instead of being restricted to classpath resources). With a "classpath:"
62  * prefix, resources can still be loaded from the classpath, but "cacheSeconds" values
63  * other than "-1" (caching forever) will not work in this case.
64  *
65  * <p>This MessageSource implementation is usually slightly faster than
66  * {@link ResourceBundleMessageSource}, which builds on {@link java.util.ResourceBundle}
67  * - in the default mode, i.e. when caching forever. With "cacheSeconds" set to 1,
68  * message lookup takes about twice as long - with the benefit that changes in
69  * individual properties files are detected with a maximum delay of 1 second.
70  * Higher "cacheSeconds" values usually <i>do not</i> make a significant difference.
71  *
72  * <p>This MessageSource can easily be used outside of an
73  * {@link org.springframework.context.ApplicationContext}: It will use a
74  * {@link org.springframework.core.io.DefaultResourceLoader} as default,
75  * simply getting overridden with the ApplicationContext's resource loader
76  * if running in a context. It does not have any other specific dependencies.
77  *
78  * <p>Thanks to Thomas Achleitner for providing the initial implementation of
79  * this message source!
80  *
81  * @author Juergen Hoeller
82  * @see #setCacheSeconds
83  * @see #setBasenames
84  * @see #setDefaultEncoding
85  * @see #setFileEncodings
86  * @see #setPropertiesPersister
87  * @see #setResourceLoader
88  * @see org.springframework.util.DefaultPropertiesPersister
89  * @see org.springframework.core.io.DefaultResourceLoader
90  * @see ResourceBundleMessageSource
91  * @see java.util.ResourceBundle
92  */

93 public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
94     implements ResourceLoaderAware {
95
96     private static final String JavaDoc PROPERTIES_SUFFIX = ".properties";
97
98     private static final String JavaDoc XML_SUFFIX = ".xml";
99
100
101     private String JavaDoc[] basenames = new String JavaDoc[0];
102
103     private String JavaDoc defaultEncoding;
104
105     private Properties JavaDoc fileEncodings;
106
107     private boolean fallbackToSystemLocale = true;
108
109     private long cacheMillis = -1;
110
111     private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
112
113     private ResourceLoader resourceLoader = new DefaultResourceLoader();
114
115     /** Cache to hold filename lists per Locale */
116     private final Map JavaDoc cachedFilenames = new HashMap JavaDoc();
117
118     /** Cache to hold already loaded properties per filename */
119     private final Map JavaDoc cachedProperties = new HashMap JavaDoc();
120
121     /** Cache to hold merged loaded properties per basename */
122     private final Map JavaDoc cachedMergedProperties = new HashMap JavaDoc();
123
124
125     /**
126      * Set a single basename, following the basic ResourceBundle convention of
127      * not specifying file extension or language codes, but in contrast to
128      * {@link ResourceBundleMessageSource} referring to a Spring resource location:
129      * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties",
130      * "WEB-INF/messages_en.properties", etc.
131      * <p>As of Spring 1.2.2, XML properties files are also supported:
132      * e.g. "WEB-INF/messages" will find and load "WEB-INF/messages.xml",
133      * "WEB-INF/messages_en.xml", etc as well. Note that this will only
134      * work on JDK 1.5+.
135      * @param basename the single basename
136      * @see #setBasenames
137      * @see org.springframework.core.io.ResourceEditor
138      * @see java.util.ResourceBundle
139      */

140     public void setBasename(String JavaDoc basename) {
141         setBasenames(new String JavaDoc[] {basename});
142     }
143
144     /**
145      * Set an array of basenames, each following the basic ResourceBundle convention
146      * of not specifying file extension or language codes, but in contrast to
147      * {@link ResourceBundleMessageSource} referring to a Spring resource location:
148      * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties",
149      * "WEB-INF/messages_en.properties", etc.
150      * <p>As of Spring 1.2.2, XML properties files are also supported:
151      * e.g. "WEB-INF/messages" will find and load "WEB-INF/messages.xml",
152      * "WEB-INF/messages_en.xml", etc as well. Note that this will only
153      * work on JDK 1.5+.
154      * <p>The associated resource bundles will be checked sequentially
155      * when resolving a message code. Note that message definitions in a
156      * <i>previous</i> resource bundle will override ones in a later bundle,
157      * due to the sequential lookup.
158      * @param basenames an array of basenames
159      * @see #setBasename
160      * @see java.util.ResourceBundle
161      */

162     public void setBasenames(String JavaDoc[] basenames) {
163         if (basenames != null) {
164             this.basenames = new String JavaDoc[basenames.length];
165             for (int i = 0; i < basenames.length; i++) {
166                 String JavaDoc basename = basenames[i];
167                 Assert.hasText(basename, "Basename must not be empty");
168                 this.basenames[i] = basename.trim();
169             }
170         }
171         else {
172             this.basenames = new String JavaDoc[0];
173         }
174     }
175
176     /**
177      * Set the default charset to use for parsing properties files.
178      * Used if no file-specific charset is specified for a file.
179      * <p>Default is none, using the <code>java.util.Properties</code>
180      * default encoding.
181      * <p>Only applies to classic properties files, not to XML files.
182      * @param defaultEncoding the default charset
183      * @see #setFileEncodings
184      * @see org.springframework.util.PropertiesPersister#load
185      */

186     public void setDefaultEncoding(String JavaDoc defaultEncoding) {
187         this.defaultEncoding = defaultEncoding;
188     }
189
190     /**
191      * Set per-file charsets to use for parsing properties files.
192      * <p>Only applies to classic properties files, not to XML files.
193      * @param fileEncodings Properties with filenames as keys and charset
194      * names as values. Filenames have to match the basename syntax,
195      * with optional locale-specific appendices: e.g. "WEB-INF/messages"
196      * or "WEB-INF/messages_en".
197      * @see #setBasenames
198      * @see org.springframework.util.PropertiesPersister#load
199      */

200     public void setFileEncodings(Properties JavaDoc fileEncodings) {
201         this.fileEncodings = fileEncodings;
202     }
203
204     /**
205      * Set whether to fall back to the system Locale if no files for a specific
206      * Locale have been found. Default is "true"; if this is turned off, the only
207      * fallback will be the default file (e.g. "messages.properties" for
208      * basename "messages").
209      * <p>Falling back to the system Locale is the default behavior of
210      * <code>java.util.ResourceBundle</code>. However, this is often not
211      * desirable in an application server environment, where the system Locale
212      * is not relevant to the application at all: Set this flag to "false"
213      * in such a scenario.
214      */

215     public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
216         this.fallbackToSystemLocale = fallbackToSystemLocale;
217     }
218
219     /**
220      * Set the number of seconds to cache loaded properties files.
221      * <ul>
222      * <li>Default is "-1", indicating to cache forever (just like
223      * <code>java.util.ResourceBundle</code>).
224      * <li>A positive number will cache loaded properties files for the given
225      * number of seconds. This is essentially the interval between refresh checks.
226      * Note that a refresh attempt will first check the last-modified timestamp
227      * of the file before actually reloading it; so if files don't change, this
228      * interval can be set rather low, as refresh attempts will not actually reload.
229      * <li>A value of "0" will check the last-modified timestamp of the file on
230      * every message access. <b>Do not use this in a production environment!</b>
231      * </ul>
232      */

233     public void setCacheSeconds(int cacheSeconds) {
234         this.cacheMillis = (cacheSeconds * 1000);
235     }
236
237     /**
238      * Set the PropertiesPersister to use for parsing properties files.
239      * <p>The default is a DefaultPropertiesPersister.
240      * @see org.springframework.util.DefaultPropertiesPersister
241      */

242     public void setPropertiesPersister(PropertiesPersister propertiesPersister) {
243         this.propertiesPersister =
244                 (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister());
245     }
246
247     /**
248      * Set the ResourceLoader to use for loading bundle properties files.
249      * <p>The default is a DefaultResourceLoader. Will get overridden by the
250      * ApplicationContext if running in a context, as it implements the
251      * ResourceLoaderAware interface. Can be manually overridden when
252      * running outside of an ApplicationContext.
253      * @see org.springframework.core.io.DefaultResourceLoader
254      * @see org.springframework.context.ResourceLoaderAware
255      */

256     public void setResourceLoader(ResourceLoader resourceLoader) {
257         this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
258     }
259
260
261     /**
262      * Resolves the given message code as key in the retrieved bundle files,
263      * returning the value found in the bundle as-is (without MessageFormat parsing).
264      */

265     protected String JavaDoc resolveCodeWithoutArguments(String JavaDoc code, Locale JavaDoc locale) {
266         if (this.cacheMillis < 0) {
267             PropertiesHolder propHolder = getMergedProperties(locale);
268             String JavaDoc result = propHolder.getProperty(code);
269             if (result != null) {
270                 return result;
271             }
272         }
273         else {
274             for (int i = 0; i < this.basenames.length; i++) {
275                 List JavaDoc filenames = calculateAllFilenames(this.basenames[i], locale);
276                 for (int j = 0; j < filenames.size(); j++) {
277                     String JavaDoc filename = (String JavaDoc) filenames.get(j);
278                     PropertiesHolder propHolder = getProperties(filename);
279                     String JavaDoc result = propHolder.getProperty(code);
280                     if (result != null) {
281                         return result;
282                     }
283                 }
284             }
285         }
286         return null;
287     }
288
289     /**
290      * Resolves the given message code as key in the retrieved bundle files,
291      * using a cached MessageFormat instance per message code.
292      */

293     protected MessageFormat JavaDoc resolveCode(String JavaDoc code, Locale JavaDoc locale) {
294         if (this.cacheMillis < 0) {
295             PropertiesHolder propHolder = getMergedProperties(locale);
296             MessageFormat JavaDoc result = propHolder.getMessageFormat(code, locale);
297             if (result != null) {
298                 return result;
299             }
300         }
301         else {
302             for (int i = 0; i < this.basenames.length; i++) {
303                 List JavaDoc filenames = calculateAllFilenames(this.basenames[i], locale);
304                 for (int j = 0; j < filenames.size(); j++) {
305                     String JavaDoc filename = (String JavaDoc) filenames.get(j);
306                     PropertiesHolder propHolder = getProperties(filename);
307                     MessageFormat JavaDoc result = propHolder.getMessageFormat(code, locale);
308                     if (result != null) {
309                         return result;
310                     }
311                 }
312             }
313         }
314         return null;
315     }
316
317
318     /**
319      * Get a PropertiesHolder that contains the actually visible properties
320      * for a Locale, after merging all specified resource bundles.
321      * Either fetches the holder from the cache or freshly loads it.
322      * <p>Only used when caching resource bundle contents forever, i.e.
323      * with cacheSeconds < 0. Therefore, merged properties are always
324      * cached forever.
325      */

326     protected PropertiesHolder getMergedProperties(Locale JavaDoc locale) {
327         synchronized (this.cachedMergedProperties) {
328             PropertiesHolder mergedHolder = (PropertiesHolder) this.cachedMergedProperties.get(locale);
329             if (mergedHolder != null) {
330                 return mergedHolder;
331             }
332             Properties JavaDoc mergedProps = new Properties JavaDoc();
333             mergedHolder = new PropertiesHolder(mergedProps, -1);
334             for (int i = this.basenames.length - 1; i >= 0; i--) {
335                 List JavaDoc filenames = calculateAllFilenames(this.basenames[i], locale);
336                 for (int j = filenames.size() - 1; j >= 0; j--) {
337                     String JavaDoc filename = (String JavaDoc) filenames.get(j);
338                     PropertiesHolder propHolder = getProperties(filename);
339                     if (propHolder.getProperties() != null) {
340                         mergedProps.putAll(propHolder.getProperties());
341                     }
342                 }
343             }
344             this.cachedMergedProperties.put(locale, mergedHolder);
345             return mergedHolder;
346         }
347     }
348
349     /**
350      * Calculate all filenames for the given bundle basename and Locale.
351      * Will calculate filenames for the given Locale, the system Locale
352      * (if applicable), and the default file.
353      * @param basename the basename of the bundle
354      * @param locale the locale
355      * @return the List of filenames to check
356      * @see #setFallbackToSystemLocale
357      * @see #calculateFilenamesForLocale
358      */

359     protected List JavaDoc calculateAllFilenames(String JavaDoc basename, Locale JavaDoc locale) {
360         synchronized (this.cachedFilenames) {
361             Map JavaDoc localeMap = (Map JavaDoc) this.cachedFilenames.get(basename);
362             if (localeMap != null) {
363                 List JavaDoc filenames = (List JavaDoc) localeMap.get(locale);
364                 if (filenames != null) {
365                     return filenames;
366                 }
367             }
368             List JavaDoc filenames = new ArrayList JavaDoc(7);
369             filenames.addAll(calculateFilenamesForLocale(basename, locale));
370             if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) {
371                 List JavaDoc fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault());
372                 for (Iterator JavaDoc it = fallbackFilenames.iterator(); it.hasNext();) {
373                     String JavaDoc fallbackFilename = (String JavaDoc) it.next();
374                     if (!filenames.contains(fallbackFilename)) {
375                         // Entry for fallback locale that isn't already in filenames list.
376
filenames.add(fallbackFilename);
377                     }
378                 }
379             }
380             filenames.add(basename);
381             if (localeMap != null) {
382                 localeMap.put(locale, filenames);
383             }
384             else {
385                 localeMap = new HashMap JavaDoc();
386                 localeMap.put(locale, filenames);
387                 this.cachedFilenames.put(basename, localeMap);
388             }
389             return filenames;
390         }
391     }
392
393     /**
394      * Calculate the filenames for the given bundle basename and Locale,
395      * appending language code, country code, and variant code.
396      * E.g.: basename "messages", Locale "de_AT_oo" -> "messages_de_AT_OO",
397      * "messages_de_AT", "messages_de".
398      * @param basename the basename of the bundle
399      * @param locale the locale
400      * @return the List of filenames to check
401      */

402     protected List JavaDoc calculateFilenamesForLocale(String JavaDoc basename, Locale JavaDoc locale) {
403         List JavaDoc result = new ArrayList JavaDoc(3);
404         String JavaDoc language = locale.getLanguage();
405         String JavaDoc country = locale.getCountry();
406         String JavaDoc variant = locale.getVariant();
407         StringBuffer JavaDoc temp = new StringBuffer JavaDoc(basename);
408
409         if (language.length() > 0) {
410             temp.append('_').append(language);
411             result.add(0, temp.toString());
412         }
413
414         if (country.length() > 0) {
415             temp.append('_').append(country);
416             result.add(0, temp.toString());
417         }
418
419         if (variant.length() > 0) {
420             temp.append('_').append(variant);
421             result.add(0, temp.toString());
422         }
423
424         return result;
425     }
426
427
428     /**
429      * Get a PropertiesHolder for the given filename, either from the
430      * cache or freshly loaded.
431      * @param filename the bundle filename (basename + Locale)
432      * @return the current PropertiesHolder for the bundle
433      */

434     protected PropertiesHolder getProperties(String JavaDoc filename) {
435         synchronized (this.cachedProperties) {
436             PropertiesHolder propHolder = (PropertiesHolder) this.cachedProperties.get(filename);
437             if (propHolder != null &&
438                     (propHolder.getRefreshTimestamp() < 0 ||
439                      propHolder.getRefreshTimestamp() > System.currentTimeMillis() - this.cacheMillis)) {
440                 // up to date
441
return propHolder;
442             }
443             return refreshProperties(filename, propHolder);
444         }
445     }
446
447     /**
448      * Refresh the PropertiesHolder for the given bundle filename.
449      * The holder can be <code>null</code> if not cached before, or a timed-out cache entry
450      * (potentially getting re-validated against the current last-modified timestamp).
451      * @param filename the bundle filename (basename + Locale)
452      * @param propHolder the current PropertiesHolder for the bundle
453      */

454     protected PropertiesHolder refreshProperties(String JavaDoc filename, PropertiesHolder propHolder) {
455         long refreshTimestamp = (this.cacheMillis < 0) ? -1 : System.currentTimeMillis();
456
457         Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX);
458         if (!resource.exists()) {
459             resource = this.resourceLoader.getResource(filename + XML_SUFFIX);
460         }
461
462         if (resource.exists()) {
463             try {
464                 long fileTimestamp = -1;
465
466                 if (this.cacheMillis >= 0) {
467                     // Last-modified timestamp of file will just be read if caching with timeout.
468
File JavaDoc file = null;
469                     try {
470                         file = resource.getFile();
471                     }
472                     catch (IOException JavaDoc ex) {
473                         // Probably a class path resource: cache it forever.
474
if (logger.isDebugEnabled()) {
475                             logger.debug(
476                                     resource + " could not be resolved in the file system - assuming that is hasn't changed", ex);
477                         }
478                         file = null;
479                     }
480                     if (file != null) {
481                         fileTimestamp = file.lastModified();
482                         if (fileTimestamp == 0) {
483                             throw new IOException JavaDoc("File [" + file.getAbsolutePath() + "] does not exist");
484                         }
485                         if (propHolder != null && propHolder.getFileTimestamp() == fileTimestamp) {
486                             if (logger.isDebugEnabled()) {
487                                 logger.debug("Re-caching properties for filename [" + filename + "] - file hasn't been modified");
488                             }
489                             propHolder.setRefreshTimestamp(refreshTimestamp);
490                             return propHolder;
491                         }
492                     }
493                 }
494
495                 Properties JavaDoc props = loadProperties(resource, filename);
496                 propHolder = new PropertiesHolder(props, fileTimestamp);
497             }
498
499             catch (IOException JavaDoc ex) {
500                 if (logger.isWarnEnabled()) {
501                     logger.warn(
502                             "Could not parse properties file [" + resource.getFilename() + "]: " + ex.getMessage(), ex);
503                 }
504                 // Empty holder representing "not valid".
505
propHolder = new PropertiesHolder();
506             }
507         }
508
509         else {
510             // Resource does not exist.
511
if (logger.isDebugEnabled()) {
512                 logger.debug("No properties file found for [" + filename + "] - neither plain properties nor XML");
513             }
514             // Empty holder representing "not found".
515
propHolder = new PropertiesHolder();
516         }
517
518         propHolder.setRefreshTimestamp(refreshTimestamp);
519         this.cachedProperties.put(filename, propHolder);
520         return propHolder;
521     }
522
523     /**
524      * Load the properties from the given resource.
525      * @param resource the resource to load from
526      * @param filename the original bundle filename (basename + Locale)
527      * @return the populated Properties instance
528      * @throws IOException if properties loading failed
529      */

530     protected Properties JavaDoc loadProperties(Resource resource, String JavaDoc filename) throws IOException JavaDoc {
531         InputStream JavaDoc is = resource.getInputStream();
532         Properties JavaDoc props = new Properties JavaDoc();
533         try {
534             if (resource.getFilename().endsWith(XML_SUFFIX)) {
535                 if (logger.isDebugEnabled()) {
536                     logger.debug("Loading properties [" + resource.getFilename() + "]");
537                 }
538                 this.propertiesPersister.loadFromXml(props, is);
539             }
540             else {
541                 String JavaDoc encoding = null;
542                 if (this.fileEncodings != null) {
543                     encoding = this.fileEncodings.getProperty(filename);
544                 }
545                 if (encoding == null) {
546                     encoding = this.defaultEncoding;
547                 }
548                 if (encoding != null) {
549                     if (logger.isDebugEnabled()) {
550                         logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'");
551                     }
552                     this.propertiesPersister.load(props, new InputStreamReader JavaDoc(is, encoding));
553                 }
554                 else {
555                     if (logger.isDebugEnabled()) {
556                         logger.debug("Loading properties [" + resource.getFilename() + "]");
557                     }
558                     this.propertiesPersister.load(props, is);
559                 }
560             }
561             return props;
562         }
563         finally {
564             is.close();
565         }
566     }
567
568
569     /**
570      * Clear the resource bundle cache.
571      * Subsequent resolve calls will lead to reloading of the properties files.
572      */

573     public void clearCache() {
574         logger.debug("Clearing entire resource bundle cache");
575         synchronized (this.cachedProperties) {
576             this.cachedProperties.clear();
577         }
578         synchronized (this.cachedMergedProperties) {
579             this.cachedMergedProperties.clear();
580         }
581     }
582
583     /**
584      * Clear the resource bundle caches of this MessageSource and all its ancestors.
585      * @see #clearCache
586      */

587     public void clearCacheIncludingAncestors() {
588         clearCache();
589         if (getParentMessageSource() instanceof ReloadableResourceBundleMessageSource) {
590             ((ReloadableResourceBundleMessageSource) getParentMessageSource()).clearCacheIncludingAncestors();
591         }
592     }
593
594
595     public String JavaDoc toString() {
596         return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
597     }
598
599
600     /**
601      * PropertiesHolder for caching.
602      * Stores the last-modified timestamp of the source file for efficient
603      * change detection, and the timestamp of the last refresh attempt
604      * (updated every time the cache entry gets re-validated).
605      */

606     protected class PropertiesHolder {
607
608         private Properties JavaDoc properties;
609
610         private long fileTimestamp = -1;
611
612         private long refreshTimestamp = -1;
613
614         /** Cache to hold already generated MessageFormats per message code */
615         private final Map JavaDoc cachedMessageFormats = new HashMap JavaDoc();
616
617         public PropertiesHolder(Properties JavaDoc properties, long fileTimestamp) {
618             this.properties = properties;
619             this.fileTimestamp = fileTimestamp;
620         }
621
622         public PropertiesHolder() {
623         }
624
625         public Properties JavaDoc getProperties() {
626             return properties;
627         }
628
629         public long getFileTimestamp() {
630             return fileTimestamp;
631         }
632
633         public void setRefreshTimestamp(long refreshTimestamp) {
634             this.refreshTimestamp = refreshTimestamp;
635         }
636
637         public long getRefreshTimestamp() {
638             return refreshTimestamp;
639         }
640
641         public String JavaDoc getProperty(String JavaDoc code) {
642             if (this.properties == null) {
643                 return null;
644             }
645             return this.properties.getProperty(code);
646         }
647
648         public MessageFormat JavaDoc getMessageFormat(String JavaDoc code, Locale JavaDoc locale) {
649             if (this.properties == null) {
650                 return null;
651             }
652             synchronized (this.cachedMessageFormats) {
653                 Map JavaDoc localeMap = (Map JavaDoc) this.cachedMessageFormats.get(code);
654                 if (localeMap != null) {
655                     MessageFormat JavaDoc result = (MessageFormat JavaDoc) localeMap.get(locale);
656                     if (result != null) {
657                         return result;
658                     }
659                 }
660                 String JavaDoc msg = this.properties.getProperty(code);
661                 if (msg != null) {
662                     if (localeMap == null) {
663                         localeMap = new HashMap JavaDoc();
664                         this.cachedMessageFormats.put(code, localeMap);
665                     }
666                     MessageFormat JavaDoc result = createMessageFormat(msg, locale);
667                     localeMap.put(locale, result);
668                     return result;
669                 }
670                 return null;
671             }
672         }
673     }
674
675 }
676
Popular Tags