Java tutorial
/* * $Id$ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.opensymphony.xwork2.util; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.conversion.impl.XWorkConverter; import com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.beans.PropertyDescriptor; import java.text.MessageFormat; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; /** * Provides support for localization in the framework, it can be used to read only default bundles, * or it can search the class hierarchy to find proper bundles. */ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider { private static final Logger LOG = LogManager.getLogger(StrutsLocalizedTextProvider.class); /** * Clears the internal list of resource bundles. * * @deprecated used only in tests */ @Deprecated public static void clearDefaultResourceBundles() { // no-op } public StrutsLocalizedTextProvider() { addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE); addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE); } /** * Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale * with language "en", country "US" and variant "foo". This will parse the output of * {@link java.util.Locale#toString()}. * * @param localeStr The locale String to parse. * @param defaultLocale The locale to use if localeStr is <tt>null</tt>. * @return requested Locale * @deprecated please use {@link org.apache.commons.lang3.LocaleUtils#toLocale(String)} */ @Deprecated public static Locale localeFromString(String localeStr, Locale defaultLocale) { if ((localeStr == null) || (localeStr.trim().length() == 0) || ("_".equals(localeStr))) { if (defaultLocale != null) { return defaultLocale; } return Locale.getDefault(); } int index = localeStr.indexOf('_'); if (index < 0) { return new Locale(localeStr); } String language = localeStr.substring(0, index); if (index == localeStr.length()) { return new Locale(language); } localeStr = localeStr.substring(index + 1); index = localeStr.indexOf('_'); if (index < 0) { return new Locale(language, localeStr); } String country = localeStr.substring(0, index); if (index == localeStr.length()) { return new Locale(language, country); } localeStr = localeStr.substring(index + 1); return new Locale(language, country, localeStr); } /** * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)} * with aTextName as the default message. * * @param aClass class name * @param aTextName text name * @param locale the locale * @return the localized text, or null if none can be found and no defaultMessage is provided * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) */ @Override public String findText(Class aClass, String aTextName, Locale locale) { return findText(aClass, aTextName, locale, aTextName, new Object[0]); } /** * <p> * Finds a localized text message for the given key, aTextName. Both the key and the message * itself is evaluated as required. The following algorithm is used to find the requested * message: * </p> * * <ol> * <li>Look for message in aClass' class hierarchy. * <ol> * <li>Look for the message in a resource bundle for aClass</li> * <li>If not found, look for the message in a resource bundle for any implemented interface</li> * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li> * </ol></li> * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in * the model's class hierarchy (repeat sub-steps listed above).</li> * <li>If not found, look for message in child property. This is determined by evaluating * the message key as an OGNL expression. For example, if the key is * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an * object. If so, repeat the entire process fromthe beginning with the object's class as * aClass and "address.state" as the message key.</li> * <li>If not found, look for the message in aClass' package hierarchy.</li> * <li>If still not found, look for the message in the default resource bundles.</li> * <li>Return defaultMessage</li> * </ol> * * <p> * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a * message for that specific key cannot be found, the general form will also be looked up * (i.e. user.phone[*]). * </p> * * <p> * If a message is found, it will also be interpolated. Anything within <code>${...}</code> * will be treated as an OGNL expression and evaluated as such. * </p> * * @param aClass the class whose name to use as the start point for the search * @param aTextName the key to find the text message for * @param locale the locale the message should be for * @param defaultMessage the message to be returned if no text message can be found in any * resource bundle * @param args arguments * resource bundle * @return the localized text, or null if none can be found and no defaultMessage is provided */ @Override public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) { ValueStack valueStack = ActionContext.getContext().getValueStack(); return findText(aClass, aTextName, locale, defaultMessage, args, valueStack); } /** * <p> * Finds a localized text message for the given key, aTextName. Both the key and the message * itself is evaluated as required. The following algorithm is used to find the requested * message: * </p> * * <ol> * <li>Look for message in aClass' class hierarchy. * <ol> * <li>Look for the message in a resource bundle for aClass</li> * <li>If not found, look for the message in a resource bundle for any implemented interface</li> * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li> * </ol></li> * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in * the model's class hierarchy (repeat sub-steps listed above).</li> * <li>If not found, look for message in child property. This is determined by evaluating * the message key as an OGNL expression. For example, if the key is * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an * object. If so, repeat the entire process fromthe beginning with the object's class as * aClass and "address.state" as the message key.</li> * <li>If not found, look for the message in aClass' package hierarchy.</li> * <li>If still not found, look for the message in the default resource bundles.</li> * <li>Return defaultMessage</li> * </ol> * * <p> * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a * message for that specific key cannot be found, the general form will also be looked up * (i.e. user.phone[*]). * </p> * * <p> * If a message is found, it will also be interpolated. Anything within <code>${...}</code> * will be treated as an OGNL expression and evaluated as such. * </p> * * <p> * If a message is <b>not</b> found a WARN log will be logged. * </p> * * @param aClass the class whose name to use as the start point for the search * @param aTextName the key to find the text message for * @param locale the locale the message should be for * @param defaultMessage the message to be returned if no text message can be found in any * resource bundle * @param args arguments * @param valueStack the value stack to use to evaluate expressions instead of the * one in the ActionContext ThreadLocal * @return the localized text, or null if none can be found and no defaultMessage is provided */ @Override public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) { String indexedTextName = null; if (aTextName == null) { LOG.warn("Trying to find text with null key!"); aTextName = ""; } // calculate indexedTextName (collection[*]) if applicable if (aTextName.contains("[")) { int i = -1; indexedTextName = aTextName; while ((i = indexedTextName.indexOf("[", i + 1)) != -1) { int j = indexedTextName.indexOf("]", i); String a = indexedTextName.substring(0, i); String b = indexedTextName.substring(j); indexedTextName = a + "[*" + b; } } // search up class hierarchy String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack); if (msg != null) { return msg; } if (ModelDriven.class.isAssignableFrom(aClass)) { ActionContext context = ActionContext.getContext(); // search up model's class hierarchy ActionInvocation actionInvocation = context.getActionInvocation(); // ActionInvocation may be null if we're being run from a Sitemesh filter, so we won't get model texts if this is null if (actionInvocation != null) { Object action = actionInvocation.getAction(); if (action instanceof ModelDriven) { Object model = ((ModelDriven) action).getModel(); if (model != null) { msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack); if (msg != null) { return msg; } } } } } // nothing still? alright, search the package hierarchy now for (Class clazz = aClass; (clazz != null) && !clazz.equals(Object.class); clazz = clazz.getSuperclass()) { String basePackageName = clazz.getName(); while (basePackageName.lastIndexOf('.') != -1) { basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.')); String packageName = basePackageName + ".package"; msg = getMessage(packageName, locale, aTextName, valueStack, args); if (msg != null) { return msg; } if (indexedTextName != null) { msg = getMessage(packageName, locale, indexedTextName, valueStack, args); if (msg != null) { return msg; } } } } // see if it's a child property int idx = aTextName.indexOf("."); if (idx != -1) { String newKey = null; String prop = null; if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) { idx = aTextName.indexOf(".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length()); if (idx != -1) { prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx); newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1); } } else { prop = aTextName.substring(0, idx); newKey = aTextName.substring(idx + 1); } if (prop != null) { Object obj = valueStack.findValue(prop); try { Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot()); if (actionObj != null) { PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance() .getPropertyDescriptor(actionObj.getClass(), prop); if (propertyDescriptor != null) { Class clazz = propertyDescriptor.getPropertyType(); if (clazz != null) { if (obj != null) { valueStack.push(obj); } msg = findText(clazz, newKey, locale, null, args); if (obj != null) { valueStack.pop(); } if (msg != null) { return msg; } } } } } catch (Exception e) { LOG.debug("unable to find property {}", prop, e); } } } // get default GetDefaultMessageReturnArg result; if (indexedTextName == null) { result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage); } else { result = getDefaultMessage(aTextName, locale, valueStack, args, null); if (result != null && result.message != null) { return result.message; } result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage); } // could we find the text, if not log a warn if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) { String warn = "Unable to find text for key '" + aTextName + "' "; if (indexedTextName != null) { warn += " or indexed key '" + indexedTextName + "' "; } warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'"; LOG.debug(warn); } return result != null ? result.message : null; } /** * <p> * Finds a localized text message for the given key, aTextName, in the specified resource bundle * with aTextName as the default message. * </p> * * <p> * If a message is found, it will also be interpolated. Anything within <code>${...}</code> * will be treated as an OGNL expression and evaluated as such. * </p> * * @param bundle a resource bundle name * @param aTextName text name * @param locale the locale * @return the localized text, or null if none can be found and no defaultMessage is provided * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[]) */ @Override public String findText(ResourceBundle bundle, String aTextName, Locale locale) { return findText(bundle, aTextName, locale, aTextName, new Object[0]); } /** * <p> * Finds a localized text message for the given key, aTextName, in the specified resource * bundle. * </p> * * <p> * If a message is found, it will also be interpolated. Anything within <code>${...}</code> * will be treated as an OGNL expression and evaluated as such. * </p> * * <p> * If a message is <b>not</b> found a WARN log will be logged. * </p> * * @param bundle the bundle * @param aTextName the key * @param locale the locale * @param defaultMessage the default message to use if no message was found in the bundle * @param args arguments for the message formatter. * @return the localized text, or null if none can be found and no defaultMessage is provided */ @Override public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) { ValueStack valueStack = ActionContext.getContext().getValueStack(); return findText(bundle, aTextName, locale, defaultMessage, args, valueStack); } }