Mega Code Archive

 
Categories / Java / Internationalization
 

Utility class providing methods to access the Locale of the current thread and to get Localised strings

//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;     } }