Index: lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleManager.java diff -u lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleManager.java:1.3 lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleManager.java:1.4 --- lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleManager.java:1.3 Sun Mar 23 06:02:24 2008 +++ lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleManager.java Sun Mar 23 21:08:16 2008 @@ -24,6 +24,7 @@ import org.codelutin.util.Resource; import org.codelutin.util.StringUtil; +import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; @@ -35,7 +36,8 @@ import java.util.regex.Matcher; /** - * Classe permettant de gerer les bundles. + * Manager of Bundle. + * TODO * * @author chemit */ @@ -47,25 +49,28 @@ /** pattern to find all i18n bundles in classloader class path */ public static final String SEARCH_BUNDLE_PATTERN = ".*18n/.+\\.properties"; - /** shared instance */ - protected static I18nBundleManager instance; - /** le cache de bundles deja charges */ protected List cache; /** le cache des urls de recheche des bundles */ protected static URL[] urls; - public static synchronized I18nBundleManager getInstance() { - if (instance == null) { - instance = new I18nBundleManager(); + protected final Locale defaultLocale; + + public static void resetURL() { + log.info(" old cache size " + (urls == null ? 0 : urls.length)); + if (urls != null) { + urls = null; } - return instance; } - public static void init() { - I18nBundleManager manager = getInstance(); - if (manager.isInit()) { + public I18nBundleManager(Locale defaultLocale) { + this.defaultLocale = defaultLocale; + } + + public void init() { + + if (isInit()) { // already init return; } @@ -73,30 +78,27 @@ // get all bundles urls if (urls == null || urls.length == 0) { // cache this expensive search - urls = manager.getURLs(Language.getLoader(), I18n.getExtraURL()); + urls = getURLs(Language.getLoader(), I18n.getExtraURL()); } long t0 = System.nanoTime(); // detect bundles - List bundles = manager.detectBundles(urls); + List bundles = detectBundles(urls); // once for all, sort entries from general to full for (I18nBundle bundle : bundles) { - Collections.sort(bundle.getEntries(), - new I18nBundleEntry.I18nBundleEntryByLevelComparator()); + Collections.sort(bundle.getEntries()); } // save bundles in cache - manager.cache = Collections.unmodifiableList(bundles); + cache = Collections.unmodifiableList(bundles); - log.info(bundles.size() + " bundle(s) found, [" + manager.getBundleEntries().length + " file(s)] in " + StringUtil.convertTime(System.nanoTime() - t0)); + log.info(bundles.size() + " bundle(s) found, [" + getBundleEntries().length + " file(s)] in " + StringUtil.convertTime(System.nanoTime() - t0)); } - public static void resetURL() { - if (urls != null) { - urls = null; - } + public Locale getDefaultLocale() { + return defaultLocale; } public I18nBundle[] getBundles() { @@ -132,14 +134,16 @@ List result = new ArrayList(); for (I18nBundle i18nBundle : cache) { I18nBundleEntry[] entries = i18nBundle.getEntries(l); - if (entries.length > 0) { - result.addAll(Arrays.asList(entries)); + if (entries.length == 0) { + //no entry found for the bundle, try pomotion + entries = promuteBundle(i18nBundle, l); } + result.addAll(Arrays.asList(entries)); } return result.toArray(new I18nBundleEntry[result.size()]); } - public void load(Language language, Properties resource) { + public void load(Language language, Properties resource) throws IOException { long t0 = System.nanoTime(); I18nBundleEntry[] entries = getBundleEntries(language.getLocale()); for (I18nBundleEntry entry : entries) { @@ -155,6 +159,53 @@ } /** + * Obtain some rescue entries for a given locale. + *

+ * Note: Calling this method implies there is no entry matched by the common method + * {@link #getBundleEntries(java.util.Locale)} return a empty array. + * + * @param bundle the bundle to promute + * @param l the locale required + * @return the table of entries promuted for the given locale + */ + protected I18nBundleEntry[] promuteBundle(I18nBundle bundle, Locale l) { + + I18nBundleScope scope = I18nBundleScope.valueOf(l); + + if (log.isDebugEnabled()) { + log.debug('[' + bundle.getBundlePrefix() + "] did not find matching entries for locale " + l + ". Try to detect best entries..."); + } + + if (bundle.size() == 0) { + // there is no entry to take... + log.warn("PROMUTE NO ENTRY FOUND"); + return new I18nBundleEntry[0]; + } + + if (bundle.size() == 1) { + // there is one entry take it,what ever... + I18nBundleEntry entry = bundle.getEntries().get(0); + log.warn("PROMUTE" + l + " to " + entry.getLocale() + " [" + bundle.getBundlePrefix() + ']'); + return new I18nBundleEntry[]{entry}; + } + + List result = new ArrayList(); + + switch (scope) { + case FULL: + promuteFull(bundle, l, result); + break; + case LANGUAGE: + promuteLanguage(bundle, l, result); + break; + case GENERAL: + promuteGeneral(bundle, l, result); + break; + } + return result.toArray(new I18nBundleEntry[result.size()]); + } + + /** * Recherche la liste des url de bundles i18n correspondant à la langue * donné. * @@ -166,9 +217,6 @@ List urls = new ArrayList(); try { - // always try to match the lutinutil class loader, - // since other url should be in this one ? - //FIXME: this does not work in a jboss application... if (loader == null) { urls.addAll(Resource.getURLs(SEARCH_BUNDLE_PATTERN)); } else { @@ -217,7 +265,7 @@ return false; } // create a new bundle entry - I18nBundleEntry entry = new I18nBundleEntry(scope.getLocale(matcher), scope, url); + I18nBundleEntry entry = new I18nBundleEntry(url, scope.getLocale(matcher), scope); if (log.isDebugEnabled()) { log.debug("bundle (" + bundles.size() + ") : " + entry); } @@ -244,6 +292,79 @@ return bundle; } + protected void promuteFull(I18nBundle bundle, Locale locale, List result) { + if (bundle.size() == 0) { + return; + } + // try with a another FULL matching locale ? + for (I18nBundleEntry entry : bundle.getEntries()) { + I18nBundleScope i18nBundleScope = entry.getScope(); + // load from general to the max scope and always if there is only one bundle entry found + if (i18nBundleScope == I18nBundleScope.FULL && + !entry.getLocale().getCountry().equals(locale.getCountry()) && + entry.getLocale().getLanguage().equals(locale.getLanguage())) { + log.warn(locale + " to " + entry.getLocale() + " [" + bundle.getBundlePrefix() + ']'); + result.add(entry); + // we take the first one, this is a resuce!!! + break; + } + } + if (result.isEmpty()) { + // full promotion failed,trylanguage promotion + promuteLanguage(bundle, locale, result); + } + + } + + protected void promuteLanguage(I18nBundle bundle, Locale locale, List result) { + if (bundle.size() == 0) { + return; + } + for (I18nBundleEntry entry : bundle.getEntries()) { + I18nBundleScope i18nBundleScope = entry.getScope(); + // load from general to the max scope and always if there is only one bundle entry found + if (i18nBundleScope == I18nBundleScope.FULL && entry.getLocale().getLanguage().equals(locale.getLanguage())) { + result.add(entry); + log.warn(locale + " to " + entry.getLocale() + " [" + bundle.getBundlePrefix() + ']'); + // we take the first one, this is a resuce!!! + break; + } + } + if (result.isEmpty()) { + // language promotion failed,try general promotion + promuteGeneral(bundle, locale, result); + } + } + + protected void promuteGeneral(I18nBundle bundle, Locale locale, List result) { + if (bundle.size() == 0) { + return; + } + if (bundle.size() == 1) { + // there is one entry take it,what ever... + I18nBundleEntry entry = bundle.getEntries().get(0); + result.add(entry); + log.warn(locale + " to " + entry.getLocale() + " [" + bundle.getBundlePrefix() + ']'); + return; + } + I18nBundleScope scope = I18nBundleScope.valueOf(defaultLocale); + for (I18nBundleEntry entry : bundle.getEntries(scope)) { + if (entry.getLocale().equals(defaultLocale)) { + // default locale found + log.warn(locale + " to " + entry.getLocale() + " [" + bundle.getBundlePrefix() + ']'); + result.add(entry); + return; + } + } + + // default locale not found, take the first one ? + I18nBundleEntry entry = bundle.getEntries().get(0); + result.add(entry); + log.warn(locale + " to " + entry.getLocale() + " [" + bundle.getBundlePrefix() + ']'); + //TODO Should try to load default en_GB from I18nLoader ? + //I18n.DEFAULT_LOCALE.getCountry() + } + protected boolean isInit() { return cache != null; } @@ -253,8 +374,4 @@ throw new IllegalStateException("should call init method on " + I18nBundleManager.class); } } - - protected I18nBundleManager() { - - } } \ No newline at end of file Index: lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleScope.java diff -u lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleScope.java:1.5 lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleScope.java:1.6 --- lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleScope.java:1.5 Sun Mar 23 16:25:22 2008 +++ lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundleScope.java Sun Mar 23 21:08:16 2008 @@ -17,7 +17,7 @@ * ##% */ package org.codelutin.i18n.bundle; -import org.codelutin.i18n.I18nLoader; +import org.codelutin.i18n.I18n; import java.util.Locale; import java.util.regex.Matcher; @@ -65,7 +65,7 @@ public Locale getLocale(Matcher matcher) { Locale result = null; if (matcher.matches()) { - result = I18nLoader.newLocale(matcher.group(2)); + result = I18n.newLocale(matcher.group(2)); } return result; } @@ -76,7 +76,7 @@ public Locale getLocale(Matcher matcher) { Locale result = null; if (matcher.matches()) { - result = I18nLoader.newLocale(matcher.group(2)); + result = I18n.newLocale(matcher.group(2)); } return result; } Index: lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundle.java diff -u lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundle.java:1.2 lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundle.java:1.3 --- lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundle.java:1.2 Sun Mar 23 06:57:41 2008 +++ lutinutil/src/java/org/codelutin/i18n/bundle/I18nBundle.java Sun Mar 23 21:08:16 2008 @@ -1,5 +1,5 @@ /* -* \#\#% Copyright (C) 2007, 2008 Code Lutin, Tony Chemit, Gabriel Landais +* ##% Copyright (C) 2007, 2008 Code Lutin, Tony Chemit, Gabriel Landais * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -* \#\#% */ +* ##% */ package org.codelutin.i18n.bundle; import org.apache.commons.logging.Log; @@ -25,12 +25,29 @@ import java.util.Locale; /** - * Cette classe représente un bundle chargé par i18n. + * Class to represent a i18n Bundle. *

- * Il encapsule un bundle et de ses différents scopes. + * A bundle is defined by a resource prefix (eg /tmp/bundle.properties), and a list of locale implemented entries. + *

+ * The property {@link #bundlePrefix} is the equals order property. + *

+ * The property {@link #entries} contains all entries defined for this bundle. + *

+ * The method {@link #getEntries(java.util.Locale)} filter entries for a given locale, including scope inclusive property. + *

+ * The method {@link #getEntries(I18nBundleScope)} filter entries for a givne scope, with no inclusive logi. + *

+ * Thoses filter methods return result in the order defines in {@link I18nBundleEntry}, e.g + *

+ * XXX.properties
+ * XXX-fr.properties
+ * XXX-fr_FR.properties
+ * 
* + * In that way, we can load resource in the godd order :load before more general scope to more specialized. * * @author chemit * @see I18nBundleScope + * @see I18nBundleEntry */ public class I18nBundle { @@ -51,12 +68,16 @@ return bundlePrefix; } + /** + * Obtain the entries for a given locale, with a inclusive scope search. + *

+ * The order of result respect {@link I18nBundleEntry} order. + * + * @param locale the required locale + * @return the array of entries matching extacly the locale or one of the lesser scope one. + */ public I18nBundleEntry[] getEntries(Locale locale) { I18nBundleScope scope = I18nBundleScope.valueOf(locale); - List entiresByScopes = getEntries(scope); - if (entiresByScopes.isEmpty()) { - // found no bundle - } List result = new ArrayList(); for (I18nBundleEntry entry : entries) { @@ -66,81 +87,40 @@ result.add(entry); } } - if (result.isEmpty()) { - log.warn("did not find matchin entry for locale "+locale+". Try to detect best entries..."); - // must try to promute and deal with limit cases - switch (scope) { - - case GENERAL: - // try with a higher locale ? - promuteGeneralScope(result); - break; - case LANGUAGE: - // try with a higher locale ? - promuteLanguageScope(locale, result); - break; - case FULL: - promuteFull(locale, result); - break; - } - - } return result.toArray(new I18nBundleEntry[result.size()]); } - private void promuteFull(Locale locale, List result) { - // try with a another FULL matching locale ? - for (I18nBundleEntry entry : entries) { - I18nBundleScope i18nBundleScope = entry.getScope(); - // load from general to the max scope and always if there is only one bundle entry found - if (i18nBundleScope == I18nBundleScope.FULL && - !entry.getLocale().getCountry().equals(locale.getCountry()) && - entry.getLocale().getLanguage().equals(locale.getLanguage())) { - result.add(entry); - // we take the first one, this is a resuce!!! - break; - } - } - } - - private void promuteGeneralScope(List result) { - if (size() == 1) { - // there is one entry take it - result.add(entries.get(0)); - //return; - } - //TODO Should try to load default en_GB from I18nLoader ? - //I18n.DEFAULT_LOCALE.getCountry() - } - - private void promuteLanguageScope(Locale locale, List result) { - for (I18nBundleEntry entry : entries) { - I18nBundleScope i18nBundleScope = entry.getScope(); - // load from general to the max scope and always if there is only one bundle entry found - if (i18nBundleScope == I18nBundleScope.FULL && entry.getLocale().getLanguage().equals(locale.getLanguage())) { - result.add(entry); - // we take the first one, this is a resuce!!! - break; - } - } - } - - public List getEntries(I18nBundleScope scope) { + /** + * Obtain the entries for a given scope + * The order of result respect {@link I18nBundleEntry} order. + * + * @param scope the required scope + * @return the list of entries matching exactly the given scope + */ + public I18nBundleEntry[] getEntries(I18nBundleScope scope) { List result = new ArrayList(); for (I18nBundleEntry entry : entries) { I18nBundleScope i18nBundleScope = entry.getScope(); // load from general to the max scope and always if there is only one bundle entry found - if (i18nBundleScope == scope || i18nBundleScope.ordinal() < scope.ordinal()) { + if (i18nBundleScope == scope) { result.add(entry); } } - return result; + return result.toArray(new I18nBundleEntry[result.size()]); } + /** @return number of entries in bundle */ public int size() { return entries == null ? 0 : entries.size(); } + @Override + public String toString() { + String s = super.toString(); + return "<" + s.substring(s.lastIndexOf(".") + 1) + ", bundlePrefix:" + bundlePrefix + ", size:" + size() + ">"; + } + protected List getEntries() { return entries; } @@ -170,11 +150,5 @@ return b; } - @Override - public String toString() { - String s = super.toString(); - return "<" + s.substring(s.lastIndexOf(".") + 1) + ", bundlePrefix:" + bundlePrefix + ", size:" + size() + ">"; - } - }