[dna-commits] DNA SVN: r98 - in trunk/dna-common/src: main/java/org/jboss/dna/common/i18n and 3 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Tue Apr 22 10:59:03 EDT 2008


Author: jverhaeg at 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 




More information about the dna-commits mailing list