Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.util » [javadoc | source]
    1   /*
    2    * Copyright (c) 2002-2006 by OpenSymphony
    3    * All rights reserved.
    4    */
    5   package com.opensymphony.xwork2.util;
    6   
    7   import java.beans.PropertyDescriptor;
    8   import java.lang.reflect.Field;
    9   import java.lang.reflect.InvocationTargetException;
   10   import java.lang.reflect.Method;
   11   import java.text.MessageFormat;
   12   import java.util.ArrayList;
   13   import java.util.Collections;
   14   import java.util.HashMap;
   15   import java.util.Iterator;
   16   import java.util.List;
   17   import java.util.Locale;
   18   import java.util.Map;
   19   import java.util.MissingResourceException;
   20   import java.util.ResourceBundle;
   21   import java.util.Set;
   22   import java.util.TreeSet;
   23   
   24   import com.opensymphony.xwork2.ActionContext;
   25   import com.opensymphony.xwork2.ActionInvocation;
   26   import com.opensymphony.xwork2.ModelDriven;
   27   import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
   28   import com.opensymphony.xwork2.util.logging.Logger;
   29   import com.opensymphony.xwork2.util.logging.LoggerFactory;
   30   import com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory;
   31   
   32   
   33   /**
   34    * Provides support for localization in XWork.
   35    *
   36    * <!-- START SNIPPET: searchorder -->
   37    * Resource bundles are searched in the following order:<p/>
   38    * <p/>
   39    * <ol>
   40    * <li>ActionClass.properties</li>
   41    * <li>Interface.properties (every interface and sub-interface)</li>
   42    * <li>BaseClass.properties (all the way to Object.properties)</li>
   43    * <li>ModelDriven's model (if implements ModelDriven), for the model object repeat from 1</li>
   44    * <li>package.properties (of the directory where class is located and every parent directory all the way to the root directory)</li>
   45    * <li>search up the i18n message key hierarchy itself</li>
   46    * <li>global resource properties</li>
   47    * </ol>
   48    * <p/>
   49    * <!-- END SNIPPET: searchorder -->
   50    *
   51    * <!-- START SNIPPET: packagenote -->
   52    * To clarify #5, while traversing the package hierarchy, Struts 2 will look for a file package.properties:<p/>
   53    * com/<br/>
   54    * &nbsp; acme/<br/>
   55    * &nbsp; &nbsp; package.properties<br/>
   56    * &nbsp; &nbsp; actions/<br/>
   57    * &nbsp; &nbsp; &nbsp; package.properties<br/>
   58    * &nbsp; &nbsp; &nbsp; FooAction.java<br/>
   59    * &nbsp; &nbsp; &nbsp; FooAction.properties<br/>
   60    * <p/>
   61    * If FooAction.properties does not exist, com/acme/action/package.properties will be searched for, if
   62    * not found com/acme/package.properties, if not found com/package.properties, etc.
   63    * <p/>
   64    * <!-- END SNIPPET: packagenote -->
   65    *
   66    * <!-- START SNIPPET: globalresource -->
   67    * A global resource bundle could be specified programatically, as well as the locale.
   68    * <p/>
   69    * <!-- END SNIPPET: globalresource -->
   70    *
   71    * @author Jason Carreira
   72    * @author Mark Woon
   73    * @author Rainer Hermanns
   74    * @author tm_jee
   75    * 
   76    * @version $Date: 2007-11-16 21:10:07 +0100 (Fri, 16 Nov 2007) $ $Id: LocalizedTextUtil.java 1667 2007-11-16 20:10:07Z mrdon $
   77    */
   78   public class LocalizedTextUtil {
   79   	
   80       private static List DEFAULT_RESOURCE_BUNDLES = null;
   81       private static final Logger LOG = LoggerFactory.getLogger(LocalizedTextUtil.class);
   82       private static boolean reloadBundles = false;
   83       private static final Map<String, String> misses = new HashMap<String, String>();
   84       private static final Map messageFormats = new HashMap();
   85   
   86       static {
   87           clearDefaultResourceBundles();
   88       }
   89   
   90   
   91       /**
   92        * Clears the internal list of resource bundles.
   93        */
   94       public static void clearDefaultResourceBundles() {
   95       	if (DEFAULT_RESOURCE_BUNDLES != null) {
   96       		DEFAULT_RESOURCE_BUNDLES.clear();
   97       	}
   98           DEFAULT_RESOURCE_BUNDLES = Collections.synchronizedList(new ArrayList());
   99           DEFAULT_RESOURCE_BUNDLES.add("com/opensymphony/xwork2/xwork-messages");
  100       }
  101   
  102       /**
  103        * Should resorce bundles be reloaded.
  104        * @param reloadBundles  reload bundles?  
  105        */
  106       public static void setReloadBundles(boolean reloadBundles) {
  107           LocalizedTextUtil.reloadBundles = reloadBundles;
  108       }
  109   
  110       /**
  111        * Add's the bundle to the internal list of default bundles.
  112        * <p/>
  113        * If the bundle already exists in the list it will be readded.
  114        * 
  115        * @param resourceBundleName   the name of the bundle to add.
  116        */
  117       public static void addDefaultResourceBundle(String resourceBundleName) {
  118           //make sure this doesn't get added more than once
  119           DEFAULT_RESOURCE_BUNDLES.remove(resourceBundleName);
  120           DEFAULT_RESOURCE_BUNDLES.add(0, resourceBundleName);
  121   
  122           if (LOG.isDebugEnabled()) {
  123               LOG.debug("Added default resource bundle '" + resourceBundleName + "' to default resource bundles = " + DEFAULT_RESOURCE_BUNDLES);
  124           }
  125       }
  126   
  127       /**
  128        * Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale
  129        * with language "en", country "US" and variant "foo". This will parse the output of
  130        * {@link java.util.Locale#toString()}.
  131        *
  132        * @param localeStr The locale String to parse.
  133        * @param defaultLocale The locale to use if localeStr is <tt>null</tt>.
  134        * @return requested Locale
  135        */
  136       public static Locale localeFromString(String localeStr, Locale defaultLocale) {
  137           if ((localeStr == null) || (localeStr.trim().length() == 0) || (localeStr.equals("_"))) {
  138               if ( defaultLocale != null) {
  139                   return defaultLocale;
  140               }
  141               return Locale.getDefault();
  142           }
  143   
  144           int index = localeStr.indexOf('_');
  145           if (index < 0) {
  146               return new Locale(localeStr);
  147           }
  148   
  149           String language = localeStr.substring(0, index);
  150           if (index == localeStr.length()) {
  151               return new Locale(language);
  152           }
  153   
  154           localeStr = localeStr.substring(index + 1);
  155           index = localeStr.indexOf('_');
  156           if (index < 0) {
  157               return new Locale(language, localeStr);
  158           }
  159   
  160           String country = localeStr.substring(0, index);
  161           if (index == localeStr.length()) {
  162               return new Locale(language, country);
  163           }
  164   
  165           localeStr = localeStr.substring(index + 1);
  166           return new Locale(language, country, localeStr);
  167       }
  168   
  169       /**
  170        * Returns a localized message for the specified key, aTextName.  Neither the key nor the
  171        * message is evaluated.
  172        *
  173        * @param aTextName the message key
  174        * @param locale    the locale the message should be for
  175        * @return a localized message based on the specified key, or null if no localized message can be found for it
  176        */
  177       public static String findDefaultText(String aTextName, Locale locale) {
  178           List localList = DEFAULT_RESOURCE_BUNDLES; // it isn't sync'd, but this is so rare, let's do it anyway
  179   
  180           for (Iterator iterator = localList.iterator(); iterator.hasNext();) {
  181               String bundleName = (String) iterator.next();
  182   
  183               ResourceBundle bundle = findResourceBundle(bundleName, locale);
  184               if (bundle != null) {
  185                   reloadBundles();
  186                   try {
  187                       return bundle.getString(aTextName);
  188                   } catch (MissingResourceException e) {
  189                       // ignore and try others
  190                   }
  191               }
  192           }
  193   
  194           return null;
  195       }
  196   
  197       /**
  198        * Returns a localized message for the specified key, aTextName, substituting variables from the
  199        * array of params into the message.  Neither the key nor the message is evaluated.
  200        *
  201        * @param aTextName the message key
  202        * @param locale    the locale the message should be for
  203        * @param params    an array of objects to be substituted into the message text
  204        * @return A formatted message based on the specified key, or null if no localized message can be found for it
  205        */
  206       public static String findDefaultText(String aTextName, Locale locale, Object[] params) {
  207           String defaultText = findDefaultText(aTextName, locale);
  208           if (defaultText != null) {
  209               MessageFormat mf = buildMessageFormat(defaultText, locale);
  210               return mf.format(params);
  211           }
  212           return null;
  213       }
  214   
  215       /**
  216        * Finds the given resorce bundle by it's name.
  217        * <p/>
  218        * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader.
  219        * 
  220        * @param aBundleName  the name of the bundle (usually it's FQN classname).
  221        * @param locale       the locale.
  222        * @return  the bundle, <tt>null</tt> if not found.
  223        */
  224       public static ResourceBundle findResourceBundle(String aBundleName, Locale locale) {
  225           synchronized (misses) {
  226               String key = createMissesKey(aBundleName, locale);
  227               try {
  228                   if (!misses.containsKey(key)) {
  229                       return ResourceBundle.getBundle(aBundleName, locale, Thread.currentThread().getContextClassLoader());
  230                   }
  231               } catch (MissingResourceException ex) {
  232                   misses.put(key, aBundleName);
  233               }
  234           }
  235   
  236           return null;
  237       }
  238   
  239       /**
  240        * Creates a key to used for lookup/storing in the bundle misses cache.
  241        *
  242        * @param aBundleName  the name of the bundle (usually it's FQN classname).
  243        * @param locale       the locale.
  244        * @return the key to use for lookup/storing in the bundle misses cache.
  245        */
  246       private static String createMissesKey(String aBundleName, Locale locale) {
  247           return aBundleName + "_" + locale.toString();
  248       }
  249   
  250       /**
  251        * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
  252        * with aTextName as the default message.
  253        *
  254        * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
  255        */
  256       public static String findText(Class aClass, String aTextName, Locale locale) {
  257           return findText(aClass, aTextName, locale, aTextName, new Object[0]);
  258       }
  259   
  260       /**
  261        * Finds a localized text message for the given key, aTextName. Both the key and the message
  262        * itself is evaluated as required.  The following algorithm is used to find the requested
  263        * message:
  264        * <p/>
  265        * <ol>
  266        * <li>Look for message in aClass' class hierarchy.
  267        * <ol>
  268        * <li>Look for the message in a resource bundle for aClass</li>
  269        * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
  270        * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
  271        * </ol></li>
  272        * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
  273        * the model's class hierarchy (repeat sub-steps listed above).</li>
  274        * <li>If not found, look for message in child property.  This is determined by evaluating
  275        * the message key as an OGNL expression.  For example, if the key is
  276        * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
  277        * object.  If so, repeat the entire process fromthe beginning with the object's class as
  278        * aClass and "address.state" as the message key.</li>
  279        * <li>If not found, look for the message in aClass' package hierarchy.</li>
  280        * <li>If still not found, look for the message in the default resource bundles.</li>
  281        * <li>Return defaultMessage</li>
  282        * </ol>
  283        * <p/>
  284        * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
  285        * message for that specific key cannot be found, the general form will also be looked up
  286        * (i.e. user.phone[*]).
  287        * <p/>
  288        * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
  289        * will be treated as an OGNL expression and evaluated as such.
  290        *
  291        * @param aClass         the class whose name to use as the start point for the search
  292        * @param aTextName      the key to find the text message for
  293        * @param locale         the locale the message should be for
  294        * @param defaultMessage the message to be returned if no text message can be found in any
  295        *                       resource bundle
  296        * @return the localized text, or null if none can be found and no defaultMessage is provided
  297        */
  298       public static String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
  299           ValueStack valueStack = ActionContext.getContext().getValueStack();
  300           return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
  301   
  302       }
  303   
  304       /**
  305        * Finds a localized text message for the given key, aTextName. Both the key and the message
  306        * itself is evaluated as required.  The following algorithm is used to find the requested
  307        * message:
  308        * <p/>
  309        * <ol>
  310        * <li>Look for message in aClass' class hierarchy.
  311        * <ol>
  312        * <li>Look for the message in a resource bundle for aClass</li>
  313        * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
  314        * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
  315        * </ol></li>
  316        * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
  317        * the model's class hierarchy (repeat sub-steps listed above).</li>
  318        * <li>If not found, look for message in child property.  This is determined by evaluating
  319        * the message key as an OGNL expression.  For example, if the key is
  320        * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
  321        * object.  If so, repeat the entire process fromthe beginning with the object's class as
  322        * aClass and "address.state" as the message key.</li>
  323        * <li>If not found, look for the message in aClass' package hierarchy.</li>
  324        * <li>If still not found, look for the message in the default resource bundles.</li>
  325        * <li>Return defaultMessage</li>
  326        * </ol>
  327        * <p/>
  328        * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
  329        * message for that specific key cannot be found, the general form will also be looked up
  330        * (i.e. user.phone[*]).
  331        * <p/>
  332        * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
  333        * will be treated as an OGNL expression and evaluated as such.
  334        * <p/>
  335        * If a message is <b>not</b> found a WARN log will be logged.
  336        *
  337        * @param aClass         the class whose name to use as the start point for the search
  338        * @param aTextName      the key to find the text message for
  339        * @param locale         the locale the message should be for
  340        * @param defaultMessage the message to be returned if no text message can be found in any
  341        *                       resource bundle
  342        * @param valueStack     the value stack to use to evaluate expressions instead of the
  343        *                       one in the ActionContext ThreadLocal
  344        * @return the localized text, or null if none can be found and no defaultMessage is provided
  345        */
  346       public static String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) {
  347           String indexedTextName = null;
  348           if (aTextName == null) {
  349               LOG.warn("Trying to find text with null key!");
  350               aTextName = "";
  351           }
  352           // calculate indexedTextName (collection[*]) if applicable
  353           if (aTextName.indexOf("[") != -1) {
  354               int i = -1;
  355   
  356               indexedTextName = aTextName;
  357   
  358               while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
  359                   int j = indexedTextName.indexOf("]", i);
  360                   String a = indexedTextName.substring(0, i);
  361                   String b = indexedTextName.substring(j);
  362                   indexedTextName = a + "[*" + b;
  363               }
  364           }
  365   
  366           // search up class hierarchy
  367           String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack);
  368   
  369           if (msg != null) {
  370               return msg;
  371           }
  372   
  373           if (ModelDriven.class.isAssignableFrom(aClass)) {
  374               ActionContext context = ActionContext.getContext();
  375               // search up model's class hierarchy
  376               ActionInvocation actionInvocation = context.getActionInvocation();
  377   
  378               // ActionInvocation may be null if we're being run from a Sitemesh filter, so we won't get model texts if this is null
  379               if (actionInvocation != null) {
  380                   Object action = actionInvocation.getAction();
  381                   if (action instanceof ModelDriven) {
  382                       Object model = ((ModelDriven) action).getModel();
  383                       if (model != null) {
  384                           msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack);
  385                           if (msg != null) {
  386                               return msg;
  387                           }
  388                       }
  389                   }
  390               }
  391           }
  392   
  393           // nothing still? alright, search the package hierarchy now
  394           for (Class clazz = aClass;
  395                (clazz != null) && !clazz.equals(Object.class);
  396                clazz = clazz.getSuperclass()) {
  397   
  398               String basePackageName = clazz.getName();
  399               while (basePackageName.lastIndexOf('.') != -1) {
  400                   basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.'));
  401                   String packageName = basePackageName + ".package";
  402                   msg = getMessage(packageName, locale, aTextName, valueStack, args);
  403   
  404                   if (msg != null) {
  405                       return msg;
  406                   }
  407   
  408                   if (indexedTextName != null) {
  409                       msg = getMessage(packageName, locale, indexedTextName, valueStack, args);
  410   
  411                       if (msg != null) {
  412                           return msg;
  413                       }
  414                   }
  415               }
  416           }
  417   
  418           // see if it's a child property
  419           int idx = aTextName.indexOf(".");
  420   
  421           if (idx != -1) {
  422               String newKey = null;
  423               String prop = null;
  424   
  425               if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) {
  426                   idx = aTextName.indexOf(".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length());
  427   
  428                   if (idx != -1) {
  429                       prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx);
  430                       newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1);
  431                   }
  432               } else {
  433                   prop = aTextName.substring(0, idx);
  434                   newKey = aTextName.substring(idx + 1);
  435               }
  436   
  437               if (prop != null) {
  438                   Object obj = valueStack.findValue(prop);
  439                   try {
  440                   	Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot());
  441                   	if (actionObj != null) {
  442                   		PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance().getPropertyDescriptor(actionObj.getClass(), prop);
  443   
  444                   		if (propertyDescriptor != null) {
  445                   			Class clazz=propertyDescriptor.getPropertyType();
  446   
  447                   			if (clazz != null) {
  448                   				if (obj != null)
  449                   					valueStack.push(obj);
  450                   				msg = findText(clazz, newKey, locale, null, args);
  451                   				if (obj != null)
  452                   					valueStack.pop();
  453   
  454                   				if (msg != null) {
  455                   					return msg;
  456                   				}
  457                   			}
  458                   		}
  459                   	}
  460                   }
  461                   catch(Exception e) {
  462                   	LOG.debug("unable to find property "+prop, e);
  463                   }
  464               }
  465           }
  466   
  467           // get default
  468           GetDefaultMessageReturnArg result = null;
  469           if (indexedTextName == null) {
  470               result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
  471           } else {
  472               result = getDefaultMessage(aTextName, locale, valueStack, args, null);
  473               if (result != null && result.message != null) {
  474                   return result.message;
  475               }
  476               result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage);
  477           }
  478           
  479           // could we find the text, if not log a warn
  480           if (unableToFindTextForKey(result)) {
  481           	String warn = "Unable to find text for key '" + aTextName + "' ";
  482           	if (indexedTextName != null) {
  483           		warn += " or indexed key '" + indexedTextName + "' ";
  484           	}
  485           	warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
  486               LOG.debug(warn);
  487           }
  488           
  489           return result != null ? result.message : null;
  490       }
  491       
  492       /**
  493        * Determines if we found the text in the bundles.
  494        * 
  495        * @param result   the result so far
  496        * @return  <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success). 
  497        */
  498       private static boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
  499       	if (result == null || result.message == null) {
  500       		return true;
  501       	}
  502       	
  503   		// did we find it in the bundle, then no problem?
  504       	if (result.foundInBundle) {
  505   			return false;
  506   		}
  507       	
  508       	// not found in bundle
  509       	return true;
  510       }
  511   
  512       /**
  513        * Finds a localized text message for the given key, aTextName, in the specified resource bundle
  514        * with aTextName as the default message.
  515        * <p/>
  516        * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
  517        * will be treated as an OGNL expression and evaluated as such.
  518        *
  519        * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
  520        */
  521       public static String findText(ResourceBundle bundle, String aTextName, Locale locale) {
  522           return findText(bundle, aTextName, locale, aTextName, new Object[0]);
  523       }
  524   
  525       /**
  526        * Finds a localized text message for the given key, aTextName, in the specified resource
  527        * bundle.
  528        * <p/>
  529        * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
  530        * will be treated as an OGNL expression and evaluated as such.
  531        * <p/>
  532        * If a message is <b>not</b> found a WARN log will be logged.
  533        * 
  534        * @param bundle     the bundle
  535        * @param aTextName  the key
  536        * @param locale     the locale
  537        * @param defaultMessage  the default message to use if no message was found in the bundle
  538        * @param args       arguments for the message formatter.
  539        */
  540       public static String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
  541           ValueStack valueStack = ActionContext.getContext().getValueStack();
  542           return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
  543       }
  544   
  545       /**
  546        * Finds a localized text message for the given key, aTextName, in the specified resource
  547        * bundle.
  548        * <p/>
  549        * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
  550        * will be treated as an OGNL expression and evaluated as such.
  551        * <p/>
  552        * If a message is <b>not</b> found a WARN log will be logged.
  553        * 
  554        * @param bundle     the bundle
  555        * @param aTextName  the key
  556        * @param locale     the locale
  557        * @param defaultMessage  the default message to use if no message was found in the bundle
  558        * @param args       arguments for the message formatter.
  559        * @param valueStack the OGNL value stack.
  560        */
  561       public static String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) {
  562           try {
  563               reloadBundles();
  564   
  565               String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
  566               MessageFormat mf = buildMessageFormat(message, locale);
  567   
  568               return mf.format(args);
  569           } catch (MissingResourceException ex) {
  570           	// ignore
  571           }
  572   
  573           GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
  574           if (unableToFindTextForKey(result)) {
  575               LOG.warn("Unable to find text for key '" + aTextName + "' in ResourceBundles for locale '" + locale + "'");
  576           }
  577           return result.message;
  578       }
  579   
  580       /**
  581        * Gets the default message.
  582        */
  583       private static GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args, String defaultMessage) {
  584           GetDefaultMessageReturnArg result = null;
  585       	boolean found = true;
  586       	
  587           if (key != null) {
  588               String message = findDefaultText(key, locale);
  589   
  590               if (message == null) {
  591                   message = defaultMessage;
  592                   found = false; // not found in bundles
  593               }
  594   
  595               // defaultMessage may be null
  596               if (message != null) {
  597                   MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
  598   
  599                   String msg = mf.format(args);
  600                   result = new GetDefaultMessageReturnArg(msg, found);
  601               }
  602           }
  603   
  604           return result;
  605       }
  606   
  607       /**
  608        * Gets the message from the named resource bundle.
  609        */
  610       private static String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
  611           ResourceBundle bundle = findResourceBundle(bundleName, locale);
  612           if (bundle == null) {
  613               return null;
  614           }
  615   
  616           reloadBundles();
  617   
  618           try {
  619               String message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
  620               MessageFormat mf = buildMessageFormat(message, locale);
  621               return mf.format(args);
  622           } catch (MissingResourceException e) {
  623               return null;
  624           }
  625       }
  626   
  627       private static MessageFormat buildMessageFormat(String pattern, Locale locale) {
  628           MessageFormatKey key = new MessageFormatKey(pattern, locale);
  629           MessageFormat format = null;
  630           synchronized(messageFormats) {
  631               format = (MessageFormat) messageFormats.get(key);
  632               if (format == null) {
  633                   format = new MessageFormat(pattern);
  634                   format.setLocale(locale);
  635                   format.applyPattern(pattern);
  636                   messageFormats.put(key, format);
  637               }
  638           }
  639   
  640           return format;
  641       }
  642   
  643       /**
  644        * Traverse up class hierarchy looking for message.  Looks at class, then implemented interface,
  645        * before going up hierarchy.
  646        */
  647       private static String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set checked, ValueStack valueStack) {
  648           if (checked == null) {
  649               checked = new TreeSet();
  650           } else if (checked.contains(clazz.getName())) {
  651               return null;
  652           }
  653   
  654           // look in properties of this class
  655           String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
  656   
  657           if (msg != null) {
  658               return msg;
  659           }
  660   
  661           if (indexedKey != null) {
  662               msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
  663   
  664               if (msg != null) {
  665                   return msg;
  666               }
  667           }
  668   
  669           // look in properties of implemented interfaces
  670           Class[] interfaces = clazz.getInterfaces();
  671   
  672           for (int x = 0; x < interfaces.length; x++) {
  673               msg = getMessage(interfaces[x].getName(), locale, key, valueStack, args);
  674   
  675               if (msg != null) {
  676                   return msg;
  677               }
  678   
  679               if (indexedKey != null) {
  680                   msg = getMessage(interfaces[x].getName(), locale, indexedKey, valueStack, args);
  681   
  682                   if (msg != null) {
  683                       return msg;
  684                   }
  685               }
  686           }
  687   
  688           // traverse up hierarchy
  689           if (clazz.isInterface()) {
  690               interfaces = clazz.getInterfaces();
  691   
  692               for (int x = 0; x < interfaces.length; x++) {
  693                   msg = findMessage(interfaces[x], key, indexedKey, locale, args, checked, valueStack);
  694   
  695                   if (msg != null) {
  696                       return msg;
  697                   }
  698               }
  699           } else {
  700               if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
  701                   return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
  702               }
  703           }
  704   
  705           return null;
  706       }
  707   
  708       private static void reloadBundles() {
  709           if (reloadBundles) {
  710               try {
  711                   clearMap(ResourceBundle.class, null, "cacheList");
  712   
  713                   // now, for the true and utter hack, if we're running in tomcat, clear
  714                   // it's class loader resource cache as well.
  715                   clearTomcatCache();
  716               }
  717               catch (Exception e) {
  718                   LOG.error("Could not reload resource bundles", e);
  719               }
  720           }
  721       }
  722   
  723   
  724       private static void clearTomcatCache() {
  725           ClassLoader loader = Thread.currentThread().getContextClassLoader();
  726           // no need for compilation here.
  727           Class cl = loader.getClass();
  728   
  729           try {
  730               if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) {
  731                   clearMap(cl, loader, "resourceEntries");
  732               } else {
  733                   if (LOG.isDebugEnabled()) {
  734                       LOG.debug("class loader " + cl.getName() + " is not tomcat loader.");
  735                   }
  736               }
  737           }
  738           catch (Exception e) {
  739               LOG.warn("couldn't clear tomcat cache", e);
  740           }
  741       }
  742   
  743   
  744       private static void clearMap(Class cl, Object obj, String name)
  745               throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
  746               InvocationTargetException {
  747           Field field = cl.getDeclaredField(name);
  748           field.setAccessible(true);
  749   
  750           Object cache = field.get(obj);
  751   
  752           synchronized (cache) {
  753               Class ccl = cache.getClass();
  754               Method clearMethod = ccl.getMethod("clear");
  755               clearMethod.invoke(cache);
  756           }
  757   
  758       }
  759   
  760       /**
  761        * Clears all the internal lists. 
  762        */
  763       public static void reset() {
  764           clearDefaultResourceBundles();
  765   
  766           synchronized (misses) {
  767               misses.clear();
  768           }
  769   
  770           synchronized (messageFormats) {
  771               messageFormats.clear();
  772           }
  773       }
  774   
  775       static class MessageFormatKey {
  776           String pattern;
  777           Locale locale;
  778   
  779           MessageFormatKey(String pattern, Locale locale) {
  780               this.pattern = pattern;
  781               this.locale = locale;
  782           }
  783   
  784           public boolean equals(Object o) {
  785               if (this == o) return true;
  786               if (!(o instanceof MessageFormatKey)) return false;
  787   
  788               final MessageFormatKey messageFormatKey = (MessageFormatKey) o;
  789   
  790               if (locale != null ? !locale.equals(messageFormatKey.locale) : messageFormatKey.locale != null)
  791                   return false;
  792               if (pattern != null ? !pattern.equals(messageFormatKey.pattern) : messageFormatKey.pattern != null)
  793                   return false;
  794   
  795               return true;
  796           }
  797   
  798           public int hashCode() {
  799               int result;
  800               result = (pattern != null ? pattern.hashCode() : 0);
  801               result = 29 * result + (locale != null ? locale.hashCode() : 0);
  802               return result;
  803           }
  804       }
  805       
  806       static class GetDefaultMessageReturnArg {
  807       	String message;
  808       	boolean foundInBundle;
  809   		
  810       	public GetDefaultMessageReturnArg(String message, boolean foundInBundle) {
  811   			this.message = message;
  812   			this.foundInBundle = foundInBundle;
  813   		}
  814       }
  815   }

Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.util » [javadoc | source]