You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2020/07/26 20:52:45 UTC
[juneau] branch master updated: Rewrite Messages API.
This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 224fafb Rewrite Messages API.
224fafb is described below
commit 224fafb055cf149b7617e1b3a0730c1d839f087b
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Sun Jul 26 16:52:37 2020 -0400
Rewrite Messages API.
---
.../java/org/apache/juneau/cp/Messages_Test.java | 149 ++++-
.../juneau/cp/test1/MessageBundleTest1.properties | 8 +
.../cp/test1/MessageBundleTest1_ja.properties | 5 +
.../org/apache/juneau/cp/test2/Test2.properties | 2 +
.../main/java/org/apache/juneau/cp/Messages.java | 653 ++++++++-------------
.../java/org/apache/juneau/cp/MessagesBuilder.java | 116 ++++
.../juneau/internal/ResourceBundleUtils.java} | 99 ++--
.../test/java/org/apache/juneau/rest/NlsTest.java | 2 +-
.../rest/annotation/RestResourceMessagesTest.java | 2 +-
.../java/org/apache/juneau/rest/RestContext.java | 10 +-
.../java/org/apache/juneau/rest/RestRequest.java | 2 +-
11 files changed, 591 insertions(+), 457 deletions(-)
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/Messages_Test.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/Messages_Test.java
index 8c6c6f9..60f8cb4 100644
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/Messages_Test.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/Messages_Test.java
@@ -20,24 +20,155 @@ import java.util.*;
import static java.util.Locale.*;
import org.apache.juneau.cp.test1.*;
+import org.apache.juneau.cp.test2.*;
import org.junit.*;
@FixMethodOrder(NAME_ASCENDING)
public class Messages_Test {
@Test
- public void a01_nonExistent() throws Exception {
- assertThrown(()->Messages.of(Test1.class)).contains("Could not find bundle path for class");
- assertThrown(()->Messages.of(Test1.class,"bad.properties")).contains("Bundle path should not end with '.properties'");
+ public void a01_sameDirectory() throws Exception {
+ Messages x1 = Messages.of(MessageBundleTest1.class);
+ assertString(x1.getString("file")).is("MessageBundleTest1.properties");
+ assertString(x1.getString(JAPANESE, "file")).is("MessageBundleTest1_ja.properties");
+ assertString(x1.forLocale(JAPANESE).getString("file")).is("MessageBundleTest1_ja.properties");
+ assertString(x1.forLocale(JAPAN).getString("file")).is("MessageBundleTest1_ja_JP.properties");
+ assertString(x1.forLocale(CHINA).getString("file")).is("MessageBundleTest1.properties");
+ assertString(x1.forLocale((Locale)null).getString("file")).is("MessageBundleTest1.properties");
}
@Test
- public void a02_sameDirectory() throws Exception {
+ public void a02_customName() throws Exception {
+ Messages x1 = Messages.of(MessageBundleTest1.class, "files/Test1");
+ assertString(x1.getString("file")).is("files/Test1.properties");
+ assertString(x1.getString(JAPANESE, "file")).is("files/Test1_ja.properties");
+ assertString(x1.forLocale(JAPANESE).getString("file")).is("files/Test1_ja.properties");
+ assertString(x1.forLocale(JAPAN).getString("file")).is("files/Test1_ja_JP.properties");
+ assertString(x1.forLocale(CHINA).getString("file")).is("files/Test1.properties");
+ assertString(x1.forLocale((Locale)null).getString("file")).is("files/Test1.properties");
+
+ Messages x2 = Messages.create(MessageBundleTest1.class).name(null).build();
+ assertString(x2.getString("file")).is("MessageBundleTest1.properties");
+ assertString(x2.getString(JAPANESE, "file")).is("MessageBundleTest1_ja.properties");
+ assertString(x2.forLocale(JAPANESE).getString("file")).is("MessageBundleTest1_ja.properties");
+ assertString(x2.forLocale(JAPAN).getString("file")).is("MessageBundleTest1_ja_JP.properties");
+ assertString(x2.forLocale(CHINA).getString("file")).is("MessageBundleTest1.properties");
+ assertString(x2.forLocale((Locale)null).getString("file")).is("MessageBundleTest1.properties");
+ }
+
+ @Test
+ public void a03_customSearchPaths() throws Exception {
+ Messages x = Messages.create(MessageBundleTest1.class).name("Test1").baseNames("{package}.files.{name}").build();
+ assertString(x.getString("file")).is("files/Test1.properties");
+ assertString(x.getString(JAPANESE, "file")).is("files/Test1_ja.properties");
+ assertString(x.forLocale(JAPANESE).getString("file")).is("files/Test1_ja.properties");
+ assertString(x.forLocale(JAPAN).getString("file")).is("files/Test1_ja_JP.properties");
+ assertString(x.forLocale(CHINA).getString("file")).is("files/Test1.properties");
+ assertString(x.forLocale((Locale)null).getString("file")).is("files/Test1.properties");
+
+ Messages x2 = Messages.create(MessageBundleTest1.class).name("Test1").baseNames((String[])null).build();
+ assertString(x2.getString("file")).is("{!file}");
+ }
+
+ @Test
+ public void a04_customLocale() throws Exception {
+ Messages x1 = Messages.create(MessageBundleTest1.class).locale(Locale.JAPAN).build();
+ assertString(x1.getString("file")).is("MessageBundleTest1_ja_JP.properties");
+ assertString(x1.getString(JAPANESE, "file")).is("MessageBundleTest1_ja.properties");
+ assertString(x1.forLocale(JAPANESE).getString("file")).is("MessageBundleTest1_ja.properties");
+ assertString(x1.forLocale(JAPAN).getString("file")).is("MessageBundleTest1_ja_JP.properties");
+ assertString(x1.forLocale(CHINA).getString("file")).is("MessageBundleTest1.properties");
+ assertString(x1.forLocale((Locale)null).getString("file")).is("MessageBundleTest1.properties");
+
+ Messages x2 = Messages.create(MessageBundleTest1.class).locale(null).build();
+ assertString(x2.getString("file")).is("MessageBundleTest1.properties");
+ assertString(x2.getString(JAPANESE, "file")).is("MessageBundleTest1_ja.properties");
+ assertString(x2.forLocale(JAPANESE).getString("file")).is("MessageBundleTest1_ja.properties");
+ assertString(x2.forLocale(JAPAN).getString("file")).is("MessageBundleTest1_ja_JP.properties");
+ assertString(x2.forLocale(CHINA).getString("file")).is("MessageBundleTest1.properties");
+ assertString(x2.forLocale((Locale)null).getString("file")).is("MessageBundleTest1.properties");
+ }
+
+ @Test
+ public void a05_nonExistentBundle() throws Exception {
+ Messages x1 = Messages.of(MessageBundleTest1.class, "Bad");
+ assertString(x1.getString("file")).is("{!file}");
+ assertString(x1.getString(JAPANESE, "file")).is("{!file}");
+ assertString(x1.forLocale(JAPANESE).getString("file")).is("{!file}");
+ assertString(x1.forLocale(JAPAN).getString("file")).is("{!file}");
+ assertString(x1.forLocale(CHINA).getString("file")).is("{!file}");
+ assertString(x1.forLocale((Locale)null).getString("file")).is("{!file}");
+
+ Messages x2 = x1.forLocale(JAPANESE);
+ assertString(x2.getString("file")).is("{!file}");
+ }
+
+ @Test
+ public void a06_parent() throws Exception {
+ Messages x1 = Messages.create(MessageBundleTest1.class).name("Bad").parent(Messages.of(Test2.class)).build();
+ assertString(x1.getString("file")).is("Test2.properties");
+ assertString(x1.getString(JAPANESE, "file")).is("Test2_ja.properties");
+ assertString(x1.forLocale(JAPANESE).getString("file")).is("Test2_ja.properties");
+ assertString(x1.forLocale(JAPAN).getString("file")).is("Test2_ja_JP.properties");
+ assertString(x1.forLocale(CHINA).getString("file")).is("Test2.properties");
+ assertString(x1.forLocale((Locale)null).getString("file")).is("Test2.properties");
+
+ Messages x2 = Messages.create(MessageBundleTest1.class).parent(Messages.of(Test2.class)).build();
+ assertString(x2.getString("file")).is("MessageBundleTest1.properties");
+ assertString(x2.getString("yyy")).is("bar");
+ }
+
+ @Test
+ public void a07_nonExistentMessage() throws Exception {
+ Messages x = Messages.create(MessageBundleTest1.class).name("Bad").parent(Messages.of(Test2.class)).build();
+ assertString(x.getString("bad")).is("{!bad}");
+ }
+
+ @Test
+ public void a08_nonExistentMessage() throws Exception {
+ Messages x = Messages.create(MessageBundleTest1.class).name("Bad").parent(Messages.of(Test2.class)).build();
+ assertString(x.getString("bad")).is("{!bad}");
+ }
+
+ @Test
+ public void a09_keySet_prefix() throws Exception {
+ Messages x = Messages.of(MessageBundleTest1.class);
+ assertObject(new TreeSet<>(x.keySet("xx"))).json().is("['xx','xx.','xx.foo']");
+ }
+
+ @Test
+ public void a10_getString() throws Exception {
Messages x = Messages.of(MessageBundleTest1.class);
- assertString(x.getString("file")).is("MessageBundleTest1.properties");
- assertString(x.getBundle(JAPANESE).getString("file")).is("MessageBundleTest1_ja.properties");
- assertString(x.getBundle(JAPAN).getString("file")).is("MessageBundleTest1_ja_JP.properties");
- assertString(x.getBundle(CHINA).getString("file")).is("MessageBundleTest1.properties");
- assertString(x.getBundle((Locale)null).getString("file")).is("MessageBundleTest1.properties");
+ assertString(x.getString("foo","bar")).is("foo bar");
+ assertString(x.getString("bar","bar")).is("bar bar");
+ assertString(x.getString("baz","bar")).is("{!baz}");
+ assertString(x.getString(JAPAN, "foo","bar")).is("fooja bar");
+ assertString(x.getString(CHINA, "foo","bar")).is("foo bar");
+ assertString(x.getString((Locale)null, "foo","bar")).is("foo bar");
+ assertString(x.getString(JAPAN, "baz")).is("baz");
+ assertString(x.getString(CHINA, "baz")).is("{!baz}");
+ assertString(x.getString((Locale)null, "baz")).is("{!baz}");
+ }
+
+ @Test
+ public void a11_findFirstString() throws Exception {
+ Messages x = Messages.of(MessageBundleTest1.class);
+ assertString(x.findFirstString("baz","foo")).is("foo {0}");
+ assertString(x.findFirstString("baz","baz")).isNull();
+ assertString(x.findFirstString(JAPAN,"baz","foo")).is("baz");
+ assertString(x.findFirstString(CHINA,"baz","baz")).isNull();
+ assertString(x.findFirstString((Locale)null,"baz","baz")).isNull();
+ }
+
+ @Test
+ public void a12_getKeys() throws Exception {
+ Messages x = Messages.of(Test2.class);
+ assertObject(x.getKeys()).json().is("['file','yyy']");
+ }
+
+ @Test
+ public void a13_toString() throws Exception {
+ Messages x = Messages.of(Test2.class);
+ assertString(x).is("{file:'Test2.properties',yyy:'bar'}");
}
}
diff --git a/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test1/MessageBundleTest1.properties b/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test1/MessageBundleTest1.properties
index 7c463ea..48f5b24 100644
--- a/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test1/MessageBundleTest1.properties
+++ b/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test1/MessageBundleTest1.properties
@@ -13,3 +13,11 @@
# ***************************************************************************************************************************
file=MessageBundleTest1.properties
+
+MessageBundleTest1.foo = foo {0}
+bar = bar {0}
+
+xx = foo
+xx. = foo
+xxx = foo
+xx.foo = foo
\ No newline at end of file
diff --git a/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test1/MessageBundleTest1_ja.properties b/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test1/MessageBundleTest1_ja.properties
index 927ee6d..5516332 100644
--- a/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test1/MessageBundleTest1_ja.properties
+++ b/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test1/MessageBundleTest1_ja.properties
@@ -13,3 +13,8 @@
# ***************************************************************************************************************************
file=MessageBundleTest1_ja.properties
+
+MessageBundleTest1.foo = fooja {0}
+
+baz = baz
+
diff --git a/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test2/Test2.properties b/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test2/Test2.properties
index 80affa9..89556df 100644
--- a/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test2/Test2.properties
+++ b/juneau-core/juneau-core-utest/src/test/resources/org/apache/juneau/cp/test2/Test2.properties
@@ -13,3 +13,5 @@
# ***************************************************************************************************************************
file=Test2.properties
+
+yyy = bar
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
index 88804b6..7cadfad 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
@@ -1,398 +1,255 @@
-// ***************************************************************************************************************************
-// * 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.juneau.cp;
-
-import static org.apache.juneau.internal.StringUtils.*;
-import static org.apache.juneau.internal.ThrowableUtils.*;
-
-import java.text.*;
-import java.util.*;
-import java.util.concurrent.*;
-
-import org.apache.juneau.collections.*;
-
-/**
- * Wraps a {@link ResourceBundle} to provide some useful additional functionality.
- *
- * <ul class='spaced-list'>
- * <li>
- * Instead of throwing {@link MissingResourceException}, the {@link #getString(String)} method
- * will return <js>"{!!key}"</js> if the bundle was not found, and <js>"{!key}"</js> if bundle
- * was found but the key is not in the bundle.
- * <li>
- * A client locale can be set as a {@link ThreadLocal} object using the static {@link #setClientLocale(Locale)}
- * so that client localized messages can be retrieved using the {@link #getClientString(String, Object...)}
- * method on all instances of this class.
- * <li>
- * Resource bundles on parent classes can be added to the search path for this class by using the
- * {@link #addSearchPath(Class, String)} method.
- * This allows messages to be retrieved from the resource bundles of parent classes.
- * <li>
- * Locale-specific bundles can be retrieved by using the {@link #getBundle(Locale)} method.
- * <li>
- * The {@link #getString(Locale, String, Object...)} method can be used to retrieve locale-specific messages.
- * <li>
- * Messages in the resource bundle can optionally be prefixed with the simple class name.
- * For example, if the class is <c>MyClass</c> and the properties file contains <js>"MyClass.myMessage"</js>,
- * the message can be retrieved using <code>getString(<js>"myMessage"</js>)</code>.
- * </ul>
- *
- * <ul class='notes'>
- * <li>
- * This class is thread-safe.
- * </ul>
- */
-public class Messages extends ResourceBundle {
-
- private static final ThreadLocal<Locale> clientLocale = new ThreadLocal<>();
-
- private final ResourceBundle rb;
- private final String bundlePath, className;
- private final Class<?> forClass;
- private final long creationThreadId;
-
- // A map that contains all keys [shortKeyName->keyName] and [keyName->keyName], where shortKeyName
- // refers to keys prefixed and stripped of the class name (e.g. "foobar"->"MyClass.foobar")
- private final Map<String,String> keyMap = new ConcurrentHashMap<>();
-
- // Contains all keys present in all bundles in searchBundles.
- private final ConcurrentSkipListSet<String> allKeys = new ConcurrentSkipListSet<>();
-
- // Bundles to search through to find properties.
- // Typically this will be a list of resource bundles for each class up the class hierarchy chain.
- private final CopyOnWriteArrayList<Messages> searchBundles = new CopyOnWriteArrayList<>();
-
- // Cache of message bundles per locale.
- private final ConcurrentHashMap<Locale,Messages> localizedBundles = new ConcurrentHashMap<>();
-
- /**
- * Sets the locale for this thread so that calls to {@link #getClientString(String, Object...)} return messages in
- * that locale.
- *
- * @param locale The new client locale.
- */
- public static void setClientLocale(Locale locale) {
- Messages.clientLocale.set(locale);
- }
-
- /**
- * Constructor.
- *
- * @param forClass The class
- * @return A new message bundle belonging to the class.
- */
- public static final Messages of(Class<?> forClass) {
- return new Messages(forClass, null, null);
- }
-
- /**
- * Constructor.
- *
- * @param forClass The class
- * @param bundlePath The location of the resource bundle.
- * @return A new message bundle belonging to the class.
- */
- public static final Messages of(Class<?> forClass, String bundlePath) {
- return new Messages(forClass, bundlePath, null);
- }
-
- /**
- * Constructor.
- *
- * @param forClass The class using this resource bundle.
- * @param bundlePath
- * The path of the resource bundle to wrap.
- * <br>This can be an absolute path (e.g. <js>"com.foo.MyMessages"</js>) or a path relative to the package of the
- * <l>forClass</l> (e.g. <js>"MyMessages"</js> if <l>forClass</l> is <js>"com.foo.MyClass"</js>).
- * <br>If <jk>null</jk>, searches for the following locations:
- * <ul>
- * <li><c>[package].ForClass.properties</c>
- * <li><c>[package].nls.ForClass.properties</c>
- * <li><c>[package].i18n.ForClass.properties</c>
- * </ul>
- * @param locale
- * The locale.
- * <br>If <jk>null</jk>, uses the default locale.
- * @throws MissingResourceException If resource bundle could not be found.
- */
- public Messages(Class<?> forClass, String bundlePath, Locale locale) throws MissingResourceException {
- this.forClass = forClass;
- this.className = forClass.getSimpleName();
-
- if (bundlePath == null)
- bundlePath = findBundlePath(forClass);
- if (bundlePath.endsWith(".properties"))
- throw new RuntimeException("Bundle path should not end with '.properties'");
- this.bundlePath = bundlePath;
-
- if (locale == null)
- locale = Locale.getDefault();
-
- this.creationThreadId = Thread.currentThread().getId();
- ClassLoader cl = forClass.getClassLoader();
- ResourceBundle trb = null;
- try {
- trb = ResourceBundle.getBundle(bundlePath, locale, cl);
- } catch (MissingResourceException e) {
- try {
- trb = ResourceBundle.getBundle(forClass.getPackage().getName() + '.' + bundlePath, locale, cl);
- } catch (MissingResourceException e2) {
- }
- }
- this.rb = trb;
- if (rb != null) {
-
- // Populate keyMap with original mappings.
- for (Enumeration<String> e = getKeys(); e.hasMoreElements();) {
- String key = e.nextElement();
- keyMap.put(key, key);
- }
-
- // Override/augment with shortname mappings (e.g. "foobar"->"MyClass.foobar")
- String c = className + '.';
- for (Enumeration<String> e = getKeys(); e.hasMoreElements();) {
- String key = e.nextElement();
- if (key.startsWith(c)) {
- String shortKey = key.substring(className.length() + 1);
- keyMap.put(shortKey, key);
- }
- }
-
- allKeys.addAll(keyMap.keySet());
- }
- searchBundles.add(this);
- }
-
-
- /**
- * Add another bundle path to this resource bundle.
- *
- * <p>
- * Order of property lookup is first-to-last.
- *
- * <p>
- * This method must be called from the same thread as the call to the constructor.
- * This eliminates the need for synchronization.
- *
- * @param forClass The class using this resource bundle.
- * @param bundlePath The bundle path.
- * @return This object (for method chaining).
- */
- public Messages addSearchPath(Class<?> forClass, String bundlePath) {
- assertSameThread(creationThreadId, "This method can only be called from the same thread that created the object.");
- Messages srb = new Messages(forClass, bundlePath, null);
- if (srb.rb != null) {
- allKeys.addAll(srb.keySet());
- searchBundles.add(srb);
- }
- return this;
- }
-
- @Override /* ResourceBundle */
- public boolean containsKey(String key) {
- return allKeys.contains(key);
- }
-
- /**
- * Similar to {@link ResourceBundle#getString(String)} except allows you to pass in {@link MessageFormat} objects.
- *
- * @param key The resource bundle key.
- * @param args Optional {@link MessageFormat}-style arguments.
- * @return
- * The resolved value. Never <jk>null</jk>.
- * <js>"{!!key}"</js> if the bundle is missing.
- * <js>"{!key}"</js> if the key is missing.
- */
- public String getString(String key, Object...args) {
- String s = getString(key);
- if (s.length() > 0 && s.charAt(0) == '{')
- return s;
- return format(s, args);
- }
-
- /**
- * Same as {@link #getString(String, Object...)} but allows you to specify the locale.
- *
- * @param locale The locale of the resource bundle to retrieve message from.
- * @param key The resource bundle key.
- * @param args Optional {@link MessageFormat}-style arguments.
- * @return
- * The resolved value. Never <jk>null</jk>.
- * <js>"{!!key}"</js> if the bundle is missing.
- * <js>"{!key}"</js> if the key is missing.
- */
- public String getString(Locale locale, String key, Object...args) {
- if (locale == null)
- return getString(key, args);
- return getBundle(locale).getString(key, args);
- }
-
- /**
- * Same as {@link #getString(String, Object...)} but uses the locale specified on the call to {@link #setClientLocale(Locale)}.
- *
- * @param key The resource bundle key.
- * @param args Optional {@link MessageFormat}-style arguments.
- * @return
- * The resolved value. Never <jk>null</jk>.
- * <js>"{!!key}"</js> if the bundle is missing.
- * <js>"{!key}"</js> if the key is missing.
- */
- public String getClientString(String key, Object...args) {
- return getString(clientLocale.get(), key, args);
- }
-
- /**
- * Looks for all the specified keys in the resource bundle and returns the first value that exists.
- *
- * @param keys The list of possible keys.
- * @return The resolved value, or <jk>null</jk> if no value is found or the resource bundle is missing.
- */
- public String findFirstString(String...keys) {
- if (rb == null)
- return null;
- for (String k : keys) {
- if (containsKey(k))
- return getString(k);
- }
- return null;
- }
-
- /**
- * Same as {@link #findFirstString(String...)}, but uses the specified locale.
- *
- * @param locale The locale of the resource bundle to retrieve message from.
- * @param keys The list of possible keys.
- * @return The resolved value, or <jk>null</jk> if no value is found or the resource bundle is missing.
- */
- public String findFirstString(Locale locale, String...keys) {
- Messages srb = getBundle(locale);
- return srb.findFirstString(keys);
- }
-
- @Override /* ResourceBundle */
- public Set<String> keySet() {
- return Collections.unmodifiableSet(allKeys);
- }
-
- /**
- * Returns all keys in this resource bundle with the specified prefix.
- *
- * @param prefix The prefix.
- * @return The set of all keys in the resource bundle with the prefix.
- */
- public Set<String> keySet(String prefix) {
- Set<String> set = new HashSet<>();
- for (String s : keySet()) {
- if (s.equals(prefix) || (s.startsWith(prefix) && s.charAt(prefix.length()) == '.'))
- set.add(s);
- }
- return set;
- }
-
- @Override /* ResourceBundle */
- public Enumeration<String> getKeys() {
- if (rb == null)
- return new Vector<String>(0).elements();
- return rb.getKeys();
- }
-
- @Override /* ResourceBundle */
- protected Object handleGetObject(String key) {
- for (Messages srb : searchBundles) {
- if (srb.rb != null) {
- String key2 = srb.keyMap.get(key);
- if (key2 != null) {
- try {
- return srb.rb.getObject(key2);
- } catch (Exception e) {
- return "{!"+key+"}";
- }
- }
- }
- }
- if (rb == null)
- return "{!!"+key+"}";
- return "{!"+key+"}";
- }
-
- /**
- * Returns this resource bundle as an {@link OMap}.
- *
- * <p>
- * Useful for debugging purposes.
- * Note that any class that implements a <c>swap()</c> method will automatically be serialized by
- * calling this method and serializing the result.
- *
- * <p>
- * This method always constructs a new {@link OMap} on each call.
- *
- * @return A new map containing all the keys and values in this bundle.
- */
- public OMap swap() {
- OMap om = new OMap();
- for (String k : allKeys)
- om.put(k, getString(k));
- return om;
- }
-
- /**
- * Returns the resource bundle for the specified locale.
- *
- * @param locale
- * The client locale.
- * <br>If <jk>null</jk>, assumes the default locale.
- * @return The resource bundle for the specified locale. Never <jk>null</jk>.
- */
- public Messages getBundle(Locale locale) {
- if (locale == null)
- locale = Locale.getDefault();
-
- Messages mb = localizedBundles.get(locale);
- if (mb != null)
- return mb;
- mb = new Messages(forClass, bundlePath, locale);
- List<Messages> l = new ArrayList<>(searchBundles.size()-1);
- for (int i = 1; i < searchBundles.size(); i++) {
- Messages srb = searchBundles.get(i);
- srb = new Messages(srb.forClass, srb.bundlePath, locale);
- l.add(srb);
- mb.allKeys.addAll(srb.keySet());
- }
- mb.searchBundles.addAll(l);
- localizedBundles.putIfAbsent(locale, mb);
- return localizedBundles.get(locale);
- }
-
- private static final String findBundlePath(Class<?> forClass) {
- String path = forClass.getName();
- if (tryBundlePath(forClass, path))
- return path;
- path = forClass.getPackage().getName() + ".nls." + forClass.getSimpleName();
- if (tryBundlePath(forClass, path))
- return path;
- path = forClass.getPackage().getName() + ".i18n." + forClass.getSimpleName();
- if (tryBundlePath(forClass, path))
- return path;
- throw new MissingResourceException("Could not find bundle path for class ", forClass.getName(), null);
- }
-
- private static final boolean tryBundlePath(Class<?> c, String path) {
- try {
- path = c.getName();
- ResourceBundle.getBundle(path, Locale.getDefault(), c.getClassLoader());
- return true;
- } catch (MissingResourceException e) {
- return false;
- }
- }
-}
\ No newline at end of file
+// ***************************************************************************************************************************
+// * 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.juneau.cp;
+
+import static org.apache.juneau.internal.ResourceBundleUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.text.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.collections.*;
+import org.apache.juneau.marshall.*;
+
+/**
+ * A wrapper around a {@link ResourceBundle}.
+ *
+ * <p>
+ * Adds support for non-existent resource bundles and associating class loaders.
+ */
+public class Messages extends ResourceBundle {
+
+ private ResourceBundle rb;
+ private Class<?> c;
+ private Messages parent;
+
+ // Cache of message bundles per locale.
+ private final ConcurrentHashMap<Locale,Messages> localizedMessages = new ConcurrentHashMap<>();
+
+ // Cache of virtual keys to actual keys.
+ private final Map<String,String> keyMap;
+
+ private final Set<String> rbKeys;
+
+ /**
+ * Creator.
+ *
+ * @param forClass
+ * The class we're creating this object for.
+ * @return A new builder.
+ */
+ public static final MessagesBuilder create(Class<?> forClass) {
+ return new MessagesBuilder(forClass);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param forClass
+ * The class we're creating this object for.
+ * @return A new message bundle belonging to the class.
+ */
+ public static final Messages of(Class<?> forClass) {
+ return create(forClass).build();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param forClass
+ * The class we're creating this object for.
+ * @param name
+ * The bundle name (e.g. <js>"Messages"</js>).
+ * <br>If <jk>null</jk>, uses the class name.
+ * @return A new message bundle belonging to the class.
+ */
+ public static final Messages of(Class<?> forClass, String name) {
+ return create(forClass).name(name).build();
+ }
+
+
+ /**
+ * Constructor.
+ *
+ * @param forClass
+ * The class we're creating this object for.
+ * @param rb
+ * The resource bundle we're encapsulating. Can be <jk>null</jk>.
+ * @param parent
+ * The parent resource. Can be <jk>null</jk>.
+ */
+ public Messages(Class<?> forClass, ResourceBundle rb, Messages parent) {
+ this.c = forClass;
+ this.rb = rb;
+ this.parent = parent;
+ if (parent != null)
+ setParent(parent);
+
+ Map<String,String> keyMap = new TreeMap<>();
+
+ String cn = c.getSimpleName() + '.';
+ if (rb != null) {
+ for (String key : rb.keySet()) {
+ keyMap.put(key, key);
+ if (key.startsWith(cn)) {
+ String shortKey = key.substring(cn.length());
+ keyMap.put(shortKey, key);
+ }
+ }
+ }
+ if (parent != null) {
+ for (String key : parent.keySet()) {
+ keyMap.put(key, key);
+ if (key.startsWith(cn)) {
+ String shortKey = key.substring(cn.length());
+ keyMap.put(shortKey, key);
+ }
+ }
+ }
+
+ this.keyMap = Collections.unmodifiableMap(new LinkedHashMap<>(keyMap));
+ this.rbKeys = rb == null ? Collections.emptySet() : rb.keySet();
+ }
+
+ /**
+ * Returns this message bundle for the specified locale.
+ *
+ * @param locale The locale to get the messages for.
+ * @return A new {@link Messages} object. Never <jk>null</jk>.
+ */
+ public Messages forLocale(Locale locale) {
+ if (locale == null)
+ locale = Locale.getDefault();
+ Messages mb = localizedMessages.get(locale);
+ if (mb == null) {
+ Messages parent = this.parent == null ? null : this.parent.forLocale(locale);
+ ResourceBundle rb = this.rb == null ? null : findBundle(this.rb.getBaseBundleName(), locale, c.getClassLoader());
+ mb = new Messages(c, rb, parent);
+ localizedMessages.put(locale, mb);
+ }
+ return mb;
+ }
+
+ /**
+ * Returns all keys in this resource bundle with the specified prefix.
+ *
+ * <p>
+ * Keys are returned in alphabetical order.
+ *
+ * @param prefix The prefix.
+ * @return The set of all keys in the resource bundle with the prefix.
+ */
+ public Set<String> keySet(String prefix) {
+ Set<String> set = new LinkedHashSet<>();
+ for (String s : keySet()) {
+ if (s.equals(prefix) || (s.startsWith(prefix) && s.charAt(prefix.length()) == '.'))
+ set.add(s);
+ }
+ return set;
+ }
+
+ /**
+ * Similar to {@link ResourceBundle#getString(String)} except allows you to pass in {@link MessageFormat} objects.
+ *
+ * @param key The resource bundle key.
+ * @param args Optional {@link MessageFormat}-style arguments.
+ * @return
+ * The resolved value. Never <jk>null</jk>.
+ * <js>"{!key}"</js> if the key is missing.
+ */
+ public String getString(String key, Object...args) {
+ String s = getString(key);
+ if (s.startsWith("{!"))
+ return s;
+ return format(s, args);
+ }
+
+ /**
+ * Same as {@link #getString(String, Object...)} but allows you to specify the locale.
+ *
+ * @param locale The locale of the resource bundle to retrieve message from.
+ * @param key The resource bundle key.
+ * @param args Optional {@link MessageFormat}-style arguments.
+ * @return
+ * The resolved value. Never <jk>null</jk>.
+ * <js>"{!!key}"</js> if the bundle is missing.
+ * <js>"{!key}"</js> if the key is missing.
+ */
+ public String getString(Locale locale, String key, Object...args) {
+ if (locale == null)
+ return getString(key, args);
+ return forLocale(locale).getString(key, args);
+ }
+
+ /**
+ * Looks for all the specified keys in the resource bundle and returns the first value that exists.
+ *
+ * @param keys The list of possible keys.
+ * @return The resolved value, or <jk>null</jk> if no value is found or the resource bundle is missing.
+ */
+ public String findFirstString(String...keys) {
+ for (String k : keys) {
+ if (containsKey(k))
+ return getString(k);
+ }
+ return null;
+ }
+
+ /**
+ * Same as {@link #findFirstString(String...)}, but uses the specified locale.
+ *
+ * @param locale The locale of the resource bundle to retrieve message from.
+ * @param keys The list of possible keys.
+ * @return The resolved value, or <jk>null</jk> if no value is found or the resource bundle is missing.
+ */
+ public String findFirstString(Locale locale, String...keys) {
+ Messages srb = forLocale(locale);
+ return srb.findFirstString(keys);
+ }
+
+ @Override /* ResourceBundle */
+ protected Object handleGetObject(String key) {
+ String k = keyMap.get(key);
+ if (k == null)
+ return "{!" + key + "}";
+ try {
+ if (rbKeys.contains(k))
+ return rb.getObject(k);
+ } catch (MissingResourceException e) { /* Shouldn't happen */ }
+ return parent.handleGetObject(key);
+ }
+
+ @Override /* ResourceBundle */
+ public boolean containsKey(String key) {
+ return keyMap.containsKey(key);
+ }
+
+ @Override /* ResourceBundle */
+ public Set<String> keySet() {
+ return keyMap.keySet();
+ }
+
+ @Override /* ResourceBundle */
+ public Enumeration<String> getKeys() {
+ return Collections.enumeration(keySet());
+ }
+
+ @Override
+ public String toString() {
+ OMap om = new OMap();
+ for (String k : new TreeSet<>(keySet()))
+ om.put(k, getString(k));
+ return SimpleJson.DEFAULT.toString(om);
+ }
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/MessagesBuilder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/MessagesBuilder.java
new file mode 100644
index 0000000..5a60e01
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/MessagesBuilder.java
@@ -0,0 +1,116 @@
+// ***************************************************************************************************************************
+// * 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.juneau.cp;
+
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.ResourceBundleUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.collections.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Builder for {@link Messages} objects.
+ */
+public class MessagesBuilder {
+
+ private Class<?> forClass;
+ private Locale locale = Locale.getDefault();
+ private String name;
+ private Messages parent;
+
+ private String[] baseNames = {"{package}.{name}","{package}.i18n.{name}","{package}.nls.{name}","{package}.messages.{name}"};
+
+ MessagesBuilder(Class<?> forClass) {
+ this.forClass = forClass;
+ this.name = forClass.getSimpleName();
+ }
+
+ /**
+ * Adds a parent bundle.
+ *
+ * @param parent The parent bundle. Can be <jk>null</jk>.
+ * @return This object (for method chaining).
+ */
+ public MessagesBuilder parent(Messages parent) {
+ this.parent = parent;
+ return this;
+ }
+
+ /**
+ * Specifies the bundle name (e.g. <js>"Messages"</js>).
+ *
+ * @param name
+ * The bundle name.
+ * <br>If <jk>null</jk>, the forClass class name is used.
+ * @return This object (for method chaining).
+ */
+ public MessagesBuilder name(String name) {
+ this.name = isEmpty(name) ? forClass.getSimpleName() : name;
+ return this;
+ }
+
+ /**
+ * Specifies the base name patterns to use for finding the resource bundle.
+ *
+ * @param baseNames
+ * The bundle base names.
+ * <br>The default is the following:
+ * <ul>
+ * <li><js>"{package}.{name}"</js>
+ * <li><js>"{package}.i18n.{name}"</js>
+ * <li><js>"{package}.nls.{name}"</js>
+ * <li><js>"{package}.messages.{name}"</js>
+ * </ul>
+ * @return This object (for method chaining).
+ */
+ public MessagesBuilder baseNames(String...baseNames) {
+ this.baseNames = baseNames == null ? new String[]{} : baseNames;
+ return this;
+ }
+
+ /**
+ * Specifies the locale.
+ *
+ * @param locale
+ * The locale.
+ * If <jk>null</jk>, the default locale is used.
+ * @return This object (for method chaining).
+ */
+ public MessagesBuilder locale(Locale locale) {
+ this.locale = locale == null ? Locale.getDefault() : locale;
+ return this;
+ }
+
+ /**
+ * Creates a new {@link Messages} based on the setting of this builder.
+ *
+ * @return A new {@link Messages} object.
+ */
+ public Messages build() {
+ return new Messages(forClass, getBundle(), parent);
+ }
+
+ private ResourceBundle getBundle() {
+ ClassLoader cl = forClass.getClassLoader();
+ OMap m = OMap.of("name", name, "package", forClass.getPackage().getName());
+ for (String bn : baseNames) {
+ bn = StringUtils.replaceVars(bn, m);
+ ResourceBundle rb = findBundle(bn, locale, cl);
+ if (rb != null)
+ return rb;
+ }
+ return null;
+ }
+}
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/Messages_Test.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ResourceBundleUtils.java
similarity index 55%
copy from juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/Messages_Test.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ResourceBundleUtils.java
index 8c6c6f9..98279d2 100644
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/cp/Messages_Test.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ResourceBundleUtils.java
@@ -1,43 +1,56 @@
-// ***************************************************************************************************************************
-// * 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.juneau.cp;
-
-import static org.apache.juneau.assertions.Assertions.*;
-import static org.junit.runners.MethodSorters.*;
-
-import java.util.*;
-
-import static java.util.Locale.*;
-
-import org.apache.juneau.cp.test1.*;
-import org.junit.*;
-
-@FixMethodOrder(NAME_ASCENDING)
-public class Messages_Test {
-
- @Test
- public void a01_nonExistent() throws Exception {
- assertThrown(()->Messages.of(Test1.class)).contains("Could not find bundle path for class");
- assertThrown(()->Messages.of(Test1.class,"bad.properties")).contains("Bundle path should not end with '.properties'");
- }
-
- @Test
- public void a02_sameDirectory() throws Exception {
- Messages x = Messages.of(MessageBundleTest1.class);
- assertString(x.getString("file")).is("MessageBundleTest1.properties");
- assertString(x.getBundle(JAPANESE).getString("file")).is("MessageBundleTest1_ja.properties");
- assertString(x.getBundle(JAPAN).getString("file")).is("MessageBundleTest1_ja_JP.properties");
- assertString(x.getBundle(CHINA).getString("file")).is("MessageBundleTest1.properties");
- assertString(x.getBundle((Locale)null).getString("file")).is("MessageBundleTest1.properties");
- }
-}
+// ***************************************************************************************************************************
+// * 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.juneau.internal;
+
+import java.util.*;
+
+/**
+ * Class-related utility methods.
+ */
+public final class ResourceBundleUtils {
+
+ private static final ResourceBundle EMPTY = new ResourceBundle() {
+ @Override
+ protected Object handleGetObject(String key) {
+ return null;
+ }
+ @Override
+ public Enumeration<String> getKeys() {
+ return Collections.emptyEnumeration();
+ }
+ };
+
+ /**
+ * Same as {@link ResourceBundle#getBundle(String, Locale, ClassLoader)} but never throws a {@link MissingResourceException}.
+ *
+ * @param baseName The base name of the resource bundle, a fully qualified class name.
+ * @param locale The locale for which a resource bundle is desired.
+ * @param loader The class loader from which to load the resource bundle.
+ * @return The matching resource bundle, or <jk>null</jk> if it could not be found.
+ */
+ public static ResourceBundle findBundle(String baseName, Locale locale, ClassLoader loader) {
+ try {
+ return ResourceBundle.getBundle(baseName, locale, loader);
+ } catch (MissingResourceException e) {}
+ return null;
+ }
+
+ /**
+ * Returns an empty resource bundle.
+ *
+ * @return An empty resource bundle.
+ */
+ public static ResourceBundle empty() {
+ return EMPTY;
+ }
+}
diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/NlsTest.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/NlsTest.java
index ec52440..b09c9f1 100644
--- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/NlsTest.java
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/NlsTest.java
@@ -109,6 +109,6 @@ public class NlsTest {
@Test
public void c01_missingResourceBundle() throws Exception {
- c.get("/test").run().assertBody().is("{!!bad}");
+ c.get("/test").run().assertBody().is("{!bad}");
}
}
diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestResourceMessagesTest.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestResourceMessagesTest.java
index 60af1d3..2807fcc 100644
--- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestResourceMessagesTest.java
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestResourceMessagesTest.java
@@ -29,7 +29,7 @@ public class RestResourceMessagesTest {
static OMap convertToMap(ResourceBundle rb) {
OMap m = new OMap();
- for (String k : rb.keySet())
+ for (String k : new TreeSet<>(rb.keySet()))
m.put(k, rb.getString(k));
return m;
}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index bc69ce1..e9bfea6 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -3852,11 +3852,13 @@ public final class RestContext extends BeanContext {
MessageBundleLocation[] mbl = getInstanceArrayProperty(REST_messages, MessageBundleLocation.class, new MessageBundleLocation[0]);
if (mbl.length == 0)
- msgs = new Messages(rci.inner(), "", null);
+ msgs = Messages.of(rci.inner());
else {
- msgs = new Messages(mbl[0] != null ? mbl[0].baseClass : rci.inner(), mbl[0].bundlePath, null);
- for (int i = 1; i < mbl.length; i++)
- msgs.addSearchPath(mbl[i] != null ? mbl[i].baseClass : rci.inner(), mbl[i].bundlePath);
+ Messages msgs = null;
+ for (int i = mbl.length-1; i >= 0; i--)
+ if (mbl[i] != null)
+ msgs = Messages.create(mbl[i].baseClass == null ? rci.inner() : mbl[i].baseClass).name(mbl[i].bundlePath).parent(msgs).build();
+ this.msgs = msgs;
}
this.fullPath = (builder.parentContext == null ? "" : (builder.parentContext.fullPath + '/')) + builder.getPath();
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 91971c6..ad4ae46 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -1250,7 +1250,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
* <br>Never <jk>null</jk>.
*/
public Messages getMessageBundle() {
- return context.getMessages().getBundle(getLocale());
+ return context.getMessages().forLocale(getLocale());
}
/**