Java tutorial
//package net.gqu.utils; import java.text.MessageFormat; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Utility class providing methods to access the Locale of the current thread and to get * Localised strings. * * @author Roy Wetherall */ public class I18NUtil { /** * Thread-local containing the general Locale for the current thread */ private static ThreadLocal<Locale> threadLocale = new ThreadLocal<Locale>(); /** * Thread-local containing the content Locale for for the current thread. This * can be used for content and property filtering. */ private static ThreadLocal<Locale> threadContentLocale = new ThreadLocal<Locale>(); /** * List of registered bundles */ private static Set<String> resouceBundleBaseNames = new HashSet<String>(); /** * Map of loaded bundles by Locale */ private static Map<Locale, Set<String>> loadedResourceBundles = new HashMap<Locale, Set<String>>(); /** * Map of cached messaged by Locale */ private static Map<Locale, Map<String, String>> cachedMessages = new HashMap<Locale, Map<String, String>>(); /** * Lock objects */ private static ReadWriteLock lock = new ReentrantReadWriteLock(); private static Lock readLock = lock.readLock(); private static Lock writeLock = lock.writeLock(); /** * Set the locale for the current thread. * * @param locale the locale */ public static void setLocale(Locale locale) { threadLocale.set(locale); } /** * Get the general local for the current thread, will revert to the default locale if none * specified for this thread. * * @return the general locale */ public static Locale getLocale() { Locale locale = threadLocale.get(); if (locale == null) { // Get the default locale locale = Locale.getDefault(); } return locale; } /** * Set the <b>content locale</b> for the current thread. * * @param locale the content locale */ public static void setContentLocale(Locale locale) { threadContentLocale.set(locale); } /** * Get the content local for the current thread.<br/> * This will revert to {@link #getLocale()} if no value has been defined. * * @return Returns the content locale */ public static Locale getContentLocale() { Locale locale = threadContentLocale.get(); if (locale == null) { // Revert to the normal locale locale = getLocale(); } return locale; } /** * Get the content local for the current thread.<br/> * This will revert <tt>null</tt> if no value has been defined. * * @return Returns the content locale */ public static Locale getContentLocaleOrNull() { Locale locale = threadContentLocale.get(); return locale; } /** * Searches for the nearest locale from the available options. To match any locale, pass in * <tt>null</tt>. * * @param templateLocale the template to search for or <tt>null</tt> to match any locale * @param options the available locales to search from * @return Returns the best match from the available options, or the <tt>null</tt> if * all matches fail */ public static Locale getNearestLocale(Locale templateLocale, Set<Locale> options) { if (options.isEmpty()) // No point if there are no options { return null; } else if (templateLocale == null) { for (Locale locale : options) { return locale; } } else if (options.contains(templateLocale)) // First see if there is an exact match { return templateLocale; } // make a copy of the set Set<Locale> remaining = new HashSet<Locale>(options); // eliminate those without matching languages Locale lastMatchingOption = null; String templateLanguage = templateLocale.getLanguage(); if (templateLanguage != null && !templateLanguage.equals("")) { Iterator<Locale> iterator = remaining.iterator(); while (iterator.hasNext()) { Locale option = iterator.next(); if (option != null && !templateLanguage.equals(option.getLanguage())) { iterator.remove(); // It doesn't match, so remove } else { lastMatchingOption = option; // Keep a record of the last match } } } if (remaining.isEmpty()) { return null; } else if (remaining.size() == 1 && lastMatchingOption != null) { return lastMatchingOption; } // eliminate those without matching country codes lastMatchingOption = null; String templateCountry = templateLocale.getCountry(); if (templateCountry != null && !templateCountry.equals("")) { Iterator<Locale> iterator = remaining.iterator(); while (iterator.hasNext()) { Locale option = iterator.next(); if (option != null && !templateCountry.equals(option.getCountry())) { // It doesn't match language - remove // Don't remove the iterator. If it matchs a langage but not the country, returns any matched language // iterator.remove(); } else { lastMatchingOption = option; // Keep a record of the last match } } } /*if (remaining.isEmpty()) { return null; } else */ if (remaining.size() == 1 && lastMatchingOption != null) { return lastMatchingOption; } else { // We have done an earlier equality check, so there isn't a matching variant // Also, we know that there are multiple options at this point, either of which will do. // This gets any country match (there will be worse matches so we take the last the country match) if (lastMatchingOption != null) { return lastMatchingOption; } else { for (Locale locale : remaining) { return locale; } } } // The logic guarantees that this code can't be called throw new RuntimeException("Logic should not allow code to get here."); } /** * Factory method to create a Locale from a <tt>lang_country_variant</tt> string. * * @param localeStr e.g. fr_FR * @return Returns the locale instance, or the {@link Locale#getDefault() default} if the * string is invalid */ public static Locale parseLocale(String localeStr) { if (localeStr == null) { return null; } Locale locale = Locale.getDefault(); StringTokenizer t = new StringTokenizer(localeStr, "_"); int tokens = t.countTokens(); if (tokens == 1) { locale = new Locale(t.nextToken()); } else if (tokens == 2) { locale = new Locale(t.nextToken(), t.nextToken()); } else if (tokens == 3) { locale = new Locale(t.nextToken(), t.nextToken(), t.nextToken()); } return locale; } /** * Register a resource bundle. * <p> * This should be the bundle base name eg, alfresco.messages.errors * <p> * Once registered the messges will be available via getMessage * * @param bundleBaseName the bundle base name */ public static void registerResourceBundle(String bundleBaseName) { try { writeLock.lock(); resouceBundleBaseNames.add(bundleBaseName); } finally { writeLock.unlock(); } } /** * Get message from registered resource bundle. * * @param messageKey message key * @return localised message string, null if not found */ public static String getMessage(String messageKey) { return getMessage(messageKey, getLocale()); } /** * Get a localised message string * * @param messageKey the message key * @param locale override the current locale * @return the localised message string, null if not found */ public static String getMessage(String messageKey, Locale locale) { String message = null; Map<String, String> props = getLocaleProperties(locale); if (props != null) { message = props.get(messageKey); } return message == null ? messageKey : message; } /** * Get a localised message string, parameterized using standard MessageFormatter. * * @param messageKey message key * @param params format parameters * @return the localised string, null if not found */ public static String getMessage(String messageKey, Object... params) { return getMessage(messageKey, getLocale(), params); } /** * Get a localised message string, parameterized using standard MessageFormatter. * * @param messageKey the message key * @param locale override current locale * @param params the localised message string * @return the localaised string, null if not found */ public static String getMessage(String messageKey, Locale locale, Object... params) { String message = getMessage(messageKey, locale); if (message != null && params != null) { message = MessageFormat.format(message, params); } return message; } /** * @return the map of all available messages for the current locale */ public static Map<String, String> getAllMessages() { return getLocaleProperties(getLocale()); } /** * @return the map of all available messages for the specified locale */ public static Map<String, String> getAllMessages(Locale locale) { return getLocaleProperties(locale); } /** * Get the messages for a locale. * <p> * Will use cache where available otherwise will load into cache from bundles. * * @param locale the locale * @return message map */ private static Map<String, String> getLocaleProperties(Locale locale) { Set<String> loadedBundles = null; Map<String, String> props = null; int loadedBundleCount = 0; try { readLock.lock(); loadedBundles = loadedResourceBundles.get(locale); props = cachedMessages.get(locale); loadedBundleCount = resouceBundleBaseNames.size(); } finally { readLock.unlock(); } if (loadedBundles == null) { try { writeLock.lock(); loadedBundles = new HashSet<String>(); loadedResourceBundles.put(locale, loadedBundles); } finally { writeLock.unlock(); } } if (props == null) { try { writeLock.lock(); props = new HashMap<String, String>(); cachedMessages.put(locale, props); } finally { writeLock.unlock(); } } if (loadedBundles.size() != loadedBundleCount) { try { writeLock.lock(); for (String resourceBundleBaseName : resouceBundleBaseNames) { if (loadedBundles.contains(resourceBundleBaseName) == false) { ResourceBundle resourcebundle = ResourceBundle.getBundle(resourceBundleBaseName, locale); Enumeration<String> enumKeys = resourcebundle.getKeys(); while (enumKeys.hasMoreElements() == true) { String key = enumKeys.nextElement(); props.put(key, resourcebundle.getString(key)); } loadedBundles.add(resourceBundleBaseName); } } } finally { writeLock.unlock(); } } return props; } }