Author: jverhaeg(a)redhat.com
Date: 2008-04-22 10:59:02 -0400 (Tue, 22 Apr 2008)
New Revision: 98
Modified:
trunk/dna-common/src/main/java/org/jboss/dna/common/CoreI18n.java
trunk/dna-common/src/main/java/org/jboss/dna/common/i18n/I18n.java
trunk/dna-common/src/main/resources/org/jboss/dna/common/CoreI18n.properties
trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/I18nTest.java
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nDuplicateProperty.properties
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nMissingProperty.properties
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nUnusedProperty.properties
Log:
DNA-27: Changed I18n to store errors encountered during the load of localization files in
a global map rather than throwing exceptions, allowing these to be handled at a later time
without hindering other current development.
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/CoreI18n.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/CoreI18n.java 2008-04-22 14:57:53
UTC (rev 97)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/CoreI18n.java 2008-04-22 14:59:02
UTC (rev 98)
@@ -2,7 +2,7 @@
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
+ * distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
@@ -47,7 +47,9 @@
public static I18n i18nReplaceArgumentMismatchedParameters;
public static I18n i18nReplaceArgumentsMismatchedParameters;
public static I18n i18nClassInterface;
+ public static I18n i18nClassNotPublic;
public static I18n i18nFieldFinal;
+ public static I18n i18nFieldInvalidType;
public static I18n i18nFieldNotPublic;
public static I18n i18nFieldNotStatic;
public static I18n i18nPropertiesFileNotFound;
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/i18n/I18n.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/i18n/I18n.java 2008-04-22 14:57:53
UTC (rev 97)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/i18n/I18n.java 2008-04-22 14:59:02
UTC (rev 98)
@@ -2,7 +2,7 @@
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
+ * distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
@@ -26,302 +26,446 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import net.jcip.annotations.Immutable;
import net.jcip.annotations.ThreadSafe;
import org.jboss.dna.common.CoreI18n;
import org.jboss.dna.common.SystemFailureException;
import org.jboss.dna.common.util.ArgCheck;
import org.jboss.dna.common.util.ClassUtil;
+import org.jboss.dna.common.util.HashCode;
/**
* Manages the initialization of internationalization (i18n) files, substitution of
values within i18n message placeholders, and
* dynamically reading properties from i18n property files.
+ *
* @author John Verhaeg
* @author Randall Hauch
*/
@ThreadSafe
public final class I18n {
- private static final Pattern PARAMETER_COUNT_PATTERN =
Pattern.compile("\\{(\\d+)\\}");
- private static final Object[] EMPTY_ARGUMENTS = new Object[] {};
- private static final LocalizationRepository DEFAULT_LOCALIZATION_REPOSITORY = new
ClasspathLocalizationRepository();
- private static LocalizationRepository localizationRepository =
DEFAULT_LOCALIZATION_REPOSITORY;
+ private static final Pattern PARAMETER_COUNT_PATTERN =
Pattern.compile("\\{(\\d+)\\}");
+ private static final Object[] EMPTY_ARGUMENTS = new Object[] {};
+ private static final LocalizationRepository DEFAULT_LOCALIZATION_REPOSITORY = new
ClasspathLocalizationRepository();
+ private static final ConcurrentMap<Localization, Map<String, String>>
LOCALIZATION_2_ID_2_ERROR_MAP = new ConcurrentHashMap<Localization, Map<String,
String>>();
+ private static final ConcurrentMap<Localization, Map<String, String>>
LOCALIZATION_2_ID_2_TEXT_MAP = new ConcurrentHashMap<Localization, Map<String,
String>>();
- /**
- * Get the repository of localized messages. By default, this instance uses a {@link
ClasspathLocalizationRepository} that
- * uses this class' classloader.
- * @return localizationRepository
- */
- public static LocalizationRepository getLocalizationRepository() {
- return localizationRepository;
- }
+ private static LocalizationRepository localizationRepository =
DEFAULT_LOCALIZATION_REPOSITORY;
- /**
- * Set the repository of localized messages. If null, a {@link
ClasspathLocalizationRepository} instance that uses this class
- * loader will be used.
- * @param localizationRepository the localization repository to use; may be null if
the default repository should be used.
- */
- public static void setLocalizationRepository( LocalizationRepository
localizationRepository ) {
- I18n.localizationRepository = localizationRepository != null ?
localizationRepository : DEFAULT_LOCALIZATION_REPOSITORY;
- }
+ /**
+ * @param i18nClass An internalization class for which errors should be returned.
+ * @return The errors encountered while initializing the specified internationalization
class for the default locale mapped to
+ * the applicable internalization IDs; never <code>null</code>.
+ */
+ public static Map<String, String> getErrorsForDefaultLocale( Class i18nClass ) {
+ return getErrorsForLocale(i18nClass, null);
+ }
- /**
- * Initializes the internationalization fields declared on the specified class.
Internationalization fields must be public,
- * static, not final, and of type <code>I18n</code>. The specified class
must not be an interface (of course), but has no
- * restrictions as to what class it may extend or what interfaces it must implement.
- * @param i18nClass A class declaring one or more public, static, non-final fields of
type <code>I18n</code>.
- */
- public static void initialize( Class i18nClass ) {
- ArgCheck.isNotNull(i18nClass, "i18nClass");
- if (i18nClass.isInterface()) {
- throw new
IllegalArgumentException(CoreI18n.i18nClassInterface.text(i18nClass.getName()));
- }
+ /**
+ * @param i18nClass An internalization class for which errors should be returned.
+ * @param locale The locale for which errors should be returned. If
<code>null</code>, errors for the the default locale
+ * will be returned.
+ * @return The errors encountered while initializing the specified internationalization
class for the specified locale mapped
+ * to the applicable internalization IDs; never <code>null</code>.
+ */
+ public static Map<String, String> getErrorsForLocale( Class i18nClass,
+ Locale locale ) {
+ ArgCheck.isNotNull(i18nClass, "i18nClass");
+ Map<String, String> errors = LOCALIZATION_2_ID_2_ERROR_MAP.get(new Localization(
+ locale
== null ? Locale.getDefault() : locale,
+
i18nClass));
+ if (errors == null) {
+ errors = Collections.emptyMap();
+ }
+ return errors;
+ }
- // Find all public static non-final String fields in the specified class and
instantiate an I18n object for each.
- try {
- for (Field fld : i18nClass.getDeclaredFields()) {
+ /**
+ * Get the repository of localized messages. By default, this instance uses a {@link
ClasspathLocalizationRepository} that
+ * uses this class' classloader.
+ *
+ * @return localizationRepository
+ */
+ public static LocalizationRepository getLocalizationRepository() {
+ return localizationRepository;
+ }
- // Ensure field is of type I18n
- if (fld.getType() == I18n.class) {
+ /**
+ * Set the repository of localized messages. If null, a {@link
ClasspathLocalizationRepository} instance that uses this class
+ * loader will be used.
+ *
+ * @param localizationRepository the localization repository to use; may be null if the
default repository should be used.
+ */
+ public static void setLocalizationRepository( LocalizationRepository
localizationRepository ) {
+ I18n.localizationRepository = localizationRepository != null ? localizationRepository :
DEFAULT_LOCALIZATION_REPOSITORY;
+ }
- // Ensure field is not final
- if ((fld.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
- throw new
SystemFailureException(CoreI18n.i18nFieldFinal.text(fld.getName(), i18nClass));
- }
+ /**
+ * Initializes the internationalization fields declared on the specified class.
Internationalization fields must be public,
+ * static, not final, and of type <code>I18n</code>. The specified class
must not be an interface (of course), but has no
+ * restrictions as to what class it may extend or what interfaces it must implement.
+ *
+ * @param i18nClass A class declaring one or more public, static, non-final fields of
type <code>I18n</code>.
+ */
+ public static void initialize( Class i18nClass ) {
+ ArgCheck.isNotNull(i18nClass, "i18nClass");
+ if (i18nClass.isInterface()) {
+ throw new
IllegalArgumentException(CoreI18n.i18nClassInterface.text(i18nClass.getName()));
+ }
- // Ensure field is public
- if ((fld.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
- throw new
SystemFailureException(CoreI18n.i18nFieldNotPublic.text(fld.getName(), i18nClass));
- }
+ // Find all public static non-final String fields in the specified class and
instantiate an I18n object for each.
+ try {
+ for (Field fld : i18nClass.getDeclaredFields()) {
- // Ensure field is static
- if ((fld.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
- throw new
SystemFailureException(CoreI18n.i18nFieldNotStatic.text(fld.getName(), i18nClass));
- }
+ // Ensure field is of type I18n
+ if (fld.getType() == I18n.class) {
- // Ensure we can access field even if it's in a private class
- ClassUtil.makeAccessible(fld);
+ // Ensure field is not final
+ if ((fld.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
+ throw new SystemFailureException(CoreI18n.i18nFieldFinal.text(fld.getName(),
i18nClass));
+ }
- // Initialize field. Do this every time the class is initialized (or
re-initialized)
- fld.set(null, new I18n(fld.getName(), i18nClass));
- }
- }
- } catch (IllegalAccessException err) {
- throw new SystemFailureException(err);
- }
- }
+ // Ensure field is public
+ if ((fld.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
+ throw new SystemFailureException(CoreI18n.i18nFieldNotPublic.text(fld.getName(),
i18nClass));
+ }
- public final String id;
- /* package */final Class i18nClass;
- private final ConcurrentMap<Locale, Map<String, String>>
locale2Id2TextMap;
- private final Lock localeLoadingLock = new ReentrantLock();
+ // Ensure field is static
+ if ((fld.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
+ throw new SystemFailureException(CoreI18n.i18nFieldNotStatic.text(fld.getName(),
i18nClass));
+ }
- private I18n( String id, Class i18nClass ) {
- this.id = id;
- this.i18nClass = i18nClass;
- this.locale2Id2TextMap = new ConcurrentHashMap<Locale, Map<String,
String>>();
- }
+ // Ensure we can access field even if it's in a private class
+ ClassUtil.makeAccessible(fld);
- protected String rawText( Locale locale ) {
- assert locale != null;
- Map<String, String> id2TextMap = null;
- id2TextMap = locale2Id2TextMap.get(locale);
- if (id2TextMap == null) {
- // Get a lock for loading the locale (this blocks loading all other locales,
but not reads for existing locales)
- try {
- this.localeLoadingLock.lock();
- id2TextMap = new HashMap<String, String>();
+ // Initialize field. Do this every time the class is initialized (or
re-initialized)
+ fld.set(null, new I18n(fld.getName(), i18nClass));
+ }
+ }
+ } catch (IllegalAccessException err) {
+ throw new
IllegalArgumentException(CoreI18n.i18nClassNotPublic.text(i18nClass.getName()));
+ }
+ }
- // Put in the new map and see if there's already one there ...
- Map<String, String> existingId2TextMap =
locale2Id2TextMap.putIfAbsent(locale, id2TextMap);
- // If there is already an existing map, then someone beat us to the punch
and there's nothing to do ...
- if (existingId2TextMap == null) {
- // We're the first to put in the map for this locale, so populate
the one we created...
+ /**
+ * Synchronized on the locale being loaded (this blocks loading all other locales, but
not reads for existing locales)
+ *
+ * @param i18nClass The internalization class being initialized
+ * @param localization
+ * @return
+ */
+ private synchronized static Map<String, String> initializeIdToTextMap( final
Localization localization ) {
+ Map<String, String> id2TextMap = new HashMap<String, String>();
- // Get the URL to the localization properties file ...
- final LocalizationRepository repos = getLocalizationRepository();
- final String bundleName = i18nClass.getName();
- URL url = null;
- url = repos.getLocalizationBundle(bundleName, locale);
+ // Put in the new map and see if there's already one there ...
+ Map<String, String> existingId2TextMap =
LOCALIZATION_2_ID_2_TEXT_MAP.putIfAbsent(localization, id2TextMap);
+ // If there is already an existing map, then someone beat us to the punch and
there's nothing to do ...
+ if (existingId2TextMap != null) {
+ return existingId2TextMap;
+ }
+ // We're the first to put in the map for this locale, so populate the one we
created...
- // Try the default locale (if it is different than the supplied
locale) ...
- if (url == null) {
- // Nothing was found, so try the default locale
- Locale defaultLocale = Locale.getDefault();
- if (defaultLocale != locale) {
- url = repos.getLocalizationBundle(bundleName,
defaultLocale);
- }
- }
+ // Get the URL to the localization properties file ...
+ final LocalizationRepository repos = getLocalizationRepository();
+ final String bundleName = localization.i18nClass.getName();
+ URL url = null;
+ Locale locale = new Locale(localization.language, localization.country,
localization.variant);
+ url = repos.getLocalizationBundle(bundleName, locale);
- // Abort if no variant of the i18n properties file for the specified
class found
- if (url == null) {
- throw new
SystemFailureException(CoreI18n.i18nPropertiesFileNotFound.text(bundleName));
- }
+ // Try the default locale (if it is different than the supplied locale) ...
+ if (url == null) {
+ // Nothing was found, so try the default locale
+ Locale defaultLocale = Locale.getDefault();
+ if (!defaultLocale.equals(locale)) {
+ url = repos.getLocalizationBundle(bundleName, defaultLocale);
+ }
+ }
- // Initialize i18n map
- final Map<String, String> finalMap = id2TextMap;
- final URL finalUrl = url;
- Properties props = new Properties() {
+ String bundleMsg = null;
+ if (url == null) {
+ // Record no variant of the i18n properties file for the specified class found
+ bundleMsg = CoreI18n.i18nPropertiesFileNotFound.text(bundleName);
+ } else {
+ // Initialize i18n map
+ final Map<String, String> finalMap = id2TextMap;
+ final URL finalUrl = url;
+ Properties props = new Properties() {
- @Override
- public synchronized Object put( Object key, Object value ) {
- String id = (String)key;
- String text = (String)value;
+ @Override
+ public synchronized Object put( Object key,
+ Object value ) {
+ String id = (String)key;
+ String text = (String)value;
- // Throw error if no corresponding field exists
- try {
- Field fld = i18nClass.getDeclaredField(id);
- if (fld.getType() != I18n.class) {
- throw new
SystemFailureException(CoreI18n.i18nPropertyUnused.text(id, finalUrl));
- }
- } catch (Exception err) {
- throw new
SystemFailureException(CoreI18n.i18nPropertyUnused.text(id, finalUrl));
- }
+ try {
+ Field fld = localization.i18nClass.getDeclaredField(id);
+ if (fld.getType() != I18n.class) {
+ // Invalid field type
+ mapErrorMessage(localization, id, CoreI18n.i18nFieldInvalidType.text(id,
+ finalUrl,
+
getClass().getName()));
+ }
+ } catch (NoSuchFieldException err) {
+ // No corresponding field exists
+ mapErrorMessage(localization, id, CoreI18n.i18nPropertyUnused.text(id, finalUrl));
+ }
- if (finalMap.put(id, text) != null) {
+ if (finalMap.put(id, text) != null) {
+ // Duplicate id encountered
+ mapErrorMessage(localization, id, CoreI18n.i18nPropertyDuplicate.text(id,
finalUrl));
+ }
- // Throw error if duplicate id encountered
- throw new
SystemFailureException(CoreI18n.i18nPropertyDuplicate.text(id, finalUrl));
- }
+ return null;
+ }
+ };
- return null;
- }
- };
+ try {
+ InputStream propStream = url.openStream();
+ try {
+ props.load(propStream);
+ } finally {
+ propStream.close();
+ }
+ } catch (IOException err) {
+ bundleMsg = err.getMessage();
+ }
+ }
- try {
- InputStream propStream = url.openStream();
- try {
- props.load(propStream);
- } finally {
- propStream.close();
- }
- } catch (IOException err) {
- throw new SystemFailureException(err);
- }
+ // Check for uninitialized fields
+ for (Field fld : localization.i18nClass.getDeclaredFields()) {
+ if (fld.getType() == I18n.class && id2TextMap.get(fld.getName()) == null) {
+ mapErrorMessage(localization,
+ fld.getName(),
+ bundleMsg == null ? CoreI18n.i18nPropertyMissing.text(fld.getName(),
url) : bundleMsg);
+ }
+ }
- // Check for uninitialized fields
- for (Field fld : i18nClass.getDeclaredFields()) {
- if (fld.getType() == I18n.class &&
id2TextMap.get(fld.getName()) == null) {
- throw new
SystemFailureException(CoreI18n.i18nPropertyMissing.text(fld.getName(), url));
- }
- }
+ return id2TextMap;
+ }
- }
- } finally {
- this.localeLoadingLock.unlock();
- } // end of locale loading ...
- }
+ static void mapErrorMessage( Localization localization,
+ String id,
+ String message ) {
+ Map<String, String> id2ErrorMap = new ConcurrentHashMap<String, String>();
+ Map<String, String> existingId2ErrorMap =
LOCALIZATION_2_ID_2_ERROR_MAP.putIfAbsent(localization, id2ErrorMap);
+ if (existingId2ErrorMap != null) {
+ id2ErrorMap = existingId2ErrorMap;
+ }
+ message = id2ErrorMap.put(id, message);
+ assert message == null;
+ }
- String text = id2TextMap.get(id);
- assert text != null;
- return text;
- }
+ /**
+ * Substitute the arguments into the message, ensuring that the number of arguments
matches the number of parameters in the
+ * text.
+ *
+ * @param id the id of the internationalization object
+ * @param text
+ * @param arguments
+ * @return the text with parameters replaced
+ */
+ protected static String replaceParameters( String id,
+ String text,
+ Object... arguments ) {
+ if (arguments == null) arguments = EMPTY_ARGUMENTS;
+ Matcher matcher = PARAMETER_COUNT_PATTERN.matcher(text);
+ StringBuffer newText = new StringBuffer();
+ int argCount = 0;
+ boolean err = false;
+ while (matcher.find()) {
+ int ndx = Integer.valueOf(matcher.group(1));
+ if (argCount <= ndx) {
+ argCount = ndx + 1;
+ }
+ if (ndx >= arguments.length) {
+ err = true;
+ matcher.appendReplacement(newText, matcher.group());
+ } else {
+ Object arg = arguments[ndx];
+ if (arg != null) {
+ matcher.appendReplacement(newText, Matcher.quoteReplacement(arg.toString()));
+ } else {
+ matcher.appendReplacement(newText, Matcher.quoteReplacement("null"));
+ }
+ }
+ }
+ if (err || argCount < arguments.length) {
+ I18n msg = null;
+ if (id != null) {
+ if (arguments.length == 1) {
+ msg = CoreI18n.i18nArgumentMismatchedParameters;
+ } else if (argCount == 1) {
+ msg = CoreI18n.i18nArgumentsMismatchedParameter;
+ } else {
+ msg = CoreI18n.i18nArgumentsMismatchedParameters;
+ }
+ throw new IllegalArgumentException(msg.text(arguments.length, id, argCount, text,
newText.toString()));
+ }
+ if (arguments.length == 1) {
+ msg = CoreI18n.i18nReplaceArgumentMismatchedParameters;
+ } else if (argCount == 1) {
+ msg = CoreI18n.i18nReplaceArgumentsMismatchedParameter;
+ } else {
+ msg = CoreI18n.i18nReplaceArgumentsMismatchedParameters;
+ }
+ throw new IllegalArgumentException(msg.text(arguments.length, argCount, text,
newText.toString()));
+ }
+ matcher.appendTail(newText);
- /**
- * Get the internationalized text localized to the {@link Locale#getDefault() current
(default) locale}, replacing the
- * parameters in the text with those supplied.
- * @param arguments the arguments for the parameter replacement; may be null or
empty
- * @return the localized text
- */
- public String text( Object... arguments ) {
- String rawText = rawText(Locale.getDefault());
- return replaceParameters(id, rawText, arguments);
- }
+ return newText.toString();
+ }
- /**
- * Get the internationalized text localized to the supplied locale, replacing the
parameters in the text with those supplied.
- * @param locale the locale, or null if the {@link Locale#getDefault() current
(default) locale} should be used
- * @param arguments the arguments for the parameter replacement; may be null or
empty
- * @return the localized text
- */
- public String text( Locale locale, Object... arguments ) {
- String rawText = rawText(locale != null ? locale : Locale.getDefault());
- return replaceParameters(id, rawText, arguments);
- }
+ /**
+ * Substitute the arguments into the message, ensuring that the number of arguments
matches the number of parameters in the
+ * text.
+ *
+ * @param text
+ * @param arguments
+ * @return the text with parameters replaced
+ */
+ public static String replaceParameters( String text,
+ Object... arguments ) {
+ return replaceParameters(null, text, arguments);
+ }
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString() {
- return rawText(Locale.getDefault());
- }
+ final String id;
+ private final Class i18nClass;
- /**
- * Substitute the arguments into the message, ensuring that the number of arguments
matches the number of parameters in the
- * text.
- * @param id the id of the internationalization object
- * @param text
- * @param arguments
- * @return the text with parameters replaced
- */
- protected static String replaceParameters( String id, String text, Object...
arguments ) {
- if (arguments == null) arguments = EMPTY_ARGUMENTS;
- Matcher matcher = PARAMETER_COUNT_PATTERN.matcher(text);
- StringBuffer newText = new StringBuffer();
- int argCount = 0;
- boolean err = false;
- while (matcher.find()) {
- int ndx = Integer.valueOf(matcher.group(1));
- if (argCount <= ndx) {
- argCount = ndx + 1;
- }
- if (ndx >= arguments.length) {
- err = true;
- matcher.appendReplacement(newText, matcher.group());
- } else {
- Object arg = arguments[ndx];
- if (arg != null) {
- matcher.appendReplacement(newText,
Matcher.quoteReplacement(arg.toString()));
- } else {
- matcher.appendReplacement(newText,
Matcher.quoteReplacement("null"));
- }
- }
- }
- if (err || argCount < arguments.length) {
- I18n msg = null;
- if (id != null) {
- if (arguments.length == 1) {
- msg = CoreI18n.i18nArgumentMismatchedParameters;
- } else if (argCount == 1) {
- msg = CoreI18n.i18nArgumentsMismatchedParameter;
- } else {
- msg = CoreI18n.i18nArgumentsMismatchedParameters;
- }
- throw new IllegalArgumentException(msg.text(arguments.length, id,
argCount, text, newText.toString()));
- }
- if (arguments.length == 1) {
- msg = CoreI18n.i18nReplaceArgumentMismatchedParameters;
- } else if (argCount == 1) {
- msg = CoreI18n.i18nReplaceArgumentsMismatchedParameter;
- } else {
- msg = CoreI18n.i18nReplaceArgumentsMismatchedParameters;
- }
- throw new IllegalArgumentException(msg.text(arguments.length, argCount, text,
newText.toString()));
- }
- matcher.appendTail(newText);
+ private I18n( String id,
+ Class i18nClass ) {
+ this.id = id;
+ this.i18nClass = i18nClass;
+ }
- return newText.toString();
- }
+ private String rawText( Locale locale ) {
+ assert locale != null;
+ Map<String, String> id2TextMap = null;
+ final Localization localization = new Localization(locale, i18nClass);
+ id2TextMap = LOCALIZATION_2_ID_2_TEXT_MAP.get(localization);
+ if (id2TextMap == null) {
+ id2TextMap = initializeIdToTextMap(localization);
+ }
- /**
- * Substitute the arguments into the message, ensuring that the number of arguments
matches the number of parameters in the
- * text.
- * @param text
- * @param arguments
- * @return the text with parameters replaced
- */
- public static String replaceParameters( String text, Object... arguments ) {
- return replaceParameters(null, text, arguments);
- }
+ String text = id2TextMap.get(id);
+ assert text != null;
+ return text;
+ }
+ /**
+ * Get the internationalized text localized to the {@link Locale#getDefault() current
(default) locale}, replacing the
+ * parameters in the text with those supplied.
+ *
+ * @param arguments the arguments for the parameter replacement; may be null or empty
+ * @return the localized text
+ */
+ public String text( Object... arguments ) {
+ return text(null, arguments);
+ }
+
+ /**
+ * Get the internationalized text localized to the supplied locale, replacing the
parameters in the text with those supplied.
+ *
+ * @param locale the locale, or null if the {@link Locale#getDefault() current (default)
locale} should be used
+ * @param arguments the arguments for the parameter replacement; may be null or empty
+ * @return the localized text
+ */
+ public String text( Locale locale,
+ Object... arguments ) {
+ if (locale == null) {
+ locale = Locale.getDefault();
+ }
+ String rawText = rawText(locale);
+ if (rawText == null) {
+ Map<String, String> id2ErrorMap = LOCALIZATION_2_ID_2_ERROR_MAP.get(new
Localization(locale, i18nClass));
+ if (id2ErrorMap != null) {
+ String msg = id2ErrorMap.get(id);
+ if (msg != null) {
+ return '<' + msg + '>';
+ }
+ }
+ }
+ return replaceParameters(id, rawText, arguments);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return rawText(Locale.getDefault());
+ }
+
+ @Immutable
+ private static class Localization {
+
+ final String language;
+ final String country;
+ final String variant;
+ final Class i18nClass;
+
+ Localization( Locale locale,
+ Class i18nClass ) {
+ this.language = locale.getLanguage();
+ this.country = locale.getCountry();
+ this.variant = locale.getVariant();
+ this.i18nClass = i18nClass;
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p>
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return HashCode.compute(country, i18nClass, language, variant);
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p>
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ Localization other = (Localization)obj;
+ if (country == null) {
+ if (other.country != null) return false;
+ } else if (!country.equals(other.country)) {
+ return false;
+ }
+ if (i18nClass == null) {
+ if (other.i18nClass != null) return false;
+ } else if (!i18nClass.equals(other.i18nClass)) {
+ return false;
+ }
+ if (language == null) {
+ if (other.language != null) return false;
+ } else if (!language.equals(other.language)) {
+ return false;
+ }
+ if (variant == null) {
+ if (other.variant != null) return false;
+ } else if (!variant.equals(other.variant)) {
+ return false;
+ }
+ return true;
+ }
+ }
}
Modified: trunk/dna-common/src/main/resources/org/jboss/dna/common/CoreI18n.properties
===================================================================
---
trunk/dna-common/src/main/resources/org/jboss/dna/common/CoreI18n.properties 2008-04-22
14:57:53 UTC (rev 97)
+++
trunk/dna-common/src/main/resources/org/jboss/dna/common/CoreI18n.properties 2008-04-22
14:59:02 UTC (rev 98)
@@ -8,16 +8,18 @@
i18nReplaceArgumentMismatchedParameters = {0} argument was specified, but {1} parameters
are required: "{2}" => "{3}"
i18nReplaceArgumentsMismatchedParameters = {0} arguments were specified, but {1}
parameters are required: "{2}" => "{3}"
i18nClassInterface = Class {0} must not be an interface.
+i18nClassNotPublic = Class {0} must be public.
i18nFieldFinal = Internationalization field "{0}" in {1} must not be final.
+i18nFieldInvalidType = Internationalization field "{0}" in {1} must be of type
{2}.
i18nFieldNotPublic = Internationalization field "{0}" in {1} must be public.
i18nFieldNotStatic = Internationalization field "{0}" in {1} must be static.
-i18nPropertiesFileNotFound = No variant of the i18n properties file for "{0}"
could be found.
-i18nPropertyDuplicate = Duplicate property values were found for property "{0}"
in internationalization properties file "{1}";
-i18nPropertyMissing = Missing property "{0}" in internationalization properties
file {1}.
-i18nPropertyUnused = An unused property, "{0}", was found in
internationalization properties file "{1}".
+i18nPropertiesFileNotFound = No variant of the localization file for "{0}"
could be found.
+i18nPropertyDuplicate = Duplicate property values were found for property "{0}"
in localization file "{1}";
+i18nPropertyMissing = Missing property "{0}" in localization file {1}.
+i18nPropertyUnused = An unused property, "{0}", was found in localization file
"{1}".
# Core-related fields
-componentClassnameNotValid = The classname {0} specified for {1} is not a valid Java
classname
+componentClassnameNotValid = The class name {0} specified for {1} is not a valid Java
class name
componentNotConfigured = The component {0} was not configured and will not be used
progressMonitorBeginTask = Beginning {0} ({1})
progressMonitorStatus = {0}
Modified: trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/I18nTest.java
===================================================================
--- trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/I18nTest.java 2008-04-22
14:57:53 UTC (rev 97)
+++ trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/I18nTest.java 2008-04-22
14:59:02 UTC (rev 98)
@@ -2,7 +2,7 @@
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
+ * distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
@@ -24,6 +24,8 @@
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNot.not;
+import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import org.jboss.dna.common.CoreI18n;
@@ -43,6 +45,21 @@
}
@Test( expected = IllegalArgumentException.class )
+ public void getErrorsForDefaultLocaleShouldFailIfClassIsNull() {
+ I18n.getErrorsForDefaultLocale(null);
+ }
+
+ @Test
+ public void getErrorsForLocaleShouldAllNullLocale() {
+ I18n.getErrorsForLocale(TestI18n.class, null);
+ }
+
+ @Test
+ public void getErrorsForDefaultLocaleShouldNotReturnNull() {
+ assertThat(I18n.getErrorsForDefaultLocale(TestI18n.class), notNullValue());
+ }
+
+ @Test( expected = IllegalArgumentException.class )
public void initializeShouldFailIfClassIsNull() {
I18n.initialize(null);
}
@@ -122,37 +139,31 @@
assertThat(TestI18n.testMessage.id, is("testMessage"));
}
- @Test( expected = RuntimeException.class )
- public void i18nTextShouldFailIfPropertyDuplicate() throws Exception {
+ @Test
+ public void i18nTextShouldHandleIfPropertyDuplicate() throws Exception {
I18n.initialize(TestI18nDuplicateProperty.class);
- try {
- TestI18nDuplicateProperty.testMessage.text();
- } catch (RuntimeException err) {
- System.err.println(err);
- throw err;
- }
+ String text = TestI18nDuplicateProperty.testMessage.text("test");
+ assertThat(text.charAt(0), not('<'));
+ assertThat(I18n.getErrorsForDefaultLocale(TestI18nDuplicateProperty.class).size(),
is(1));
+ System.out.println(text);
}
- @Test( expected = RuntimeException.class )
- public void i18nTextShouldFailIfPropertyMissing() throws Exception {
+ @Test
+ public void i18nTextShouldHandleIfPropertyMissing() throws Exception {
I18n.initialize(TestI18nMissingProperty.class);
- try {
- TestI18nMissingProperty.testMessage.text();
- } catch (RuntimeException err) {
- System.err.println(err);
- throw err;
- }
+ String text = TestI18nMissingProperty.testMessage1.text();
+ assertThat(text.charAt(0), is('<'));
+ assertThat(I18n.getErrorsForDefaultLocale(TestI18nDuplicateProperty.class).size(),
is(1));
+ System.out.println(text);
}
- @Test( expected = RuntimeException.class )
- public void i18nTextShouldFailIfPropertyUnused() throws Exception {
+ @Test
+ public void i18nTextShouldHandleIfPropertyUnused() throws Exception {
I18n.initialize(TestI18nUnusedProperty.class);
- try {
- TestI18nUnusedProperty.testMessage.text();
- } catch (RuntimeException err) {
- System.err.println(err);
- throw err;
- }
+ String text = TestI18nUnusedProperty.testMessage.text("test");
+ assertThat(text.charAt(0), not('<'));
+ assertThat(I18n.getErrorsForDefaultLocale(TestI18nDuplicateProperty.class).size(),
is(1));
+ System.out.println(text);
}
@Test
Modified:
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nDuplicateProperty.properties
===================================================================
---
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nDuplicateProperty.properties 2008-04-22
14:57:53 UTC (rev 97)
+++
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nDuplicateProperty.properties 2008-04-22
14:59:02 UTC (rev 98)
@@ -1,2 +1,2 @@
-testMessage = Test Message
-testMessage = Test Message
+testMessage = Message {0}
+testMessage = Message {0}
Modified:
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nMissingProperty.properties
===================================================================
---
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nMissingProperty.properties 2008-04-22
14:57:53 UTC (rev 97)
+++
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nMissingProperty.properties 2008-04-22
14:59:02 UTC (rev 98)
@@ -1 +1 @@
-testMessage = Test Message
+testMessage = Message {0}
Modified:
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nUnusedProperty.properties
===================================================================
---
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nUnusedProperty.properties 2008-04-22
14:57:53 UTC (rev 97)
+++
trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18nUnusedProperty.properties 2008-04-22
14:59:02 UTC (rev 98)
@@ -1,2 +1,2 @@
-testMessage = Test Message
-testMessage1 = Test Message 1
+testMessage = Message {0}
+testMessage1 = Message {0} 1