diff --git a/icu4j/src/com/ibm/icu/dev/test/format/NumberRegression.java b/icu4j/src/com/ibm/icu/dev/test/format/NumberRegression.java index 9cf058d9403..b711a61ae86 100755 --- a/icu4j/src/com/ibm/icu/dev/test/format/NumberRegression.java +++ b/icu4j/src/com/ibm/icu/dev/test/format/NumberRegression.java @@ -1,7 +1,7 @@ /***************************************************************************************** * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/format/NumberRegression.java,v $ - * $Date: 2002/08/21 00:05:59 $ - * $Revision: 1.11 $ + * $Date: 2002/09/14 21:36:28 $ + * $Revision: 1.12 $ * ***************************************************************************************** **/ @@ -634,7 +634,7 @@ public class NumberRegression extends com.ibm.icu.dev.test.TestFmwk { if (tempString.equals(expectedDefault)) { logln ("Bug 4071859 default test passed."); } else { - errln("Failed:" + + errln("a) Failed:" + " Expected " + expectedDefault + " Received " + tempString ); } @@ -645,7 +645,7 @@ public class NumberRegression extends com.ibm.icu.dev.test.TestFmwk { if (tempString.equals(expectedCurrency) ) { logln ("Bug 4071859 currency test passed."); } else { - errln("Failed:" + + errln("b) Failed:" + " Expected " + expectedCurrency + " Received " + tempString ); } @@ -656,7 +656,7 @@ public class NumberRegression extends com.ibm.icu.dev.test.TestFmwk { if (tempString.equals(expectedPercent) ) { logln ("Bug 4071859 percentage test passed."); } else { - errln("Failed:" + + errln("c) Failed:" + " Expected " + expectedPercent + " Received " + tempString ); } diff --git a/icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTest.java b/icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTest.java index b86799b8692..f34b2bdd3ac 100644 --- a/icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTest.java +++ b/icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTest.java @@ -5,8 +5,8 @@ ******************************************************************************* * * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTest.java,v $ - * $Date: 2002/08/13 22:10:20 $ - * $Revision: 1.4 $ + * $Date: 2002/09/14 21:36:30 $ + * $Revision: 1.5 $ * ******************************************************************************* */ @@ -30,6 +30,7 @@ import com.ibm.icu.impl.ICULocaleService.ICUResourceBundleFactory; import java.util.Arrays; import java.util.EventListener; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -479,11 +480,10 @@ public class ICUServiceTest extends TestFmwk return null; } - protected void handleUpdateVisibleIDs(Set result) { - for (int i = 0; i < ids.length; ++i) { - result.add(ids[i]); - } + protected Set handleGetSupportedIDs() { + return new HashSet(Arrays.asList(ids)); } + protected String handleGetDisplayName(String id, Locale locale) { return factoryID + LocaleUtility.getLocaleFromName(id).getDisplayName(locale); } @@ -544,13 +544,15 @@ public class ICUServiceTest extends TestFmwk private static String surfer = californio + "_SURFER"; private static String geek = californio + "_GEEK"; - public void handleUpdateVisibleIDs(Set result) { - super.handleUpdateVisibleIDs(result); + public Set handleGetSupportedIDs() { + Set result = super.handleGetSupportedIDs(); result.add(californio); result.add(valley); result.add(surfer); result.add(geek); + + return result; } protected String handleGetDisplayName(String id, Locale locale) { @@ -620,6 +622,11 @@ public class ICUServiceTest extends TestFmwk confirmEqual("test with en locale", "root", target); } + public void errln(String msg) { + System.out.println(msg); + (new String[0])[1] = "foo"; + } + // misc coverage tests public void TestCoverage() { // Key @@ -640,7 +647,7 @@ public class ICUServiceTest extends TestFmwk logln("OK: " + e.getMessage()); } catch (Exception e) { - errln("threw wrong exception"); + errln("threw wrong exception" + e); } logln(sf.getDisplayName("object", null)); @@ -655,10 +662,11 @@ public class ICUServiceTest extends TestFmwk catch (NullPointerException e) { logln("OK: " + e.getMessage()); } + /* catch (Exception e) { - errln("threw wrong exception"); + errln("threw wrong exception" + e); } - + */ try { service.registerFactory(null); errln("didn't throw exception"); @@ -667,7 +675,7 @@ public class ICUServiceTest extends TestFmwk logln("OK: " + e.getMessage()); } catch (Exception e) { - errln("threw wrong exception"); + errln("threw wrong exception" + e); } try { @@ -678,7 +686,7 @@ public class ICUServiceTest extends TestFmwk logln("OK: " + e.getMessage()); } catch (Exception e) { - errln("threw wrong exception"); + errln("threw wrong exception" + e); } logln("object is: " + service.get("object")); @@ -718,9 +726,9 @@ public class ICUServiceTest extends TestFmwk } // LocaleKey - LocaleKey lkey = LocaleKey.create("en_US", "ja_JP"); - lkey = LocaleKey.create(null, null); - lkey = LocaleKey.createWithCanonical("en_US", "ja_JP"); + // LocaleKey lkey = LocaleKey.create("en_US", "ja_JP"); + // lkey = LocaleKey.create(null, null); + LocaleKey lkey = LocaleKey.createWithCanonicalFallback("en_US", "ja_JP"); // MultipleKeyFactory MultipleKeyFactory mkf = new MKFSubclass(false); @@ -736,10 +744,10 @@ public class ICUServiceTest extends TestFmwk invisibleMKF.updateVisibleIDs(new HashMap()); // ResourceBundleFactory - ICUResourceBundleFactory rbf = new ICUResourceBundleFactory(null, true); + ICUResourceBundleFactory rbf = new ICUResourceBundleFactory(true); logln("RB: " + rbf.create(lkey)); - LocaleKey nokey = LocaleKey.create(null, null); - logln("RB: " + rbf.create(nokey)); + // LocaleKey nokey = LocaleKey.create(null, null); + // logln("RB: " + rbf.create(nokey)); rbf = new ICUResourceBundleFactory("foobar", true); logln("RB: " + rbf.create(lkey)); @@ -814,7 +822,8 @@ public class ICUServiceTest extends TestFmwk return null; } - public void handleUpdateVisibleIDs(Set result) { + public Set handleGetSupportedIDs() { + return null; } } } diff --git a/icu4j/src/com/ibm/icu/impl/ICULocaleData.java b/icu4j/src/com/ibm/icu/impl/ICULocaleData.java index e63dc062806..99a0c5b9d25 100644 --- a/icu4j/src/com/ibm/icu/impl/ICULocaleData.java +++ b/icu4j/src/com/ibm/icu/impl/ICULocaleData.java @@ -185,7 +185,7 @@ public class ICULocaleData { * returns an 'unparented' bundle that exactly matches the bundle name and locale name. */ public static ResourceBundle loadResourceBundle(String bundleName, String localeName) { - if (localeName != null) { + if (localeName != null && localeName.length() > 0) { bundleName = bundleName + "_" + localeName; } for (int i = 0; i < packageNames.length; ++i) { diff --git a/icu4j/src/com/ibm/icu/impl/ICULocaleService.java b/icu4j/src/com/ibm/icu/impl/ICULocaleService.java index 47ca13e573d..acf783976a0 100644 --- a/icu4j/src/com/ibm/icu/impl/ICULocaleService.java +++ b/icu4j/src/com/ibm/icu/impl/ICULocaleService.java @@ -5,8 +5,8 @@ ******************************************************************************* * * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/impl/ICULocaleService.java,v $ - * $Date: 2002/09/07 00:15:33 $ - * $Revision: 1.6 $ + * $Date: 2002/09/14 21:36:30 $ + * $Revision: 1.7 $ * ******************************************************************************* */ @@ -14,25 +14,31 @@ package com.ibm.icu.impl; import java.lang.ref.SoftReference; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; +import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Set; import java.util.TreeSet; public class ICULocaleService extends ICUService { - Locale fallbackLocale; - String fallbackLocaleName; + private Locale fallbackLocale; + private String fallbackLocaleName; /** - * Construct an ICULocaleService with a fallback locale string based on the current - * default locale at the time of construction. + * Construct an ICULocaleService. This uses the current default locale as a fallback. */ public ICULocaleService() { - fallbackLocale = Locale.getDefault(); - fallbackLocaleName = LocaleUtility.canonicalLocaleString(fallbackLocale.toString()); + } + + /** + * Construct an ICULocaleService with a name (useful for debugging). + */ + public ICULocaleService(String name) { + super(name); } /** @@ -45,13 +51,15 @@ public class ICULocaleService extends ICUService { /** * Convenience override for callers using locales. */ - public Object get(Locale locale, Locale[] actualLocaleReturn) { - if (actualLocaleReturn == null) { + public Object get(Locale locale, Locale[] actualReturn) { + if (actualReturn == null) { return get(locale.toString()); } String[] temp = new String[1]; Object result = get(locale.toString(), temp); - actualLocaleReturn[0] = LocaleUtility.getLocaleFromName(temp[0]); + if (result != null) { + actualReturn[0] = LocaleUtility.getLocaleFromName(temp[0]); + } return result; } @@ -70,14 +78,13 @@ public class ICULocaleService extends ICUService { } /** - * Convenience method for callers using locales. This is the typical - * current API for this operation. + * Convenience method for callers using locales. This is the + * current typical API for this operation, though perhaps it should change. */ public Locale[] getAvailableLocales() { - TreeSet sort = new TreeSet(String.CASE_INSENSITIVE_ORDER); - sort.addAll(getVisibleIDs()); - Iterator iter = sort.iterator(); - Locale[] locales = new Locale[sort.size()]; + Set visIDs = getVisibleIDs(); + Iterator iter = visIDs.iterator(); + Locale[] locales = new Locale[visIDs.size()]; int n = 0; while (iter.hasNext()) { Locale loc = LocaleUtility.getLocaleFromName((String)iter.next()); @@ -89,42 +96,34 @@ public class ICULocaleService extends ICUService { /** * A subclass of Key that implements a locale fallback mechanism. * The first locale to search for is the locale provided by the - * client, and the fallback locale to search for is the current default - * locale. This is instantiated by ICULocaleService.

+ * client, and the fallback locale to search for is the current + * default locale. If a prefix is present, the currentDescriptor + * includes it before the locale proper, separated by "/". This + * is the default key instantiated by ICULocaleService.

* *

Canonicalization adjusts the locale string so that the * section before the first understore is in lower case, and the rest - * is in upper case, with no trailing underscores.

+ * is in upper case, with no trailing underscores.

*/ public static class LocaleKey extends ICUService.Key { + private String prefix; private String primaryID; private String fallbackID; private String currentID; /** - * Convenience method for createWithCanonical that canonicalizes both the - * primary and fallback IDs first. - */ - public static LocaleKey create(String primaryID, String fallbackID) { - String canonicalPrimaryID = LocaleUtility.canonicalLocaleString(primaryID); - String canonicalFallbackID = LocaleUtility.canonicalLocaleString(fallbackID); - return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID); - } - - /** - * Convenience method for createWithCanonical that canonicalizes the - * primary ID first, the fallback is assumed to already be canonical. + * Create a LocaleKey with canonical primary and fallback IDs. */ public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID) { - String canonicalPrimaryID = LocaleUtility.canonicalLocaleString(primaryID); - return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID); + return createWithCanonicalFallback(primaryID, canonicalFallbackID, null); } - + /** * Create a LocaleKey with canonical primary and fallback IDs. */ - public static LocaleKey createWithCanonical(String canonicalPrimaryID, String canonicalFallbackID) { - return new LocaleKey(canonicalPrimaryID, canonicalPrimaryID, canonicalFallbackID); + public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID, String prefix) { + String canonicalPrimaryID = LocaleUtility.canonicalLocaleString(primaryID); + return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID, prefix); } /** @@ -133,8 +132,10 @@ public class ICULocaleService extends ICUService { * fallbackID is the current default locale's string in * canonical form. */ - protected LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID) { + protected LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID, String prefix) { super(primaryID); + + this.prefix = prefix; if (canonicalPrimaryID == null) { this.primaryID = ""; @@ -154,6 +155,13 @@ public class ICULocaleService extends ICUService { this.currentID = this.primaryID; } + /** + * Return the prefix, or null if none was defined. + */ + public String prefix() { + return prefix; + } + /** * Return the (canonical) original ID. */ @@ -162,12 +170,37 @@ public class ICULocaleService extends ICUService { } /** - * Return the (canonical) current ID. + * Return the (canonical) current ID, or null if no current id. */ public String currentID() { return currentID; } + /** + * Return the (canonical) current descriptor, or null if no current id. + */ + public String currentDescriptor() { + String result = currentID(); + if (result != null && prefix != null) { + result = prefix + "/" + result; + } + return result; + } + + /** + * Convenience method to return the locale corresponding to the (canonical) original ID. + */ + public Locale canonicalLocale() { + return LocaleUtility.getLocaleFromName(primaryID); + } + + /** + * Convenience method to return the locale corresponding to the (canonical) current ID. + */ + public Locale currentLocale() { + return LocaleUtility.getLocaleFromName(currentID); + } + /** * If the key has a fallback, modify the key and return true, * otherwise return false.

@@ -178,10 +211,9 @@ public class ICULocaleService extends ICUService { * there is no fallback. */ public boolean fallback() { - String current = currentID(); - int x = current.lastIndexOf('_'); + int x = currentID.lastIndexOf('_'); if (x != -1) { - currentID = current.substring(0, x); + currentID = currentID.substring(0, x); return true; } if (fallbackID != null) { @@ -207,7 +239,41 @@ public class ICULocaleService extends ICUService { public static abstract class MultipleKeyFactory implements ICUService.Factory { protected final boolean visible; private SoftReference cacheref; - private boolean included; + + private static final class CacheInfo { + final Set cache; + final boolean included; + + CacheInfo() { + this.cache = new HashSet(); + this.included = false; + } + + CacheInfo(Set cache) { + this.cache = cache; + this.included = true; + } + + /** + * Return true if we're known to support id, or not known to not support id. + */ + boolean tryCreate(String id) { + boolean result = cache.contains(id) == included; + return result; + } + + /** + * Update information about whether we support this id. Since if we are storing + * information on included ids, we already know all of them, we only need to + * update if we're storing information on ids we don't support and we don't + * support the id (the result is null). + */ + void addCreate(String id, Object result) { + if (!included && result == null) { + cache.add(id); + } + } + } /** * Convenience overload of MultipleKeyFactory(boolean) that defaults @@ -227,62 +293,58 @@ public class ICULocaleService extends ICUService { /** * Get the cache of IDs. These are either the ids that we know we * don't understand, if included is false, or the entire set of ids - * we do know we understand, if included is true. Note that if - * the cache has been freed by gc, we reset the included flag, so - * it must not be tested before this method is called. + * we do know we understand, if included is true. If the cache has + * been flushed, included is false. */ - private HashSet getCache() { - HashSet cache = null; + private CacheInfo getCache() { + CacheInfo result = null; if (cacheref != null) { - cache = (HashSet)cacheref.get(); + result = (CacheInfo)cacheref.get(); } - if (cache == null) { - cache = new HashSet(); - cacheref = new SoftReference(cache); - included = false; + if (result == null) { + result = new CacheInfo(); + cacheref = new SoftReference(result); } - return cache; + return result; } /** * Get the cache of IDs we understand. */ - private HashSet getIncludedCache() { - HashSet cache = getCache(); - if (!included) { - cache.clear(); - handleUpdateVisibleIDs(cache); - included = true; - } - return cache; + protected Set getSupportedIDs() { + CacheInfo ci = getCache(); + Set result = ci.cache; + if (!ci.included) { + result = handleGetSupportedIDs(); + cacheref = new SoftReference(new CacheInfo(result)); + } + + return result; } - public final Object create(Key key) { + public Object create(Key key) { Object result = null; String id = key.currentID(); - HashSet cache = getCache(); - if (cache.contains(id) == included) { + CacheInfo ci = getCache(); + if (ci.tryCreate(id)) { result = handleCreate(key); - if (!included && result == null) { - cache.add(id); - } + ci.addCreate(id, result); } return result; } - public final void updateVisibleIDs(Map result) { + public void updateVisibleIDs(Map result) { if (visible) { - Set cache = getIncludedCache(); - Iterator iter = cache.iterator(); - while (iter.hasNext()) { - result.put((String)iter.next(), this); - } + Iterator iter = getSupportedIDs().iterator(); + while (iter.hasNext()) { + result.put(iter.next(), this); + } } } - public final String getDisplayName(String id, Locale locale) { + public String getDisplayName(String id, Locale locale) { if (visible) { - Set cache = getIncludedCache(); + Set cache = getSupportedIDs(); if (cache.contains(id)) { return handleGetDisplayName(id, locale); } @@ -296,11 +358,11 @@ public class ICULocaleService extends ICUService { protected abstract Object handleCreate(Key key); /** - * Subclasses implement this instead of updateVisibleIDs. Any - * id known to and handled by this class should be added to - * result. + * Subclasses implement this instead of getSupportedIDs. Any + * id known to and handled by this class should be included in + * the returned Set. */ - protected abstract void handleUpdateVisibleIDs(Set result); + protected abstract Set handleGetSupportedIDs(); /** * Subclasses implement this instead of getDisplayName. @@ -313,6 +375,64 @@ public class ICULocaleService extends ICUService { } } + /** + * A subclass of MultipleKeyFactory that uses LocaleKeys. It is + * able to optionally 'hide' more specific locales with more general + * locales that it supports. + */ + public static abstract class LocaleKeyFactory extends MultipleKeyFactory { + protected final boolean hides; + + /** + * Create a LocaleKeyFactory. + */ + public LocaleKeyFactory(boolean visible, boolean hides) { + super(visible); + + this.hides = hides; + } + + /** + * Override of superclass method. If this is visible, it will update + * result with the ids it supports. If this hides ids, more specific + * ids already in result will be remapped to this. + */ + public void updateVisibleIDs(Map result) { + if (visible) { + Set cache = getSupportedIDs(); + Map toRemap = new HashMap(); + Iterator iter = cache.iterator(); + while (iter.hasNext()) { + String id = (String)iter.next(); + if (hides) { + int idlen = id.length(); + Iterator miter = result.keySet().iterator(); + while (miter.hasNext()) { + String mid = (String)miter.next(); + if (mid.startsWith(id) && + (mid.length() == idlen || + mid.charAt(idlen) == '_')) { + + toRemap.put(mid, this); + miter.remove(); + } + } + } + toRemap.put(id, this); + } + result.putAll(toRemap); + } + } + + /** + * Return a localized name for the locale represented by id. + */ + protected String handleGetDisplayName(String id, Locale locale) { + // use java's display name formatting for now + return LocaleUtility.getLocaleFromName(id).getDisplayName(locale); + } + } + /** * A factory that creates a service based on the ICU locale data. * Subclasses specify a prefix (default is LocaleElements), a @@ -325,7 +445,11 @@ public class ICULocaleService extends ICUService { */ public static class ICUResourceBundleFactory extends MultipleKeyFactory { protected final String name; - protected final String[] requiredContents; + protected final String[][] requiredContents; + + public ICUResourceBundleFactory(boolean visible) { + this((String)null, visible); + } /** * A service factory based on ICU resource data in the LocaleElements resources. @@ -340,29 +464,89 @@ public class ICULocaleService extends ICUService { * listed resources must come directly from the same bundle. */ public ICUResourceBundleFactory(String name, String requiredContents, boolean visible) { - super(visible); + this(name, buildRcAndOr(requiredContents), true, visible); + } - this.name = name; - if (requiredContents != null) { + private static class Node { + public boolean test(ResourceBundle rb) { + return rb != null; + } + } + + private static class ResourceNode { + String name; + } + + private static class BoolNode extends Node { + BoolNode car; + BoolNode cdr; + } + + private static String[][] buildRcAndOr(String requiredContents) { + String[][] rcAndOr = null; + if (requiredContents != null) { + rcAndOr = new String[][] { parseDelimitedString(requiredContents) }; + } + return rcAndOr; + } + + public ICUResourceBundleFactory(String[] rcOr, boolean visible) { + this(ICULocaleData.LOCALE_ELEMENTS, rcOr, visible); + } + + public ICUResourceBundleFactory(String name, String[] rcOr, boolean visible) { + this(name, buildRcAndOr(rcOr), true, visible); + } + + private static String[][] buildRcAndOr(String[] rcOr) { + String[][] rcOrAnd = null; + if (rcOr != null) { + rcOrAnd = new String[rcOr.length][]; + for (int i = 0; i < rcOr.length; ++i) { + rcOrAnd[i] = parseDelimitedString(rcOr[i]); + } + } + return rcOrAnd; + } + + public ICUResourceBundleFactory(String[][] rcOrAnd, boolean adopt, boolean visible) { + this(ICULocaleData.LOCALE_ELEMENTS, rcOrAnd, adopt, visible); + } + + private static String[] parseDelimitedString(String str) { + if (str != null) { ArrayList list = new ArrayList(); - for (int i = 0, len = requiredContents.length();;) { - while (i < len && requiredContents.charAt(i) == ';') { + for (int i = 0, len = str.length();;) { + while (i < len && str.charAt(i) == ';') { ++i; } if (i == len) { break; } - int j = requiredContents.indexOf(';', i); + int j = str.indexOf(';', i); if (j == -1) { j = len; } - list.add(requiredContents.substring(i, j)); + list.add(str.substring(i, j)); i = j; } - this.requiredContents = (String[])list.toArray(new String[list.size()]); - } else { - this.requiredContents = null; + return (String[])list.toArray(new String[list.size()]); } + return null; + } + + public ICUResourceBundleFactory(String name, String[][] rcOrAnd, boolean adopt, boolean visible) { + super(visible); + + this.name = name; + + if (!adopt && rcOrAnd != null) { + rcOrAnd = (String[][])rcOrAnd.clone(); + for (int i = 0; i < rcOrAnd.length; ++i) { + rcOrAnd[i] = (String[])(rcOrAnd[i].clone()); + } + } + this.requiredContents = rcOrAnd; } /** @@ -384,21 +568,23 @@ public class ICULocaleService extends ICUService { * time-consuming so we don't want to do it more than once if * we have to. This is only called if we are visible. */ - protected void handleUpdateVisibleIDs(Set result) { + protected Set handleGetSupportedIDs() { + Set result = new TreeSet(String.CASE_INSENSITIVE_ORDER); Locale[] locales = ICULocaleData.getAvailableLocales(name); for (int i = 0; i < locales.length; ++i) { Locale locale = locales[i]; if (acceptsLocale(locale)) { - result.add(LocaleUtility.canonicalLocaleString(locale.toString())); + String str = LocaleUtility.canonicalLocaleString(locale.toString()); + result.add(str); } } + return result; } /** * Return a localized name for the locale represented by id. */ protected String handleGetDisplayName(String id, Locale locale) { - // use java's display name formatting for now return LocaleUtility.getLocaleFromName(id).getDisplayName(locale); } @@ -408,18 +594,41 @@ public class ICULocaleService extends ICUService { * inherited bundle); */ protected boolean acceptsLocale(Locale loc) { - try { - ResourceBundle bundle = ICULocaleData.loadResourceBundle(name, loc); // single resource bundle lookup - if (requiredContents != null) { - for (int i = 0; i < requiredContents.length; ++i) { - if (bundle.getObject(requiredContents[i]) == null) { - return false; - } - } - } - return true; + boolean debug = false; + if (debug) System.out.println("al name: " + name + " loc: '" + loc + "'"); + try { + ResourceBundle bundle = ICULocaleData.loadResourceBundle(name, loc); + if (bundle == null) { + if (debug) System.out.println("no bundle"); + return false; + } + if (requiredContents == null) { + if (debug) System.out.println("always accepts"); + return true; + } + + loop: + for (int i = 0; i < requiredContents.length; ++i) { + String[] andRC = requiredContents[i]; + + for (int j = 0; j < andRC.length; ++j) { + try { + if (debug) System.out.println("al["+i+"]["+j+"] " + andRC[j]); + bundle.getObject(andRC[j]); + } + catch (MissingResourceException ex) { + if (debug) System.out.println("nope"); + continue loop; + } + } + if (debug) System.out.println("ok"); + return true; + } } catch (Exception e) { + Thread.dumpStack(); + if (debug) System.out.println("whoops: " + e); + System.exit(0); } return false; } @@ -433,7 +642,11 @@ public class ICULocaleService extends ICUService { } } - protected Key createKey(String id) { + /** + * Return the name of the current fallback locale. If it has changed since this was + * last accessed, the service cache is cleared. + */ + public String validateFallbackLocale() { Locale loc = Locale.getDefault(); if (loc != fallbackLocale) { synchronized (this) { @@ -444,7 +657,10 @@ public class ICULocaleService extends ICUService { } } } - - return LocaleKey.createWithCanonicalFallback(id, fallbackLocaleName); + return fallbackLocaleName; + } + + protected Key createKey(String id) { + return LocaleKey.createWithCanonicalFallback(id, validateFallbackLocale()); } } diff --git a/icu4j/src/com/ibm/icu/impl/ICUService.java b/icu4j/src/com/ibm/icu/impl/ICUService.java index d49d1fbe81e..182c3cb27c8 100644 --- a/icu4j/src/com/ibm/icu/impl/ICUService.java +++ b/icu4j/src/com/ibm/icu/impl/ICUService.java @@ -5,8 +5,8 @@ ******************************************************************************* * * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/impl/ICUService.java,v $ - * $Date: 2002/08/13 23:40:52 $ - * $Revision: 1.7 $ + * $Date: 2002/09/14 21:36:29 $ + * $Revision: 1.8 $ * ******************************************************************************* */ @@ -38,11 +38,11 @@ import java.util.TreeMap; * generally it should not be mutable, or the caller should clone the * object before modifying it.

* - *

Services 'canonicalize' the query id and use the canonical id - * to query for the service. The service also defines a mechanism - * to 'fallback' the id multiple times. Clients can optionally - * request the actual id that was matched by a query when they - * use an id to retrieve a service object.

+ *

Services 'canonicalize' the query id and use the canonical id to + * query for the service. The service also defines a mechanism to + * 'fallback' the id multiple times. Clients can optionally request + * the actual id that was matched by a query when they use an id to + * retrieve a service object.

* *

Service objects are instantiated by Factory objects registered with * the service. The service queries each Factory in turn, from most recently @@ -81,9 +81,25 @@ import java.util.TreeMap; * ICUService by overriding it, for example, to customize the Key and * fallback strategy. ICULocaleService is a customized service that * uses Locale names as ids and uses Keys that implement the standard - * resource bundle fallback strategy.

+ * resource bundle fallback strategy.

*/ public class ICUService extends ICUNotifier { + protected final String name; + + /** + * Constructor. + */ + public ICUService() { + name = ""; + } + + /** + * Construct with a name (useful for debugging). + */ + public ICUService(String name) { + this.name = name; + } + /** * Access to factories is protected by a read-write lock. This is * to allow multiple threads to read concurrently, but keep @@ -97,10 +113,12 @@ public class ICUService extends ICUNotifier { private final List factories = new ArrayList(); /** - * Keys define how ids are canonicalized, and determine the - * fallback strategy used when querying the factories. The default - * key just takes as its canonicalID the id assigned to it when it - * is constructed, and has no fallbacks. + * Keys are used to communicate with factories to generate an + * instance of the service. They define how ids are + * canonicalized, provide both a current id and a current + * descriptor to use in querying the cache and factories, and + * determine the fallback strategy. The default key has no + * fallbacks. */ public static class Key { private final String id; @@ -113,7 +131,7 @@ public class ICUService extends ICUNotifier { } /** - * Return the original ID. + * Return the original ID used to construct this key. */ public final String id() { return id; @@ -128,13 +146,26 @@ public class ICUService extends ICUNotifier { } /** - * Return the (canonical) current ID. This implementation returns the - * canonical ID. + * Return the (canonical) current ID. This implementation + * returns the canonical ID. */ public String currentID() { return canonicalID(); } + /** + * Return the current descriptor. This implementation returns + * the current ID. The current descriptor is used to fully + * identify an instance of the service in the cache. The + * current ID is that part of the descriptor that a factory + * can examine to identify whether it handles the key. The + * factory can either parse the descriptor or use custom API + * on the key in order to instantiate the service. + */ + public String currentDescriptor() { + return currentID(); + } + /** * If the key has a fallback, modify the key and return true, * otherwise return false. The current ID will change if there @@ -161,17 +192,18 @@ public class ICUService extends ICUNotifier { public Object create(Key key); /** - * Add IDs understood by this factory to the result map, with - * this factory as the value. If this factory hides IDs + * Add IDs this factory publicly handles to the result map, + * with this factory as the value. If this factory hides IDs * currently in result, it should remove or reset the mappings - * for those IDs. - */ + * for those IDs. Result should contain only ids, not + * descriptors. + */ public void updateVisibleIDs(Map result); /** * Return the display name for this id in the provided locale. * If the id is not visible or not defined by the factory, - * return null. + * return null. This is an id, not a descriptor. */ public String getDisplayName(String id, Locale locale); } @@ -182,7 +214,8 @@ public class ICUService extends ICUNotifier { * factory that matches a single id and returns a single * (possibly deferred-initialized) instance. If visible is * true, updates the map passed to updateVisibleIDs with a - * mapping from id to itself. + * mapping from id to itself. This ignores the key descriptor + * and only examines the id. */ public static class SimpleFactory implements Factory { protected Object instance; @@ -238,33 +271,55 @@ public class ICUService extends ICUNotifier { } /** - * Convenience override for get(String, String[]). + * Convenience override for get(String, String[]). This uses + * createKey to create a key for the provided descriptor. */ - public Object get(String id) { - return get(id, null); + public Object get(String descriptor) { + return getKey(createKey(descriptor), null); } /** - *

Given an id, return a service object, and, if actualIDReturn - * is not null, the actual id under which it was found in the - * first element of actualIDReturn. If no service object matches - * this id, return null, and leave actualIDReturn unchanged.

- * - *

This tries each registered factory in order, and if none can - * generate a service object for the key, repeats the process with - * each fallback of the key until one returns a service object, or - * the key has no fallback.

+ * Convenience override for get(Key, String[]). This uses + * createKey to create a key from the provided descriptor. */ - public Object get(String id, String[] actualIDReturn) { - if (id == null) { - throw new NullPointerException(); - } + public Object get(String descriptor, String[] actualReturn) { + if (descriptor == null) { + throw new NullPointerException("descriptor must not be null"); + } + return getKey(createKey(descriptor), actualReturn); + } + + /** + * Convenience override for get(Key, String[]). + */ + public Object getKey(Key key) { + return getKey(key, null); + } + + /** + *

Given a key, return a service object, and, if actualReturn + * is not null, the descriptor with which it was found in the + * first element of actualReturn. If no service object matches + * this key, return null, and leave actualReturn unchanged.

+ * + *

This queries the cache using the key's descriptor, and if no + * object in the cache matches it, tries the key on each + * registered factory, in order. If none generates a service + * object for the key, repeats the process with each fallback of + * the key, until either one returns a service object, or the key + * has no fallback.

+ * + *

If key is null, just returns null.

+ */ + public Object getKey(Key key, String[] actualReturn) { if (factories.size() == 0) { return null; } - + + boolean debug = false; + if (debug) System.out.println("Service: " + name + " key: " + key); + CacheEntry result = null; - Key key = createKey(id); if (key != null) { try { // The factory list can't be modified until we're done, @@ -284,15 +339,19 @@ public class ICUService extends ICUNotifier { cref = new SoftReference(cache); } - String currentID = null; - ArrayList cacheIDList = null; + String currentDescriptor = null; + ArrayList cacheDescriptorList = null; boolean putInCache = false; + + int NDebug = 0; + outer: do { - currentID = key.currentID(); - - result = (CacheEntry)cache.get(currentID); + currentDescriptor = key.currentDescriptor(); + if (debug) System.out.println(name + "[" + NDebug++ + "] looking for: " + currentDescriptor); + result = (CacheEntry)cache.get(currentDescriptor); if (result != null) { + if (debug) System.out.println(name + " found with descriptor: " + currentDescriptor); break outer; } @@ -300,11 +359,13 @@ public class ICUService extends ICUNotifier { // the cache if we eventually succeed. putInCache = true; + int n = 0; Iterator fi = factories.iterator(); while (fi.hasNext()) { Object service = ((Factory)fi.next()).create(key); if (service != null) { - result = new CacheEntry(currentID, service); + result = new CacheEntry(currentDescriptor, service); + if (debug) System.out.println(name + " factory cache with descriptor: " + currentDescriptor); break outer; } } @@ -314,20 +375,23 @@ public class ICUService extends ICUNotifier { // don't want to keep querying on an id that's going to // fallback to the one that succeeded, we want to hit the // cache the first time next goaround. - if (cacheIDList == null) { - cacheIDList = new ArrayList(5); + if (cacheDescriptorList == null) { + cacheDescriptorList = new ArrayList(5); } - cacheIDList.add(currentID); + cacheDescriptorList.add(currentDescriptor); } while (key.fallback()); if (result != null) { if (putInCache) { - cache.put(result.actualID, result); - if (cacheIDList != null) { - Iterator iter = cacheIDList.iterator(); + cache.put(result.actualDescriptor, result); + if (cacheDescriptorList != null) { + Iterator iter = cacheDescriptorList.iterator(); while (iter.hasNext()) { - cache.put((String)iter.next(), result); + String desc = (String)iter.next(); + if (debug) System.out.println(name + " adding descriptor: '" + desc + "' for actual: '" + result.actualDescriptor + "'"); + + cache.put(desc, result); } } // Atomic update. We held the read lock all this time @@ -337,10 +401,12 @@ public class ICUService extends ICUNotifier { cacheref = cref; } - if (actualIDReturn != null) { - actualIDReturn[0] = result.actualID; + if (actualReturn != null) { + actualReturn[0] = result.actualDescriptor; } + if (debug) System.out.println("found in service: " + name); + return result.service; } } @@ -349,6 +415,8 @@ public class ICUService extends ICUNotifier { } } + if (debug) System.out.println("not found in service: " + name); + return null; } private SoftReference cacheref; @@ -356,10 +424,10 @@ public class ICUService extends ICUNotifier { // Record the actual id for this service in the cache, so we can return it // even if we succeed later with a different id. private static final class CacheEntry { - String actualID; - Object service; - CacheEntry(String actualID, Object service) { - this.actualID = actualID; + final String actualDescriptor; + final Object service; + CacheEntry(String actualDescriptor, Object service) { + this.actualDescriptor = actualDescriptor; this.service = service; } } @@ -391,9 +459,7 @@ public class ICUService extends ICUNotifier { // grab the factory list and update it ourselves try { factoryLock.acquireRead(); - idcache = new TreeMap(String.CASE_INSENSITIVE_ORDER); - ListIterator lIter = factories.listIterator(factories.size()); while (lIter.hasPrevious()) { Factory f = (Factory)lIter.previous(); @@ -490,8 +556,8 @@ public class ICUService extends ICUNotifier { // we define a class so we get atomic simultaneous access to both the // locale and corresponding map private static class LocaleRef { - Locale locale; - SoftReference ref; + final Locale locale; + final SoftReference ref; LocaleRef(Map dnCache, Locale locale) { this.locale = locale; @@ -690,5 +756,19 @@ public class ICUService extends ICUNotifier { } return "no stats"; } + + /** + * Return the name of this service. This will be the empty string if none was assigned. + */ + public String getName() { + return name; + } + + /** + * Returns the result of super.toString, appending the name in curly braces. + */ + public String toString() { + return super.toString() + "{" + name + "}"; + } } diff --git a/icu4j/src/com/ibm/icu/impl/LocaleUtility.java b/icu4j/src/com/ibm/icu/impl/LocaleUtility.java index 1033e4240a2..063af17614b 100644 --- a/icu4j/src/com/ibm/icu/impl/LocaleUtility.java +++ b/icu4j/src/com/ibm/icu/impl/LocaleUtility.java @@ -5,8 +5,8 @@ ******************************************************************************* * * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/impl/LocaleUtility.java,v $ - * $Date: 2002/06/19 21:03:12 $ - * $Revision: 1.4 $ + * $Date: 2002/09/14 21:36:29 $ + * $Revision: 1.5 $ * ***************************************************************************************** */ @@ -97,27 +97,4 @@ public class LocaleUtility { } return id; } - - /* - public static String getDisplayLanguage(String languageID, Locale l) { - } - - public static String getDisplayRegion(String regionID, Locale l) { - } - - public static String getDisplayVariant(String variantID, Locale l) { - } - - public static String getDisplayName(String localeName) { - return getDisplayName(getLocaleFromString(localeName)); - } - - public static String getDisplayName(Locale locale) { - String lang = locale.getLanguage(); - String region = locale.getCountry(); - String var = locale.getVariant(); - - StringBuffer buf = new StringBuffer(lang); - } - */ } diff --git a/icu4j/src/com/ibm/icu/text/NumberFormat.java b/icu4j/src/com/ibm/icu/text/NumberFormat.java index aae45720142..b11ab2b8376 100755 --- a/icu4j/src/com/ibm/icu/text/NumberFormat.java +++ b/icu4j/src/com/ibm/icu/text/NumberFormat.java @@ -5,15 +5,13 @@ ******************************************************************************* * * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/text/NumberFormat.java,v $ - * $Date: 2002/03/20 05:11:15 $ - * $Revision: 1.13 $ + * $Date: 2002/09/14 21:36:28 $ + * $Revision: 1.14 $ * ***************************************************************************************** */ package com.ibm.icu.text; -import com.ibm.icu.impl.ICULocaleData; - import java.io.InvalidObjectException; //Bug 4185761 [Richard/GCL] import java.io.IOException; import java.io.ObjectInputStream; @@ -23,9 +21,22 @@ import java.text.FieldPosition; import java.text.Format; import java.text.ParseException; import java.text.ParsePosition; +import java.util.HashSet; import java.util.Hashtable; import java.util.Locale; +import java.util.Map; import java.util.ResourceBundle; +import java.util.Set; + +import com.ibm.icu.impl.ICULocaleData; +import com.ibm.icu.impl.ICUService; +import com.ibm.icu.impl.ICUService.Key; +import com.ibm.icu.impl.ICUService.Factory; +import com.ibm.icu.impl.ICULocaleService; +import com.ibm.icu.impl.ICULocaleService.ICUResourceBundleFactory; +import com.ibm.icu.impl.ICULocaleService.LocaleKey; +import com.ibm.icu.impl.ICULocaleService.LocaleKeyFactory; +import com.ibm.icu.impl.LocaleUtility; /** * NumberFormat is the abstract base class for all number @@ -151,13 +162,20 @@ import java.util.ResourceBundle; * * see DecimalFormat * see java.text.ChoiceFormat - * @version $Revision: 1.13 $ + * @version $Revision: 1.14 $ * @author Mark Davis * @author Helena Shih * @author Alan Liu */ public abstract class NumberFormat extends Format{ + // Constants used by factory methods to specify a style of format. + private static final int NUMBERSTYLE = 0; + private static final int CURRENCYSTYLE = 1; + private static final int PERCENTSTYLE = 2; + private static final int SCIENTIFICSTYLE = 3; + private static final int INTEGERSTYLE = 4; + /** * Field constant used to construct a FieldPosition object. Signifies that * the position of the integer part of a formatted number should be returned. @@ -455,13 +473,168 @@ public abstract class NumberFormat extends Format{ return getInstance(inLocale, SCIENTIFICSTYLE); } + // ===== Factory stuff ===== + + public static abstract class NumberFormatFactory { + public static final int FORMAT_NUMBER = NUMBERSTYLE; + public static final int FORMAT_CURRENCY = CURRENCYSTYLE; + public static final int FORMAT_PERCENT = PERCENTSTYLE; + public static final int FORMAT_SCIENTIFIC = SCIENTIFICSTYLE; + public static final int FORMAT_INTEGER = INTEGERSTYLE; + + /** + * Return true if this factory will 'hide' other locales that + * are more specific than the ones in this factory. E.g., if + * this 'hides' other locales, then by supporting the 'en' + * locale, this also supports 'en_US', 'en_US_ETC' and so on, + * even though only the 'en' locale is listed. + */ + public abstract boolean hasGenericLocales(); + + /** + * Return an array of the locales directly supported by this factory. + */ + public abstract Locale[] getSupportedLocales(); + + /** + * Return a number format of the appropriate type. If the locale + * is not supported, return null. All the defined types must be + * supported. + */ + public abstract NumberFormat createFormat(Locale loc, int formatType); + } + + public static abstract class SimpleNumberFormatFactory extends NumberFormatFactory { + final Locale locale; + final boolean isGeneric; + + public SimpleNumberFormatFactory(Locale locale, boolean isGeneric) { + this.locale = locale; + this.isGeneric = isGeneric; + } + + public final boolean hasGenericLocales() { + return isGeneric; + } + + public final Locale[] getSupportedLocales() { + return new Locale[] { locale }; + } + } + + private static final class NFFactory extends LocaleKeyFactory { + private NumberFormatFactory delegate; + + NFFactory(NumberFormatFactory delegate) { + super(true, delegate.hasGenericLocales()); + + this.delegate = delegate; + } + + protected Object handleCreate(Key key) { + NFKey nfkey = (NFKey)key; + Locale loc = nfkey.canonicalLocale(); + int choice = nfkey.choice(); + return delegate.createFormat(loc, choice); + } + + protected Set handleGetSupportedIDs() { + Locale[] locales = delegate.getSupportedLocales(); + Set result = new HashSet(); + for (int i = 0; i < locales.length; ++i) { + result.add(locales.toString()); + } + return result; + } + } /** * Get the set of Locales for which NumberFormats are installed * @return available locales */ public static Locale[] getAvailableLocales() { - return ICULocaleData.getAvailableLocales("NumberPatterns"); + // return ICULocaleData.getAvailableLocales("NumberPatterns"); + return getService().getAvailableLocales(); + } + + private static final class NFKey extends LocaleKey { + private int choice; + + private static String[] CHOICE_NAMES = { + "number", "currency", "percent", "scientific", "integer" + }; + + private NFKey(String id, String canonicalID, String canonicalFallback, int choice) { + super(id, canonicalID, canonicalFallback, CHOICE_NAMES[choice]); + this.choice = choice; + } + + int choice() { + return choice; + } + + static NFKey createKey(ICULocaleService service, Locale locale, int choice) { + String id = LocaleUtility.canonicalLocaleString(locale.toString()); + String fallback = service.validateFallbackLocale(); + return new NFKey(id, id, fallback, choice); + } + } + + /** + * Registers a new NumberFormat for the provided locale of the defined + * type. The returned object is a key that can be used to unregister this + * NumberFormat object. + */ + public static Object register(NumberFormatFactory factory) { + if (factory == null) { + throw new IllegalArgumentException("factory must not be null"); + } + return getService().registerFactory(new NFFactory(factory)); + } + + /** + * Unregister the currency associated with this key (obtained from + * registerInstance). + */ + public static boolean unregister(Object registryKey) { + return getService().unregisterFactory((Factory)registryKey); + } + + private static ICULocaleService service = null; + private static ICULocaleService getService() { + if (service == null) { + + final String[][] pattern = { + new String[] { "NumberPatterns" }, + new String[] { "NumberElements" }, + new String[] { "CurrencyElements" }, + }; + + class RBNumberFormatFactory extends ICUResourceBundleFactory { + RBNumberFormatFactory() { + super ("LocaleElements", pattern, true, true); + } + + protected Object handleCreate(Key key) { + NFKey nfkey = (NFKey)key; + Locale locale = nfkey.currentLocale(); + // System.out.println("testing locale: " + locale); + if (acceptsLocale(locale)) { + // System.out.println("creating with locale: " + nfkey.canonicalLocale()); + return createInstance(nfkey.canonicalLocale(), nfkey.choice()); + } + // System.out.println("did not support locale"); + return null; + } + } + + ICULocaleService newService = new ICULocaleService("NumberFormat"); + + newService.registerFactory(new RBNumberFormatFactory()); + + service = newService; // atomic + } + return service; } /** @@ -624,11 +797,28 @@ public abstract class NumberFormat extends Format{ // =======================privates=============================== + // Hook for service + private static NumberFormat getInstance(Locale desiredLocale, int choice) { + ICULocaleService service = getService(); + NFKey key = NFKey.createKey(service, desiredLocale, choice); + NumberFormat result = (NumberFormat)service.getKey(key); + + //System.out.println("desired locale: " + desiredLocale + " service result:" + result); + + //NumberFormat result2 = createInstance(desiredLocale, choice); + //System.out.println("defaultResult: " + result2); + + return (NumberFormat)result.clone(); + } + // [NEW] - private static NumberFormat getInstance(Locale desiredLocale, + private static NumberFormat createInstance(Locale desiredLocale, int choice) { - DecimalFormat format = new DecimalFormat(getPattern(desiredLocale, choice), - new DecimalFormatSymbols(desiredLocale)); + String pattern = getPattern(desiredLocale, choice); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(desiredLocale); + DecimalFormat format = new DecimalFormat(pattern, symbols); + // System.out.println("loc: " + desiredLocale + " choice: " + choice + " pat: " + pattern + " sym: " + symbols + " result: " + format); + /*Bug 4408066 Add codes for the new method getIntegerInstance() [Richard/GCL] */ @@ -763,15 +953,9 @@ public abstract class NumberFormat extends Format{ */ private static final Hashtable cachedLocaleData = new Hashtable(3); - // Constants used by factory methods to specify a style of format. - private static final int NUMBERSTYLE = 0; - private static final int CURRENCYSTYLE = 1; - private static final int PERCENTSTYLE = 2; - private static final int SCIENTIFICSTYLE = 3; /*Bug 4408066 Add Field for the new method getIntegerInstance() [Richard/GCL] */ - private static final int INTEGERSTYLE = 4; /** * True if the the grouping (i.e. thousands) separator is used when diff --git a/icu4j/src/com/ibm/icu/util/Currency.java b/icu4j/src/com/ibm/icu/util/Currency.java index cd111d06f13..373c8c2616e 100644 --- a/icu4j/src/com/ibm/icu/util/Currency.java +++ b/icu4j/src/com/ibm/icu/util/Currency.java @@ -5,8 +5,8 @@ ******************************************************************************* * * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/Currency.java,v $ - * $Date: 2002/09/07 00:15:35 $ - * $Revision: 1.5 $ + * $Date: 2002/09/14 21:36:30 $ + * $Revision: 1.6 $ * ******************************************************************************* */ @@ -56,11 +56,11 @@ public class Currency implements Serializable { private static ICULocaleService getService() { if (service == null) { - service = new ICULocaleService(); + service = new ICULocaleService("Currency"); class CurrencyFactory extends ICUResourceBundleFactory { CurrencyFactory() { - super ("LocaleElements", "CurrencyElements", true); + super("CurrencyElements", true); } protected Object createFromBundle(ResourceBundle bundle, Key key) {