diff --git a/core/src/main/java/org/apache/struts2/ActionSupport.java b/core/src/main/java/org/apache/struts2/ActionSupport.java index f5d58b091..6da71ae39 100644 --- a/core/src/main/java/org/apache/struts2/ActionSupport.java +++ b/core/src/main/java/org/apache/struts2/ActionSupport.java @@ -25,6 +25,8 @@ import org.apache.struts2.conversion.impl.ConversionData; import org.apache.struts2.inject.Container; import org.apache.struts2.inject.Inject; import org.apache.struts2.interceptor.ValidationAware; +import org.apache.struts2.text.TextProvider; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.util.ValueStack; import java.io.Serializable; diff --git a/core/src/main/java/org/apache/struts2/DefaultActionProxy.java b/core/src/main/java/org/apache/struts2/DefaultActionProxy.java index 82a5da14b..43fb695ec 100644 --- a/core/src/main/java/org/apache/struts2/DefaultActionProxy.java +++ b/core/src/main/java/org/apache/struts2/DefaultActionProxy.java @@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.text.LocalizedTextProvider; import java.io.Serial; import java.io.Serializable; diff --git a/core/src/main/java/org/apache/struts2/LocaleProvider.java b/core/src/main/java/org/apache/struts2/LocaleProvider.java index 0b502aa74..6536b62a3 100644 --- a/core/src/main/java/org/apache/struts2/LocaleProvider.java +++ b/core/src/main/java/org/apache/struts2/LocaleProvider.java @@ -23,15 +23,14 @@ import org.apache.commons.lang3.StringUtils; import java.util.Locale; - /** * Indicates that the implementing class can provide its own {@link Locale}. * *
* This is useful for when an action may wish override the default locale. All that is * needed is to implement this interface and return your own custom locale. - * The {@link TextProvider} interface uses this interface heavily for retrieving - * internationalized messages from resource bundles. + * The {@link org.apache.struts2.text.TextProvider} interface uses this interface + * heavily for retrieving internationalized messages from resource bundles. *
* * @author Jason Carreira diff --git a/core/src/main/java/org/apache/struts2/LocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/LocalizedTextProvider.java deleted file mode 100644 index 967a6d478..000000000 --- a/core/src/main/java/org/apache/struts2/LocalizedTextProvider.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 org.apache.struts2; - -import org.apache.struts2.util.ValueStack; - -import java.io.Serializable; -import java.util.Locale; -import java.util.ResourceBundle; - -public interface LocalizedTextProvider extends Serializable { - - String findDefaultText(String aTextName, Locale locale); - - String findDefaultText(String aTextName, Locale locale, Object[] params); - - ResourceBundle findResourceBundle(String aBundleName, Locale locale); - - String findText(Class aClass, String aTextName, Locale locale); - - String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args); - - String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack); - - String findText(ResourceBundle bundle, String aTextName, Locale locale); - - String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args); - - String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack); - - void addDefaultResourceBundle(String resourceBundleName); - -} diff --git a/core/src/main/java/org/apache/struts2/components/Date.java b/core/src/main/java/org/apache/struts2/components/Date.java index 08d84ab77..d00917858 100644 --- a/core/src/main/java/org/apache/struts2/components/Date.java +++ b/core/src/main/java/org/apache/struts2/components/Date.java @@ -18,7 +18,7 @@ */ package org.apache.struts2.components; -import org.apache.struts2.TextProvider; +import org.apache.struts2.text.TextProvider; import org.apache.struts2.inject.Inject; import org.apache.struts2.util.ValueStack; import org.apache.logging.log4j.LogManager; diff --git a/core/src/main/java/org/apache/struts2/components/I18n.java b/core/src/main/java/org/apache/struts2/components/I18n.java index 44ff427df..d8ef748da 100644 --- a/core/src/main/java/org/apache/struts2/components/I18n.java +++ b/core/src/main/java/org/apache/struts2/components/I18n.java @@ -22,14 +22,14 @@ import java.io.Writer; import java.util.ResourceBundle; import org.apache.struts2.LocaleProviderFactory; -import org.apache.struts2.LocalizedTextProvider; -import org.apache.struts2.TextProviderFactory; +import org.apache.struts2.text.LocalizedTextProvider; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.views.annotations.StrutsTag; import org.apache.struts2.views.annotations.StrutsTagAttribute; import org.apache.struts2.StrutsException; import org.apache.struts2.LocaleProvider; -import org.apache.struts2.TextProvider; +import org.apache.struts2.text.TextProvider; import org.apache.struts2.inject.Inject; import org.apache.struts2.util.ValueStack; import org.apache.logging.log4j.LogManager; diff --git a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java index 16e1c7ca3..a31b35896 100644 --- a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java +++ b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java @@ -23,11 +23,11 @@ import org.apache.struts2.ActionProxyFactory; import org.apache.struts2.FileManager; import org.apache.struts2.FileManagerFactory; import org.apache.struts2.LocaleProviderFactory; -import org.apache.struts2.LocalizedTextProvider; +import org.apache.struts2.text.LocalizedTextProvider; import org.apache.struts2.ObjectFactory; import org.apache.struts2.StrutsConstants; -import org.apache.struts2.TextProvider; -import org.apache.struts2.TextProviderFactory; +import org.apache.struts2.text.TextProvider; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.UnknownHandlerManager; import org.apache.struts2.components.UrlRenderer; import org.apache.struts2.components.date.DateFormatter; diff --git a/core/src/main/java/org/apache/struts2/config/impl/DefaultConfiguration.java b/core/src/main/java/org/apache/struts2/config/impl/DefaultConfiguration.java index 3ea854b21..0d77695ba 100644 --- a/core/src/main/java/org/apache/struts2/config/impl/DefaultConfiguration.java +++ b/core/src/main/java/org/apache/struts2/config/impl/DefaultConfiguration.java @@ -20,15 +20,15 @@ package org.apache.struts2.config.impl; import org.apache.struts2.ActionContext; import org.apache.struts2.DefaultLocaleProviderFactory; -import org.apache.struts2.DefaultTextProvider; +import org.apache.struts2.text.DefaultTextProvider; import org.apache.struts2.FileManager; import org.apache.struts2.FileManagerFactory; import org.apache.struts2.LocaleProviderFactory; -import org.apache.struts2.LocalizedTextProvider; +import org.apache.struts2.text.LocalizedTextProvider; import org.apache.struts2.ObjectFactory; -import org.apache.struts2.StrutsTextProviderFactory; -import org.apache.struts2.TextProvider; -import org.apache.struts2.TextProviderFactory; +import org.apache.struts2.text.StrutsTextProviderFactory; +import org.apache.struts2.text.TextProvider; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.config.Configuration; import org.apache.struts2.config.ConfigurationException; import org.apache.struts2.config.ContainerProvider; @@ -92,7 +92,7 @@ import org.apache.struts2.ognl.accessor.RootAccessor; import org.apache.struts2.ognl.accessor.XWorkMethodAccessor; import org.apache.struts2.util.OgnlTextParser; import org.apache.struts2.util.PatternMatcher; -import org.apache.struts2.util.StrutsLocalizedTextProvider; +import org.apache.struts2.text.StrutsLocalizedTextProvider; import org.apache.struts2.util.TextParser; import org.apache.struts2.util.ValueStack; import org.apache.struts2.util.ValueStackFactory; diff --git a/core/src/main/java/org/apache/struts2/conversion/impl/DateConverter.java b/core/src/main/java/org/apache/struts2/conversion/impl/DateConverter.java index 6d89be284..753e72766 100644 --- a/core/src/main/java/org/apache/struts2/conversion/impl/DateConverter.java +++ b/core/src/main/java/org/apache/struts2/conversion/impl/DateConverter.java @@ -19,7 +19,7 @@ package org.apache.struts2.conversion.impl; import org.apache.struts2.ActionContext; -import org.apache.struts2.TextProvider; +import org.apache.struts2.text.TextProvider; import org.apache.struts2.util.ValueStack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/core/src/main/java/org/apache/struts2/conversion/impl/XWorkConverter.java b/core/src/main/java/org/apache/struts2/conversion/impl/XWorkConverter.java index 507f765d0..89d3ecceb 100644 --- a/core/src/main/java/org/apache/struts2/conversion/impl/XWorkConverter.java +++ b/core/src/main/java/org/apache/struts2/conversion/impl/XWorkConverter.java @@ -21,7 +21,7 @@ package org.apache.struts2.conversion.impl; import org.apache.struts2.ActionContext; import org.apache.struts2.FileManager; import org.apache.struts2.FileManagerFactory; -import org.apache.struts2.LocalizedTextProvider; +import org.apache.struts2.text.LocalizedTextProvider; import org.apache.struts2.conversion.ConversionAnnotationProcessor; import org.apache.struts2.conversion.ConversionFileProcessor; import org.apache.struts2.conversion.TypeConverter; diff --git a/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java index 87c1d32f3..9bad6896c 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java @@ -20,12 +20,10 @@ package org.apache.struts2.interceptor; import org.apache.struts2.LocaleProvider; import org.apache.struts2.LocaleProviderFactory; -import org.apache.struts2.TextProvider; -import org.apache.struts2.TextProviderFactory; +import org.apache.struts2.text.TextProvider; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.inject.Container; import org.apache.struts2.inject.Inject; -import org.apache.struts2.interceptor.AbstractInterceptor; -import org.apache.struts2.interceptor.ValidationAware; import org.apache.struts2.util.TextParseUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java index 5a02dad96..37b5c383c 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java @@ -22,7 +22,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.ActionContext; import org.apache.struts2.ActionInvocation; -import org.apache.struts2.LocalizedTextProvider; +import org.apache.struts2.text.LocalizedTextProvider; import org.apache.struts2.StrutsConstants; import org.apache.struts2.config.entities.ActionConfig; import org.apache.struts2.dispatcher.HttpParameters; diff --git a/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java index 65c6d57fa..e4b1e73ae 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java @@ -23,7 +23,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.ActionContext; import org.apache.struts2.ActionInvocation; -import org.apache.struts2.LocalizedTextProvider; +import org.apache.struts2.text.LocalizedTextProvider; import org.apache.struts2.StrutsConstants; import org.apache.struts2.config.entities.ActionConfig; import org.apache.struts2.config.entities.Parameterizable; diff --git a/core/src/main/java/org/apache/struts2/interceptor/TokenInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/TokenInterceptor.java index 83d344474..7f12ea7f1 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/TokenInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/TokenInterceptor.java @@ -19,11 +19,9 @@ package org.apache.struts2.interceptor; import org.apache.struts2.ActionInvocation; -import org.apache.struts2.TextProvider; -import org.apache.struts2.TextProviderFactory; -import org.apache.struts2.interceptor.ValidationAware; +import org.apache.struts2.text.TextProvider; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.inject.Inject; -import org.apache.struts2.interceptor.MethodFilterInterceptor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.ServletActionContext; diff --git a/core/src/main/java/org/apache/struts2/ognl/OgnlValueStack.java b/core/src/main/java/org/apache/struts2/ognl/OgnlValueStack.java index a155c1cbc..26a76f7f5 100644 --- a/core/src/main/java/org/apache/struts2/ognl/OgnlValueStack.java +++ b/core/src/main/java/org/apache/struts2/ognl/OgnlValueStack.java @@ -19,7 +19,7 @@ package org.apache.struts2.ognl; import org.apache.struts2.ActionContext; -import org.apache.struts2.TextProvider; +import org.apache.struts2.text.TextProvider; import org.apache.struts2.conversion.impl.XWorkConverter; import org.apache.struts2.inject.Container; import org.apache.struts2.inject.Inject; diff --git a/core/src/main/java/org/apache/struts2/ognl/OgnlValueStackFactory.java b/core/src/main/java/org/apache/struts2/ognl/OgnlValueStackFactory.java index ec32f8986..ac60de0dc 100644 --- a/core/src/main/java/org/apache/struts2/ognl/OgnlValueStackFactory.java +++ b/core/src/main/java/org/apache/struts2/ognl/OgnlValueStackFactory.java @@ -18,7 +18,7 @@ */ package org.apache.struts2.ognl; -import org.apache.struts2.TextProvider; +import org.apache.struts2.text.TextProvider; import org.apache.struts2.conversion.NullHandler; import org.apache.struts2.conversion.impl.XWorkConverter; import org.apache.struts2.inject.Container; diff --git a/core/src/main/java/org/apache/struts2/util/AbstractLocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/text/AbstractLocalizedTextProvider.java similarity index 77% rename from core/src/main/java/org/apache/struts2/util/AbstractLocalizedTextProvider.java rename to core/src/main/java/org/apache/struts2/text/AbstractLocalizedTextProvider.java index 316cbb99e..6185552c1 100644 --- a/core/src/main/java/org/apache/struts2/util/AbstractLocalizedTextProvider.java +++ b/core/src/main/java/org/apache/struts2/text/AbstractLocalizedTextProvider.java @@ -16,15 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.util; +package org.apache.struts2.text; -import org.apache.struts2.ActionContext; -import org.apache.struts2.LocalizedTextProvider; -import org.apache.struts2.inject.Inject; import org.apache.commons.lang3.ObjectUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionContext; import org.apache.struts2.StrutsConstants; +import org.apache.struts2.inject.Inject; +import org.apache.struts2.util.TextParseUtil; +import org.apache.struts2.util.ValueStack; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -66,24 +67,18 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { private final Set- * Finds a localized text message for the given key, aTextName, in the specified resource - * bundle. - *
- * - *
- * If a message is found, it will also be interpolated. Anything within ${...}
- * will be treated as an OGNL expression and evaluated as such.
- *
- * If a message is not found a WARN log will be logged. - *
- * - * @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. - * @param valueStack the OGNL value stack. - * @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, + public String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) { try { reloadBundles(valueStack.getContext()); - String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack); + String message = TextParseUtil.translateVariables(bundle.getString(textKey), valueStack); MessageFormat mf = buildMessageFormat(message, locale); return formatWithNullDetection(mf, args); } catch (MissingResourceException ex) { if (devMode) { - LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle); + LOG.warn("Missing key [{}] in bundle [{}]!", textKey, bundle); } else { - LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle); + LOG.debug("Missing key [{}] in bundle [{}]!", textKey, bundle); } } - GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage); + GetDefaultMessageReturnArg result = getDefaultMessage(textKey, locale, valueStack, args, defaultMessage); if (unableToFindTextForKey(result)) { - LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale); + LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", textKey, locale); } return result != null ? result.message : null; } @@ -224,11 +178,10 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { /** * Clear a specific bundle + locale combination from thebundlesMap.
- * Intended for descendants to use clear a bundle + locale combination.
+ * Intended for descendants to use clear a bundle + locale combination.
*
* @param bundleName The bundle (combined with locale) to remove from the bundle map
* @param locale Provides the locale to combine with the bundle to get the key
- *
* @since 6.0.0
*/
protected void clearBundle(final String bundleName, Locale locale) {
@@ -239,13 +192,13 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
/**
* Clears the missingBundles contents. This allows descendants to
- * clear the "missing bundles cache" when desired (or needed).
- *
+ * clear the "missing bundles cache" when desired (or needed).
+ *
* Note: This method may be used when the bundlesMap state has changed
- * in such a way that bundles that were previously "missing" may now be available
- * (e.g. after calling {@link #addDefaultResourceBundle(java.lang.String)} when the
- * {@link AbstractLocalizedTextProvider} has already been used for failed bundle
- * lookups of a given key, or some transitory state made a bundle lookup fail.
+ * in such a way that bundles that were previously "missing" may now be available
+ * (e.g. after calling {@link #addDefaultResourceBundle(java.lang.String)} when the
+ * {@link AbstractLocalizedTextProvider} has already been used for failed bundle
+ * lookups of a given key, or some transitory state made a bundle lookup fail.
*
* @since 6.0.0
*/
@@ -287,10 +240,10 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
/**
* A helper method for {@link ResourceBundle} bundle reload logic.
- *
+ *
* Uses standard {@link ResourceBundle} methods to clear the bundle caches for the * {@link ClassLoader} instances that this class is aware of at the time of the call. - * + *
* The clearCache() methods have been available since Java 1.6, so
* it is anticipated the logic will work on any subsequent JVM versions.
*
@@ -301,27 +254,29 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
ResourceBundle.clearCache(); // Bundles loaded by the caller's classloader.
ResourceBundle.clearCache(ccl); // Bundles loaded by the context classloader (may be the same).
// Clear the bundle cache for any non-null delegated classloaders.
- delegatedClassLoaderMap.forEach( (key, value) -> { if (value != null) ResourceBundle.clearCache(value) ;} );
+ delegatedClassLoaderMap.forEach((key, value) -> {
+ if (value != null) ResourceBundle.clearCache(value);
+ });
}
/**
* "Hacky" helper method that attempts to clear the Tomcat ResourceEntry
* {@link Map} using knowledge of the Tomcat source code.
- *
+ *
* It relies on the {@link #TOMCAT_RESOURCE_ENTRIES_FIELD} field name, base class name * {@link #TOMCAT_WEBAPP_CLASSLOADER_BASE}. and descendant class names {@link #TOMCAT_WEBAPP_CLASSLOADER}, * {@link #TOMCAT_PARALLEL_WEBAPP_CLASSLOADER}, to keep the values identified in the constants. * It appears to be valid for Tomcat versions 7-10 so far, but could become invalid at any time in the future * when the resource handling logic in Tomcat changes. - * + *
* Note: With Java 9+, calling this method may result in "Illegal reflective access" warnings. Be aware - * its logic may fail in a future version of Java that blocks the reflection calls needed for this method. + * its logic may fail in a future version of Java that blocks the reflection calls needed for this method. */ private void clearTomcatCache() { ClassLoader loader = getCurrentThreadContextClassLoader(); // no need for compilation here. - Class cl = loader.getClass(); - Class superCl = cl.getSuperclass(); + Class> cl = loader.getClass(); + Class> superCl = cl.getSuperclass(); try { if ((TOMCAT_WEBAPP_CLASSLOADER.equals(cl.getName()) || TOMCAT_PARALLEL_WEBAPP_CLASSLOADER.equals(cl.getName())) && @@ -348,19 +303,19 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { /** * Helper method that is intended to clear a {@link Map} instance by name. - * + *
* This method relies on reflection to perform its operations, and may be blocked in Java 9 and later,
* depending on the accessibility of the field.
*
- * @param cl The {@link Class} of the obj parameter.
- * @param obj The {@link Object} from which the named field is to be extracted (may be null for a static field).
+ * @param cl The {@link Class} of the obj parameter.
+ * @param obj The {@link Object} from which the named field is to be extracted (may be null for a static field).
* @param name The name of the field containing a {@link Map} reference.
- * @throws NoSuchFieldException if a field accessed by this call does not exist.
- * @throws IllegalAccessException if a field, method or or class accessed by this call cannot be accessed.
- * @throws NoSuchMethodException if a method accessed by this call does not exist.
+ * @throws NoSuchFieldException if a field accessed by this call does not exist.
+ * @throws IllegalAccessException if a field, method or or class accessed by this call cannot be accessed.
+ * @throws NoSuchMethodException if a method accessed by this call does not exist.
* @throws InvocationTargetException if a method accessed by this call fails invocation.
*/
- private void clearMap(Class cl, Object obj, String name)
+ private void clearMap(Class> cl, Object obj, String name)
throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Field field = cl.getDeclaredField(name);
@@ -372,7 +327,7 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
Object cache = field.get(obj);
synchronized (cache) {
- Class ccl = cache.getClass();
+ Class> ccl = cache.getClass();
Method clearMethod = ccl.getMethod("clear");
clearMethod.invoke(cache);
}
@@ -416,7 +371,6 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
* flow of the {@link LocalizedTextProvider} implementation the descendant provides).
*
* @param searchDefaultBundlesFirst provide {@link String} "true" or "false" to set the flag state accordingly.
- *
* @since 6.0.0
*/
@Inject(value = StrutsConstants.STRUTS_I18N_SEARCH_DEFAULTBUNDLES_FIRST, required = false)
@@ -424,20 +378,10 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
this.searchDefaultBundlesFirst = Boolean.parseBoolean(searchDefaultBundlesFirst);
}
- /**
- * Finds the given resource bundle by it's name.
- *
- * Will use Thread.currentThread().getContextClassLoader() as the classloader.
- *
null).
- * @param defaultMessage the default message {@link String} to use if both key lookup operations fail.
+ * @param key the initial key to search for a value within the default resource bundles.
+ * @param alternateKey the alternate (fall-back) key to search for a value within the default resource bundles, if the initial key lookup fails.
+ * @param locale the {@link Locale} to be used for the default resource bundle lookup.
+ * @param valueStack the {@link ValueStack} associated with the operation.
+ * @param args the argument array for parameterized messages (may be null).
+ * @param defaultMessage the default message {@link String} to use if both key lookup operations fail.
* @return the {@link GetDefaultMessageReturnArg} result containing the processed message lookup (by key first, then alternateKey if key's lookup fails).
- * If both key lookup operations fail, defaultMessage is used for processing.
- * If defaultMessage is null then the return result may be null.
+ * If both key lookup operations fail, defaultMessage is used for processing.
+ * If defaultMessage is null then the return result may be null.
*/
protected GetDefaultMessageReturnArg getDefaultMessageWithAlternateKey(String key, String alternateKey, Locale locale, ValueStack valueStack,
- Object[] args, String defaultMessage) {
+ Object[] args, String defaultMessage) {
GetDefaultMessageReturnArg result;
if (alternateKey == null || alternateKey.isEmpty()) {
result = getDefaultMessage(key, locale, valueStack, args, defaultMessage);
@@ -593,8 +537,8 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
*
* @return the message
*/
- protected String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set+ * Note that unlike {@link StrutsLocalizedTextProvider}, this class {@link GlobalLocalizedTextProvider} will + * only search the default bundles for localized text. + */ +public class GlobalLocalizedTextProvider extends AbstractLocalizedTextProvider { + + private static final Logger LOG = LogManager.getLogger(GlobalLocalizedTextProvider.class); + + public GlobalLocalizedTextProvider() { + addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE); + addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE); + } + + @Override + public String findText(Class> startClazz, String textKey, Locale locale) { + return findText(startClazz, textKey, locale, textKey, new Object[0]); + } + + @Override + public String findText(Class> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args) { + ValueStack valueStack = ActionContext.getContext().getValueStack(); + return findText(startClazz, textKey, locale, defaultMessage, args, valueStack); + } + + @Override + public String findText(Class> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) { + if (textKey == null) { + LOG.debug("Key is null, short-circuit to default message"); + return defaultMessage; + } + String indexedTextName = extractIndexedName(textKey); + + // get default + GetDefaultMessageReturnArg result = getDefaultMessageWithAlternateKey(textKey, indexedTextName, locale, valueStack, args, defaultMessage); + + logMissingText(startClazz, textKey, locale, result, indexedTextName); + + return result != null ? result.message : null; + } + + @Override + public String findText(ResourceBundle bundle, String textKey, Locale locale) { + return findText(bundle, textKey, locale, textKey, new Object[0]); + } + + @Override + public String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args) { + ValueStack valueStack = ActionContext.getContext().getValueStack(); + return findText(bundle, textKey, locale, defaultMessage, args, valueStack); + } + +} diff --git a/core/src/main/java/org/apache/struts2/util/GlobalLocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/text/LocalizedTextProvider.java similarity index 53% rename from core/src/main/java/org/apache/struts2/util/GlobalLocalizedTextProvider.java rename to core/src/main/java/org/apache/struts2/text/LocalizedTextProvider.java index 620aa2912..472ab35f4 100644 --- a/core/src/main/java/org/apache/struts2/util/GlobalLocalizedTextProvider.java +++ b/core/src/main/java/org/apache/struts2/text/LocalizedTextProvider.java @@ -16,70 +16,80 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.util; +package org.apache.struts2.text; -import org.apache.struts2.ActionContext; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.apache.struts2.util.ValueStack; +import java.io.Serializable; import java.util.Locale; import java.util.ResourceBundle; -/** - * Provides support for localization in the framework, it can be used to read only default bundles. - * - * Note that unlike {@link StrutsLocalizedTextProvider}, this class {@link GlobalLocalizedTextProvider} will - * only search the default bundles for localized text. - */ -public class GlobalLocalizedTextProvider extends AbstractLocalizedTextProvider { - - private static final Logger LOG = LogManager.getLogger(GlobalLocalizedTextProvider.class); - - public GlobalLocalizedTextProvider() { - addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE); - addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE); - } +public interface LocalizedTextProvider extends Serializable { /** - * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)} - * with aTextName as the default message. + * Returns a localized message for the specified key, aTextName. Neither the key nor the + * message is evaluated. * - * @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) + * @param textKey the message key + * @param locale the locale the message should be for + * @return a localized message based on the specified key, or null if no localized message can be found for it */ - @Override - public String findText(Class aClass, String aTextName, Locale locale) { - return findText(aClass, aTextName, locale, aTextName, new Object[0]); - } + String findDefaultText(String textKey, Locale locale); /** + * Returns a localized message for the specified key, aTextName, substituting variables from the + * array of params into the message. Neither the key nor the message is evaluated. + * + * @param textKey the message key + * @param locale the locale the message should be for + * @param params an array of objects to be substituted into the message text + * @return A formatted message based on the specified key, or null if no localized message can be found for it + */ + String findDefaultText(String textKey, Locale locale, Object[] params); + + /** + * Finds the given resource bundle by it's name. *
- * Finds a localized text message for the given key, aTextName. Both the key and the message
+ * Will use Thread.currentThread().getContextClassLoader() as the classloader.
+ *
* 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[*]). - *
- * *
* If a message is found, it will also be interpolated. Anything within ${...}
* will be treated as an OGNL expression and evaluated as such.
- *
- * Finds a localized text message for the given key, aTextName. Both the key and the message + * Finds a localized text message for the given key, textKey. Both the key and the message * itself is evaluated as required. The following algorithm is used to find the requested * message: - *
* ** 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[*]). - *
- * *
* If a message is found, it will also be interpolated. Anything within ${...}
* will be treated as an OGNL expression and evaluated as such.
- *
* If a message is not found a DEBUG level log warning will be logged. - *
* - * @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 startClazz the class whose name to use as the start point for the search + * @param textKey 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 @@ -131,91 +128,66 @@ public class GlobalLocalizedTextProvider extends AbstractLocalizedTextProvider { * 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; - } - } - - // get default - GetDefaultMessageReturnArg result = getDefaultMessageWithAlternateKey(aTextName, 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; - } + String findText(Class> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack); /** - ** Finds a localized text message for the given key, aTextName, in the specified resource bundle * with aTextName as the default message. - *
- * *
* If a message is found, it will also be interpolated. Anything within ${...}
* will be treated as an OGNL expression and evaluated as such.
- *
* Finds a localized text message for the given key, aTextName, in the specified resource * bundle. - *
- * *
* If a message is found, it will also be interpolated. Anything within ${...}
* will be treated as an OGNL expression and evaluated as such.
- *
* If a message is not found a WARN log will be logged. - *
* * @param bundle the bundle - * @param aTextName the key + * @param textKey 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); - } + String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args); + + /** + * Finds a localized text message for the given key, aTextName, in the specified resource + * bundle. + *
+ * If a message is found, it will also be interpolated. Anything within ${...}
+ * will be treated as an OGNL expression and evaluated as such.
+ *
+ * If a message is not found a WARN log will be logged. + * + * @param bundle the bundle + * @param textKey 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. + * @param valueStack the OGNL value stack. + * @return the localized text, or null if none can be found and no defaultMessage is provided + */ + String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack); + + /** + * Adds the bundle to the internal list of default bundles. + * If the bundle already exists in the list it will be re-added. + * + * @param bundleName the name of the bundle to add. + */ + void addDefaultResourceBundle(String bundleName); } diff --git a/core/src/main/java/org/apache/struts2/ResourceBundleTextProvider.java b/core/src/main/java/org/apache/struts2/text/ResourceBundleTextProvider.java similarity index 93% rename from core/src/main/java/org/apache/struts2/ResourceBundleTextProvider.java rename to core/src/main/java/org/apache/struts2/text/ResourceBundleTextProvider.java index b2ee3cad7..a0385fd6e 100644 --- a/core/src/main/java/org/apache/struts2/ResourceBundleTextProvider.java +++ b/core/src/main/java/org/apache/struts2/text/ResourceBundleTextProvider.java @@ -16,7 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2; +package org.apache.struts2.text; + +import org.apache.struts2.LocaleProvider; import java.util.ResourceBundle; @@ -39,7 +41,7 @@ public interface ResourceBundleTextProvider extends TextProvider { * * @param clazz the class to use for loading. */ - void setClazz(Class clazz); + void setClazz(Class> clazz); /** * Set the LocaleProvider to use. diff --git a/core/src/main/java/org/apache/struts2/text/StrutsLocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/text/StrutsLocalizedTextProvider.java new file mode 100644 index 000000000..bfdfe22fb --- /dev/null +++ b/core/src/main/java/org/apache/struts2/text/StrutsLocalizedTextProvider.java @@ -0,0 +1,213 @@ +/* + * 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 org.apache.struts2.text; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionContext; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.ModelDriven; +import org.apache.struts2.conversion.impl.XWorkConverter; +import org.apache.struts2.inject.Inject; +import org.apache.struts2.util.ValueStack; +import org.apache.struts2.util.reflection.ReflectionProvider; + +import java.beans.PropertyDescriptor; +import java.util.Locale; +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); + private transient ReflectionProvider reflectionProvider; + + public StrutsLocalizedTextProvider() { + addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE); + addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE); + } + + @Override + public String findText(Class> startClazz, String textKey, Locale locale) { + return findText(startClazz, textKey, locale, textKey, new Object[0]); + } + + @Override + public String findText(Class> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args) { + ValueStack valueStack = ActionContext.getContext().getValueStack(); + return findText(startClazz, textKey, locale, defaultMessage, args, valueStack); + + } + + @Override + public String findText(Class> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args, + ValueStack valueStack) { + if (textKey == null) { + LOG.debug("Key is null, short-circuit to default message"); + return defaultMessage; + } + String indexedTextName = extractIndexedName(textKey); + + // Allow for and track an early lookup for the message in the default resource bundles first, before searching the class hierarchy. + // The early lookup is only performed when the text provider has been configured to do so, otherwise follow the standard processing order. + boolean performedInitialDefaultBundlesMessageLookup = false; + GetDefaultMessageReturnArg result = null; + + // If search default bundles first is set true, call alternative logic first. + if (searchDefaultBundlesFirst) { + result = getDefaultMessageWithAlternateKey(textKey, indexedTextName, locale, valueStack, args, defaultMessage); + performedInitialDefaultBundlesMessageLookup = true; + if (!unableToFindTextForKey(result)) { + return result.message; // Found a message in the default resource bundles for textKey or indexedTextName. + } + } + + // search up class hierarchy + String msg = findMessage(startClazz, textKey, indexedTextName, locale, args, null, valueStack); + + if (msg != null) { + return msg; + } + + if (ModelDriven.class.isAssignableFrom(startClazz)) { + 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(), textKey, indexedTextName, locale, args, null, valueStack); + if (msg != null) { + return msg; + } + } + } + } + } + + // nothing still? alright, search the package hierarchy now + for (Class> clazz = startClazz; + (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, textKey, 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 = textKey.indexOf('.'); + + if (idx != -1) { + String newKey = null; + String prop = null; + + if (textKey.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) { + idx = textKey.indexOf('.', XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length()); + + if (idx != -1) { + prop = textKey.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx); + newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + textKey.substring(idx + 1); + } + } else { + prop = textKey.substring(0, idx); + newKey = textKey.substring(idx + 1); + } + + if (prop != null) { + Object obj = valueStack.findValue(prop); + try { + Object actionObj = reflectionProvider.getRealTarget(prop, valueStack.getContext(), valueStack.getRoot()); + if (actionObj != null) { + PropertyDescriptor propertyDescriptor = reflectionProvider.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 + // Note: The default bundles lookup may already have been performed (via alternate early lookup), + // so we check first to avoid repeating the same operation twice. + if (!performedInitialDefaultBundlesMessageLookup) { + result = getDefaultMessageWithAlternateKey(textKey, indexedTextName, locale, valueStack, args, defaultMessage); + } + + logMissingText(startClazz, textKey, locale, result, indexedTextName); + + return result != null ? result.message : null; + } + + @Override + public String findText(ResourceBundle bundle, String textKey, Locale locale) { + return findText(bundle, textKey, locale, textKey, new Object[0]); + } + + @Override + public String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args) { + ValueStack valueStack = ActionContext.getContext().getValueStack(); + return findText(bundle, textKey, locale, defaultMessage, args, valueStack); + } + + @Inject + public void setReflectionProvider(ReflectionProvider reflectionProvider) { + this.reflectionProvider = reflectionProvider; + } + +} diff --git a/core/src/main/java/org/apache/struts2/StrutsTextProviderFactory.java b/core/src/main/java/org/apache/struts2/text/StrutsTextProviderFactory.java similarity index 95% rename from core/src/main/java/org/apache/struts2/StrutsTextProviderFactory.java rename to core/src/main/java/org/apache/struts2/text/StrutsTextProviderFactory.java index 06a4617a0..4f7331263 100644 --- a/core/src/main/java/org/apache/struts2/StrutsTextProviderFactory.java +++ b/core/src/main/java/org/apache/struts2/text/StrutsTextProviderFactory.java @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2; +package org.apache.struts2.text; +import org.apache.struts2.LocaleProviderFactory; import org.apache.struts2.inject.Inject; import java.util.ResourceBundle; @@ -49,7 +50,7 @@ public class StrutsTextProviderFactory implements TextProviderFactory { } @Override - public TextProvider createInstance(Class clazz) { + public TextProvider createInstance(Class> clazz) { TextProvider instance = getTextProvider(clazz); if (instance instanceof ResourceBundleTextProvider) { ((ResourceBundleTextProvider) instance).setClazz(clazz); diff --git a/core/src/main/java/org/apache/struts2/TextProvider.java b/core/src/main/java/org/apache/struts2/text/TextProvider.java similarity index 96% rename from core/src/main/java/org/apache/struts2/TextProvider.java rename to core/src/main/java/org/apache/struts2/text/TextProvider.java index 16924a757..bdd2f026c 100644 --- a/core/src/main/java/org/apache/struts2/TextProvider.java +++ b/core/src/main/java/org/apache/struts2/text/TextProvider.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2; +package org.apache.struts2.text; import org.apache.struts2.util.ValueStack; @@ -33,9 +33,9 @@ import java.util.ResourceBundle; * global texts to be defined for an application base class. * *
- * You can override {@link LocaleProvider#getLocale()} to change the behaviour of how + * You can override {@link org.apache.struts2.LocaleProvider#getLocale()} to change the behaviour of how * to choose locale for the bundles that are returned. Typically you would - * use the {@link LocaleProvider} interface to get the users configured locale. + * use the {@link org.apache.struts2.LocaleProvider} interface to get the users configured locale. *
* *@@ -56,7 +56,7 @@ import java.util.ResourceBundle; *
* @author Jason Carreira * @author Rainer Hermanns - * @see LocaleProvider + * @see org.apache.struts2.LocaleProvider * @see TextProviderSupport */ public interface TextProvider { diff --git a/core/src/main/java/org/apache/struts2/TextProviderFactory.java b/core/src/main/java/org/apache/struts2/text/TextProviderFactory.java similarity index 91% rename from core/src/main/java/org/apache/struts2/TextProviderFactory.java rename to core/src/main/java/org/apache/struts2/text/TextProviderFactory.java index b7c5a1f5d..5ef4070cd 100644 --- a/core/src/main/java/org/apache/struts2/TextProviderFactory.java +++ b/core/src/main/java/org/apache/struts2/text/TextProviderFactory.java @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2; +package org.apache.struts2.text; import java.util.ResourceBundle; public interface TextProviderFactory { - TextProvider createInstance(Class clazz); + TextProvider createInstance(Class> clazz); TextProvider createInstance(ResourceBundle bundle); diff --git a/core/src/main/java/org/apache/struts2/TextProviderSupport.java b/core/src/main/java/org/apache/struts2/text/TextProviderSupport.java similarity index 90% rename from core/src/main/java/org/apache/struts2/TextProviderSupport.java rename to core/src/main/java/org/apache/struts2/text/TextProviderSupport.java index cee8b8601..bf2fa685a 100644 --- a/core/src/main/java/org/apache/struts2/TextProviderSupport.java +++ b/core/src/main/java/org/apache/struts2/text/TextProviderSupport.java @@ -16,12 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2; +package org.apache.struts2.text; +import org.apache.struts2.LocaleProvider; +import org.apache.struts2.LocaleProviderFactory; import org.apache.struts2.inject.Inject; import org.apache.struts2.util.ValueStack; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; /** * Default TextProvider implementation. @@ -31,7 +38,7 @@ import java.util.*; */ public class TextProviderSupport implements ResourceBundleTextProvider { - protected Class clazz; + protected Class> clazz; protected LocaleProvider localeProvider; protected ResourceBundle bundle; protected LocalizedTextProvider localizedTextProvider; @@ -42,7 +49,7 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * @param clazz a clazz to use for reading the resource bundle. * @param provider a locale provider. */ - public TextProviderSupport(Class clazz, LocaleProvider provider, LocalizedTextProvider localizedTextProvider) { + public TextProviderSupport(Class> clazz, LocaleProvider provider, LocalizedTextProvider localizedTextProvider) { this.clazz = clazz; this.localeProvider = provider; this.localizedTextProvider = localizedTextProvider; @@ -72,7 +79,7 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * @param clazz a clazz to use for reading the resource bundle. */ @Override - public void setClazz(Class clazz) { + public void setClazz(Class> clazz) { this.clazz = clazz; } @@ -103,13 +110,13 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * class. */ public boolean hasKey(String key) { - String message; - if (clazz != null) { - message = localizedTextProvider.findText(clazz, key, getLocale(), null, new Object[0] ); + String message; + if (clazz != null) { + message = localizedTextProvider.findText(clazz, key, getLocale(), null, new Object[0]); } else { message = localizedTextProvider.findText(bundle, key, getLocale(), null, new Object[0]); } - return message != null; + return message != null; } /** @@ -135,7 +142,7 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * a cascading style that allow global texts to be defined for an application base * class. If no text is found for this text name, the default value is returned. * - * @param key name of text to be found + * @param key name of text to be found * @param defaultValue the default value which will be returned if no text is found * @return value of named text or the provided defaultValue if no value is found */ @@ -151,7 +158,7 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * a cascading style that allow global texts to be defined for an application base * class. If no text is found for this text name, the default value is returned. * - * @param key name of text to be found + * @param key name of text to be found * @param defaultValue the default value which will be returned if no text is found * @return value of named text or the provided defaultValue if no value is found */ @@ -169,8 +176,8 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * a cascading style that allow global texts to be defined for an application base * class. If no text is found for this text name, the default value is returned. * - * @param key name of text to be found - * @param args a List of args to be used in a MessageFormat message + * @param key name of text to be found + * @param args a List of args to be used in a MessageFormat message * @return value of named text or the provided key if no value is found */ public String getText(String key, List> args) { @@ -185,8 +192,8 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * a cascading style that allow global texts to be defined for an application base * class. If no text is found for this text name, the default value is returned. * - * @param key name of text to be found - * @param args an array of args to be used in a MessageFormat message + * @param key name of text to be found + * @param args an array of args to be used in a MessageFormat message * @return value of named text or the provided key if no value is found */ public String getText(String key, String[] args) { @@ -201,7 +208,7 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * a cascading style that allow global texts to be defined for an application base * class. If no text is found for this text name, the default value is returned. * - * @param key name of text to be found + * @param key name of text to be found * @param defaultValue the default value which will be returned if no text is found * @param args a List of args to be used in a MessageFormat message * @return value of named text or the provided defaultValue if no value is found @@ -242,7 +249,7 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * default value is returned. Instead of using the value stack in the ActionContext * this version of the getText() method uses the provided value stack. * - * @param key the resource bundle key that is to be searched for + * @param key the resource bundle key that is to be searched for * @param defaultValue the default value which will be returned if no message is found * @param args a list args to be used in a {@link java.text.MessageFormat} message * @param stack the value stack to use for finding the text @@ -251,10 +258,10 @@ public class TextProviderSupport implements ResourceBundleTextProvider { public String getText(String key, String defaultValue, List> args, ValueStack stack) { Object[] argsArray = ((args != null) ? args.toArray() : null); Locale locale; - if (stack == null){ - locale = getLocale(); - }else{ - locale = stack.getActionContext().getLocale(); + if (stack == null) { + locale = getLocale(); + } else { + locale = stack.getActionContext().getLocale(); } if (locale == null) { locale = getLocale(); @@ -266,7 +273,6 @@ public class TextProviderSupport implements ResourceBundleTextProvider { } } - /** * Gets a message based on a key using the supplied args, as defined in * {@link java.text.MessageFormat}, or, if the message is not found, a supplied @@ -280,21 +286,7 @@ public class TextProviderSupport implements ResourceBundleTextProvider { * @return the message as found in the resource bundle, or defaultValue if none is found */ public String getText(String key, String defaultValue, String[] args, ValueStack stack) { - Locale locale; - if (stack == null){ - locale = getLocale(); - }else{ - locale = stack.getActionContext().getLocale(); - } - if (locale == null) { - locale = getLocale(); - } - if (clazz != null) { - return localizedTextProvider.findText(clazz, key, locale, defaultValue, args, stack); - } else { - return localizedTextProvider.findText(bundle, key, locale, defaultValue, args, stack); - } - + return getText(key, defaultValue, args != null ? Arrays.stream(args).toList() : List.of(), stack); } /** diff --git a/core/src/main/java/org/apache/struts2/util/DebugUtils.java b/core/src/main/java/org/apache/struts2/util/DebugUtils.java index 099f70171..a7cb8dcce 100644 --- a/core/src/main/java/org/apache/struts2/util/DebugUtils.java +++ b/core/src/main/java/org/apache/struts2/util/DebugUtils.java @@ -18,7 +18,7 @@ */ package org.apache.struts2.util; -import org.apache.struts2.TextProvider; +import org.apache.struts2.text.TextProvider; import org.apache.struts2.interceptor.ValidationAware; import org.apache.logging.log4j.Logger; diff --git a/core/src/main/java/org/apache/struts2/util/StrutsLocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/util/StrutsLocalizedTextProvider.java deleted file mode 100644 index ea37726b9..000000000 --- a/core/src/main/java/org/apache/struts2/util/StrutsLocalizedTextProvider.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * 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 org.apache.struts2.util; - -import org.apache.struts2.ActionContext; -import org.apache.struts2.ActionInvocation; -import org.apache.struts2.ModelDriven; -import org.apache.struts2.conversion.impl.XWorkConverter; -import org.apache.struts2.inject.Inject; -import org.apache.struts2.util.reflection.ReflectionProvider; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.beans.PropertyDescriptor; -import java.util.Locale; -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); - private transient ReflectionProvider reflectionProvider; - - public StrutsLocalizedTextProvider() { - addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE); - addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE); - } - - /** - * 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]); - } - - /** - *- * 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: - *
- * - *true, look for the message in the default resource bundles first.true).- * 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[*]). - *
- * - *
- * If a message is found, it will also be interpolated. Anything within ${...}
- * will be treated as an OGNL expression and evaluated as such.
- *
- * 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: - *
- * - *true, look for the message in the default resource bundles first.true).- * 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[*]). - *
- * - *
- * If a message is found, it will also be interpolated. Anything within ${...}
- * will be treated as an OGNL expression and evaluated as such.
- *
- * If a message is not found a DEBUG level log warning will be logged. - *
- * - * @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; - } - } - - // Allow for and track an early lookup for the message in the default resource bundles first, before searching the class hierarchy. - // The early lookup is only performed when the text provider has been configured to do so, otherwise follow the standard processing order. - boolean performedInitialDefaultBundlesMessageLookup = false; - GetDefaultMessageReturnArg result = null; - - // If search default bundles first is set true, call alternative logic first. - if (searchDefaultBundlesFirst) { - result = getDefaultMessageWithAlternateKey(aTextName, indexedTextName, locale, valueStack, args, defaultMessage); - performedInitialDefaultBundlesMessageLookup = true; - if (!unableToFindTextForKey(result)) { - return result.message; // Found a message in the default resource bundles for aTextName or indexedTextName. - } - } - - // 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 = reflectionProvider.getRealTarget(prop, valueStack.getContext(), valueStack.getRoot()); - if (actionObj != null) { - PropertyDescriptor propertyDescriptor = reflectionProvider.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 - // Note: The default bundles lookup may already have been performed (via alternate early lookup), - // so we check first to avoid repeating the same operation twice. - if (!performedInitialDefaultBundlesMessageLookup) { - result = getDefaultMessageWithAlternateKey(aTextName, 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; - } - - /** - *- * Finds a localized text message for the given key, aTextName, in the specified resource bundle - * with aTextName as the default message. - *
- * - *
- * If a message is found, it will also be interpolated. Anything within ${...}
- * will be treated as an OGNL expression and evaluated as such.
- *
- * Finds a localized text message for the given key, aTextName, in the specified resource - * bundle. - *
- * - *
- * If a message is found, it will also be interpolated. Anything within ${...}
- * will be treated as an OGNL expression and evaluated as such.
- *
- * If a message is not found a WARN log will be logged. - *
- * - * @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); - } - - @Inject - public void setReflectionProvider(ReflectionProvider reflectionProvider) { - this.reflectionProvider = reflectionProvider; - } -} diff --git a/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java b/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java index 7cda5d490..dc5120ef0 100644 --- a/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java +++ b/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java @@ -18,8 +18,7 @@ */ package org.apache.struts2.util; -import org.apache.struts2.TextProvider; -import org.apache.struts2.util.ValueStack; +import org.apache.struts2.text.TextProvider; import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/core/src/main/java/org/apache/struts2/util/TokenHelper.java b/core/src/main/java/org/apache/struts2/util/TokenHelper.java index 985ad6580..4b4b939fa 100644 --- a/core/src/main/java/org/apache/struts2/util/TokenHelper.java +++ b/core/src/main/java/org/apache/struts2/util/TokenHelper.java @@ -19,7 +19,7 @@ package org.apache.struts2.util; import org.apache.struts2.ActionContext; -import org.apache.struts2.LocalizedTextProvider; +import org.apache.struts2.text.LocalizedTextProvider; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.dispatcher.Parameter; diff --git a/core/src/main/java/org/apache/struts2/validator/DefaultActionValidatorManager.java b/core/src/main/java/org/apache/struts2/validator/DefaultActionValidatorManager.java index 275aaf169..2f2b758b7 100644 --- a/core/src/main/java/org/apache/struts2/validator/DefaultActionValidatorManager.java +++ b/core/src/main/java/org/apache/struts2/validator/DefaultActionValidatorManager.java @@ -21,7 +21,7 @@ package org.apache.struts2.validator; import org.apache.struts2.ActionContext; import org.apache.struts2.FileManager; import org.apache.struts2.FileManagerFactory; -import org.apache.struts2.TextProviderFactory; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.inject.Inject; import org.apache.struts2.util.ClassLoaderUtil; import org.apache.struts2.util.ValueStack; diff --git a/core/src/main/java/org/apache/struts2/validator/DelegatingValidatorContext.java b/core/src/main/java/org/apache/struts2/validator/DelegatingValidatorContext.java index 56500f71f..255369745 100644 --- a/core/src/main/java/org/apache/struts2/validator/DelegatingValidatorContext.java +++ b/core/src/main/java/org/apache/struts2/validator/DelegatingValidatorContext.java @@ -21,11 +21,11 @@ package org.apache.struts2.validator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.ActionContext; -import org.apache.struts2.CompositeTextProvider; +import org.apache.struts2.text.CompositeTextProvider; import org.apache.struts2.LocaleProvider; import org.apache.struts2.LocaleProviderFactory; -import org.apache.struts2.TextProvider; -import org.apache.struts2.TextProviderFactory; +import org.apache.struts2.text.TextProvider; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.interceptor.ValidationAware; import org.apache.struts2.util.ValueStack; diff --git a/core/src/main/java/org/apache/struts2/validator/ValidatorContext.java b/core/src/main/java/org/apache/struts2/validator/ValidatorContext.java index f5401b7ce..8460ae031 100644 --- a/core/src/main/java/org/apache/struts2/validator/ValidatorContext.java +++ b/core/src/main/java/org/apache/struts2/validator/ValidatorContext.java @@ -19,7 +19,7 @@ package org.apache.struts2.validator; import org.apache.struts2.LocaleProvider; -import org.apache.struts2.TextProvider; +import org.apache.struts2.text.TextProvider; import org.apache.struts2.interceptor.ValidationAware; /** diff --git a/core/src/main/java/org/apache/struts2/validator/validators/ValidatorSupport.java b/core/src/main/java/org/apache/struts2/validator/validators/ValidatorSupport.java index 4362884e3..3b896d4dc 100644 --- a/core/src/main/java/org/apache/struts2/validator/validators/ValidatorSupport.java +++ b/core/src/main/java/org/apache/struts2/validator/validators/ValidatorSupport.java @@ -18,7 +18,7 @@ */ package org.apache.struts2.validator.validators; -import org.apache.struts2.TextProviderFactory; +import org.apache.struts2.text.TextProviderFactory; import org.apache.struts2.inject.Inject; import org.apache.struts2.util.TextParseUtil; import org.apache.struts2.util.ValueStack; diff --git a/core/src/main/java/org/apache/struts2/validator/validators/VisitorFieldValidator.java b/core/src/main/java/org/apache/struts2/validator/validators/VisitorFieldValidator.java index 3b4d87805..70a8e4c03 100644 --- a/core/src/main/java/org/apache/struts2/validator/validators/VisitorFieldValidator.java +++ b/core/src/main/java/org/apache/struts2/validator/validators/VisitorFieldValidator.java @@ -19,8 +19,8 @@ package org.apache.struts2.validator.validators; import org.apache.struts2.ActionContext; -import org.apache.struts2.CompositeTextProvider; -import org.apache.struts2.TextProvider; +import org.apache.struts2.text.CompositeTextProvider; +import org.apache.struts2.text.TextProvider; import org.apache.struts2.inject.Inject; import org.apache.struts2.util.ValueStack; import org.apache.struts2.validator.ActionValidatorManager; diff --git a/core/src/main/resources/struts-beans.xml b/core/src/main/resources/struts-beans.xml index d3f8c879a..6b266f0f8 100644 --- a/core/src/main/resources/struts-beans.xml +++ b/core/src/main/resources/struts-beans.xml @@ -128,15 +128,15 @@CompositeTextProviderTest
- *
- * @author Rainer Hermanns
- * @version $Id$
- */
public class CompositeTextProviderTest extends XWorkTestCase {
-
private CompositeTextProvider textProvider = null;
-
- public void testGetText() throws Exception {
+ public void testGetText() {
// we should get the text from the 1st text provider
assertEquals(textProvider.getText("name"), "1 name");
assertEquals(textProvider.getText("age"), "1 age");
@@ -45,8 +40,7 @@ public class CompositeTextProviderTest extends XWorkTestCase {
assertEquals(textProvider.getText("someNonExistingKey"), "someNonExistingKey");
}
-
- public void testGetTextWithDefaultValues() throws Exception {
+ public void testGetTextWithDefaultValues() {
assertEquals(textProvider.getText("name", "some default name"), "1 name");
assertEquals(textProvider.getText("age", "some default age"), "1 age");
assertEquals(textProvider.getText("no_such_key", "default value"), "default value");
@@ -56,16 +50,24 @@ public class CompositeTextProviderTest extends XWorkTestCase {
assertEquals(textProvider.getText("bike", "some default bike"), "This is a bike");
}
-
- public void testGetTextWithDefaultValuesAndArgs() throws Exception {
+ public void testGetTextWithDefaultValuesAndArgs() {
assertEquals(textProvider.getText("goodnight", "say good night", "Adam"), "1 good night Adam");
- assertEquals(textProvider.getText("goodnight", "say good night", new String[] { "Adam" }), "1 good night Adam");
- assertEquals(textProvider.getText("goodnight", "say good night", new ArrayList