/* PropertyBundle.java Purpose: Description: History: 90/12/06 20:09:36, Create, Tom M. Yeh. Copyright (C) 2001 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.util.resource; import java.io.InputStream; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.lang.Objects; import org.zkoss.lang.SystemException; import org.zkoss.util.Cache; import org.zkoss.util.CacheMap; import org.zkoss.util.Maps; /** * The property bundle. * * <p>It is similar to java.util.ResourceBundle, but they differ as follows. * * <ul> * <li>It uses {@link Maps#load(Map, InputStream)} to load the properties. * Thus, It is capable to handle UTF-16 and UTF-8 (but not ISO-8859-1).</li> * <li>The locator could be any object as long as it implements * <code>InputStream getResourceAsStream(String)</code>.</li> * <li>It supports only property files.</li> * <li>The getBundle method returns null if the resource not found, * while ResourceBundle throws MissingResourceException.</li> * </ul> * * <p>Instances of PropertyBundle are cached, so the performance is good. * However, it implies the property file mapped by the giving * class loader, name and locale is immutable. In other words, if you * update the content of a property file, it might not be reflected to * getString unless it is cleared out of the cache. * * <p>Thread safe. * * @author tomyeh */ public class PropertyBundle { private static final Logger log = LoggerFactory.getLogger(PropertyBundle.class); /** The cache to hold bundles (Key, PropertyBundle). */ private static final Cache<Key, PropertyBundle> _cache; static { _cache = new CacheMap<Key, PropertyBundle>(); _cache.setMaxSize(100); } /** The map of properties. */ private final Map<String, String> _map; /** The locale of the bundle. */ private final Locale _locale; /** The key used to look up the cache. */ private static class Key { private String baseName; private Locale locale; private Locator locator; private boolean caseInsensitive; private Key(String baseName, Locale locale, Locator locator, boolean caseInsensitive) { this.baseName = baseName; this.locale = locale; this.locator = locator; this.caseInsensitive = caseInsensitive; } //-- Object --// public int hashCode() { return baseName.hashCode() ^ (locale != null ? locale.hashCode(): 0) ^ locator.hashCode(); } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Key)) return false; Key k = (Key)o; return k.baseName.equals(baseName) && Objects.equals(k.locale, locale) && k.locator.equals(locator) && k.caseInsensitive == caseInsensitive; } } /** * Gets a resource bundle using the specified * base name, locale, and locator. * * @param locator the locator (never null). See {@link Locators#getDefault}. * @param caseInsensitive whether the key used to access the map * is case-insensitive. If true, all keys are converted to lower cases. * @return the bundle; null if not found */ public static final PropertyBundle getBundle( String baseName, Locale locale, Locator locator, boolean caseInsensitive) { if (baseName == null || locator == null) throw new IllegalArgumentException(); //We don't lock the whole method, so it is possible that //more than one thread are loading the same property file. //However, it is OK since any result is correct. //On the other hand, it is more likely that two or more //threads are asking different property files at the same //time, so we avoid the big lock. final Key key = new Key(baseName, locale, locator, caseInsensitive); synchronized(_cache) { PropertyBundle bundle = _cache.get(key); if (bundle != null) return bundle; } final PropertyBundle bundle = new PropertyBundle(baseName, locale, locator, caseInsensitive); if (bundle._map == null) //failed return null; synchronized(_cache) { _cache.put(key, bundle); } return bundle; } /** * Gets a resource bundle using the specified * base name, locale, and locator. */ public static final PropertyBundle getBundle(String baseName, Locale locale, Locator locator) { return getBundle(baseName, locale, locator, false); } /** * Gets a resource bundle using the specified * base name, locale, and the default locator, {@link Locators#getDefault}. * * @param caseInsensitive whether the key used to access the map * is case-insensitive. If true, all keys are converted to lower cases. * @return the bundle; null if not found */ public static final PropertyBundle getBundle(String baseName, Locale locale, boolean caseInsensitive) { return getBundle(baseName, locale, Locators.getDefault(), caseInsensitive); } /** * Gets a resource bundle using the specified * base name, locale, and the default locator, {@link Locators#getDefault}. */ public static final PropertyBundle getBundle(String baseName, Locale locale) { return getBundle(baseName, locale, false); } /** * Constructor. * * @param caseInsensitive whether the key used to access the map * is case-insensitive. If true, all keys are converted to lower cases. */ protected PropertyBundle(String baseName, Locale locale, Locator locator, boolean caseInsensitive) { try { final Locators.StreamLocation loc = Locators.locateAsStream(baseName + ".properties", locale, locator); if (loc != null) { _map = new HashMap<String, String>(32); Maps.load(_map, loc.stream, caseInsensitive); _locale = loc.locale; } else { _map = null; //we use _map to denote failure _locale = null; } }catch(RuntimeException ex) { throw ex; }catch(Exception ex) { throw SystemException.Aide.wrap(ex, "Unable to load " + baseName + ".properties"); } } /** Returns the property for the given key from this resource bundle * or one of its parents. */ public final String getProperty(String key) { //It is OK not to sync because _map is immutable return _map.get(key); } /** Returns a map of all properties, (String key , String value). */ public final Map<String, String> getProperties() { return _map; } /** Returns the locale of the bundle, or null if it is the default. * Note: it is value might not be the same as the locale being passed * to the constructor, because the constructor will do some fallback. */ public final Locale getLocale() { return _locale; } }