[dna-commits] DNA SVN: r105 - 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
Wed Apr 23 20:45:28 EDT 2008


Author: jverhaeg at redhat.com
Date: 2008-04-23 20:45:28 -0400 (Wed, 23 Apr 2008)
New Revision: 105

Added:
   trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/CommonI18nTest.java
   trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_en.properties
   trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_fr.properties
Removed:
   trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n.properties
Modified:
   trunk/dna-common/src/main/java/org/jboss/dna/common/CommonI18n.java
   trunk/dna-common/src/main/java/org/jboss/dna/common/i18n/I18n.java
   trunk/dna-common/src/main/resources/org/jboss/dna/common/CommonI18n.properties
   trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/I18nTest.java
Log:
DNA-27: Re-designed I18n to store text and instance-specific problems within instances.  Corrected some synchronization- and exception-related bugs.  Changed terminology related to localization.  Supplied many more tests, including for other locales.

Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/CommonI18n.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/CommonI18n.java	2008-04-22 20:42:49 UTC (rev 104)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/CommonI18n.java	2008-04-24 00:45:28 UTC (rev 105)
@@ -21,6 +21,8 @@
  */
 package org.jboss.dna.common;
 
+import java.util.Locale;
+import java.util.Set;
 import org.jboss.dna.common.i18n.I18n;
 
 /**
@@ -29,17 +31,18 @@
  */
 public final class CommonI18n {
 
-    static {
-        try {
-            I18n.initialize(CommonI18n.class);
-        } catch (final Exception err) {
-            System.err.println(err);
-        }
-    }
-
     // Make sure the following I18n.java-related fields are defined before all other fields to ensure a valid error message is
     // produced in the event of a missing/duplicate/unused property
 
+	/**
+	 * Parameters:
+	 * <ol>
+	 * <li>{@link I18n#id() ID}</li>
+	 * <li>Number of supplied arguments</li>
+	 * <li>Localized text before parameter substitution</li>
+	 * <li>Localized text after parameter substitution</li>
+	 * </ol>
+	 */
     public static I18n i18nArgumentsMismatchedParameter;
     public static I18n i18nArgumentMismatchedParameters;
     public static I18n i18nArgumentsMismatchedParameters;
@@ -52,7 +55,16 @@
     public static I18n i18nFieldInvalidType;
     public static I18n i18nFieldNotPublic;
     public static I18n i18nFieldNotStatic;
-    public static I18n i18nPropertiesFileNotFound;
+    public static I18n i18nLocalizationFileNotFound;
+	public static I18n i18nLocalizationProblems;
+
+	/**
+	 * Parameters:
+	 * <ol>
+	 * <li>{@link I18n#id() Property}</li>
+	 * <li>Localization file URL</li>
+	 * </ol>
+	 */
     public static I18n i18nPropertyDuplicate;
     public static I18n i18nPropertyMissing;
     public static I18n i18nPropertyUnused;
@@ -90,4 +102,24 @@
     public static I18n pathIsNotAbsolute;
     public static I18n pathIsNotRelative;
     public static I18n pathCannotBeNormalized;
+
+    static {
+        try {
+            I18n.initialize(CommonI18n.class);
+        } catch (final Exception err) {
+            System.err.println(err);
+        }
+    }
+
+    public static Set<Locale> getLocalizationProblemLocales() {
+		return I18n.getLocalizationProblemLocales(CommonI18n.class);
+	}
+
+    public static Set<String> getLocalizationProblems() {
+		return I18n.getLocalizationProblems(CommonI18n.class);
+	}
+
+    public static Set<String> getLocalizationProblems( Locale locale ) {
+		return I18n.getLocalizationProblems(CommonI18n.class, locale);
+	}
 }

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 20:42:49 UTC (rev 104)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/i18n/I18n.java	2008-04-24 00:45:28 UTC (rev 105)
@@ -27,21 +27,22 @@
 import java.lang.reflect.Modifier;
 import java.net.URL;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
+import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
 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.CommonI18n;
 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
@@ -56,37 +57,68 @@
 	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>>();
 
+	/**
+	 * The first level of this map indicates whether an i18n class has been localized to a particular locale. The second level
+	 * contains any problems encountered during localization.
+	 */
+	static final ConcurrentMap<Locale, Map<Class, Set<String>>> LOCALE_TO_CLASS_TO_PROBLEMS_MAP = new ConcurrentHashMap<Locale, Map<Class, Set<String>>>();
+
 	private static LocalizationRepository 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>.
+	 * Note, calling this method will <em>not</em> trigger localization of the supplied internationalization class.
+	 *
+	 * @param i18nClass The internalization class for which localization problem locales should be returned.
+	 * @return The locales for which localization problems were encountered while localizing the supplied internationalization
+	 *         class; never <code>null</code>.
 	 */
-	public static Map<String, String> getErrorsForDefaultLocale( Class i18nClass ) {
-		return getErrorsForLocale(i18nClass, null);
+	public static Set<Locale> getLocalizationProblemLocales( Class i18nClass ) {
+		ArgCheck.isNotNull(i18nClass, "i18nClass");
+		Set<Locale> locales = new HashSet<Locale>(LOCALE_TO_CLASS_TO_PROBLEMS_MAP.size());
+		for (Entry<Locale, Map<Class, Set<String>>> localeEntry : LOCALE_TO_CLASS_TO_PROBLEMS_MAP.entrySet()) {
+			for (Entry<Class, Set<String>> classEntry : localeEntry.getValue().entrySet()) {
+				if (!classEntry.getValue().isEmpty()) {
+					locales.add(localeEntry.getKey());
+					break;
+				}
+			}
+		}
+		return locales;
 	}
 
 	/**
-	 * @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>.
+	 * Note, calling this method will <em>not</em> trigger localization of the supplied internationalization class.
+	 *
+	 * @param i18nClass The internalization class for which localization problems should be returned.
+	 * @return The localization problems encountered while localizing the supplied internationalization class to the default
+	 *         locale; never <code>null</code>.
 	 */
-	public static Map<String, String> getErrorsForLocale( Class i18nClass,
-	                                                      Locale locale ) {
+	public static Set<String> getLocalizationProblems( Class i18nClass ) {
+		return getLocalizationProblems(i18nClass, null);
+	}
+
+	/**
+	 * Note, calling this method will <em>not</em> trigger localization of the supplied internationalization class.
+	 *
+	 * @param i18nClass The internalization class for which localization problems should be returned.
+	 * @param locale The locale for which localization problems should be returned. If <code>null</code>, the default locale
+	 *        will be used.
+	 * @return The localization problems encountered while localizing the supplied internationalization class to the supplied
+	 *         locale; never <code>null</code>.
+	 */
+	public static Set<String> getLocalizationProblems( 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();
+		Map<Class, Set<String>> classToProblemsMap = LOCALE_TO_CLASS_TO_PROBLEMS_MAP.get(locale == null ? Locale.getDefault() : locale);
+		if (classToProblemsMap == null) {
+			return Collections.emptySet();
 		}
-		return errors;
+		Set<String> problems = classToProblemsMap.get(i18nClass);
+		if (problems == null) {
+			return Collections.emptySet();
+		}
+		return problems;
 	}
 
 	/**
@@ -100,18 +132,19 @@
 	}
 
 	/**
-	 * Set the repository of localized messages. If null, a {@link ClasspathLocalizationRepository} instance that uses this class
-	 * loader will be used.
+	 * Set the repository of localized messages. If <code>null</code>, 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.
+	 * @param localizationRepository the localization repository to use; may be <code>null</code> if the default repository
+	 *        should be used.
 	 */
 	public static void setLocalizationRepository( LocalizationRepository localizationRepository ) {
 		I18n.localizationRepository = localizationRepository != null ? localizationRepository : DEFAULT_LOCALIZATION_REPOSITORY;
 	}
 
 	/**
-	 * 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
+	 * Initializes the internationalization fields declared on the supplied class. Internationalization fields must be public,
+	 * static, not final, and of type <code>I18n</code>. The supplied 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>.
@@ -122,82 +155,98 @@
 			throw new IllegalArgumentException(CommonI18n.i18nClassInterface.text(i18nClass.getName()));
 		}
 
-		// 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()) {
+		synchronized (i18nClass) {
+			// Find all public static non-final String fields in the supplied class and instantiate an I18n object for each.
+			try {
+				for (Field fld : i18nClass.getDeclaredFields()) {
 
-				// Ensure field is of type I18n
-				if (fld.getType() == I18n.class) {
+					// Ensure field is of type I18n
+					if (fld.getType() == I18n.class) {
 
-					// Ensure field is not final
-					if ((fld.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
-						throw new SystemFailureException(CommonI18n.i18nFieldFinal.text(fld.getName(), i18nClass));
-					}
+						// Ensure field is public
+						if ((fld.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
+							throw new SystemFailureException(CommonI18n.i18nFieldNotPublic.text(fld.getName(), i18nClass));
+						}
 
-					// Ensure field is public
-					if ((fld.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
-						throw new SystemFailureException(CommonI18n.i18nFieldNotPublic.text(fld.getName(), i18nClass));
-					}
+						// Ensure field is static
+						if ((fld.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
+							throw new SystemFailureException(CommonI18n.i18nFieldNotStatic.text(fld.getName(), i18nClass));
+						}
 
-					// Ensure field is static
-					if ((fld.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
-						throw new SystemFailureException(CommonI18n.i18nFieldNotStatic.text(fld.getName(), i18nClass));
-					}
+						// Ensure field is not final
+						if ((fld.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
+							throw new SystemFailureException(CommonI18n.i18nFieldFinal.text(fld.getName(), i18nClass));
+						}
 
-					// Ensure we can access field even if it's in a private class
-					ClassUtil.makeAccessible(fld);
+						// Ensure we can access field even if it's in a private class
+						ClassUtil.makeAccessible(fld);
 
-					// Initialize field. Do this every time the class is initialized (or re-initialized)
-					fld.set(null, new I18n(fld.getName(), i18nClass));
+						// Initialize field. Do this every time the class is initialized (or re-initialized)
+						fld.set(null, new I18n(fld.getName(), i18nClass));
+					}
 				}
+
+				// Remove all entries for the supplied i18n class to indicate it has not been localized.
+				for (Entry<Locale, Map<Class, Set<String>>> entry : LOCALE_TO_CLASS_TO_PROBLEMS_MAP.entrySet()) {
+					entry.getValue().remove(i18nClass);
+				}
+			} catch (IllegalAccessException err) {
+				// If this happens, it will happen with the first field visited in the above loop
+				throw new IllegalArgumentException(CommonI18n.i18nClassNotPublic.text(i18nClass));
 			}
-		} catch (IllegalAccessException err) {
-			throw new IllegalArgumentException(CommonI18n.i18nClassNotPublic.text(i18nClass.getName()));
 		}
 	}
 
 	/**
-	 * Synchronized on the locale being loaded (this blocks loading all other locales, but not reads for existing locales)
+	 * Synchronized on the supplied internalization class.
 	 *
-	 * @param i18nClass The internalization class being initialized
-	 * @param localization
-	 * @return
+	 * @param i18nClass The internalization class being localized
+	 * @param locale The locale to which the supplied internationalization class should be localized.
 	 */
-	private synchronized static Map<String, String> initializeIdToTextMap( final Localization localization ) {
-		Map<String, String> id2TextMap = new HashMap<String, String>();
-
-		// 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;
+	private static void localize( final Class i18nClass,
+	                              final Locale locale ) {
+		assert i18nClass != null;
+		assert locale != null;
+		// Create a class-to-problem map for this locale if one doesn't exist, else get the existing one.
+		Map<Class, Set<String>> classToProblemsMap = new ConcurrentHashMap<Class, Set<String>>();
+		Map<Class, Set<String>> existingClassToProblemsMap = LOCALE_TO_CLASS_TO_PROBLEMS_MAP.putIfAbsent(locale,
+		                                                                                                 classToProblemsMap);
+		if (existingClassToProblemsMap != null) {
+			classToProblemsMap = existingClassToProblemsMap;
 		}
-		// We're the first to put in the map for this locale, so populate the one we created...
-
-		// 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);
-
-		// 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);
+		// Check if already localized outside of synchronization block for 99% use-case
+		if (classToProblemsMap.get(i18nClass) != null) {
+			return;
+		}
+		synchronized (i18nClass) {
+			// Return if the supplied i18n class has already been localized to the supplied locale, despite the check outside of
+			// the synchronization block (1% use-case), else create a class-to-problems map for the class.
+			Set<String> problems = classToProblemsMap.get(i18nClass);
+			if (problems == null) {
+				problems = new CopyOnWriteArraySet<String>();
+				classToProblemsMap.put(i18nClass, problems);
+			} else {
+				return;
 			}
-		}
-
-		String bundleMsg = null;
-		if (url == null) {
-			// Record no variant of the i18n properties file for the specified class found
-			bundleMsg = CommonI18n.i18nPropertiesFileNotFound.text(bundleName);
-		} else {
+			// Get the URL to the localization properties file ...
+			final LocalizationRepository repos = getLocalizationRepository();
+			final String localizationBaseName = i18nClass.getName();
+			URL url = repos.getLocalizationBundle(localizationBaseName, locale);
+			if (url == null) {
+				// Nothing was found, so try the default locale
+				Locale defaultLocale = Locale.getDefault();
+				if (!defaultLocale.equals(locale)) {
+					url = repos.getLocalizationBundle(localizationBaseName, defaultLocale);
+				}
+				// Return if no applicable localization file could be found
+				if (url == null) {
+					problems.add(CommonI18n.i18nLocalizationFileNotFound.text(localizationBaseName));
+					return;
+				}
+			}
 			// Initialize i18n map
-			final Map<String, String> finalMap = id2TextMap;
 			final URL finalUrl = url;
+			final Set<String> finalProblems = problems;
 			Properties props = new Properties() {
 
 				@Override
@@ -207,23 +256,28 @@
 					String text = (String)value;
 
 					try {
-						Field fld = localization.i18nClass.getDeclaredField(id);
+						Field fld = i18nClass.getDeclaredField(id);
 						if (fld.getType() != I18n.class) {
 							// Invalid field type
-							mapErrorMessage(localization, id, CommonI18n.i18nFieldInvalidType.text(id,
-							                                                                     finalUrl,
-							                                                                     getClass().getName()));
+							finalProblems.add(CommonI18n.i18nFieldInvalidType.text(id, finalUrl, getClass().getName()));
+						} else {
+							I18n i18n = (I18n)fld.get(null);
+							if (i18n.localeToTextMap.putIfAbsent(locale, text) != null) {
+								// Duplicate id encountered
+								String prevProblem = i18n.localeToProblemMap.putIfAbsent(locale,
+								                                                         CommonI18n.i18nPropertyDuplicate.text(id,
+								                                                                                               finalUrl));
+								assert prevProblem == null;
+							}
 						}
 					} catch (NoSuchFieldException err) {
 						// No corresponding field exists
-						mapErrorMessage(localization, id, CommonI18n.i18nPropertyUnused.text(id, finalUrl));
+						finalProblems.add(CommonI18n.i18nPropertyUnused.text(id, finalUrl));
+					} catch (IllegalAccessException notPossible) {
+						// Would have already occurred in initialize method, but allowing for the impossible...
+						finalProblems.add(notPossible.getMessage());
 					}
 
-					if (finalMap.put(id, text) != null) {
-						// Duplicate id encountered
-						mapErrorMessage(localization, id, CommonI18n.i18nPropertyDuplicate.text(id, finalUrl));
-					}
-
 					return null;
 				}
 			};
@@ -232,38 +286,29 @@
 				InputStream propStream = url.openStream();
 				try {
 					props.load(propStream);
+					// Check for uninitialized fields
+					for (Field fld : i18nClass.getDeclaredFields()) {
+						if (fld.getType() == I18n.class) {
+							try {
+								I18n i18n = (I18n)fld.get(null);
+								if (i18n.localeToTextMap.get(locale) == null) {
+									i18n.localeToProblemMap.put(locale, CommonI18n.i18nPropertyMissing.text(fld.getName(), url));
+								}
+							} catch (IllegalAccessException notPossible) {
+								// Would have already occurred in initialize method, but allowing for the impossible...
+								finalProblems.add(notPossible.getMessage());
+							}
+						}
+					}
 				} finally {
 					propStream.close();
 				}
 			} catch (IOException err) {
-				bundleMsg = err.getMessage();
+				finalProblems.add(err.getMessage());
 			}
 		}
-
-		// 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 ? CommonI18n.i18nPropertyMissing.text(fld.getName(), url) : bundleMsg);
-			}
-		}
-
-		return id2TextMap;
 	}
 
-	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;
-	}
-
 	/**
 	 * Substitute the arguments into the message, ensuring that the number of arguments matches the number of parameters in the
 	 * text.
@@ -299,25 +344,32 @@
 			}
 		}
 		if (err || argCount < arguments.length) {
-			I18n msg = null;
+			String msg = null;
 			if (id != null) {
 				if (arguments.length == 1) {
-					msg = CommonI18n.i18nArgumentMismatchedParameters;
+					msg = CommonI18n.i18nArgumentMismatchedParameters.text(id, argCount, text, newText.toString());
 				} else if (argCount == 1) {
-					msg = CommonI18n.i18nArgumentsMismatchedParameter;
+					msg = CommonI18n.i18nArgumentsMismatchedParameter.text(arguments.length, id, text, newText.toString());
 				} else {
-					msg = CommonI18n.i18nArgumentsMismatchedParameters;
+					msg = CommonI18n.i18nArgumentsMismatchedParameters.text(arguments.length,
+					                                                        id,
+					                                                        argCount,
+					                                                        text,
+					                                                        newText.toString());
 				}
-				throw new IllegalArgumentException(msg.text(arguments.length, id, argCount, text, newText.toString()));
+				throw new IllegalArgumentException(msg);
 			}
 			if (arguments.length == 1) {
-				msg = CommonI18n.i18nReplaceArgumentMismatchedParameters;
+				msg = CommonI18n.i18nReplaceArgumentMismatchedParameters.text(argCount, text, newText.toString());
 			} else if (argCount == 1) {
-				msg = CommonI18n.i18nReplaceArgumentsMismatchedParameter;
+				msg = CommonI18n.i18nReplaceArgumentsMismatchedParameter.text(arguments.length, text, newText.toString());
 			} else {
-				msg = CommonI18n.i18nReplaceArgumentsMismatchedParameters;
+				msg = CommonI18n.i18nReplaceArgumentsMismatchedParameters.text(arguments.length,
+				                                                               argCount,
+				                                                               text,
+				                                                               newText.toString());
 			}
-			throw new IllegalArgumentException(msg.text(arguments.length, argCount, text, newText.toString()));
+			throw new IllegalArgumentException(msg);
 		}
 		matcher.appendTail(newText);
 
@@ -337,8 +389,10 @@
 		return replaceParameters(null, text, arguments);
 	}
 
-	final String id;
+	private final String id;
 	private final Class i18nClass;
+	final ConcurrentHashMap<Locale, String> localeToTextMap = new ConcurrentHashMap<Locale, String>();
+	final ConcurrentHashMap<Locale, String> localeToProblemMap = new ConcurrentHashMap<Locale, String>();
 
 	private I18n( String id,
 	              Class i18nClass ) {
@@ -346,23 +400,84 @@
 		this.i18nClass = i18nClass;
 	}
 
+	/**
+	 * @return This internationalization object's ID, which will match both the name of the relevant static field in the
+	 *         internationalization class and the relevant property name in the associated localization files.
+	 */
+	public String id() {
+		return id;
+	}
+
+	/**
+	 * @return <code>true</code> if a problem was encountered while localizing this internationalization object to the default
+	 *         locale.
+	 */
+	public boolean hasProblem() {
+		return (problem() != null);
+	}
+
+	/**
+	 * @param locale The locale for which to check whether a problem was encountered.
+	 * @return <code>true</code> if a problem was encountered while localizing this internationalization object to the supplied
+	 *         locale.
+	 */
+	public boolean hasProblem( Locale locale ) {
+		return (problem(locale) != null);
+	}
+
+	/**
+	 * @return The problem encountered while localizing this internationalization object to the default locale, or
+	 *         <code>null</code> if none was encountered.
+	 */
+	public String problem() {
+		return problem(null);
+	}
+
+	/**
+	 * @param locale The locale for which to return the problem.
+	 * @return The problem encountered while localizing this internationalization object to the supplied locale, or
+	 *         <code>null</code> if none was encountered.
+	 */
+	public String problem( Locale locale ) {
+		if (locale == null) {
+			locale = Locale.getDefault();
+		}
+		localize(i18nClass, locale);
+		// Check for field/property error
+		String problem = localeToProblemMap.get(locale);
+		if (problem != null) {
+			return problem;
+		}
+		// Check if text exists
+		if (localeToTextMap.get(locale) != null) {
+			// If so, no problem exists
+			return null;
+		}
+		// If we get here, which will be at most once, there was at least one global localization error, so just return a message
+		// indicating to look them up.
+		problem = CommonI18n.i18nLocalizationProblems.text(i18nClass, locale);
+		localeToProblemMap.put(locale, problem);
+		return problem;
+	}
+
 	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);
+		localize(i18nClass, locale);
+		// Check if text exists
+		String text = localeToTextMap.get(locale);
+		if (text != null) {
+			return text;
 		}
-
-		return id2TextMap.get(id);
+		// If not, there was a problem, so throw it within an exception so upstream callers can tell the difference between normal
+		// text and problem text.
+		throw new SystemFailureException(problem(locale));
 	}
 
 	/**
-	 * Get the internationalized text localized to the {@link Locale#getDefault() current (default) locale}, replacing the
-	 * parameters in the text with those supplied.
+	 * Get the localized text for 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
+	 * @param arguments the arguments for the parameter replacement; may be <code>null</code> or empty
 	 * @return the localized text
 	 */
 	public String text( Object... arguments ) {
@@ -370,28 +485,20 @@
 	}
 
 	/**
-	 * Get the internationalized text localized to the supplied locale, replacing the parameters in the text with those supplied.
+	 * Get the localized text for 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
+	 * @param locale the locale, or <code>null</code> if the {@link Locale#getDefault() current (default) locale} should be used
+	 * @param arguments the arguments for the parameter replacement; may be <code>null</code> or empty
 	 * @return the localized text
 	 */
 	public String text( Locale locale,
 	                    Object... arguments ) {
-		if (locale == null) {
-			locale = Locale.getDefault();
+		try {
+			String rawText = rawText(locale == null ? Locale.getDefault() : locale);
+			return replaceParameters(id, rawText, arguments);
+		} catch (SystemFailureException err) {
+			return '<' + err.getMessage() + '>';
 		}
-		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);
 	}
 
 	/**
@@ -399,71 +506,10 @@
 	 */
 	@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;
+		try {
+			return rawText(Locale.getDefault());
+		} catch (SystemFailureException err) {
+			return '<' + err.getMessage() + '>';
 		}
-
-		/**
-		 * <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/CommonI18n.properties
===================================================================
--- trunk/dna-common/src/main/resources/org/jboss/dna/common/CommonI18n.properties	2008-04-22 20:42:49 UTC (rev 104)
+++ trunk/dna-common/src/main/resources/org/jboss/dna/common/CommonI18n.properties	2008-04-24 00:45:28 UTC (rev 105)
@@ -1,21 +1,22 @@
 # Make sure the following I18n.java-related properties are defined before all other properties to ensure a valid error message is
 # produced in the event of a missing/duplicate/unused property
 
-i18nArgumentsMismatchedParameter = {0} arguments were specified for internationalization object "{1}", but {2} parameter is required: "{3}" => "{4}"
-i18nArgumentMismatchedParameters = {0} argument was specified for internationalization object "{1}", but {2} parameters are required: "{3}" => "{4}"
-i18nArgumentsMismatchedParameters = {0} arguments were specified for internationalization object "{1}", but {2} parameters are required: "{3}" => "{4}"
-i18nReplaceArgumentsMismatchedParameter = {0} arguments were specified, but {1} parameter is required: "{2}" => "{3}"
-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.
+i18nArgumentsMismatchedParameter = {0} arguments were supplied to internationalization object "{1}", but 1 parameter is required: "{2}" => "{3}"
+i18nArgumentMismatchedParameters = 1 argument was supplied to internationalization object "{0}", but {1} parameters are required: "{2}" => "{3}"
+i18nArgumentsMismatchedParameters = {0} arguments were supplied to internationalization object "{1}", but {2} parameters are required: "{3}" => "{4}"
+i18nReplaceArgumentsMismatchedParameter = {0} arguments were supplied, but 1 parameter is required: "{1}" => "{2}"
+i18nReplaceArgumentMismatchedParameters = 1 argument was supplied, but {0} parameters are required: "{1}" => "{2}"
+i18nReplaceArgumentsMismatchedParameters = {0} arguments were supplied, but {1} parameters are required: "{2}" => "{3}"
+i18nClassInterface = Internationalization class {0} must not be an interface.
+i18nClassNotPublic = Internationalization {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 localization file for "{0}" could be found.
+i18nLocalizationFileNotFound = No variant of the localization file for "{0}" could be found.
+i18nLocalizationProblems = Problems were encountered while localizing internationalization {0} to locale "{1}" 
 i18nPropertyDuplicate = Duplicate property values were found for property "{0}"  in localization file "{1}";
-i18nPropertyMissing = Missing 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

Added: trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/CommonI18nTest.java
===================================================================
--- trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/CommonI18nTest.java	                        (rev 0)
+++ trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/CommonI18nTest.java	2008-04-24 00:45:28 UTC (rev 105)
@@ -0,0 +1,23 @@
+/*
+ *
+ */
+package org.jboss.dna.common.i18n;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import java.util.Locale;
+import org.jboss.dna.common.CommonI18n;
+import org.junit.Test;
+
+/**
+ * @author John Verhaeg
+ */
+public class CommonI18nTest {
+
+	@Test
+	public void shouldNotHaveLocalizationProblems() {
+		for (Locale locale : CommonI18n.getLocalizationProblemLocales()) {
+			assertThat(CommonI18n.getLocalizationProblems(locale).isEmpty(), is(true));
+		}
+	}
+}


Property changes on: trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/CommonI18nTest.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

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 20:42:49 UTC (rev 104)
+++ trunk/dna-common/src/test/java/org/jboss/dna/common/i18n/I18nTest.java	2008-04-24 00:45:28 UTC (rev 105)
@@ -28,6 +28,13 @@
 import static org.hamcrest.core.IsNull.notNullValue;
 import static org.hamcrest.core.IsNull.nullValue;
 import static org.junit.Assert.assertThat;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
 import org.jboss.dna.common.CommonI18n;
 import org.jboss.dna.common.SystemFailureException;
 import org.junit.Before;
@@ -39,222 +46,409 @@
  */
 public final class I18nTest {
 
-    @Before
-    public void beforeEach() {
-        I18n.initialize(TestI18n.class);
-    }
+	@Before
+	public void beforeEach() throws Exception {
+		clearFields(TestI18n.class);
+		clearFields(TestI18nDuplicateProperty.class);
+		clearFields(TestI18nFinal.class);
+		clearFields(TestI18nFinalField.class);
+		clearFields(TestI18nInterface.class);
+		clearFields(TestI18nMissingLocalization.class);
+		clearFields(TestI18nMissingProperty.class);
+		clearFields(TestI18nNotPublicField.class);
+		clearFields(TestI18nNotStaticField.class);
+		clearFields(TestI18nPrivate.class);
+		clearFields(TestI18nUnusedProperty.class);
+		for (Entry<Locale, Map<Class, Set<String>>> localeToMapEntry : I18n.LOCALE_TO_CLASS_TO_PROBLEMS_MAP.entrySet()) {
+			for (Iterator<Entry<Class, Set<String>>> iter = localeToMapEntry.getValue().entrySet().iterator(); iter.hasNext();) {
+				if (iter.next().getKey() != CommonI18n.class) {
+					iter.remove();
+				}
+			}
+		}
+	}
 
-    @Test( expected = IllegalArgumentException.class )
-	public void getErrorsForDefaultLocaleShouldFailIfClassIsNull() {
-		I18n.getErrorsForDefaultLocale(null);
+	private void clearFields( Class i18nClass ) throws Exception {
+		for (Field fld : i18nClass.getDeclaredFields()) {
+			if (fld.getType() == I18n.class && (fld.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC
+			    && (fld.getModifiers() & Modifier.STATIC) == Modifier.STATIC
+			    && (fld.getModifiers() & Modifier.FINAL) != Modifier.FINAL) {
+				fld.set(null, null);
+			}
+		}
 	}
 
-    @Test
-	public void getErrorsForLocaleShouldAllNullLocale() {
-		I18n.getErrorsForLocale(TestI18n.class, null);
+	@Test( expected = IllegalArgumentException.class )
+	public void shouldFailToGetLocalizationProblemLocalesIfNoClassSupplied() {
+		I18n.getLocalizationProblemLocales(null);
 	}
 
 	@Test
-	public void getErrorsForDefaultLocaleShouldNotReturnNull() {
-		assertThat(I18n.getErrorsForDefaultLocale(TestI18n.class), notNullValue());
+	public void shouldNeverReturnNullWhenGettingLocalizationProblemLocales() {
+		assertThat(I18n.getLocalizationProblemLocales(TestI18n.class), notNullValue());
 	}
 
-    @Test( expected = IllegalArgumentException.class )
-    public void initializeShouldFailIfClassIsNull() {
-        I18n.initialize(null);
-    }
+	@Test( expected = IllegalArgumentException.class )
+	public void shouldFailToGetLocalizationProblemsForDefaultLocaleIfNoClassSupplied() {
+		I18n.getLocalizationProblems(null);
+	}
 
-    @Test
-    public void initializeShouldSkipNonI18nFields() {
-        assertThat(TestI18n.nonI18n, is(nullValue()));
-    }
+	@Test( expected = IllegalArgumentException.class )
+	public void shouldFailToGetLocalizationProblemsForSuppliedLocaleIfNoClassSupplied() {
+		I18n.getLocalizationProblems(null, Locale.US);
+	}
 
-    @Test( expected = SystemFailureException.class )
-    public void initializeShouldFailIfI18nFieldIsFinal() {
-        try {
-            I18n.initialize(TestI18nFinalField.class);
-        } catch (SystemFailureException err) {
-            assertThat(err.getMessage(), is(CommonI18n.i18nFieldFinal.text("testMessage", TestI18nFinalField.class)));
-            System.err.println(err);
-            throw err;
-        }
-    }
+	@Test
+	public void shouldNeverReturnNullWhenGettingLocalizationProblemsForDefaultLocale() {
+		assertThat(I18n.getLocalizationProblems(TestI18n.class), notNullValue());
+	}
 
-    @Test( expected = RuntimeException.class )
-    public void initializeShouldFailIfI18nFieldIsNotPublic() {
-        try {
-            I18n.initialize(TestI18nNotPublicField.class);
-        } catch (RuntimeException err) {
-            assertThat(err.getMessage(), is(CommonI18n.i18nFieldNotPublic.text("testMessage", TestI18nNotPublicField.class)));
-            System.err.println(err);
-            throw err;
-        }
-    }
+	@Test
+	public void shouldNeverReturnNullWhenGettingLocalizationProblemsForSuppliedLocale() {
+		assertThat(I18n.getLocalizationProblems(TestI18n.class, Locale.US), notNullValue());
+	}
 
-    @Test( expected = RuntimeException.class )
-    public void initializeShouldFailIfI18nFieldIsNotStatic() {
-        try {
-            I18n.initialize(TestI18nNotStaticField.class);
-        } catch (RuntimeException err) {
-            assertThat(err.getMessage(), is(CommonI18n.i18nFieldNotStatic.text("testMessage", TestI18nNotStaticField.class)));
-            System.err.println(err);
-            throw err;
-        }
-    }
+	@Test
+	public void shouldNotHaveLocalizationProblemsAfterInitializationButBeforeLocalization() {
+		I18n.initialize(TestI18nUnusedProperty.class);
+		assertThat(I18n.getLocalizationProblems(TestI18nUnusedProperty.class, null).isEmpty(), is(true));
+		assertThat(I18n.getLocalizationProblemLocales(TestI18nUnusedProperty.class).isEmpty(), is(true));
+	}
 
-    @Test
-    public void initializeShouldAllowFinalClass() {
-        I18n.initialize(TestI18nFinal.class);
-    }
+	@Test
+	public void shouldGetLocalizationProblemsForDefaultLocaleIfNoLocaleSupplied() {
+		I18n.initialize(TestI18nUnusedProperty.class);
+		TestI18nUnusedProperty.testMessage.text("test");
+		assertThat(I18n.getLocalizationProblems(TestI18nUnusedProperty.class, null).isEmpty(), is(false));
+	}
 
-    @Test
-    public void initializeShouldAllowPrivateClasses() {
-        I18n.initialize(TestI18nPrivate.class);
-        assertThat(TestI18nPrivate.testMessage, instanceOf(I18n.class));
-    }
+	@Test( expected = IllegalArgumentException.class )
+	public void shouldFailToInitializeIfNoClassSupplied() {
+		I18n.initialize(null);
+	}
 
-    @Test
-    public void initializeShouldAssignI18nInstanceToI18nFields() {
-        assertThat(TestI18n.testMessage, instanceOf(I18n.class));
-    }
+	@Test( expected = SystemFailureException.class )
+	public void shouldFailToInitializeFinalI18nField() {
+		try {
+			I18n.initialize(TestI18nFinalField.class);
+		} catch (SystemFailureException err) {
+			assertThat(err.getMessage(), is(CommonI18n.i18nFieldFinal.text("testMessage", TestI18nFinalField.class)));
+			System.err.println(err);
+			throw err;
+		}
+	}
 
-    @Test( expected = IllegalArgumentException.class )
-    public void initializeShouldFailIfClassIsInterface() {
-        try {
-            I18n.initialize(TestI18nInterface.class);
-        } catch (IllegalArgumentException err) {
-            assertThat(err.getMessage(), is(CommonI18n.i18nClassInterface.text(TestI18nInterface.class.getName())));
-            System.err.println(err);
-            throw err;
-        }
-    }
+	@Test( expected = SystemFailureException.class )
+	public void shouldFailToInitializeNonPublicI18nField() {
+		try {
+			I18n.initialize(TestI18nNotPublicField.class);
+		} catch (SystemFailureException err) {
+			assertThat(err.getMessage(), is(CommonI18n.i18nFieldNotPublic.text("testMessage", TestI18nNotPublicField.class)));
+			System.err.println(err);
+			throw err;
+		}
+	}
 
-    @Test
-    public void initializeShouldBeIdempotent() {
-        I18n.initialize(TestI18n.class);
-    }
+	@Test( expected = SystemFailureException.class )
+	public void shouldFailToInitializeNonStaticI18nField() {
+		try {
+			I18n.initialize(TestI18nNotStaticField.class);
+		} catch (SystemFailureException err) {
+			assertThat(err.getMessage(), is(CommonI18n.i18nFieldNotStatic.text("testMessage", TestI18nNotStaticField.class)));
+			System.err.println(err);
+			throw err;
+		}
+	}
 
-    @Test
-    public void i18nIdShouldMatchFieldName() throws Exception {
-        assertThat(TestI18n.testMessage.id, is("testMessage"));
-    }
+	@Test
+	public void shouldInitializeFinalClasses() {
+		I18n.initialize(TestI18nFinal.class);
+		assertThat(TestI18nFinal.testMessage, instanceOf(I18n.class));
+	}
 
-    @Test
-    public void i18nTextShouldHandleIfPropertyDuplicate() throws Exception {
-        I18n.initialize(TestI18nDuplicateProperty.class);
-        String text = TestI18nDuplicateProperty.testMessage.text("test");
+	@Test
+	public void shouldInitializePrivateClasses() {
+		I18n.initialize(TestI18nPrivate.class);
+		assertThat(TestI18nPrivate.testMessage, instanceOf(I18n.class));
+	}
+
+	@Test
+	public void shouldInitializeI18nFields() {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage, instanceOf(I18n.class));
+	}
+
+	@Test
+	public void shouldNotInitializeNonI18nFields() {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.nonI18n, nullValue());
+	}
+
+	@Test( expected = IllegalArgumentException.class )
+	public void shouldFailToInitializeInterfaces() {
+		try {
+			I18n.initialize(TestI18nInterface.class);
+		} catch (IllegalArgumentException err) {
+			assertThat(err.getMessage(), is(CommonI18n.i18nClassInterface.text(TestI18nInterface.class.getName())));
+			System.err.println(err);
+			throw err;
+		}
+	}
+
+	@Test
+	public void shouldProvideIdempotentInitialization() {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage1.text("test"), is("test"));
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage1.text("test"), is("test"));
+	}
+
+	@Test
+	public void shouldNotBeLocalizedAfterInitialization() {
+		I18n.initialize(TestI18nDuplicateProperty.class);
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToTextMap.get(Locale.getDefault()), nullValue());
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToProblemMap.get(Locale.getDefault()), nullValue());
+	}
+
+	@Test
+	public void shouldHaveIdThatMatchesFieldName() throws Exception {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage.id(), is("testMessage"));
+	}
+
+	@Test
+	public void shouldNotBeLocalizedIfAskedForId() {
+		I18n.initialize(TestI18nDuplicateProperty.class);
+		TestI18nDuplicateProperty.testMessage.id();
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToTextMap.get(Locale.getDefault()), nullValue());
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToProblemMap.get(Locale.getDefault()), nullValue());
+	}
+
+	@Test
+	public void shouldBeLocalizedIfAskedIfHasProblem() {
+		I18n.initialize(TestI18nDuplicateProperty.class);
+		TestI18nDuplicateProperty.testMessage.hasProblem();
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToTextMap.get(Locale.getDefault()), notNullValue());
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToProblemMap.get(Locale.getDefault()), notNullValue());
+	}
+
+	@Test
+	public void shouldBeLocalizedIfAskedForProblem() {
+		I18n.initialize(TestI18nDuplicateProperty.class);
+		TestI18nDuplicateProperty.testMessage.problem();
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToTextMap.get(Locale.getDefault()), notNullValue());
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToProblemMap.get(Locale.getDefault()), notNullValue());
+	}
+
+	@Test
+	public void shouldBeLocalizedIfConvertedToString() {
+		I18n.initialize(TestI18nDuplicateProperty.class);
+		TestI18nDuplicateProperty.testMessage.toString();
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToTextMap.get(Locale.getDefault()), notNullValue());
+		assertThat(TestI18nDuplicateProperty.testMessage.localeToProblemMap.get(Locale.getDefault()), notNullValue());
+	}
+
+	@Test
+	public void shouldContainAngleBracketedProblemInTextIfMissingLocalization() throws Exception {
+		I18n.initialize(TestI18nMissingLocalization.class);
+		String text = TestI18nMissingLocalization.testMessage.text();
+		assertThat(text,
+		           is('<' + CommonI18n.i18nLocalizationProblems.text(TestI18nMissingLocalization.class, Locale.getDefault()) + '>'));
+		System.out.println("Text: " + text);
+	}
+
+	@Test
+	public void shouldHaveProblemIfMissingLocalization() throws Exception {
+		I18n.initialize(TestI18nMissingLocalization.class);
+		assertThat(TestI18nMissingLocalization.testMessage.hasProblem(), is(true));
+		String problem = TestI18nMissingLocalization.testMessage.problem();
+		assertThat(problem, is(CommonI18n.i18nLocalizationProblems.text(TestI18nMissingLocalization.class, Locale.getDefault())));
+		System.out.println("Problem: " + problem);
+	}
+
+	@Test
+	public void shouldHaveLocalicationProblemIfMissingLocalization() throws Exception {
+		I18n.initialize(TestI18nMissingLocalization.class);
+		TestI18nMissingLocalization.testMessage.text();
+		assertThat(I18n.getLocalizationProblems(TestI18nMissingLocalization.class).size(), is(1));
+		assertThat(I18n.getLocalizationProblems(TestI18nMissingLocalization.class).iterator().next(),
+		           is(CommonI18n.i18nLocalizationFileNotFound.text(TestI18nMissingLocalization.class.getName())));
+		assertThat(I18n.getLocalizationProblemLocales(TestI18nMissingLocalization.class).size(), is(1));
+		assertThat(I18n.getLocalizationProblemLocales(TestI18nMissingLocalization.class).iterator().next(),
+		           is(Locale.getDefault()));
+	}
+
+	@Test
+	public void shouldHaveTextIfPropertyDuplicate() throws Exception {
+		I18n.initialize(TestI18nDuplicateProperty.class);
+		String text = TestI18nDuplicateProperty.testMessage.text("test");
 		assertThat(text.charAt(0), not('<'));
-		assertThat(I18n.getErrorsForDefaultLocale(TestI18nDuplicateProperty.class).size(), is(1));
-		System.out.println(text);
-    }
+		System.out.println("Text: " + text);
+	}
 
-    @Test
-    public void i18nTextShouldHandleIfPropertyMissing() throws Exception {
-        I18n.initialize(TestI18nMissingProperty.class);
-        String text = TestI18nMissingProperty.testMessage1.text();
+	@Test
+	public void shouldHaveProblemIfPropertyDuplicate() throws Exception {
+		I18n.initialize(TestI18nDuplicateProperty.class);
+		assertThat(TestI18nDuplicateProperty.testMessage.hasProblem(), is(true));
+		String problem = TestI18nDuplicateProperty.testMessage.problem();
+		assertThat(problem, notNullValue());
+		System.out.println("Problem: " + problem);
+	}
+
+	@Test
+	public void shouldNotHaveLocalicationProblemIfPropertyDuplicate() throws Exception {
+		I18n.initialize(TestI18nDuplicateProperty.class);
+		TestI18nDuplicateProperty.testMessage.text("test");
+		assertThat(I18n.getLocalizationProblems(TestI18nDuplicateProperty.class).isEmpty(), is(true));
+		assertThat(I18n.getLocalizationProblemLocales(TestI18nDuplicateProperty.class).isEmpty(), is(true));
+	}
+
+	@Test
+	public void shouldContainAngleBracketedProblemInTextIfPropertyMissing() throws Exception {
+		I18n.initialize(TestI18nMissingProperty.class);
+		String text = TestI18nMissingProperty.testMessage1.text("test");
 		assertThat(text.charAt(0), is('<'));
-		assertThat(I18n.getErrorsForDefaultLocale(TestI18nDuplicateProperty.class).size(), is(1));
-		System.out.println(text);
-    }
+		System.out.println("Text: " + text);
+	}
 
-    @Test
-	public void i18nTextShouldHandleIfPropertyUnused() throws Exception {
-        I18n.initialize(TestI18nUnusedProperty.class);
-        String text = TestI18nUnusedProperty.testMessage.text("test");
-		assertThat(text.charAt(0), not('<'));
-		assertThat(I18n.getErrorsForDefaultLocale(TestI18nDuplicateProperty.class).size(), is(1));
-		System.out.println(text);
-    }
+	@Test
+	public void shouldHaveProblemIfPropertyMissing() throws Exception {
+		I18n.initialize(TestI18nMissingProperty.class);
+		assertThat(TestI18nMissingProperty.testMessage1.hasProblem(), is(true));
+		String problem = TestI18nMissingProperty.testMessage1.problem();
+		assertThat(problem, notNullValue());
+		System.out.println("Problem: " + problem);
+	}
 
-    @Test
-    public void i18nTextShouldMatchPropertiesFile() throws Exception {
-        assertThat(TestI18n.testMessage.text(), is("Test Message"));
-    }
+	@Test
+	public void shouldNotHaveLocalicationProblemIfPropertyMissing() throws Exception {
+		I18n.initialize(TestI18nMissingProperty.class);
+		TestI18nMissingProperty.testMessage1.text("test");
+		assertThat(I18n.getLocalizationProblems(TestI18nMissingProperty.class).isEmpty(), is(true));
+		assertThat(I18n.getLocalizationProblemLocales(TestI18nMissingProperty.class).isEmpty(), is(true));
+	}
 
-    @Test( expected = IllegalArgumentException.class )
-    public void i18nTextShouldFailIfTooFewArgumentsSpecified() throws Exception {
-        try {
-            TestI18n.testMessage1.text();
-        } catch (IllegalArgumentException err) {
-            assertThat(err.getMessage(), is(CommonI18n.i18nArgumentsMismatchedParameter.text(0, "testMessage1", 1, "{0}", "{0}")));
-            System.err.println(err);
-            throw err;
-        }
-    }
+	@Test
+	public void shouldHaveLocalicationProblemIfPropertyUnused() throws Exception {
+		I18n.initialize(TestI18nUnusedProperty.class);
+		TestI18nUnusedProperty.testMessage.text("test");
+		assertThat(I18n.getLocalizationProblems(TestI18nUnusedProperty.class).size(), is(1));
+		assertThat(I18n.getLocalizationProblemLocales(TestI18nUnusedProperty.class).size(), is(1));
+		assertThat(I18n.getLocalizationProblemLocales(TestI18nUnusedProperty.class).iterator().next(), is(Locale.getDefault()));
+	}
 
-    @Test( expected = IllegalArgumentException.class )
-    public void i18nTextShouldFailIfTooManyArgumentsSpecified() throws Exception {
-        try {
-            TestI18n.testMessage1.text("Test", "Message");
-        } catch (IllegalArgumentException err) {
-            assertThat(err.getMessage(), is(CommonI18n.i18nArgumentsMismatchedParameter.text(2, "testMessage1", 1, "{0}", "Test")));
-            System.err.println(err);
-            throw err;
-        }
-    }
+	@Test
+	public void shouldHaveTextMatchingLocalizationFilePropertyValue() throws Exception {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage.text(), is("Test Message"));
+	}
 
-    @Test
-    public void i18nTextShouldContainArgumentsInRightOrder() throws Exception {
-        assertThat(TestI18n.testMessage2.text("Test", "Message"), is("Message Test"));
-    }
+	@Test( expected = IllegalArgumentException.class )
+	public void shouldFailIfTooFewArgumentsSuppliedToText() throws Exception {
+		I18n.initialize(TestI18n.class);
+		try {
+			TestI18n.testMessage1.text();
+		} catch (IllegalArgumentException err) {
+			assertThat(err.getMessage(), is(CommonI18n.i18nArgumentsMismatchedParameter.text(0, "testMessage1", 1, "{0}", "{0}")));
+			System.err.println(err);
+			throw err;
+		}
+	}
 
-    @Test
-    public void i18nTextShouldAllowReuseOfArguments() throws Exception {
-        assertThat(TestI18n.testMessage3.text("Test", "Message"), is("Message Test Message"));
-    }
+	@Test( expected = IllegalArgumentException.class )
+	public void shouldFailIfTooManyArgumentsSuppliedToText() throws Exception {
+		I18n.initialize(TestI18n.class);
+		try {
+			TestI18n.testMessage1.text("Test", "Message");
+		} catch (IllegalArgumentException err) {
+			assertThat(err.getMessage(),
+			           is(CommonI18n.i18nArgumentsMismatchedParameter.text(2, "testMessage1", 1, "{0}", "Test")));
+			System.err.println(err);
+			throw err;
+		}
+	}
 
-    public static class TestI18n {
+	@Test
+	public void shouldContainArgumentsInRightOrderInText() throws Exception {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage2.text("Test", "Message"), is("Message Test"));
+	}
 
-        public static I18n testMessage;
-        public static I18n testMessage1;
-        public static I18n testMessage2;
-        public static I18n testMessage3;
-        public static Object nonI18n;
-    }
+	@Test
+	public void shouldAllowReuseOfArgumentsInText() throws Exception {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage3.text("Test", "Message"), is("Message Test Message"));
+	}
 
-    private static class TestI18nDuplicateProperty {
+	@Test
+	public void shouldContainLocaleSpecificText() {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage.text(Locale.FRENCH), is("Message de Test"));
+	}
 
-        public static I18n testMessage;
-    }
+	@Test
+	public void shouldContainTextForDefaultLocaleIfMissingLocalizationForSuppliedLocale() {
+		I18n.initialize(TestI18n.class);
+		assertThat(TestI18n.testMessage.text(Locale.CHINESE), is("Test Message"));
+	}
 
-    public static final class TestI18nFinal {
+	public static class TestI18n {
 
-        public static I18n testMessage;
-    }
+		public static I18n testMessage;
+		public static I18n testMessage1;
+		public static I18n testMessage2;
+		public static I18n testMessage3;
+		public static Object nonI18n;
+	}
 
-    public static class TestI18nFinalField {
+	private static class TestI18nDuplicateProperty {
 
-        public static final I18n testMessage = null;
-    }
+		public static I18n testMessage;
+	}
 
-    public static interface TestI18nInterface {
+	public static final class TestI18nFinal {
 
-        I18n testMessage = null;
-    }
+		public static I18n testMessage;
+	}
 
-    private static class TestI18nMissingProperty {
+	public static class TestI18nFinalField {
 
-        public static I18n testMessage;
-        public static I18n testMessage1;
-    }
+		public static final I18n testMessage = null;
+	}
 
-    public static class TestI18nNotPublicField {
+	public static interface TestI18nInterface {
 
-        static I18n testMessage;
-    }
+		I18n testMessage = null;
+	}
 
-    public static class TestI18nNotStaticField {
+	private static class TestI18nMissingProperty {
 
-        public I18n testMessage;
-    }
+		public static I18n testMessage;
+		public static I18n testMessage1;
+	}
 
-    private static class TestI18nPrivate {
+	public static class TestI18nNotPublicField {
 
-        public static I18n testMessage;
-    }
+		static I18n testMessage;
+	}
 
-    private static class TestI18nUnusedProperty {
+	public static class TestI18nNotStaticField {
 
-        public static I18n testMessage;
-    }
+		public I18n testMessage;
+	}
+
+	private static class TestI18nPrivate {
+
+		public static I18n testMessage;
+	}
+
+	private static class TestI18nUnusedProperty {
+
+		public static I18n testMessage;
+	}
+
+	private static class TestI18nMissingLocalization {
+
+		public static I18n testMessage;
+	}
 }

Deleted: trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n.properties
===================================================================
--- trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n.properties	2008-04-22 20:42:49 UTC (rev 104)
+++ trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n.properties	2008-04-24 00:45:28 UTC (rev 105)
@@ -1,4 +0,0 @@
-testMessage = Test Message
-testMessage1 = {0}
-testMessage2 = {1} {0}
-testMessage3 = {1} {0} {1}
\ No newline at end of file

Copied: trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_en.properties (from rev 100, trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n.properties)
===================================================================
--- trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_en.properties	                        (rev 0)
+++ trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_en.properties	2008-04-24 00:45:28 UTC (rev 105)
@@ -0,0 +1,4 @@
+testMessage = Test Message
+testMessage1 = {0}
+testMessage2 = {1} {0}
+testMessage3 = {1} {0} {1}
\ No newline at end of file


Property changes on: trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_en.properties
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_fr.properties
===================================================================
--- trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_fr.properties	                        (rev 0)
+++ trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_fr.properties	2008-04-24 00:45:28 UTC (rev 105)
@@ -0,0 +1,4 @@
+testMessage = Message de Test
+testMessage1 = {0}
+testMessage2 = {1} {0}
+testMessage3 = {1} {0} {1}
\ No newline at end of file


Property changes on: trunk/dna-common/src/test/resources/org/jboss/dna/common/i18n/I18nTest$TestI18n_fr.properties
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:mime-type
   + text/plain




More information about the dna-commits mailing list