This commit was manufactured by cvs2svn to create tag 'merged-3-4-3'.
X-SVN-Rev: 19196
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..4d99a35
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,58 @@
+* text=auto !eol
+
+*.c text !eol
+*.cc text !eol
+*.classpath text !eol
+*.cpp text !eol
+*.css text !eol
+*.dsp text !eol
+*.dsw text !eol
+*.filters text !eol
+*.h text !eol
+*.htm text !eol
+*.html text !eol
+*.in text !eol
+*.java text !eol
+*.launch text !eol
+*.mak text !eol
+*.md text !eol
+*.MF text !eol
+*.mk text !eol
+*.pl text !eol
+*.pm text !eol
+*.project text !eol
+*.properties text !eol
+*.py text !eol
+*.rc text !eol
+*.sh text eol=lf
+*.sln text !eol
+*.stub text !eol
+*.txt text !eol
+*.ucm text !eol
+*.vcproj text !eol
+*.vcxproj text !eol
+*.xml text !eol
+*.xsl text !eol
+*.xslt text !eol
+Makefile text !eol
+configure text !eol
+LICENSE text !eol
+README text !eol
+
+*.bin -text
+*.brk -text
+*.cnv -text
+*.icu -text
+*.res -text
+*.nrm -text
+*.spp -text
+*.tri2 -text
+
+# The following file types are stored in Git-LFS.
+*.jar filter=lfs diff=lfs merge=lfs -text
+*.dat filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.bz2 filter=lfs diff=lfs merge=lfs -text
+*.gif filter=lfs diff=lfs merge=lfs -text
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..229f478
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/.classpath
+/.externalToolBuilders
+/.project
+/classes
+/doc
diff --git a/src/com/ibm/icu/dev/test/util/ICUServiceTest.java b/src/com/ibm/icu/dev/test/util/ICUServiceTest.java
new file mode 100644
index 0000000..e738f9b
--- /dev/null
+++ b/src/com/ibm/icu/dev/test/util/ICUServiceTest.java
@@ -0,0 +1,1015 @@
+/**
+ *******************************************************************************
+ * Copyright (C) 2001-2006, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+package com.ibm.icu.dev.test.util;
+
+import com.ibm.icu.dev.test.TestFmwk;
+import com.ibm.icu.impl.ICUNotifier;
+import com.ibm.icu.impl.ICURWLock;
+import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.impl.ICUService;
+import com.ibm.icu.impl.ICUService.Factory;
+import com.ibm.icu.impl.ICUService.Key;
+import com.ibm.icu.impl.ICUService.ServiceListener;
+import com.ibm.icu.impl.ICUService.SimpleFactory;
+import com.ibm.icu.impl.LocaleUtility;
+//import com.ibm.icu.impl.ICULocaleData;
+import com.ibm.icu.impl.ICULocaleService;
+import com.ibm.icu.impl.ICULocaleService.LocaleKey;
+import com.ibm.icu.impl.ICULocaleService.LocaleKeyFactory;
+import com.ibm.icu.impl.ICULocaleService.ICUResourceBundleFactory;
+import com.ibm.icu.text.Collator;
+import com.ibm.icu.util.ULocale;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.SortedMap;
+
+public class ICUServiceTest extends TestFmwk
+{
+ public static void main(String[] args) throws Exception {
+ ICUServiceTest test = new ICUServiceTest();
+ test.run(args);
+ }
+
+ private String lrmsg(String message, Object lhs, Object rhs) {
+ return message + " lhs: " + lhs + " rhs: " + rhs;
+ }
+
+ public void confirmBoolean(String message, boolean val) {
+ msg(message, val ? LOG : ERR, !val, true);
+ }
+
+ public void confirmEqual(String message, Object lhs, Object rhs) {
+ msg(lrmsg(message, lhs, rhs), (lhs == null ? rhs == null : lhs.equals(rhs)) ? LOG : ERR, true, true);
+ }
+
+ public void confirmIdentical(String message, Object lhs, Object rhs) {
+ msg(lrmsg(message, lhs, rhs), lhs == rhs ? LOG : ERR, true, true);
+ }
+
+ public void confirmIdentical(String message, int lhs, int rhs) {
+ msg(message + " lhs: " + lhs + " rhs: " + rhs, lhs == rhs ? LOG : ERR, true, true);
+ }
+
+ /**
+ * Convenience override of getDisplayNames(ULocale, Comparator, String) that
+ * uses the current default ULocale as the locale, the default collator for
+ * the locale as the comparator to sort the display names, and null for
+ * the matchID.
+ */
+ public SortedMap getDisplayNames(ICUService service) {
+ ULocale locale = ULocale.getDefault();
+ Collator col = Collator.getInstance(locale);
+ return service.getDisplayNames(locale, col, null);
+ }
+
+ /**
+ * Convenience override of getDisplayNames(ULocale, Comparator, String) that
+ * uses the default collator for the locale as the comparator to
+ * sort the display names, and null for the matchID.
+ */
+ public SortedMap getDisplayNames(ICUService service, ULocale locale) {
+ Collator col = Collator.getInstance(locale);
+ return service.getDisplayNames(locale, col, null);
+ }
+ /**
+ * Convenience override of getDisplayNames(ULocale, Comparator, String) that
+ * uses the default collator for the locale as the comparator to
+ * sort the display names.
+ */
+ public SortedMap getDisplayNames(ICUService service, ULocale locale, String matchID) {
+ Collator col = Collator.getInstance(locale);
+ return service.getDisplayNames(locale, col, matchID);
+ }
+
+ // use locale keys
+ static final class TestService extends ICUService {
+ public TestService() {
+ super("Test Service");
+ }
+
+ public Key createKey(String id) {
+ return LocaleKey.createWithCanonicalFallback(id, null); // no fallback locale
+ }
+ }
+
+ public void TestAPI() {
+ // create a service using locale keys,
+ ICUService service = new TestService();
+
+ logln("service name:" + service.getName());
+
+ // register an object with one locale,
+ // search for an object with a more specific locale
+ // should return the original object
+ Integer singleton0 = new Integer(0);
+ service.registerObject(singleton0, "en_US");
+ Object result = service.get("en_US_FOO");
+ confirmIdentical("1) en_US_FOO -> en_US", result, singleton0);
+
+ // register a new object with the more specific locale
+ // search for an object with that locale
+ // should return the new object
+ Integer singleton1 = new Integer(1);
+ service.registerObject(singleton1, "en_US_FOO");
+ result = service.get("en_US_FOO");
+ confirmIdentical("2) en_US_FOO -> en_US_FOO", result, singleton1);
+
+ // search for an object that falls back to the first registered locale
+ result = service.get("en_US_BAR");
+ confirmIdentical("3) en_US_BAR -> en_US", result, singleton0);
+
+ // get a list of the factories, should be two
+ List factories = service.factories();
+ confirmIdentical("4) factory size", factories.size(), 2);
+
+ // register a new object with yet another locale
+ // original factory list is unchanged
+ Integer singleton2 = new Integer(2);
+ service.registerObject(singleton2, "en");
+ confirmIdentical("5) factory size", factories.size(), 2);
+
+ // search for an object with the new locale
+ // stack of factories is now en, en_US_FOO, en_US
+ // search for en_US should still find en_US object
+ result = service.get("en_US_BAR");
+ confirmIdentical("6) en_US_BAR -> en_US", result, singleton0);
+
+ // register a new object with an old id, should hide earlier factory using this id, but leave it there
+ Integer singleton3 = new Integer(3);
+ service.registerObject(singleton3, "en_US");
+ factories = service.factories();
+ confirmIdentical("9) factory size", factories.size(), 4);
+
+ // should get data from that new factory
+ result = service.get("en_US_BAR");
+ confirmIdentical("10) en_US_BAR -> (3)", result, singleton3);
+
+ // remove new factory
+ // should have fewer factories again
+ service.unregisterFactory((Factory)factories.get(0));
+ factories = service.factories();
+ confirmIdentical("11) factory size", factories.size(), 3);
+
+ // should get original data again after remove factory
+ result = service.get("en_US_BAR");
+ confirmIdentical("12) en_US_BAR -> 0", result, singleton0);
+
+ // shouldn't find unregistered ids
+ result = service.get("foo");
+ confirmIdentical("13) foo -> null", result, null);
+
+ // should find non-canonical strings
+ String[] resultID = new String[1];
+ result = service.get("EN_us_fOo", resultID);
+ confirmEqual("14) find non-canonical", resultID[0], "en_US_FOO");
+
+ // should be able to register non-canonical strings and get them canonicalized
+ service.registerObject(singleton3, "eN_ca_dUde");
+ result = service.get("En_Ca_DuDe", resultID);
+ confirmEqual("15) register non-canonical", resultID[0], "en_CA_DUDE");
+
+ // should be able to register invisible factories, these will not
+ // be visible by default, but if you know the secret password you
+ // can still access these services...
+ Integer singleton4 = new Integer(4);
+ service.registerObject(singleton4, "en_US_BAR", false);
+ result = service.get("en_US_BAR");
+ confirmIdentical("17) get invisible", result, singleton4);
+
+ // should not be able to locate invisible services
+ Set ids = service.getVisibleIDs();
+ confirmBoolean("18) find invisible", !ids.contains("en_US_BAR"));
+
+ service.reset();
+ // an anonymous factory than handles all ids
+ {
+ Factory factory = new Factory() {
+ public Object create(Key key, ICUService service) {
+ return new ULocale(key.currentID());
+ }
+
+ ///CLOVER:OFF
+ public void updateVisibleIDs(Map result) {
+ }
+ ///CLOVER:ON
+
+ ///CLOVER:OFF
+ public String getDisplayName(String id, ULocale l) {
+ return null;
+ }
+ ///CLOVER:ON
+ };
+ service.registerFactory(factory);
+
+ // anonymous factory will still handle the id
+ result = service.get(ULocale.US.toString());
+ confirmEqual("21) locale", result, ULocale.US);
+
+ // still normalizes id
+ result = service.get("EN_US_BAR");
+ confirmEqual("22) locale", result, new ULocale("en_US_BAR"));
+
+ // we can override for particular ids
+ service.registerObject(singleton3, "en_US_BAR");
+ result = service.get("en_US_BAR");
+ confirmIdentical("23) override super", result, singleton3);
+
+ }
+
+ // empty service should not recognize anything
+ service.reset();
+ result = service.get("en_US");
+ confirmIdentical("24) empty", result, null);
+
+ // create a custom multiple key factory
+ {
+ String[] xids = { "en_US_VALLEY_GIRL",
+ "en_US_VALLEY_BOY",
+ "en_US_SURFER_GAL",
+ "en_US_SURFER_DUDE"
+ };
+ service.registerFactory(new TestLocaleKeyFactory(xids, "Later"));
+ }
+
+ // iterate over the visual ids returned by the multiple factory
+ {
+ Set vids = service.getVisibleIDs();
+ Iterator iter = vids.iterator();
+ int count = 0;
+ while (iter.hasNext()) {
+ ++count;
+ String id = (String)iter.next();
+ logln(" " + id + " --> " + service.get(id));
+ }
+ // four visible ids
+ confirmIdentical("25) visible ids", count, 4);
+ }
+
+ // iterate over the display names
+ {
+ Map dids = getDisplayNames(service, ULocale.GERMANY);
+ Iterator iter = dids.entrySet().iterator();
+ int count = 0;
+ while (iter.hasNext()) {
+ ++count;
+ Entry e = (Entry)iter.next();
+ logln(" " + e.getKey() + " -- > " + e.getValue());
+ }
+ // four display names, in german
+ confirmIdentical("26) display names", count, 4);
+ }
+
+ // no valid display name
+ confirmIdentical("27) get display name", service.getDisplayName("en_US_VALLEY_GEEK"), null);
+
+ {
+ String name = service.getDisplayName("en_US_SURFER_DUDE", ULocale.US);
+ confirmEqual("28) get display name", name, "English (United States, SURFER_DUDE)");
+ }
+
+ // register another multiple factory
+ {
+ String[] xids = {
+ "en_US_SURFER", "en_US_SURFER_GAL", "en_US_SILICON", "en_US_SILICON_GEEK"
+ };
+ service.registerFactory(new TestLocaleKeyFactory(xids, "Rad dude"));
+ }
+
+ // this time, we have seven display names
+ // Rad dude's surfer gal 'replaces' later's surfer gal
+ {
+ Map dids = getDisplayNames(service);
+ Iterator iter = dids.entrySet().iterator();
+ int count = 0;
+ while (iter.hasNext()) {
+ ++count;
+ Entry e = (Entry)iter.next();
+ logln(" " + e.getKey() + " --> " + e.getValue());
+ }
+ // seven display names, in spanish
+ confirmIdentical("29) display names", count, 7);
+ }
+
+ // we should get the display name corresponding to the actual id
+ // returned by the id we used.
+ {
+ String[] actualID = new String[1];
+ String id = "en_us_surfer_gal";
+ String gal = (String)service.get(id, actualID);
+ if (gal != null) {
+ logln("actual id: " + actualID[0]);
+ String displayName = service.getDisplayName(actualID[0], ULocale.US);
+ logln("found actual: " + gal + " with display name: " + displayName);
+ confirmBoolean("30) found display name for actual", displayName != null);
+
+ displayName = service.getDisplayName(id, ULocale.US);
+ logln("found query: " + gal + " with display name: " + displayName);
+ // this is no longer a bug, we want to return display names for anything
+ // that a factory handles. since we handle it, we should return a display
+ // name. see jb3549
+ // confirmBoolean("31) found display name for query", displayName == null);
+ } else {
+ errln("30) service could not find entry for " + id);
+ }
+
+ // this should be handled by the 'dude' factory, since it overrides en_US_SURFER.
+ id = "en_US_SURFER_BOZO";
+ String bozo = (String)service.get(id, actualID);
+ if (bozo != null) {
+ String displayName = service.getDisplayName(actualID[0], ULocale.US);
+ logln("found actual: " + bozo + " with display name: " + displayName);
+ confirmBoolean("32) found display name for actual", displayName != null);
+
+ displayName = service.getDisplayName(id, ULocale.US);
+ logln("found actual: " + bozo + " with display name: " + displayName);
+ // see above and jb3549
+ // confirmBoolean("33) found display name for query", displayName == null);
+ } else {
+ errln("32) service could not find entry for " + id);
+ }
+
+ confirmBoolean("34) is default ", !service.isDefault());
+ }
+
+ /*
+ // disallow hiding for now
+
+ // hiding factory should obscure 'sublocales'
+ {
+ String[] xids = {
+ "en_US_VALLEY", "en_US_SILICON"
+ };
+ service.registerFactory(new TestHidingFactory(xids, "hiding"));
+ }
+
+ {
+ Map dids = service.getDisplayNames();
+ Iterator iter = dids.entrySet().iterator();
+ int count = 0;
+ while (iter.hasNext()) {
+ ++count;
+ Entry e = (Entry)iter.next();
+ logln(" " + e.getKey() + " -- > " + e.getValue());
+ }
+ confirmIdentical("35) hiding factory", count, 5);
+ }
+ */
+
+ {
+ Set xids = service.getVisibleIDs();
+ Iterator iter = xids.iterator();
+ while (iter.hasNext()) {
+ String xid = (String)iter.next();
+ logln(xid + "? " + service.get(xid));
+ }
+
+ logln("valleygirl? " + service.get("en_US_VALLEY_GIRL"));
+ logln("valleyboy? " + service.get("en_US_VALLEY_BOY"));
+ logln("valleydude? " + service.get("en_US_VALLEY_DUDE"));
+ logln("surfergirl? " + service.get("en_US_SURFER_GIRL"));
+ }
+
+ // resource bundle factory.
+ service.reset();
+ service.registerFactory(new ICUResourceBundleFactory());
+
+ // list all of the resources
+ {
+ logln("all visible ids: " + service.getVisibleIDs());
+ /*
+ Set xids = service.getVisibleIDs();
+ StringBuffer buf = new StringBuffer("{");
+ boolean notfirst = false;
+ Iterator iter = xids.iterator();
+ while (iter.hasNext()) {
+ String xid = (String)iter.next();
+ if (notfirst) {
+ buf.append(", ");
+ } else {
+ notfirst = true;
+ }
+ buf.append(xid);
+ }
+ buf.append("}");
+ logln(buf.toString());
+ */
+ }
+
+ // list only the resources for es, default locale
+ // since we're using the default Key, only "es" is matched
+ {
+ logln("visible ids for es locale: " + service.getVisibleIDs("es"));
+ }
+
+ // list only the spanish display names for es, spanish collation order
+ // since we're using the default Key, only "es" is matched
+ {
+ logln("display names: " + getDisplayNames(service, new ULocale("es"), "es"));
+ }
+
+ // list the display names in reverse order
+ {
+ logln("display names in reverse order: " +
+ service.getDisplayNames(ULocale.US, new Comparator() {
+ public int compare(Object lhs, Object rhs) {
+ return -String.CASE_INSENSITIVE_ORDER.compare((String)lhs, (String)rhs);
+ }
+ }));
+ }
+
+ // get all the display names of these resources
+ // this should be fast since the display names were cached.
+ {
+ logln("service display names for de_DE");
+ Map names = getDisplayNames(service, new ULocale("de_DE"));
+ StringBuffer buf = new StringBuffer("{");
+ Iterator iter = names.entrySet().iterator();
+ while (iter.hasNext()) {
+ Entry e = (Entry)iter.next();
+ String name = (String)e.getKey();
+ String id = (String)e.getValue();
+ buf.append("\n " + name + " --> " + id);
+ }
+ buf.append("\n}");
+ logln(buf.toString());
+ }
+
+ CalifornioLanguageFactory califactory = new CalifornioLanguageFactory();
+ service.registerFactory(califactory);
+ // get all the display names of these resources
+ {
+ logln("californio language factory");
+ StringBuffer buf = new StringBuffer("{");
+ String[] idNames = {
+ CalifornioLanguageFactory.californio,
+ CalifornioLanguageFactory.valley,
+ CalifornioLanguageFactory.surfer,
+ CalifornioLanguageFactory.geek
+ };
+ for (int i = 0; i < idNames.length; ++i) {
+ String idName = idNames[i];
+ buf.append("\n --- " + idName + " ---");
+ Map names = getDisplayNames(service, new ULocale(idName));
+ Iterator iter = names.entrySet().iterator();
+ while (iter.hasNext()) {
+ Entry e = (Entry)iter.next();
+ String name = (String)e.getKey();
+ String id = (String)e.getValue();
+ buf.append("\n " + name + " --> " + id);
+ }
+ }
+ buf.append("\n}");
+ logln(buf.toString());
+ }
+
+ // test notification
+ // simple registration
+ {
+ logln("simple registration notification");
+ ICULocaleService ls = new ICULocaleService();
+ ServiceListener l1 = new ServiceListener() {
+ private int n;
+ public void serviceChanged(ICUService s) {
+ logln("listener 1 report " + n++ + " service changed: " + s);
+ }
+ };
+ ls.addListener(l1);
+ ServiceListener l2 = new ServiceListener() {
+ private int n;
+ public void serviceChanged(ICUService s) {
+ logln("listener 2 report " + n++ + " service changed: " + s);
+ }
+ };
+ ls.addListener(l2);
+ logln("registering foo... ");
+ ls.registerObject("Foo", "en_FOO");
+ logln("registering bar... ");
+ ls.registerObject("Bar", "en_BAR");
+ logln("getting foo...");
+ logln((String)ls.get("en_FOO"));
+ logln("removing listener 2...");
+ ls.removeListener(l2);
+ logln("registering baz...");
+ ls.registerObject("Baz", "en_BAZ");
+ logln("removing listener 1");
+ ls.removeListener(l1);
+ logln("registering burp...");
+ ls.registerObject("Burp", "en_BURP");
+
+ // should only get one notification even if register multiple times
+ logln("... trying multiple registration");
+ ls.addListener(l1);
+ ls.addListener(l1);
+ ls.addListener(l1);
+ ls.addListener(l2);
+ ls.registerObject("Foo", "en_FOO");
+ logln("... registered foo");
+
+ // since in a separate thread, we can callback and not deadlock
+ ServiceListener l3 = new ServiceListener() {
+ private int n;
+ public void serviceChanged(ICUService s) {
+ logln("listener 3 report " + n++ + " service changed...");
+ if (s.get("en_BOINK") == null) { // don't recurse on ourselves!!!
+ logln("registering boink...");
+ s.registerObject("boink", "en_BOINK");
+ }
+ }
+ };
+ ls.addListener(l3);
+ logln("registering boo...");
+ ls.registerObject("Boo", "en_BOO");
+ logln("...done");
+
+ try {
+ Thread.sleep(100);
+ }
+ catch (InterruptedException e) {
+ }
+ }
+ }
+
+ static class TestLocaleKeyFactory extends LocaleKeyFactory {
+ protected final Set ids;
+ protected final String factoryID;
+
+ public TestLocaleKeyFactory(String[] ids, String factoryID) {
+ super(VISIBLE, factoryID);
+
+ this.ids = Collections.unmodifiableSet(new HashSet(Arrays.asList(ids)));
+ this.factoryID = factoryID + ": ";
+ }
+
+ protected Object handleCreate(ULocale loc, int kind, ICUService service) {
+ return factoryID + loc.toString();
+ }
+
+ protected Set getSupportedIDs() {
+ return ids;
+ }
+ }
+
+ /*
+ // Disallow hiding for now since it causes gnarly problems, like
+ // how do you localize the hidden (but still exported) names.
+
+ static class TestHidingFactory implements ICUService.Factory {
+ protected final String[] ids;
+ protected final String factoryID;
+
+ public TestHidingFactory(String[] ids) {
+ this(ids, "Hiding");
+ }
+
+ public TestHidingFactory(String[] ids, String factoryID) {
+ this.ids = (String[])ids.clone();
+
+ if (factoryID == null || factoryID.length() == 0) {
+ this.factoryID = "";
+ } else {
+ this.factoryID = factoryID + ": ";
+ }
+ }
+
+ public Object create(Key key, ICUService service) {
+ for (int i = 0; i < ids.length; ++i) {
+ if (LocaleUtility.isFallbackOf(ids[i], key.currentID())) {
+ return factoryID + key.canonicalID();
+ }
+ }
+ return null;
+ }
+
+ public void updateVisibleIDs(Map result) {
+ for (int i = 0; i < ids.length; ++i) {
+ String id = ids[i];
+ Iterator iter = result.keySet().iterator();
+ while (iter.hasNext()) {
+ if (LocaleUtility.isFallbackOf(id, (String)iter.next())) {
+ iter.remove();
+ }
+ }
+ result.put(id, this);
+ }
+ }
+
+ public String getDisplayName(String id, ULocale locale) {
+ return factoryID + new ULocale(id).getDisplayName(locale);
+ }
+ }
+ */
+
+ static class CalifornioLanguageFactory extends ICUResourceBundleFactory {
+ public static String californio = "en_US_CA";
+ public static String valley = californio + "_VALLEY";
+ public static String surfer = californio + "_SURFER";
+ public static String geek = californio + "_GEEK";
+ public static Set supportedIDs;
+ static {
+ HashSet result = new HashSet();
+ result.addAll(ICUResourceBundle.getAvailableLocaleNameSet());
+ result.add(californio);
+ result.add(valley);
+ result.add(surfer);
+ result.add(geek);
+ supportedIDs = Collections.unmodifiableSet(result);
+ }
+
+ public Set getSupportedIDs() {
+ return supportedIDs;
+ }
+
+ public String getDisplayName(String id, ULocale locale) {
+ String prefix = "";
+ String suffix = "";
+ String ls = locale.toString();
+ if (LocaleUtility.isFallbackOf(californio, ls)) {
+ if (ls.equalsIgnoreCase(valley)) {
+ prefix = "Like, you know, it's so totally ";
+ } else if (ls.equalsIgnoreCase(surfer)) {
+ prefix = "Dude, its ";
+ } else if (ls.equalsIgnoreCase(geek)) {
+ prefix = "I'd estimate it's approximately ";
+ } else {
+ prefix = "Huh? Maybe ";
+ }
+ }
+ if (LocaleUtility.isFallbackOf(californio, id)) {
+ if (id.equalsIgnoreCase(valley)) {
+ suffix = "like the Valley, you know? Let's go to the mall!";
+ } else if (id.equalsIgnoreCase(surfer)) {
+ suffix = "time to hit those gnarly waves, Dude!!!";
+ } else if (id.equalsIgnoreCase(geek)) {
+ suffix = "all systems go. T-Minus 9, 8, 7...";
+ } else {
+ suffix = "No Habla Englais";
+ }
+ } else {
+ suffix = super.getDisplayName(id, locale);
+ }
+
+ return prefix + suffix;
+ }
+ }
+
+ public void TestLocale() {
+ ICULocaleService service = new ICULocaleService("test locale");
+ service.registerObject("root", ULocale.ROOT);
+ service.registerObject("german", "de");
+ service.registerObject("german_Germany", ULocale.GERMANY);
+ service.registerObject("japanese", "ja");
+ service.registerObject("japanese_Japan", ULocale.JAPAN);
+
+ Object target = service.get("de_US");
+ confirmEqual("test de_US", "german", target);
+
+ ULocale de = new ULocale("de");
+ ULocale de_US = new ULocale("de_US");
+
+ target = service.get(de_US);
+ confirmEqual("test de_US 2", "german", target);
+
+ target = service.get(de_US, LocaleKey.KIND_ANY);
+ confirmEqual("test de_US 3", "german", target);
+
+ target = service.get(de_US, 1234);
+ confirmEqual("test de_US 4", "german", target);
+
+ ULocale[] actualReturn = new ULocale[1];
+ target = service.get(de_US, actualReturn);
+ confirmEqual("test de_US 5", "german", target);
+ confirmEqual("test de_US 6", actualReturn[0], de);
+
+ actualReturn[0] = null;
+ target = service.get(de_US, LocaleKey.KIND_ANY, actualReturn);
+ confirmEqual("test de_US 7", actualReturn[0], de);
+
+ actualReturn[0] = null;
+ target = service.get(de_US, 1234, actualReturn);
+ confirmEqual("test de_US 8", "german", target);
+ confirmEqual("test de_US 9", actualReturn[0], de);
+
+ service.registerObject("one/de_US", de_US, 1);
+ service.registerObject("two/de_US", de_US, 2);
+
+ target = service.get(de_US, 1);
+ confirmEqual("test de_US kind 1", "one/de_US", target);
+
+ target = service.get(de_US, 2);
+ confirmEqual("test de_US kind 2", "two/de_US", target);
+
+ target = service.get(de_US);
+ confirmEqual("test de_US kind 3", "german", target);
+
+ LocaleKey lkey = LocaleKey.createWithCanonicalFallback("en", null, 1234);
+ logln("lkey prefix: " + lkey.prefix());
+ logln("lkey descriptor: " + lkey.currentDescriptor());
+ logln("lkey current locale: " + lkey.currentLocale());
+
+ lkey.fallback();
+ logln("lkey descriptor 2: " + lkey.currentDescriptor());
+
+ lkey.fallback();
+ logln("lkey descriptor 3: " + lkey.currentDescriptor());
+
+ target = service.get("za_PPP");
+ confirmEqual("test zappp", "root", target);
+
+ ULocale loc = ULocale.getDefault();
+ ULocale.setDefault(ULocale.JAPANESE);
+ target = service.get("za_PPP");
+ confirmEqual("test with ja locale", "japanese", target);
+
+ Set ids = service.getVisibleIDs();
+ for (Iterator iter = ids.iterator(); iter.hasNext();) {
+ logln("id: " + iter.next());
+ }
+
+ ULocale.setDefault(loc);
+ ids = service.getVisibleIDs();
+ for (Iterator iter = ids.iterator(); iter.hasNext();) {
+ logln("id: " + iter.next());
+ }
+
+ target = service.get("za_PPP");
+ confirmEqual("test with en locale", "root", target);
+
+ ULocale[] locales = service.getAvailableULocales();
+ confirmIdentical("test available locales", locales.length, 6);
+ logln("locales: ");
+ for (int i = 0; i < locales.length; ++i) {
+ log("\n [" + i + "] " + locales[i]);
+ }
+ logln(" ");
+
+ service.registerFactory(new ICUResourceBundleFactory());
+ target = service.get(ULocale.JAPAN);
+
+ {
+ int n = 0;
+ List factories = service.factories();
+ Iterator iter = factories.iterator();
+ while (iter.hasNext()) {
+ logln("[" + n++ + "] " + iter.next());
+ }
+ }
+
+ // list only the english display names for es, in reverse order
+ // since we're using locale keys, we should get all and only the es locales
+ // hmmm, the default toString function doesn't print in sorted order for TreeMap
+ {
+ SortedMap map = service.getDisplayNames(ULocale.US,
+ new Comparator() {
+ public int compare(Object lhs, Object rhs) {
+ return -String.CASE_INSENSITIVE_ORDER.compare((String)lhs, (String)rhs);
+ }
+ },
+ "es");
+
+ logln("es display names in reverse order " + map);
+ }
+ }
+
+ public void TestWrapFactory() {
+ final String greeting = "Hello There";
+ final String greetingID = "greeting";
+
+ ICUService service = new ICUService("wrap");
+ service.registerObject(greeting, greetingID);
+
+ logln("test one: " + service.get(greetingID));
+
+ class WrapFactory implements Factory {
+ public Object create(Key key, ICUService service) {
+ if (key.currentID().equals(greetingID)) {
+ Object previous = service.getKey(key, null, this);
+ return "A different greeting: \"" + previous + "\"";
+ }
+ return null;
+ }
+
+ public void updateVisibleIDs(Map result) {
+ result.put("greeting", this);
+ }
+
+ public String getDisplayName(String id, ULocale locale) {
+ return "wrap '" + id + "'";
+ }
+ }
+ service.registerFactory(new WrapFactory());
+
+ confirmEqual("wrap test: ", service.get(greetingID), "A different greeting: \"" + greeting + "\"");
+ }
+
+ // misc coverage tests
+ public void TestCoverage() {
+ // Key
+ Key key = new Key("foobar");
+ logln("ID: " + key.id());
+ logln("canonicalID: " + key.canonicalID());
+ logln("currentID: " + key.currentID());
+ logln("has fallback: " + key.fallback());
+
+ // SimpleFactory
+ Object obj = new Object();
+ SimpleFactory sf = new SimpleFactory(obj, "object");
+ try {
+ sf = new SimpleFactory(null, null);
+ errln("didn't throw exception");
+ }
+ catch (IllegalArgumentException e) {
+ logln("OK: " + e.getMessage());
+ }
+ catch (Exception e) {
+ errln("threw wrong exception" + e);
+ }
+ logln(sf.getDisplayName("object", null));
+
+ // ICUService
+ ICUService service = new ICUService();
+ service.registerFactory(sf);
+
+ try {
+ service.get(null, null);
+ errln("didn't throw exception");
+ }
+ catch (NullPointerException e) {
+ logln("OK: " + e.getMessage());
+ }
+ /*
+ catch (Exception e) {
+ errln("threw wrong exception" + e);
+ }
+ */
+ try {
+ service.registerFactory(null);
+ errln("didn't throw exception");
+ }
+ catch (NullPointerException e) {
+ logln("OK: " + e.getMessage());
+ }
+ catch (Exception e) {
+ errln("threw wrong exception" + e);
+ }
+
+ try {
+ service.unregisterFactory(null);
+ errln("didn't throw exception");
+ }
+ catch (NullPointerException e) {
+ logln("OK: " + e.getMessage());
+ }
+ catch (Exception e) {
+ errln("threw wrong exception" + e);
+ }
+
+ logln("object is: " + service.get("object"));
+
+ logln("stats: " + service.stats());
+
+ // ICURWLock
+
+ ICURWLock rwlock = new ICURWLock();
+ rwlock.acquireRead();
+ rwlock.releaseRead();
+
+ rwlock.acquireWrite();
+ rwlock.releaseWrite();
+ logln("stats: " + rwlock.getStats());
+ logln("stats: " + rwlock.clearStats());
+ rwlock.acquireRead();
+ rwlock.releaseRead();
+ rwlock.acquireWrite();
+ rwlock.releaseWrite();
+ logln("stats: " + rwlock.getStats());
+
+ try {
+ rwlock.releaseRead();
+ errln("no error thrown");
+ }
+ catch (InternalError e) {
+ logln("OK: " + e.getMessage());
+ }
+
+ try {
+ rwlock.releaseWrite();
+ errln("no error thrown");
+ }
+ catch (InternalError e) {
+ logln("OK: " + e.getMessage());
+ }
+
+ // ICULocaleService
+
+ // LocaleKey
+
+ // LocaleKey lkey = LocaleKey.create("en_US", "ja_JP");
+ // lkey = LocaleKey.create(null, null);
+ LocaleKey lkey = LocaleKey.createWithCanonicalFallback("en_US", "ja_JP");
+ logln("lkey: " + lkey);
+
+ lkey = LocaleKey.createWithCanonicalFallback(null, null);
+ logln("lkey from null,null: " + lkey);
+
+ // LocaleKeyFactory
+ LocaleKeyFactory lkf = new LKFSubclass(false);
+ logln("lkf: " + lkf);
+ logln("obj: " + lkf.create(lkey, null));
+ logln(lkf.getDisplayName("foo", null));
+ logln(lkf.getDisplayName("bar", null));
+ lkf.updateVisibleIDs(new HashMap());
+
+ LocaleKeyFactory invisibleLKF = new LKFSubclass(false);
+ logln("obj: " + invisibleLKF.create(lkey, null));
+ logln(invisibleLKF.getDisplayName("foo", null));
+ logln(invisibleLKF.getDisplayName("bar", null));
+ invisibleLKF.updateVisibleIDs(new HashMap());
+
+ // ResourceBundleFactory
+ ICUResourceBundleFactory rbf = new ICUResourceBundleFactory();
+ logln("RB: " + rbf.create(lkey, null));
+
+ // ICUNotifier
+ ICUNotifier nf = new ICUNSubclass();
+ try {
+ nf.addListener(null);
+ errln("added null listener");
+ }
+ catch (NullPointerException e) {
+ logln(e.getMessage());
+ }
+ catch (Exception e) {
+ errln("got wrong exception");
+ }
+
+ try {
+ nf.addListener(new WrongListener());
+ errln("added wrong listener");
+ }
+ catch (InternalError e) {
+ logln(e.getMessage());
+ }
+ catch (Exception e) {
+ errln("got wrong exception");
+ }
+
+ try {
+ nf.removeListener(null);
+ errln("removed null listener");
+ }
+ catch (NullPointerException e) {
+ logln(e.getMessage());
+ }
+ catch (Exception e) {
+ errln("got wrong exception");
+ }
+
+ nf.removeListener(new MyListener());
+ nf.notifyChanged();
+ nf.addListener(new MyListener());
+ nf.removeListener(new MyListener());
+ }
+
+ static class MyListener implements EventListener {
+ }
+
+ static class WrongListener implements EventListener {
+ }
+
+ static class ICUNSubclass extends ICUNotifier {
+ public boolean acceptsListener(EventListener l) {
+ return l instanceof MyListener;
+ }
+
+ // not used, just needed to implement abstract base
+ ///CLOVER:OFF
+ public void notifyListener(EventListener l) {
+ }
+ ///CLOVER:ON
+ }
+
+ static class LKFSubclass extends LocaleKeyFactory {
+ LKFSubclass(boolean visible) {
+ super(visible ? VISIBLE : INVISIBLE);
+ }
+
+ protected Set getSupportedIDs() {
+ return Collections.EMPTY_SET;
+ }
+ }
+}
diff --git a/src/com/ibm/icu/dev/tool/docs/CodeMangler.java b/src/com/ibm/icu/dev/tool/docs/CodeMangler.java
new file mode 100644
index 0000000..1d5531f
--- /dev/null
+++ b/src/com/ibm/icu/dev/tool/docs/CodeMangler.java
@@ -0,0 +1,841 @@
+/**
+*******************************************************************************
+* Copyright (C) 2004-2006, International Business Machines Corporation and *
+* others. All Rights Reserved. *
+*******************************************************************************
+*/
+
+package com.ibm.icu.dev.tool.docs;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.TreeMap;
+// import java.util.regex.*;
+
+/**
+ * A simple facility for adding C-like preprocessing to .java files.
+ * This only understands a subset of the C preprocessing syntax.
+ * Its used to manage files that with only small differences can be
+ * compiled for different JVMs. This changes files in place,
+ * commenting out lines based on the current flag settings.
+ */
+public class CodeMangler {
+ private File indir; // root of input
+ private File outdir; // root of output
+ private String suffix; // suffix to process, default '.jpp'
+ private boolean recurse; // true if recurse on directories
+ private boolean force; // true if force reprocess of files
+ private boolean clean; // true if output is to be cleaned
+ private boolean timestamp; // true if we read/write timestamp
+ private HashMap map; // defines
+ private ArrayList names; // files/directories to process
+ private String header; // sorted list of defines passed in
+
+ private boolean verbose; // true if we emit debug output
+
+ private static final String IGNORE_PREFIX = "//##";
+ private static final String HEADER_PREFIX = "//##header";
+
+// static final Pattern pat = Pattern.compile(
+// "(?i)^(\\s*(?://+)??\\s*)#(ifdef\\s|ifndef\\s|else|endif|undef\\s|define\\s|if\\s|elif\\s)\\s*(.*)$");
+// // static final Pattern pat2 = Pattern.compile("([^=!]+)\\s*([!=]?=)??\\s*(\\w+)");
+// static final Pattern pat2 = Pattern.compile("\\s*(\\w+)\\s*([!=]?=)??\\s*([^\\s]?.*$)");
+// static final Pattern pat3 = Pattern.compile("^(\\s*//##).*");
+
+ public static void main(String[] args) {
+// test();
+ new CodeMangler(args).run();
+ }
+
+// private static final void test() {
+// testPat();
+// testPat2();
+// testPat3();
+// }
+// private static final void testPat() {
+// System.out.println("test pat");
+// String[] tests = {
+// "",
+// " ",
+// "#endif",
+// "# endif",
+// "#ENDIF",
+// "#eNdIf",
+// "//#endif",
+// "// #endif",
+// "// # endif",
+// " // #ifdef foo",
+// " // #ifndef foo",
+// " // #else",
+// " // #endif",
+// " // #undef foo",
+// " // #define foo bar",
+// " // #if foo == bar",
+// " // #elif bar != baz",
+// };
+// for (int i = 0; i < tests.length; ++i) {
+// System.out.print("pat '" + tests[i] + "' --> ");
+// Matcher m = pat.matcher(tests[i]);
+// if (m.find()) {
+// System.out.println("'" + m.group(1) + "' '" + m.group(2) + "' '" + m.group(3) + "'");
+// } else {
+// System.out.println("didn't match");
+// }
+// System.out.print("dug '" + tests[i] + "' --> ");
+// String[] res = new String[3];
+// if (patMatch(tests[i], res)) {
+// System.out.println("'" + res[0] + "' '" + res[1] + "' '" + res[2] + "'");
+// } else {
+// System.out.println("didn't match");
+// }
+// }
+// }
+
+// private static final void testPat2() {
+// System.out.println("test pat2");
+// String[] tests = {
+// "",
+// " ",
+// "test",
+// " test",
+// "test ",
+// " test ",
+// " test ==",
+// " !=",
+// " !=foo",
+// "foo==bar",
+// "foo ==bar",
+// "foo== bar",
+// "foo == bar",
+// "foo bar baz, wompf",
+// "foo=bar=baz, wompf a loo",
+// };
+// for (int i = 0; i < tests.length; ++i) {
+// System.out.print("pat '" + tests[i] + "' --> ");
+// Matcher m2 = pat2.matcher(tests[i]);
+// if (m2.find()) {
+// System.out.println("'" + m2.group(1) + "' '" + m2.group(2) + "' '" + m2.group(3) + "'");
+// } else {
+// System.out.println("didn't match");
+// }
+// System.out.print("dug '" + tests[i] + "' --> ");
+// String[] res = new String[3];
+// if (pat2Match(tests[i], res)) {
+// System.out.println("'" + res[0] + "' '" + res[1] + "' '" + res[2] + "'");
+// } else {
+// System.out.println("didn't match");
+// }
+// }
+// }
+// private static final void testPat3() {
+// System.out.println("test pat3");
+// String[] tests = {
+// "",
+// " ",
+// " //#",
+// " /##",
+// "//##",
+// " //##",
+// " //##//",
+// " /////##",
+// };
+// for (int i = 0; i < tests.length; ++i) {
+// System.out.print("pat '" + tests[i] + "' --> ");
+// Matcher m = pat3.matcher(tests[i]);
+// if (m.find()) {
+// System.out.println("'" + m.group(1) + "'");
+// } else {
+// System.out.println("didn't match");
+// }
+// System.out.print("dug '" + tests[i] + "' --> ");
+// String match = pat3Match(tests[i]);
+// if (match != null) {
+// System.out.println("'" + match + "'");
+// } else {
+// System.out.println("didn't match");
+// }
+// }
+// }
+
+ private static final String usage = "Usage:\n" +
+ " CodeMangler [flags] file... dir... @argfile... \n" +
+ "-in[dir] path - root directory of input files, otherwise use current directory\n" +
+ "-out[dir] path - root directory of output files, otherwise use input directory\n" +
+ "-s[uffix] string - suffix of inputfiles to process, otherwise use '.java' (directories only)\n" +
+ "-c[lean] - remove all control flags from code on output (does not proceed if overwriting)\n" +
+ "-r[ecurse] - if present, recursively process subdirectories\n" +
+ "-f[orce] - force reprocessing of files even if timestamp and headers match\n" +
+ "-t[imestamp] - expect/write timestamp in header\n" +
+ "-dNAME[=VALUE] - define NAME with optional value VALUE\n" +
+ " (or -d NAME[=VALUE])\n" +
+ "-help - print this usage message and exit.\n" +
+ "\n" +
+ "For file arguments, output '.java' files using the same path/name under the output directory.\n" +
+ "For directory arguments, process all files with the defined suffix in the directory.\n" +
+ " (if recursing, do the same for all files recursively under each directory)\n" +
+ "For @argfile arguments, read the specified text file (strip the '@'), and process each line of that file as \n" +
+ "an argument.\n" +
+ "\n" +
+ "Directives are one of the following:\n" +
+ " #ifdef, #ifndef, #else, #endif, #if, #elif, #define, #undef\n" +
+ "These may optionally be preceeded by whitespace or //.\n" +
+ "#if, #elif args are of the form 'key == value' or 'key != value'.\n" +
+ "Only exact character match key with value is performed.\n" +
+ "#define args are 'key [==] value', the '==' is optional.\n";
+
+ CodeMangler(String[] args) {
+ map = new HashMap();
+ names = new ArrayList();
+ suffix = ".java";
+ clean = false;
+ timestamp = false;
+
+ String inname = null;
+ String outname = null;
+ boolean processArgs = true;
+ String arg = null;
+ try {
+ for (int i = 0; i < args.length; ++i) {
+ arg = args[i];
+ if ("--".equals(arg)) {
+ processArgs = false;
+ } else if (processArgs && arg.charAt(0) == '-') {
+ if (arg.startsWith("-in")) {
+ inname = args[++i];
+ } else if (arg.startsWith("-out")) {
+ outname = args[++i];
+ } else if (arg.startsWith("-d")) {
+ String id = arg.substring(2);
+ if (id.length() == 0) {
+ id = args[++i];
+ }
+ String val = "";
+ int ix = id.indexOf('=');
+ if (ix >= 0) {
+ val = id.substring(ix+1);
+ id = id.substring(0,ix);
+ }
+ map.put(id, val);
+ } else if (arg.startsWith("-s")) {
+ suffix = args[++i];
+ } else if (arg.startsWith("-r")) {
+ recurse = true;
+ } else if (arg.startsWith("-f")) {
+ force = true;
+ } else if (arg.startsWith("-c")) {
+ clean = true;
+ } else if (arg.startsWith("-t")) {
+ timestamp = true;
+ } else if (arg.startsWith("-h")) {
+ System.out.print(usage);
+ break; // stop before processing arguments, so we will do nothing
+ } else if (arg.startsWith("-v")) {
+ verbose = true;
+ } else {
+ System.err.println("Error: unrecognized argument '" + arg + "'");
+ System.err.println(usage);
+ throw new IllegalArgumentException(arg);
+ }
+ } else {
+ if (arg.charAt(0) == '@') {
+ File argfile = new File(arg.substring(1));
+ if (argfile.exists() && !argfile.isDirectory()) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(argfile)));
+ ArrayList list = new ArrayList();
+ for (int x = 0; x < args.length; ++x) {
+ list.add(args[x]);
+ }
+ String line;
+ while (null != (line = br.readLine())) {
+ line = line.trim();
+ if (line.length() > 0 && line.charAt(0) != '#') {
+ if (verbose) System.out.println("adding argument: " + line);
+ list.add(line);
+ }
+ }
+ args = (String[])list.toArray(new String[list.size()]);
+ }
+ catch (IOException e) {
+ System.err.println("error reading arg file: " + e);
+ }
+ }
+ } else {
+ names.add(arg);
+ }
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ String msg = "Error: argument '" + arg + "' missing value";
+ System.err.println(msg);
+ System.err.println(usage);
+ throw new IllegalArgumentException(msg);
+ }
+
+ String username = System.getProperty("user.dir");
+ if (inname == null) {
+ inname = username;
+ } else if (!(inname.startsWith("\\") || inname.startsWith("/"))) {
+ inname = username + File.separator + inname;
+ }
+ indir = new File(inname);
+ try {
+ indir = indir.getCanonicalFile();
+ }
+ catch (IOException e) {
+ // continue, but most likely we'll fail later
+ }
+ if (!indir.exists()) {
+ throw new IllegalArgumentException("Input directory '" + indir.getAbsolutePath() + "' does not exist.");
+ } else if (!indir.isDirectory()) {
+ throw new IllegalArgumentException("Input path '" + indir.getAbsolutePath() + "' is not a directory.");
+ }
+ if (verbose) System.out.println("indir: " + indir.getAbsolutePath());
+
+ if (outname == null) {
+ outname = inname;
+ } else if (!(outname.startsWith("\\") || outname.startsWith("/"))) {
+ outname = username + File.separator + outname;
+ }
+ outdir = new File(outname);
+ try {
+ outdir = outdir.getCanonicalFile();
+ }
+ catch (IOException e) {
+ // continue, but most likely we'll fail later
+ }
+ if (!outdir.exists()) {
+ throw new IllegalArgumentException("Output directory '" + outdir.getAbsolutePath() + "' does not exist.");
+ } else if (!outdir.isDirectory()) {
+ throw new IllegalArgumentException("Output path '" + outdir.getAbsolutePath() + "' is not a directory.");
+ }
+ if (verbose) System.out.println("outdir: " + outdir.getAbsolutePath());
+
+ if (clean && suffix.equals(".java")) {
+ try {
+ if (outdir.getCanonicalPath().equals(indir.getCanonicalPath())) {
+ throw new IllegalArgumentException("Cannot use 'clean' to overwrite .java files in same directory tree");
+ }
+ }
+ catch (IOException e) {
+ System.err.println("possible overwrite, error: " + e.getMessage());
+ throw new IllegalArgumentException("Cannot use 'clean' to overrwrite .java files");
+ }
+ }
+
+ if (names.isEmpty()) {
+ names.add(".");
+ }
+
+ TreeMap sort = new TreeMap(String.CASE_INSENSITIVE_ORDER);
+ sort.putAll(map);
+ Iterator iter = sort.entrySet().iterator();
+ StringBuffer buf = new StringBuffer();
+ while (iter.hasNext()) {
+ Map.Entry e = (Map.Entry)iter.next();
+ if (buf.length() > 0) {
+ buf.append(", ");
+ }
+ buf.append(e.getKey());
+ String v = (String)e.getValue();
+ if (v != null && v.length() > 0) {
+ buf.append('=');
+ buf.append(v);
+ }
+ }
+ header = buf.toString();
+ }
+
+ public int run() {
+ return process("", (String[])names.toArray(new String[names.size()]));
+ }
+
+ public int process(String path, String[] filenames) {
+ if (verbose) System.out.println("path: '" + path + "'");
+ int count = 0;
+ for (int i = 0; i < filenames.length; ++i) {
+ if (verbose) System.out.println("name " + i + " of " + filenames.length + ": '" + filenames[i] + "'");
+ String name = path + filenames[i];
+ File fin = new File(indir, name);
+ try {
+ fin = fin.getCanonicalFile();
+ }
+ catch (IOException e) {
+ }
+ if (!fin.exists()) {
+ System.err.println("File " + fin.getAbsolutePath() + " does not exist.");
+ continue;
+ }
+ if (fin.isFile()) {
+ if (verbose) System.out.println("processing file: '" + fin.getAbsolutePath() + "'");
+ String oname;
+ int ix = name.lastIndexOf(".");
+ if (ix != -1) {
+ oname = name.substring(0, ix);
+ } else {
+ oname = name;
+ }
+ oname += ".java";
+ File fout = new File(outdir, oname);
+ if (processFile(fin, fout)) {
+ ++count;
+ }
+ } else if (fin.isDirectory()) {
+ if (verbose) System.out.println("recursing on directory '" + fin.getAbsolutePath() + "'");
+ String npath = ".".equals(name) ? path : path + fin.getName() + File.separator;
+ count += process(npath, fin.list(filter)); // recursive call
+ }
+ }
+ return count;
+ }
+
+
+ private final FilenameFilter filter = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ File f = new File(dir, name);
+ return (f.isFile() && name.endsWith(suffix)) || (f.isDirectory() && recurse);
+ }
+ };
+
+ public boolean processFile(File infile, File outfile) {
+ File backup = null;
+
+ class State {
+ int lc;
+ String line;
+ boolean emit = true;
+ boolean tripped;
+ private State next;
+
+ public String toString() {
+ return "line " + lc
+ + ": '" + line
+ + "' (emit: " + emit
+ + " tripped: " + tripped
+ + ")";
+ }
+
+ void trip(boolean trip) {
+ if (!tripped & trip) {
+ tripped = true;
+ emit = next != null ? next.emit : true;
+ } else {
+ emit = false;
+ }
+ }
+
+ State push(int lc, String line, boolean trip) {
+ this.lc = lc;
+ this.line = line;
+ State ret = new State();
+ ret.next = this;
+ ret.emit = this.emit & trip;
+ ret.tripped = trip;
+ return ret;
+ }
+
+ State pop() {
+ return next;
+ }
+ };
+
+ HashMap oldMap = null;
+
+ long outModTime = 0;
+
+ try {
+ PrintStream outstream = null;
+ InputStream instream = new FileInputStream(infile);
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(instream));
+ int lc = 0;
+ State state = new State();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (lc == 0) { // check and write header for output file if needed
+ boolean hasHeader = line.startsWith(HEADER_PREFIX);
+ if (hasHeader && !force) {
+ long expectLastModified = ((infile.lastModified() + 999)/1000)*1000;
+ String headerline = HEADER_PREFIX + ' ' +
+ (timestamp ? String.valueOf(expectLastModified) : "")
+ + ' ' + header;
+ headerline = headerline.trim();
+ if (line.equals(headerline)) {
+ if (verbose) System.out.println("no changes necessary to " + infile.getCanonicalPath());
+ instream.close();
+ return false; // nothing to do
+ }
+ if (verbose) {
+ System.out.println(" old header: " + line);
+ System.out.println(" != expected: " + headerline);
+ }
+ }
+
+ // create output file directory structure
+ String outpname = outfile.getParent();
+ if (outpname != null) {
+ File outp = new File(outpname);
+ if (!(outp.exists() || outp.mkdirs())) {
+ System.err.println("could not create directory: '" + outpname + "'");
+ return false;
+ }
+ }
+
+ // if we're overwriting, use a temporary file
+ if (suffix.equals(".java")) {
+ backup = outfile;
+ try {
+ outfile = File.createTempFile(outfile.getName(), null, outfile.getParentFile());
+ }
+ catch (IOException ex) {
+ System.err.println(ex.getMessage());
+ return false;
+ }
+ }
+
+ outModTime = ((outfile.lastModified()+999)/1000)*1000; // round up
+ outstream = new PrintStream(new FileOutputStream(outfile));
+ String headerline = HEADER_PREFIX + ' ' +
+ (timestamp ? String.valueOf(outModTime) : "")
+ + ' ' + header;
+ headerline = headerline.trim();
+ outstream.println(headerline);
+ if (verbose) System.out.println("header: " + headerline);
+
+ // discard the old header if we had one, otherwise match this line like any other
+ if (hasHeader) {
+ ++lc; // mark as having read a line so we never reexecute this block
+ continue;
+ }
+ }
+
+ String[] res = new String[3];
+ if (patMatch(line, res)) {
+ String lead = res[0];
+ String key = res[1];
+ String val = res[2];
+
+// Matcher m = pat.matcher(line);
+// if (m.find()) {
+// String lead = m.group(1);
+// String key = m.group(2).toLowerCase().trim();
+// String val = m.group(3).trim();
+
+ if (verbose) System.out.println("directive: " + line
+ + " key: '" + key
+ + "' val: '" + val
+ + "' " + state);
+ if (key.equals("ifdef")) {
+ state = state.push(lc, line, map.get(val) != null);
+ } else if (key.equals("ifndef")) {
+ state = state.push(lc, line, map.get(val) == null);
+ } else if (key.equals("else")) {
+ state.trip(true);
+ } else if (key.equals("endif")) {
+ state = state.pop();
+ } else if (key.equals("undef")) {
+ if (state.emit) {
+ if (oldMap == null) {
+ oldMap = (HashMap)map.clone();
+ }
+ map.remove(val);
+ }
+ } else { // #define, #if, #elif
+ if (pat2Match(val, res)) {
+ String key2 = res[0];
+ boolean neq = "!=".equals(res[1]); // optional
+ String val2 = res[2];
+
+// Matcher m2 = pat2.matcher(val);
+// if (m2.find()) {
+// String key2 = m2.group(1).trim();
+// boolean neq = "!=".equals(m2.group(2)); // optional
+// String val2 = m2.group(3).trim();
+ if (verbose) System.out.println("val2: '" + val2
+ + "' neq: '" + neq
+ + "' key2: '" + key2
+ + "'");
+ if (key.equals("if")) {
+ state = state.push(lc, line, val2.equals(map.get(key2)) != neq);
+ } else if (key.equals("elif")) {
+ state.trip(val2.equals(map.get(key2)) != neq);
+ } else if (key.equals("define")) {
+ if (state.emit) {
+ if (oldMap == null) {
+ oldMap = (HashMap)map.clone();
+ }
+ map.put(key2, val2);
+ }
+ }
+ }
+ }
+ if (!clean) {
+ lc++;
+ if (!lead.equals("//")) {
+ outstream.print("//");
+ line = line.substring(lead.length());
+ }
+ outstream.println(line);
+ }
+ continue;
+ }
+
+ lc++;
+ String found = pat3Match(line);
+ boolean hasIgnore = found != null;
+ if (state.emit == hasIgnore) {
+ if (state.emit) {
+ line = line.substring(found.length());
+ } else {
+ line = IGNORE_PREFIX + line;
+ }
+ } else if (hasIgnore && !found.equals(IGNORE_PREFIX)) {
+ line = IGNORE_PREFIX + line.substring(found.length());
+ }
+// m = pat3.matcher(line);
+// boolean hasIgnore = m.find();
+// if (state.emit == hasIgnore) {
+// if (state.emit) {
+// line = line.substring(m.group(1).length());
+// } else {
+// line = IGNORE_PREFIX + line;
+// }
+// } else if (hasIgnore && !m.group(1).equals(IGNORE_PREFIX)) {
+// line = IGNORE_PREFIX + line.substring(m.group(1).length());
+// }
+ if (!clean || state.emit) {
+ outstream.println(line);
+ }
+ }
+
+ state = state.pop();
+ if (state != null) {
+ System.err.println("Error: unclosed directive(s):");
+ do {
+ System.err.println(state);
+ } while ((state = state.pop()) != null);
+ System.err.println(" in file: " + outfile.getCanonicalPath());
+ if (oldMap != null) {
+ map = oldMap;
+ }
+ outstream.close();
+ return false;
+ }
+
+ outstream.close();
+ instream.close();
+
+ if (backup != null) {
+ if (backup.exists()) {
+ backup.delete();
+ }
+ outfile.renameTo(backup);
+ }
+
+ if (timestamp) {
+ outfile.setLastModified(outModTime); // synch with timestamp
+ }
+
+ if (oldMap != null) {
+ map = oldMap;
+ }
+ }
+ catch (IOException e) {
+ System.err.println(e);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Perform same operation as matching on pat. on exit
+ * leadKeyValue contains the three strings lead, key, and value.
+ * 'lead' is the portion before the #ifdef directive. 'key' is
+ * the directive. 'value' is the portion after the directive. if
+ * there is a match, return true, else return false.
+ */
+ static boolean patMatch(String line, String[] leadKeyValue) {
+// final Pattern pat = Pattern.compile(
+// "(?i)^(\\s*(?://+)??\\s*)#(ifdef\\s|ifndef\\s|else|endif|undef\\s|define\\s|if\\s|elif\\s)\\s*(.*)$");
+
+ if (line.length() == 0) {
+ return false;
+ }
+ if (!line.endsWith("\n")) {
+ line = line + '\n';
+ }
+ int mark = 0;
+ int state = 0;
+ loop: for (int i = 0; i < line.length(); ++i) {
+ char c = line.charAt(i);
+ switch (state) {
+ case 0: // at start of line, haven't seen anything but whitespace yet
+ if (c == ' ' || c == '\t' || c == '\r') continue;
+ if (c == '/') { state = 1; continue; }
+ if (c == '#') { state = 4; continue; }
+ return false;
+ case 1: // have seen a single slash after start of line
+ if (c == '/') { state = 2; continue; }
+ return false;
+ case 2: // have seen two or more slashes
+ if (c == '/') continue;
+ if (c == ' ' || c == '\t' || c == '\r') { state = 3; continue; }
+ if (c == '#') { state = 4; continue; }
+ return false;
+ case 3: // have seen a space after two or more slashes
+ if (c == ' ' || c == '\t' || c == '\r') continue;
+ if (c == '#') { state = 4; continue; }
+ return false;
+ case 4: // have seen a '#'
+ leadKeyValue[0] = line.substring(mark, i-1);
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { mark = i; state = 5; continue; }
+ return false;
+ case 5: // an ascii char followed the '#'
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) continue;
+ if (c == ' ' || c == '\t' || c == '\n') {
+ String key = line.substring(mark, i).toLowerCase();
+ if (key.equals("ifdef") ||
+ key.equals("ifndef") ||
+ key.equals("else") ||
+ key.equals("endif") ||
+ key.equals("undef") ||
+ key.equals("define") ||
+ key.equals("if") ||
+ key.equals("elif")) {
+ leadKeyValue[1] = key;
+ mark = i;
+ state = 6;
+ break loop;
+ }
+ }
+ return false;
+ default:
+ throw new InternalError();
+ }
+ }
+ if (state == 6) {
+ leadKeyValue[2] = line.substring(mark, line.length()).trim();
+ return true;
+ }
+ return false; // never reached, does the compiler know this?
+ }
+
+ /**
+ * Perform same operation as matching on pat2. on exit
+ * keyRelValue contains the three strings key, rel, and value.
+ * 'key' is the portion before the relation (or final word). 'rel' is
+ * the relation, if present, either == or !=. 'value' is the final
+ * word. if there is a match, return true, else return false.
+ */
+ static boolean pat2Match(String line, String[] keyRelVal) {
+// final Pattern pat2 = Pattern.compile("([^=!]+)\\s*([!=]?=)??\\s*(\\w+)");
+// hmmm, this pattern doesn't look right. a pattern consisting of 'abcd' should
+// return {"abcd", "", ""} but it looks like it returns {"", "", "abcd"}.
+
+ if (line.length() == 0) {
+ return false;
+ }
+ keyRelVal[0] = keyRelVal[1] = keyRelVal[2] = "";
+ int mark = 0;
+ int state = 0;
+ loop: for (int i = 0; i < line.length(); ++i) {
+ char c = line.charAt(i);
+ switch (state) {
+ case 0: // saw beginning or space, no rel yet
+ if (c == ' ' || c == '\t' || c == '\n') {
+ continue;
+ }
+ if ((c == '!' || c == '=')) {
+ return false;
+ }
+ state = 1;
+ continue;
+ case 1: // saw start of a word
+ if (c == ' ' || c == '\t') {
+ state = 2;
+ }
+ else if (c == '!' || c == '=') {
+ state = 3;
+ }
+ continue;
+ case 2: // saw end of word, and space
+ if (c == ' ' || c == '\t') {
+ continue;
+ }
+ else if (c == '!' || c == '=') {
+ state = 3;
+ continue;
+ }
+ keyRelVal[0] = line.substring(0, i-1).trim();
+ mark = i;
+ state = 4;
+ break loop;
+ case 3: // saw end of word, and '!' or '='
+ if (c == '=') {
+ keyRelVal[0] = line.substring(0, i-1).trim();
+ keyRelVal[1] = line.substring(i-1, i+1);
+ mark = i+1;
+ state = 4;
+ break loop;
+ }
+ return false;
+ default:
+ break;
+ }
+ }
+ switch (state) {
+ case 0:
+ return false; // found nothing
+ case 1:
+ case 2:
+ keyRelVal[0] = line.trim(); break; // found only a word
+ case 3:
+ return false; // found a word and '!' or '=" then end of line, incomplete
+ case 4:
+ keyRelVal[2] = line.substring(mark).trim(); break; // found a word, possible rel, and who knows what
+ default:
+ throw new InternalError();
+ }
+ return true;
+ }
+
+ static String pat3Match(String line) {
+ int state = 0;
+ loop: for (int i = 0; i < line.length(); ++i) {
+ char c = line.charAt(i);
+ switch(state) {
+ case 0: if (c == ' ' || c == '\t') continue;
+ if (c == '/') { state = 1; continue; }
+ break loop;
+ case 1:
+ if (c == '/') { state = 2; continue; }
+ break loop;
+ case 2:
+ if (c == '#') { state = 3; continue; }
+ break loop;
+ case 3:
+ if (c == '#') return line.substring(0, i+1);
+ break loop;
+ default:
+ break loop;
+ }
+ }
+ return null;
+ }
+
+
+// final Pattern pat3 = Pattern.compile("^(\\s*//##).*");
+
+}
diff --git a/src/com/ibm/icu/impl/ICULocaleService.java b/src/com/ibm/icu/impl/ICULocaleService.java
new file mode 100644
index 0000000..44195be
--- /dev/null
+++ b/src/com/ibm/icu/impl/ICULocaleService.java
@@ -0,0 +1,591 @@
+/**
+ *******************************************************************************
+ * Copyright (C) 2001-2006, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+package com.ibm.icu.impl;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.ibm.icu.util.ULocale;
+
+public class ICULocaleService extends ICUService {
+ private ULocale fallbackLocale;
+ private String fallbackLocaleName;
+
+ /**
+ * Construct an ICULocaleService.
+ */
+ public ICULocaleService() {
+ }
+
+ /**
+ * Construct an ICULocaleService with a name (useful for debugging).
+ */
+ public ICULocaleService(String name) {
+ super(name);
+ }
+
+ /**
+ * Convenience override for callers using locales. This calls
+ * get(ULocale, int, ULocale[]) with KIND_ANY for kind and null for
+ * actualReturn.
+ */
+ public Object get(ULocale locale) {
+ return get(locale, LocaleKey.KIND_ANY, null);
+ }
+
+ /**
+ * Convenience override for callers using locales. This calls
+ * get(ULocale, int, ULocale[]) with a null actualReturn.
+ */
+ public Object get(ULocale locale, int kind) {
+ return get(locale, kind, null);
+ }
+
+ /**
+ * Convenience override for callers using locales. This calls
+ * get(ULocale, int, ULocale[]) with KIND_ANY for kind.
+ */
+ public Object get(ULocale locale, ULocale[] actualReturn) {
+ return get(locale, LocaleKey.KIND_ANY, actualReturn);
+ }
+
+ /**
+ * Convenience override for callers using locales. This uses
+ * createKey(ULocale.toString(), kind) to create a key, calls getKey, and then
+ * if actualReturn is not null, returns the actualResult from
+ * getKey (stripping any prefix) into a ULocale.
+ */
+ public Object get(ULocale locale, int kind, ULocale[] actualReturn) {
+ Key key = createKey(locale.toString(), kind);
+ if (actualReturn == null) {
+ return getKey(key);
+ }
+
+ String[] temp = new String[1];
+ Object result = getKey(key, temp);
+ if (result != null) {
+ int n = temp[0].indexOf("/");
+ if (n >= 0) {
+ temp[0] = temp[0].substring(n+1);
+ }
+ actualReturn[0] = new ULocale(temp[0]);
+ }
+ return result;
+ }
+
+ /**
+ * Convenience override for callers using locales. This calls
+ * registerObject(Object, ULocale, int kind, boolean visible)
+ * passing KIND_ANY for the kind, and true for the visibility.
+ */
+ public Factory registerObject(Object obj, ULocale locale) {
+ return registerObject(obj, locale, LocaleKey.KIND_ANY, true);
+ }
+
+ /**
+ * Convenience override for callers using locales. This calls
+ * registerObject(Object, ULocale, int kind, boolean visible)
+ * passing KIND_ANY for the kind.
+ */
+ public Factory registerObject(Object obj, ULocale locale, boolean visible) {
+ return registerObject(obj, locale, LocaleKey.KIND_ANY, visible);
+ }
+
+ /**
+ * Convenience function for callers using locales. This calls
+ * registerObject(Object, ULocale, int kind, boolean visible)
+ * passing true for the visibility.
+ */
+ public Factory registerObject(Object obj, ULocale locale, int kind) {
+ return registerObject(obj, locale, kind, true);
+ }
+
+ /**
+ * Convenience function for callers using locales. This instantiates
+ * a SimpleLocaleKeyFactory, and registers the factory.
+ */
+ public Factory registerObject(Object obj, ULocale locale, int kind, boolean visible) {
+ Factory factory = new SimpleLocaleKeyFactory(obj, locale, kind, visible);
+ return registerFactory(factory);
+ }
+
+ /**
+ * Convenience method for callers using locales. This returns the standard
+ * Locale list, built from the Set of visible ids.
+ */
+ public Locale[] getAvailableLocales() {
+ // TODO make this wrap getAvailableULocales later
+ 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());
+ locales[n++] = loc;
+ }
+ return locales;
+ }
+
+ /**
+ * Convenience method for callers using locales. This returns the standard
+ * ULocale list, built from the Set of visible ids.
+ */
+ public ULocale[] getAvailableULocales() {
+ Set visIDs = getVisibleIDs();
+ Iterator iter = visIDs.iterator();
+ ULocale[] locales = new ULocale[visIDs.size()];
+ int n = 0;
+ while (iter.hasNext()) {
+ locales[n++] = new ULocale((String)iter.next());
+ }
+ return locales;
+ }
+
+ /**
+ * 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. If a prefix is present, the currentDescriptor
+ * includes it before the locale proper, separated by "/". This
+ * is the default key instantiated by ICULocaleService.</p>
+ *
+ * <p>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.</p>
+ */
+ public static class LocaleKey extends ICUService.Key {
+ private int kind;
+ private int varstart;
+ private String primaryID;
+ private String fallbackID;
+ private String currentID;
+
+ public static final int KIND_ANY = -1;
+
+ /**
+ * Create a LocaleKey with canonical primary and fallback IDs.
+ */
+ public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID) {
+ return createWithCanonicalFallback(primaryID, canonicalFallbackID, KIND_ANY);
+ }
+
+ /**
+ * Create a LocaleKey with canonical primary and fallback IDs.
+ */
+ public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID, int kind) {
+ if (primaryID == null) {
+ return null;
+ }
+ if (primaryID.length() == 0) {
+ primaryID = "root";
+ }
+ String canonicalPrimaryID = ULocale.getName(primaryID);
+ return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID, kind);
+ }
+
+ /**
+ * PrimaryID is the user's requested locale string,
+ * canonicalPrimaryID is this string in canonical form,
+ * fallbackID is the current default locale's string in
+ * canonical form.
+ */
+ protected LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID, int kind) {
+ super(primaryID);
+
+ this.kind = kind;
+ if (canonicalPrimaryID == null) {
+ this.primaryID = "";
+ } else {
+ this.primaryID = canonicalPrimaryID;
+ this.varstart = this.primaryID.indexOf('@');
+ }
+ if (this.primaryID == "") {
+ this.fallbackID = null;
+ } else {
+ if (canonicalFallbackID == null || this.primaryID.equals(canonicalFallbackID)) {
+ this.fallbackID = "";
+ } else {
+ this.fallbackID = canonicalFallbackID;
+ }
+ }
+
+ this.currentID = varstart == -1 ? this.primaryID : this.primaryID.substring(0, varstart);
+ }
+
+ /**
+ * Return the prefix associated with the kind, or null if the kind is KIND_ANY.
+ */
+ public String prefix() {
+ return kind == KIND_ANY ? null : Integer.toString(kind());
+ }
+
+ /**
+ * Return the kind code associated with this key.
+ */
+ public int kind() {
+ return kind;
+ }
+
+ /**
+ * Return the (canonical) original ID.
+ */
+ public String canonicalID() {
+ return primaryID;
+ }
+
+ /**
+ * 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.
+ * Includes the keywords, whereas the ID does not include keywords.
+ */
+ public String currentDescriptor() {
+ String result = currentID();
+ if (result != null) {
+ result = "/" + result;
+ if (varstart != -1) {
+ result += primaryID.substring(varstart);
+ }
+ if (kind != KIND_ANY) {
+ result = prefix() + result;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Convenience method to return the locale corresponding to the (canonical) original ID.
+ */
+ public ULocale canonicalLocale() {
+ return new ULocale(primaryID);
+ }
+
+ /**
+ * Convenience method to return the ulocale corresponding to the (canonical) currentID.
+ */
+ public ULocale currentLocale() {
+ if (varstart == -1) {
+ return new ULocale(currentID);
+ } else {
+ return new ULocale(currentID + primaryID.substring(varstart));
+ }
+ }
+
+ /**
+ * If the key has a fallback, modify the key and return true,
+ * otherwise return false.</p>
+ *
+ * <p>First falls back through the primary ID, then through
+ * the fallbackID. The final fallback is the empty string,
+ * unless the primary id was the empty string, in which case
+ * there is no fallback.
+ */
+ public boolean fallback() {
+ int x = currentID.lastIndexOf('_');
+ if (x != -1) {
+ while (--x >= 0 && currentID.charAt(x) == '_') { // handle zh__PINYIN
+ }
+ currentID = currentID.substring(0, x+1);
+ return true;
+ }
+ if (fallbackID != null) {
+ currentID = fallbackID;
+ fallbackID = fallbackID.length() == 0 ? null : "";
+ return true;
+ }
+ currentID = null;
+ return false;
+ }
+
+ /**
+ * If a key created from id would eventually fallback to match the
+ * canonical ID of this key, return true.
+ */
+ public boolean isFallbackOf(String id) {
+ return LocaleUtility.isFallbackOf(canonicalID(), id);
+ }
+ }
+
+ /**
+ * A subclass of Factory that uses LocaleKeys. If 'visible' the
+ * factory reports its IDs.
+ */
+ public static abstract class LocaleKeyFactory implements Factory {
+ protected final String name;
+ protected final boolean visible;
+
+ public static final boolean VISIBLE = true;
+ public static final boolean INVISIBLE = false;
+
+ /**
+ * Constructor used by subclasses.
+ */
+ protected LocaleKeyFactory(boolean visible) {
+ this.visible = visible;
+ this.name = null;
+ }
+
+ /**
+ * Constructor used by subclasses.
+ */
+ protected LocaleKeyFactory(boolean visible, String name) {
+ this.visible = visible;
+ this.name = name;
+ }
+
+ /**
+ * Implement superclass abstract method. This checks the currentID of
+ * the key against the supported IDs, and passes the canonicalLocale and
+ * kind off to handleCreate (which subclasses must implement).
+ */
+ public Object create(Key key, ICUService service) {
+ if (handlesKey(key)) {
+ LocaleKey lkey = (LocaleKey)key;
+ int kind = lkey.kind();
+
+ ULocale uloc = lkey.currentLocale();
+ return handleCreate(uloc, kind, service);
+ } else {
+ // System.out.println("factory: " + this + " did not support id: " + key.currentID());
+ // System.out.println("supported ids: " + getSupportedIDs());
+ }
+ return null;
+ }
+
+ protected boolean handlesKey(Key key) {
+ if (key != null) {
+ String id = key.currentID();
+ Set supported = getSupportedIDs();
+ return supported.contains(id);
+ }
+ return false;
+ }
+
+ /**
+ * Override of superclass method.
+ */
+ public void updateVisibleIDs(Map result) {
+ Set cache = getSupportedIDs();
+ Iterator iter = cache.iterator();
+ while (iter.hasNext()) {
+ String id = (String)iter.next();
+ if (visible) {
+ result.put(id, this);
+ } else {
+ result.remove(id);
+ }
+ }
+ }
+
+ /**
+ * Return a localized name for the locale represented by id.
+ */
+ public String getDisplayName(String id, ULocale locale) {
+ // assume if the user called this on us, we must have handled some fallback of this id
+ // if (isSupportedID(id)) {
+ if (locale == null) {
+ return id;
+ }
+ ULocale loc = new ULocale(id);
+ return loc.getDisplayName(locale);
+ // }
+ // return null;
+ }
+
+ ///CLOVER:OFF
+ /**
+ * Utility method used by create(Key, ICUService). Subclasses can
+ * implement this instead of create.
+ */
+ protected Object handleCreate(ULocale loc, int kind, ICUService service) {
+ return null;
+ }
+ ///CLOVER:ON
+
+ /**
+ * Return true if this id is one the factory supports (visible or
+ * otherwise).
+ */
+ protected boolean isSupportedID(String id) {
+ return getSupportedIDs().contains(id);
+ }
+
+ /**
+ * Return the set of ids that this factory supports (visible or
+ * otherwise). This can be called often and might need to be
+ * cached if it is expensive to create.
+ */
+ protected Set getSupportedIDs() {
+ return Collections.EMPTY_SET;
+ }
+
+ /**
+ * For debugging.
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer(super.toString());
+ if (name != null) {
+ buf.append(", name: ");
+ buf.append(name);
+ }
+ buf.append(", visible: ");
+ buf.append(visible);
+ return buf.toString();
+ }
+ }
+
+ /**
+ * A LocaleKeyFactory that just returns a single object for a kind/locale.
+ */
+ public static class SimpleLocaleKeyFactory extends LocaleKeyFactory {
+ private final Object obj;
+ private final String id;
+ private final int kind;
+
+ // TODO: remove when we no longer need this
+ public SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible) {
+ this(obj, locale, kind, visible, null);
+ }
+
+ public SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible, String name) {
+ super(visible, name);
+
+ this.obj = obj;
+ this.id = locale.getBaseName();
+ this.kind = kind;
+ }
+
+ /**
+ * Returns the service object if kind/locale match. Service is not used.
+ */
+ public Object create(Key key, ICUService service) {
+ LocaleKey lkey = (LocaleKey)key;
+ if (kind == LocaleKey.KIND_ANY || kind == lkey.kind()) {
+ String keyID = lkey.currentID();
+ if (id.equals(keyID)) {
+ return obj;
+ }
+ }
+ return null;
+ }
+
+ protected boolean isSupportedID(String id) {
+ return this.id.equals(id);
+ }
+
+ public void updateVisibleIDs(Map result) {
+ if (visible) {
+ result.put(id, this);
+ } else {
+ result.remove(id);
+ }
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer(super.toString());
+ buf.append(", id: ");
+ buf.append(id);
+ buf.append(", kind: ");
+ buf.append(kind);
+ return buf.toString();
+ }
+ }
+
+ /**
+ * A LocaleKeyFactory that creates a service based on the ICU locale data.
+ * This is a base class for most ICU factories. Subclasses instantiate it
+ * with a constructor that takes a bundle name, which determines the supported
+ * IDs. Subclasses then override handleCreate to create the actual service
+ * object. The default implementation returns a resource bundle.
+ */
+ public static class ICUResourceBundleFactory extends LocaleKeyFactory {
+ protected final String bundleName;
+
+ /**
+ * Convenience constructor that uses the main ICU bundle name.
+ */
+ public ICUResourceBundleFactory() {
+ this(ICUResourceBundle.ICU_BASE_NAME);
+ }
+
+ /**
+ * A service factory based on ICU resource data in resources
+ * with the given name.
+ */
+ public ICUResourceBundleFactory(String bundleName) {
+ super(true);
+
+ this.bundleName = bundleName;
+ }
+
+ /**
+ * Return the supported IDs. This is the set of all locale names for the bundleName.
+ */
+ protected Set getSupportedIDs() {
+ // note: "root" is one of the ids, but "" is not. Must convert ULocale.ROOT.
+ return ICUResourceBundle.getFullLocaleNameSet(bundleName);
+ }
+
+ /**
+ * Override of superclass method.
+ */
+ public void updateVisibleIDs(Map result) {
+ Set visibleIDs = ICUResourceBundle.getAvailableLocaleNameSet(bundleName); // only visible ids
+ Iterator iter = visibleIDs.iterator();
+ while (iter.hasNext()) {
+ String id = (String)iter.next();
+ result.put(id, this);
+ }
+ }
+
+ /**
+ * Create the service. The default implementation returns the resource bundle
+ * for the locale, ignoring kind, and service.
+ */
+ protected Object handleCreate(ULocale loc, int kind, ICUService service) {
+ return ICUResourceBundle.getBundleInstance(bundleName, loc);
+ }
+
+ public String toString() {
+ return super.toString() + ", bundle: " + bundleName;
+ }
+ }
+
+ /**
+ * 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() {
+ ULocale loc = ULocale.getDefault();
+ if (loc != fallbackLocale) {
+ synchronized (this) {
+ if (loc != fallbackLocale) {
+ fallbackLocale = loc;
+ fallbackLocaleName = loc.getBaseName();
+ clearServiceCache();
+ }
+ }
+ }
+ return fallbackLocaleName;
+ }
+
+ public Key createKey(String id) {
+ return LocaleKey.createWithCanonicalFallback(id, validateFallbackLocale());
+ }
+
+ public Key createKey(String id, int kind) {
+ return LocaleKey.createWithCanonicalFallback(id, validateFallbackLocale(), kind);
+ }
+}
diff --git a/src/com/ibm/icu/impl/JDKTimeZone.java b/src/com/ibm/icu/impl/JDKTimeZone.java
new file mode 100644
index 0000000..cdf2be0
--- /dev/null
+++ b/src/com/ibm/icu/impl/JDKTimeZone.java
@@ -0,0 +1,257 @@
+/*
+**********************************************************************
+* Copyright (c) 2003-2006, International Business Machines
+* Corporation and others. All Rights Reserved.
+**********************************************************************
+* Author: Alan Liu
+* Created: October 2 2003
+* Since: ICU 2.8
+**********************************************************************
+*/
+package com.ibm.icu.impl;
+import com.ibm.icu.util.TimeZone;
+import com.ibm.icu.util.SimpleTimeZone;
+import java.util.Date;
+import java.io.IOException;
+
+/**
+ * Wrapper around OlsonTimeZone object. Due to serialziation constraints
+ * SimpleTimeZone cannot be a subclass of OlsonTimeZone.
+ *
+ * The complement of this is TimeZoneAdapter, which makes a
+ * com.ibm.icu.util.TimeZone look like a java.util.TimeZone.
+ *
+ * @see com.ibm.icu.impl.JDKTimeZone
+ * @author Alan Liu
+ * @since ICU 2.8
+ */
+public class JDKTimeZone extends TimeZone {
+
+ private static final long serialVersionUID = -3724907649889455280L;
+
+ /**
+ * The java.util.TimeZone wrapped by this object. Must not be null.
+ */
+ // give access to SimpleTimeZone
+ protected transient OlsonTimeZone zone;
+ /**
+ * Given a java.util.TimeZone, wrap it in the appropriate adapter
+ * subclass of com.ibm.icu.util.TimeZone and return the adapter.
+ */
+ public static TimeZone wrap(java.util.TimeZone tz) {
+ if (tz instanceof TimeZoneAdapter) {
+ return ((TimeZoneAdapter) tz).unwrap();
+ }
+ if (tz instanceof java.util.SimpleTimeZone) {
+ return new SimpleTimeZone((java.util.SimpleTimeZone) tz, tz.getID());
+ }
+ return new JDKTimeZone(tz);
+ }
+
+
+ /**
+ * Constructs a JDKTimeZone given a java.util.TimeZone reference
+ * which must not be null.
+ * @param tz the time zone to wrap
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public JDKTimeZone(java.util.TimeZone tz) {
+ String id = tz.getID();
+ try{
+ zone = new OlsonTimeZone(id);
+ }catch(Exception ex){
+ // throw away exception
+ }
+ super.setID(id);
+ }
+ protected JDKTimeZone(OlsonTimeZone tz) {
+ zone = tz;
+ super.setID(zone.getID());
+ }
+ /**
+ * Default constructor
+ */
+ protected JDKTimeZone() {
+ }
+ /**
+ * Sets the time zone ID. This does not change any other data in
+ * the time zone object.
+ * @param ID the new time zone ID.
+ */
+ public void setID(String ID) {
+ super.setID(ID);
+ if(zone!=null){
+ zone.setID(ID);
+ }
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public int getOffset(int era, int year, int month, int day,
+ int dayOfWeek, int milliseconds) {
+
+ if(zone!=null){
+ return zone.getOffset(era, year, month, day,
+ dayOfWeek, milliseconds);
+ }
+ //should never occur except while serializing JDKTimeZone object
+ return 0;
+ }
+
+
+ public void getOffset(long date, boolean local, int[] offsets) {
+
+ if(zone!=null){
+ zone.getOffset(date, local, offsets);
+ }else{
+ super.getOffset(date, local, offsets);
+ }
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public void setRawOffset(int offsetMillis) {
+ if(zone!=null){
+ zone.setRawOffset(offsetMillis);
+ }
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public int getRawOffset() {
+ if(zone!=null){
+ return zone.getRawOffset();
+ }
+ // should never happen except when serializing the JDKTimeZone object
+ return 0;
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public boolean useDaylightTime() {
+ if(zone!=null){
+ return zone.useDaylightTime();
+ }
+ // should never happen except when serializing the JDKTimeZone object
+ return false;
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public boolean inDaylightTime(Date date) {
+ if(zone!=null){
+ return zone.inDaylightTime(date);
+ }
+ //should never happen except when serializing the JDKTimeZone object
+ return false;
+ }
+
+ /**
+ * TimeZone API.
+ */
+ public boolean hasSameRules(TimeZone other) {
+ if (other == null) {
+ return false;
+ }
+ if (other instanceof JDKTimeZone) {
+ if(zone!=null){
+ return zone.hasSameRules(((JDKTimeZone) other).zone);
+ }
+ }
+ return super.hasSameRules(other);
+ }
+
+ /**
+ * Boilerplate API; calls through to wrapped object.
+ */
+ public Object clone() {
+ JDKTimeZone clone = new JDKTimeZone();
+ if(zone!=null){
+ clone.zone = (OlsonTimeZone)zone.clone();
+ }
+ return clone;
+ }
+
+ /**
+ * Boilerplate API; calls through to wrapped object.
+ */
+ public synchronized int hashCode() {
+ if(zone!=null){
+ return zone.hashCode();
+ }
+ return super.hashCode();
+ }
+
+ /**
+ * Returns the amount of time in ms that the clock is advanced during DST.
+ * @return the number of milliseconds the time is
+ * advanced with respect to standard time when the daylight savings rules
+ * are in effect. A positive number, typically one hour (3600000).
+ * @stable ICU 2.0
+ */
+ public int getDSTSavings() {
+ if (useDaylightTime()) {
+ if(zone!=null){
+ return zone.getDSTSavings();
+ }
+ return 3600000;
+ }
+ return 0;
+ }
+
+ /**
+ * Boilerplate API; calls through to wrapped object.
+ */
+ public boolean equals(Object obj) {
+ try {
+ if(obj !=null){
+ TimeZone tz1 = zone;
+ TimeZone tz2 = ((JDKTimeZone) obj).zone;
+ boolean equal = true;
+ if(tz1!=null && tz2!=null){
+ equal = tz1.equals(tz2);
+ }
+ return equal;
+ }
+ return false;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns a string representation of this object.
+ * @return a string representation of this object.
+ */
+ public String toString() {
+ return "JDKTimeZone: " + zone.toString();
+ }
+
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+ if(zone!=null){
+ out.writeObject(zone.getID());
+ }
+ out.writeObject(getID());
+ }
+
+ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+ String id = (String)in.readObject();
+
+ // create the TimeZone object if reading the old version of object
+ try{
+ zone = new OlsonTimeZone(id);
+ }catch(Exception ex){
+ //throw away exception
+ }
+ setID(id);
+ }
+}
+
+//eof
diff --git a/src/com/ibm/icu/impl/TimeZoneAdapter.java b/src/com/ibm/icu/impl/TimeZoneAdapter.java
new file mode 100644
index 0000000..bad0b1c
--- /dev/null
+++ b/src/com/ibm/icu/impl/TimeZoneAdapter.java
@@ -0,0 +1,153 @@
+/*
+**********************************************************************
+* Copyright (c) 2003-2006, International Business Machines
+* Corporation and others. All Rights Reserved.
+**********************************************************************
+* Author: Alan Liu
+* Created: October 2 2003
+* Since: ICU 2.8
+**********************************************************************
+*/
+package com.ibm.icu.impl;
+import com.ibm.icu.util.TimeZone;
+import java.util.Date;
+
+/**
+ * <code>TimeZoneAdapter</code> wraps a com.ibm.icu.util.TimeZone
+ * subclass that is NOT a JDKTimeZone, that is, that does not itself
+ * wrap a java.util.TimeZone. It inherits from java.util.TimeZone.
+ * Without this class, we would need to 'port' java.util.Date to
+ * com.ibm.icu.util as well, so that Date could interoperate properly
+ * with the com.ibm.icu.util TimeZone and Calendar classes. With this
+ * class, we can use java.util.Date together with com.ibm.icu.util
+ * classes.
+ *
+ * The complement of this is JDKTimeZone, which makes a
+ * java.util.TimeZone look like a com.ibm.icu.util.TimeZone.
+ *
+ * @see com.ibm.icu.impl.JDKTimeZone
+ * @see com.ibm.icu.util.TimeZone#setDefault
+ * @author Alan Liu
+ * @since ICU 2.8
+ */
+public class TimeZoneAdapter extends java.util.TimeZone {
+
+ // Generated by serialver from JDK 1.4.1_01
+ static final long serialVersionUID = -2040072218820018557L;
+
+ /**
+ * The contained com.ibm.icu.util.TimeZone object. Must not be null.
+ * We delegate all methods to this object.
+ */
+ private TimeZone zone;
+
+ /**
+ * Given a java.util.TimeZone, wrap it in the appropriate adapter
+ * subclass of com.ibm.icu.util.TimeZone and return the adapter.
+ */
+ public static java.util.TimeZone wrap(com.ibm.icu.util.TimeZone tz) {
+ return new TimeZoneAdapter(tz);
+ }
+
+ /**
+ * Return the java.util.TimeZone wrapped by this object.
+ */
+ public com.ibm.icu.util.TimeZone unwrap() {
+ return zone;
+ }
+
+ /**
+ * Constructs an adapter for a com.ibm.icu.util.TimeZone object.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public TimeZoneAdapter(TimeZone zone) {
+ this.zone = zone;
+ super.setID(zone.getID());
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public void setID(String ID) {
+ super.setID(ID);
+ zone.setID(ID);
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public boolean hasSameRules(java.util.TimeZone other) {
+ return other instanceof TimeZoneAdapter &&
+ zone.hasSameRules(((TimeZoneAdapter)other).zone);
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public int getOffset(int era, int year, int month, int day, int dayOfWeek,
+ int millis) {
+ return zone.getOffset(era, year, month, day, dayOfWeek, millis);
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public int getRawOffset() {
+ return zone.getRawOffset();
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public void setRawOffset(int offsetMillis) {
+ zone.setRawOffset(offsetMillis);
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public boolean useDaylightTime() {
+ return zone.useDaylightTime();
+ }
+
+ /**
+ * TimeZone API; calls through to wrapped time zone.
+ */
+ public boolean inDaylightTime(Date date) {
+ return zone.inDaylightTime(date);
+ }
+
+ /**
+ * Boilerplate API; calls through to wrapped object.
+ */
+ public Object clone() {
+ return new TimeZoneAdapter((TimeZone)zone.clone());
+ }
+
+ /**
+ * Boilerplate API; calls through to wrapped object.
+ */
+ public synchronized int hashCode() {
+ return zone.hashCode();
+ }
+
+ /**
+ * Boilerplate API; calls through to wrapped object.
+ */
+ public boolean equals(Object obj) {
+ if (obj instanceof TimeZoneAdapter) {
+ obj = ((TimeZoneAdapter) obj).zone;
+ }
+ return zone.equals(obj);
+ }
+
+ /**
+ * Returns a string representation of this object.
+ * @return a string representation of this object.
+ */
+ public String toString() {
+ return "TimeZoneAdapter: " + zone.toString();
+ }
+}
diff --git a/src/com/ibm/icu/text/MessageFormat.java b/src/com/ibm/icu/text/MessageFormat.java
new file mode 100644
index 0000000..2e0ede4
--- /dev/null
+++ b/src/com/ibm/icu/text/MessageFormat.java
@@ -0,0 +1,1716 @@
+/*
+**********************************************************************
+* Copyright (c) 2004-2006, International Business Machines
+* Corporation and others. All Rights Reserved.
+**********************************************************************
+* Author: Alan Liu
+* Created: April 6, 2004
+* Since: ICU 3.0
+**********************************************************************
+*/
+package com.ibm.icu.text;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.text.ChoiceFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Date;
+import java.util.Locale;
+
+import com.ibm.icu.impl.Utility;
+import com.ibm.icu.text.RuleBasedNumberFormat;
+import com.ibm.icu.util.ULocale;
+
+/**
+ * <code>MessageFormat</code> provides a means to produce concatenated
+ * messages in language-neutral way. Use this to construct messages
+ * displayed for end users.
+ *
+ * <p>
+ * <code>MessageFormat</code> takes a set of objects, formats them, then
+ * inserts the formatted strings into the pattern at the appropriate places.
+ *
+ * <p>
+ * <strong>Note:</strong>
+ * <code>MessageFormat</code> differs from the other <code>Format</code>
+ * classes in that you create a <code>MessageFormat</code> object with one
+ * of its constructors (not with a <code>getInstance</code> style factory
+ * method). The factory methods aren't necessary because <code>MessageFormat</code>
+ * itself doesn't implement locale specific behavior. Any locale specific
+ * behavior is defined by the pattern that you provide as well as the
+ * subformats used for inserted arguments.
+ *
+ * <h4><a name="patterns">Patterns and Their Interpretation</a></h4>
+ *
+ * <code>MessageFormat</code> uses patterns of the following form:
+ * <blockquote><pre>
+ * <i>MessageFormatPattern:</i>
+ * <i>String</i>
+ * <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>
+ *
+ * <i>FormatElement:</i>
+ * { <i>ArgumentIndex</i> }
+ * { <i>ArgumentIndex</i> , <i>FormatType</i> }
+ * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }
+ *
+ * <i>FormatType: one of </i>
+ * number date time choice
+ *
+ * <i>FormatStyle:</i>
+ * short
+ * medium
+ * long
+ * full
+ * integer
+ * currency
+ * percent
+ * <i>SubformatPattern</i>
+ *
+ * <i>String:</i>
+ * <i>StringPart<sub>opt</sub></i>
+ * <i>String</i> <i>StringPart</i>
+ *
+ * <i>StringPart:</i>
+ * ''
+ * ' <i>QuotedString</i> '
+ * <i>UnquotedString</i>
+ *
+ * <i>SubformatPattern:</i>
+ * <i>SubformatPatternPart<sub>opt</sub></i>
+ * <i>SubformatPattern</i> <i>SubformatPatternPart</i>
+ *
+ * <i>SubFormatPatternPart:</i>
+ * ' <i>QuotedPattern</i> '
+ * <i>UnquotedPattern</i>
+ * </pre></blockquote>
+ *
+ * <p>
+ * Within a <i>String</i>, <code>"''"</code> represents a single
+ * quote. A <i>QuotedString</i> can contain arbitrary characters
+ * except single quotes; the surrounding single quotes are removed.
+ * An <i>UnquotedString</i> can contain arbitrary characters
+ * except single quotes and left curly brackets. Thus, a string that
+ * should result in the formatted message "'{0}'" can be written as
+ * <code>"'''{'0}''"</code> or <code>"'''{0}'''"</code>.
+ * <p>
+ * Within a <i>SubformatPattern</i>, different rules apply.
+ * A <i>QuotedPattern</i> can contain arbitrary characters
+ * except single quotes; but the surrounding single quotes are
+ * <strong>not</strong> removed, so they may be interpreted by the
+ * subformat. For example, <code>"{1,number,$'#',##}"</code> will
+ * produce a number format with the pound-sign quoted, with a result
+ * such as: "$#31,45".
+ * An <i>UnquotedPattern</i> can contain arbitrary characters
+ * except single quotes, but curly braces within it must be balanced.
+ * For example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code>
+ * are valid subformat patterns, but <code>"ab {0'}' de"</code> and
+ * <code>"ab } de"</code> are not.
+ * <p>
+ * <dl><dt><b>Warning:</b><dd>The rules for using quotes within message
+ * format patterns unfortunately have shown to be somewhat confusing.
+ * In particular, it isn't always obvious to localizers whether single
+ * quotes need to be doubled or not. Make sure to inform localizers about
+ * the rules, and tell them (for example, by using comments in resource
+ * bundle source files) which strings will be processed by MessageFormat.
+ * Note that localizers may need to use single quotes in translated
+ * strings where the original version doesn't have them.
+ * <br>Note also that the simplest way to avoid the problem is to
+ * use the real apostrophe (single quote) character \u2019 (') for
+ * human-readable text, and to use the ASCII apostrophe (\u0027 ' )
+ * only in program syntax, like quoting in MessageFormat.
+ * See the annotations for U+0027 Apostrophe in The Unicode Standard.</p>
+ * </dl>
+ * <p>
+ * The <i>ArgumentIndex</i> value is a non-negative integer written
+ * using the digits '0' through '9', and represents an index into the
+ * <code>arguments</code> array passed to the <code>format</code> methods
+ * or the result array returned by the <code>parse</code> methods.
+ * <p>
+ * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create
+ * a <code>Format</code> instance for the format element. The following
+ * table shows how the values map to Format instances. Combinations not
+ * shown in the table are illegal. A <i>SubformatPattern</i> must
+ * be a valid pattern string for the Format subclass used.
+ * <p>
+ * <table border=1>
+ * <tr>
+ * <th>Format Type
+ * <th>Format Style
+ * <th>Subformat Created
+ * <tr>
+ * <td colspan=2><i>(none)</i>
+ * <td><code>null</code>
+ * <tr>
+ * <td rowspan=5><code>number</code>
+ * <td><i>(none)</i>
+ * <td><code>NumberFormat.getInstance(getLocale())</code>
+ * <tr>
+ * <td><code>integer</code>
+ * <td><code>NumberFormat.getIntegerInstance(getLocale())</code>
+ * <tr>
+ * <td><code>currency</code>
+ * <td><code>NumberFormat.getCurrencyInstance(getLocale())</code>
+ * <tr>
+ * <td><code>percent</code>
+ * <td><code>NumberFormat.getPercentInstance(getLocale())</code>
+ * <tr>
+ * <td><i>SubformatPattern</i>
+ * <td><code>new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))</code>
+ * <tr>
+ * <td rowspan=6><code>date</code>
+ * <td><i>(none)</i>
+ * <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
+ * <tr>
+ * <td><code>short</code>
+ * <td><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>
+ * <tr>
+ * <td><code>medium</code>
+ * <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
+ * <tr>
+ * <td><code>long</code>
+ * <td><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>
+ * <tr>
+ * <td><code>full</code>
+ * <td><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>
+ * <tr>
+ * <td><i>SubformatPattern</i>
+ * <td><code>new SimpleDateFormat(subformatPattern, getLocale())
+ * <tr>
+ * <td rowspan=6><code>time</code>
+ * <td><i>(none)</i>
+ * <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
+ * <tr>
+ * <td><code>short</code>
+ * <td><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>
+ * <tr>
+ * <td><code>medium</code>
+ * <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
+ * <tr>
+ * <td><code>long</code>
+ * <td><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>
+ * <tr>
+ * <td><code>full</code>
+ * <td><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>
+ * <tr>
+ * <td><i>SubformatPattern</i>
+ * <td><code>new SimpleDateFormat(subformatPattern, getLocale())
+ * <tr>
+ * <td><code>choice</code>
+ * <td><i>SubformatPattern</i>
+ * <td><code>new ChoiceFormat(subformatPattern)</code>
+ * </table>
+ * <p>
+ *
+ * <h4>Usage Information</h4>
+ *
+ * <p>
+ * Here are some examples of usage:
+ * <blockquote>
+ * <pre>
+ * Object[] arguments = {
+ * new Integer(7),
+ * new Date(System.currentTimeMillis()),
+ * "a disturbance in the Force"
+ * };
+ *
+ * String result = MessageFormat.format(
+ * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
+ * arguments);
+ *
+ * <em>output</em>: At 12:30 PM on Jul 3, 2053, there was a disturbance
+ * in the Force on planet 7.
+ *
+ * </pre>
+ * </blockquote>
+ * Typically, the message format will come from resources, and the
+ * arguments will be dynamically set at runtime.
+ *
+ * <p>
+ * Example 2:
+ * <blockquote>
+ * <pre>
+ * Object[] testArgs = {new Long(3), "MyDisk"};
+ *
+ * MessageFormat form = new MessageFormat(
+ * "The disk \"{1}\" contains {0} file(s).");
+ *
+ * System.out.println(form.format(testArgs));
+ *
+ * // output, with different testArgs
+ * <em>output</em>: The disk "MyDisk" contains 0 file(s).
+ * <em>output</em>: The disk "MyDisk" contains 1 file(s).
+ * <em>output</em>: The disk "MyDisk" contains 1,273 file(s).
+ * </pre>
+ * </blockquote>
+ *
+ * <p>
+ * For more sophisticated patterns, you can use a <code>ChoiceFormat</code> to get
+ * output such as:
+ * <blockquote>
+ * <pre>
+ * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
+ * double[] filelimits = {0,1,2};
+ * String[] filepart = {"no files","one file","{0,number} files"};
+ * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
+ * form.setFormatByArgumentIndex(0, fileform);
+ *
+ * Object[] testArgs = {new Long(12373), "MyDisk"};
+ *
+ * System.out.println(form.format(testArgs));
+ *
+ * // output, with different testArgs
+ * output: The disk "MyDisk" contains no files.
+ * output: The disk "MyDisk" contains one file.
+ * output: The disk "MyDisk" contains 1,273 files.
+ * </pre>
+ * </blockquote>
+ * You can either do this programmatically, as in the above example,
+ * or by using a pattern (see
+ * {@link ChoiceFormat}
+ * for more information) as in:
+ * <blockquote>
+ * <pre>
+ * form.applyPattern(
+ * "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");
+ * </pre>
+ * </blockquote>
+ * <p>
+ * <strong>Note:</strong> As we see above, the string produced
+ * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated specially;
+ * occurances of '{' are used to indicated subformats, and cause recursion.
+ * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>
+ * programmatically (instead of using the string patterns), then be careful not to
+ * produce a format that recurses on itself, which will cause an infinite loop.
+ * <p>
+ * When a single argument is parsed more than once in the string, the last match
+ * will be the final result of the parsing. For example,
+ * <pre>
+ * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
+ * Object[] objs = {new Double(3.1415)};
+ * String result = mf.format( objs );
+ * // result now equals "3.14, 3.1"
+ * objs = null;
+ * objs = mf.parse(result, new ParsePosition(0));
+ * // objs now equals {new Double(3.1)}
+ * </pre>
+ * <p>
+ * Likewise, parsing with a MessageFormat object using patterns containing
+ * multiple occurances of the same argument would return the last match. For
+ * example,
+ * <pre>
+ * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
+ * String forParsing = "x, y, z";
+ * Object[] objs = mf.parse(forParsing, new ParsePosition(0));
+ * // result now equals {new String("z")}
+ * </pre>
+ *
+ * <h4><a name="synchronization">Synchronization</a></h4>
+ *
+ * <p>
+ * Message formats are not synchronized.
+ * It is recommended to create separate format instances for each thread.
+ * If multiple threads access a format concurrently, it must be synchronized
+ * externally.
+ *
+ * @see java.util.Locale
+ * @see Format
+ * @see NumberFormat
+ * @see DecimalFormat
+ * @see ChoiceFormat
+ * @author Mark Davis
+ * @stable ICU 3.0
+ */
+public class MessageFormat extends UFormat {
+
+ // Generated by serialver from JDK 1.4.1_01
+ static final long serialVersionUID = 7136212545847378651L;
+
+ /**
+ * Constructs a MessageFormat for the default locale and the
+ * specified pattern.
+ * The constructor first sets the locale, then parses the pattern and
+ * creates a list of subformats for the format elements contained in it.
+ * Patterns and their interpretation are specified in the
+ * <a href="#patterns">class description</a>.
+ *
+ * @param pattern the pattern for this message format
+ * @exception IllegalArgumentException if the pattern is invalid
+ * @stable ICU 3.0
+ */
+ public MessageFormat(String pattern) {
+ this.ulocale = ULocale.getDefault();
+ applyPattern(pattern);
+ }
+
+ /**
+ * Constructs a MessageFormat for the specified locale and
+ * pattern.
+ * The constructor first sets the locale, then parses the pattern and
+ * creates a list of subformats for the format elements contained in it.
+ * Patterns and their interpretation are specified in the
+ * <a href="#patterns">class description</a>.
+ *
+ * @param pattern the pattern for this message format
+ * @param locale the locale for this message format
+ * @exception IllegalArgumentException if the pattern is invalid
+ * @stable ICU 3.0
+ */
+ public MessageFormat(String pattern, Locale locale) {
+ this(pattern, ULocale.forLocale(locale));
+ }
+
+ /**
+ * Constructs a MessageFormat for the specified locale and
+ * pattern.
+ * The constructor first sets the locale, then parses the pattern and
+ * creates a list of subformats for the format elements contained in it.
+ * Patterns and their interpretation are specified in the
+ * <a href="#patterns">class description</a>.
+ *
+ * @param pattern the pattern for this message format
+ * @param locale the locale for this message format
+ * @exception IllegalArgumentException if the pattern is invalid
+ * @stable ICU 3.2
+ */
+ public MessageFormat(String pattern, ULocale locale) {
+ this.ulocale = locale;
+ applyPattern(pattern);
+ }
+
+ /**
+ * Sets the locale to be used when creating or comparing subformats.
+ * This affects subsequent calls to the {@link #applyPattern applyPattern}
+ * and {@link #toPattern toPattern} methods as well as to the
+ * <code>format</code> and
+ * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
+ *
+ * @param locale the locale to be used when creating or comparing subformats
+ * @stable ICU 3.0
+ */
+ public void setLocale(Locale locale) {
+ setLocale(ULocale.forLocale(locale));
+ }
+
+ /**
+ * Sets the locale to be used when creating or comparing subformats.
+ * This affects subsequent calls to the {@link #applyPattern applyPattern}
+ * and {@link #toPattern toPattern} methods as well as to the
+ * <code>format</code> and
+ * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
+ *
+ * @param locale the locale to be used when creating or comparing subformats
+ * @stable ICU 3.2
+ */
+ public void setLocale(ULocale locale) {
+ /* Save the pattern, and then reapply so that */
+ /* we pick up any changes in locale specific */
+ /* elements */
+ String existingPattern = toPattern(); /*ibm.3550*/
+ this.ulocale = locale;
+ applyPattern(existingPattern); /*ibm.3550*/
+ }
+
+ /**
+ * Gets the locale that's used when creating or comparing subformats.
+ *
+ * @return the locale used when creating or comparing subformats
+ * @stable ICU 3.0
+ */
+ public Locale getLocale() {
+ return ulocale.toLocale();
+ }
+
+ /**
+ * Gets the locale that's used when creating or comparing subformats.
+ *
+ * @return the locale used when creating or comparing subformats
+ * @stable ICU 3.2
+ */
+ public ULocale getULocale() {
+ return ulocale;
+ }
+
+ /**
+ * Sets the pattern used by this message format.
+ * The method parses the pattern and creates a list of subformats
+ * for the format elements contained in it.
+ * Patterns and their interpretation are specified in the
+ * <a href="#patterns">class description</a>.
+ *
+ * @param pattern the pattern for this message format
+ * @exception IllegalArgumentException if the pattern is invalid
+ * @stable ICU 3.0
+ */
+ public void applyPattern(String pattern) {
+ StringBuffer[] segments = new StringBuffer[4];
+ for (int i = 0; i < segments.length; ++i) {
+ segments[i] = new StringBuffer();
+ }
+ int part = 0;
+ int formatNumber = 0;
+ boolean inQuote = false;
+ int braceStack = 0;
+ maxOffset = -1;
+ for (int i = 0; i < pattern.length(); ++i) {
+ char ch = pattern.charAt(i);
+ if (part == 0) {
+ if (ch == '\'') {
+ if (i + 1 < pattern.length()
+ && pattern.charAt(i+1) == '\'') {
+ segments[part].append(ch); // handle doubles
+ ++i;
+ } else {
+ inQuote = !inQuote;
+ }
+ } else if (ch == '{' && !inQuote) {
+ part = 1;
+ } else {
+ segments[part].append(ch);
+ }
+ } else if (inQuote) { // just copy quotes in parts
+ segments[part].append(ch);
+ if (ch == '\'') {
+ inQuote = false;
+ }
+ } else {
+ switch (ch) {
+ case ',':
+ if (part < 3)
+ part += 1;
+ else
+ segments[part].append(ch);
+ break;
+ case '{':
+ ++braceStack;
+ segments[part].append(ch);
+ break;
+ case '}':
+ if (braceStack == 0) {
+ part = 0;
+ makeFormat(i, formatNumber, segments);
+ formatNumber++;
+ } else {
+ --braceStack;
+ segments[part].append(ch);
+ }
+ break;
+ case '\'':
+ inQuote = true;
+ // fall through, so we keep quotes in other parts
+ default:
+ segments[part].append(ch);
+ break;
+ }
+ }
+ }
+ if (braceStack == 0 && part != 0) {
+ maxOffset = -1;
+ throw new IllegalArgumentException("Unmatched braces in the pattern.");
+ }
+ this.pattern = segments[0].toString();
+ }
+
+
+ /**
+ * Returns a pattern representing the current state of the message format.
+ * The string is constructed from internal information and therefore
+ * does not necessarily equal the previously applied pattern.
+ *
+ * @return a pattern representing the current state of the message format
+ * @stable ICU 3.0
+ */
+ public String toPattern() {
+ // later, make this more extensible
+ int lastOffset = 0;
+ StringBuffer result = new StringBuffer();
+ for (int i = 0; i <= maxOffset; ++i) {
+ copyAndFixQuotes(pattern, lastOffset, offsets[i],result);
+ lastOffset = offsets[i];
+ result.append('{');
+ result.append(argumentNumbers[i]);
+ if (formats[i] == null) {
+ // do nothing, string format
+ } else if (formats[i] instanceof DecimalFormat) {
+ if (formats[i].equals(NumberFormat.getInstance(ulocale))) {
+ result.append(",number");
+ } else if (formats[i].equals(NumberFormat.getCurrencyInstance(ulocale))) {
+ result.append(",number,currency");
+ } else if (formats[i].equals(NumberFormat.getPercentInstance(ulocale))) {
+ result.append(",number,percent");
+ } else if (formats[i].equals(NumberFormat.getIntegerInstance(ulocale))) {
+ result.append(",number,integer");
+ } else {
+ result.append(",number," +
+ ((DecimalFormat)formats[i]).toPattern());
+ }
+ } else if (formats[i] instanceof SimpleDateFormat) {
+ if (formats[i].equals(DateFormat.getDateInstance(DateFormat.DEFAULT,ulocale))) {
+ result.append(",date");
+ } else if (formats[i].equals(DateFormat.getDateInstance(DateFormat.SHORT,ulocale))) {
+ result.append(",date,short");
+// This code will never be executed [alan]
+// } else if (formats[i].equals(DateFormat.getDateInstance(DateFormat.DEFAULT,ulocale))) {
+// result.append(",date,medium");
+ } else if (formats[i].equals(DateFormat.getDateInstance(DateFormat.LONG,ulocale))) {
+ result.append(",date,long");
+ } else if (formats[i].equals(DateFormat.getDateInstance(DateFormat.FULL,ulocale))) {
+ result.append(",date,full");
+ } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.DEFAULT,ulocale))) {
+ result.append(",time");
+ } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.SHORT,ulocale))) {
+ result.append(",time,short");
+// This code will never be executed [alan]
+// } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.DEFAULT,ulocale))) {
+// result.append(",time,medium");
+ } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.LONG,ulocale))) {
+ result.append(",time,long");
+ } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.FULL,ulocale))) {
+ result.append(",time,full");
+ } else {
+ result.append(",date," + ((SimpleDateFormat)formats[i]).toPattern());
+ }
+ } else if (formats[i] instanceof ChoiceFormat) {
+ result.append(",choice," + ((ChoiceFormat)formats[i]).toPattern());
+ } else {
+ //result.append(", unknown");
+ }
+ result.append('}');
+ }
+ copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
+ return result.toString();
+ }
+
+ /**
+ * Sets the formats to use for the values passed into
+ * <code>format</code> methods or returned from <code>parse</code>
+ * methods. The indices of elements in <code>newFormats</code>
+ * correspond to the argument indices used in the previously set
+ * pattern string.
+ * The order of formats in <code>newFormats</code> thus corresponds to
+ * the order of elements in the <code>arguments</code> array passed
+ * to the <code>format</code> methods or the result array returned
+ * by the <code>parse</code> methods.
+ * <p>
+ * If an argument index is used for more than one format element
+ * in the pattern string, then the corresponding new format is used
+ * for all such format elements. If an argument index is not used
+ * for any format element in the pattern string, then the
+ * corresponding new format is ignored. If fewer formats are provided
+ * than needed, then only the formats for argument indices less
+ * than <code>newFormats.length</code> are replaced.
+ *
+ * @param newFormats the new formats to use
+ * @exception NullPointerException if <code>newFormats</code> is null
+ * @stable ICU 3.0
+ */
+ public void setFormatsByArgumentIndex(Format[] newFormats) {
+ for (int i = 0; i <= maxOffset; i++) {
+ int j = argumentNumbers[i];
+ if (j < newFormats.length) {
+ formats[i] = newFormats[j];
+ }
+ }
+ }
+
+ /**
+ * Sets the formats to use for the format elements in the
+ * previously set pattern string.
+ * The order of formats in <code>newFormats</code> corresponds to
+ * the order of format elements in the pattern string.
+ * <p>
+ * If more formats are provided than needed by the pattern string,
+ * the remaining ones are ignored. If fewer formats are provided
+ * than needed, then only the first <code>newFormats.length</code>
+ * formats are replaced.
+ * <p>
+ * Since the order of format elements in a pattern string often
+ * changes during localization, it is generally better to use the
+ * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
+ * method, which assumes an order of formats corresponding to the
+ * order of elements in the <code>arguments</code> array passed to
+ * the <code>format</code> methods or the result array returned by
+ * the <code>parse</code> methods.
+ *
+ * @param newFormats the new formats to use
+ * @exception NullPointerException if <code>newFormats</code> is null
+ * @stable ICU 3.0
+ */
+ public void setFormats(Format[] newFormats) {
+ int runsToCopy = newFormats.length;
+ if (runsToCopy > maxOffset + 1) {
+ runsToCopy = maxOffset + 1;
+ }
+ for (int i = 0; i < runsToCopy; i++) {
+ formats[i] = newFormats[i];
+ }
+ }
+
+ /**
+ * Sets the format to use for the format elements within the
+ * previously set pattern string that use the given argument
+ * index.
+ * The argument index is part of the format element definition and
+ * represents an index into the <code>arguments</code> array passed
+ * to the <code>format</code> methods or the result array returned
+ * by the <code>parse</code> methods.
+ * <p>
+ * If the argument index is used for more than one format element
+ * in the pattern string, then the new format is used for all such
+ * format elements. If the argument index is not used for any format
+ * element in the pattern string, then the new format is ignored.
+ *
+ * @param argumentIndex the argument index for which to use the new format
+ * @param newFormat the new format to use
+ * @stable ICU 3.0
+ */
+ public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
+ for (int j = 0; j <= maxOffset; j++) {
+ if (argumentNumbers[j] == argumentIndex) {
+ formats[j] = newFormat;
+ }
+ }
+ }
+
+ /**
+ * Sets the format to use for the format element with the given
+ * format element index within the previously set pattern string.
+ * The format element index is the zero-based number of the format
+ * element counting from the start of the pattern string.
+ * <p>
+ * Since the order of format elements in a pattern string often
+ * changes during localization, it is generally better to use the
+ * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
+ * method, which accesses format elements based on the argument
+ * index they specify.
+ *
+ * @param formatElementIndex the index of a format element within the pattern
+ * @param newFormat the format to use for the specified format element
+ * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or
+ * larger than the number of format elements in the pattern string
+ * @stable ICU 3.0
+ */
+ public void setFormat(int formatElementIndex, Format newFormat) {
+ formats[formatElementIndex] = newFormat;
+ }
+
+ /**
+ * Gets the formats used for the values passed into
+ * <code>format</code> methods or returned from <code>parse</code>
+ * methods. The indices of elements in the returned array
+ * correspond to the argument indices used in the previously set
+ * pattern string.
+ * The order of formats in the returned array thus corresponds to
+ * the order of elements in the <code>arguments</code> array passed
+ * to the <code>format</code> methods or the result array returned
+ * by the <code>parse</code> methods.
+ * <p>
+ * If an argument index is used for more than one format element
+ * in the pattern string, then the format used for the last such
+ * format element is returned in the array. If an argument index
+ * is not used for any format element in the pattern string, then
+ * null is returned in the array.
+ *
+ * @return the formats used for the arguments within the pattern
+ * @stable ICU 3.0
+ */
+ public Format[] getFormatsByArgumentIndex() {
+ int maximumArgumentNumber = -1;
+ for (int i = 0; i <= maxOffset; i++) {
+ if (argumentNumbers[i] > maximumArgumentNumber) {
+ maximumArgumentNumber = argumentNumbers[i];
+ }
+ }
+ Format[] resultArray = new Format[maximumArgumentNumber + 1];
+ for (int i = 0; i <= maxOffset; i++) {
+ resultArray[argumentNumbers[i]] = formats[i];
+ }
+ return resultArray;
+ }
+
+ /**
+ * Gets the formats used for the format elements in the
+ * previously set pattern string.
+ * The order of formats in the returned array corresponds to
+ * the order of format elements in the pattern string.
+ * <p>
+ * Since the order of format elements in a pattern string often
+ * changes during localization, it's generally better to use the
+ * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}
+ * method, which assumes an order of formats corresponding to the
+ * order of elements in the <code>arguments</code> array passed to
+ * the <code>format</code> methods or the result array returned by
+ * the <code>parse</code> methods.
+ *
+ * @return the formats used for the format elements in the pattern
+ * @stable ICU 3.0
+ */
+ public Format[] getFormats() {
+ Format[] resultArray = new Format[maxOffset + 1];
+ System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);
+ return resultArray;
+ }
+
+ /**
+ * Formats an array of objects and appends the <code>MessageFormat</code>'s
+ * pattern, with format elements replaced by the formatted objects, to the
+ * provided <code>StringBuffer</code>.
+ * <p>
+ * The text substituted for the individual format elements is derived from
+ * the current subformat of the format element and the
+ * <code>arguments</code> element at the format element's argument index
+ * as indicated by the first matching line of the following table. An
+ * argument is <i>unavailable</i> if <code>arguments</code> is
+ * <code>null</code> or has fewer than argumentIndex+1 elements.
+ * <p>
+ * <table border=1>
+ * <tr>
+ * <th>Subformat
+ * <th>Argument
+ * <th>Formatted Text
+ * <tr>
+ * <td><i>any</i>
+ * <td><i>unavailable</i>
+ * <td><code>"{" + argumentIndex + "}"</code>
+ * <tr>
+ * <td><i>any</i>
+ * <td><code>null</code>
+ * <td><code>"null"</code>
+ * <tr>
+ * <td><code>instanceof ChoiceFormat</code>
+ * <td><i>any</i>
+ * <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br>
+ * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :
+ * subformat.format(argument)</code>
+ * <tr>
+ * <td><code>!= null</code>
+ * <td><i>any</i>
+ * <td><code>subformat.format(argument)</code>
+ * <tr>
+ * <td><code>null</code>
+ * <td><code>instanceof Number</code>
+ * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
+ * <tr>
+ * <td><code>null</code>
+ * <td><code>instanceof Date</code>
+ * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>
+ * <tr>
+ * <td><code>null</code>
+ * <td><code>instanceof String</code>
+ * <td><code>argument</code>
+ * <tr>
+ * <td><code>null</code>
+ * <td><i>any</i>
+ * <td><code>argument.toString()</code>
+ * </table>
+ * <p>
+ * If <code>pos</code> is non-null, and refers to
+ * <code>Field.ARGUMENT</code>, the location of the first formatted
+ * string will be returned.
+ *
+ * @param arguments an array of objects to be formatted and substituted.
+ * @param result where text is appended.
+ * @param pos On input: an alignment field, if desired.
+ * On output: the offsets of the alignment field.
+ * @exception IllegalArgumentException if an argument in the
+ * <code>arguments</code> array is not of the type
+ * expected by the format element(s) that use it.
+ * @stable ICU 3.0
+ */
+ public final StringBuffer format(Object[] arguments, StringBuffer result,
+ FieldPosition pos)
+ {
+ return subformat(arguments, result, pos);
+ }
+
+ /**
+ * Creates a MessageFormat with the given pattern and uses it
+ * to format the given arguments. This is equivalent to
+ * <blockquote>
+ * <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
+ * </blockquote>
+ *
+ * @exception IllegalArgumentException if the pattern is invalid,
+ * or if an argument in the <code>arguments</code> array
+ * is not of the type expected by the format element(s)
+ * that use it.
+ * @stable ICU 3.0
+ */
+ public static String format(String pattern, Object[] arguments) {
+ MessageFormat temp = new MessageFormat(pattern);
+ return temp.format(arguments);
+ }
+
+ // Overrides
+ /**
+ * Formats an array of objects and appends the <code>MessageFormat</code>'s
+ * pattern, with format elements replaced by the formatted objects, to the
+ * provided <code>StringBuffer</code>.
+ * This is equivalent to
+ * <blockquote>
+ * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
+ * </blockquote>
+ *
+ * @param arguments an array of objects to be formatted and substituted.
+ * @param result where text is appended.
+ * @param pos On input: an alignment field, if desired.
+ * On output: the offsets of the alignment field.
+ * @exception IllegalArgumentException if an argument in the
+ * <code>arguments</code> array is not of the type
+ * expected by the format element(s) that use it.
+ * @stable ICU 3.0
+ */
+ public final StringBuffer format(Object arguments, StringBuffer result,
+ FieldPosition pos)
+ {
+ return subformat((Object[]) arguments, result, pos);
+ }
+
+// TODO Do not remove, this is API in JDK that we need to implement
+// /**
+// * Formats an array of objects and inserts them into the
+// * <code>MessageFormat</code>'s pattern, producing an
+// * <code>AttributedCharacterIterator</code>.
+// * You can use the returned <code>AttributedCharacterIterator</code>
+// * to build the resulting String, as well as to determine information
+// * about the resulting String.
+// * <p>
+// * The text of the returned <code>AttributedCharacterIterator</code> is
+// * the same that would be returned by
+// * <blockquote>
+// * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
+// * </blockquote>
+// * <p>
+// * In addition, the <code>AttributedCharacterIterator</code> contains at
+// * least attributes indicating where text was generated from an
+// * argument in the <code>arguments</code> array. The keys of these attributes are of
+// * type <code>MessageFormat.Field</code>, their values are
+// * <code>Integer</code> objects indicating the index in the <code>arguments</code>
+// * array of the argument from which the text was generated.
+// * <p>
+// * The attributes/value from the underlying <code>Format</code>
+// * instances that <code>MessageFormat</code> uses will also be
+// * placed in the resulting <code>AttributedCharacterIterator</code>.
+// * This allows you to not only find where an argument is placed in the
+// * resulting String, but also which fields it contains in turn.
+// *
+// * @param arguments an array of objects to be formatted and substituted.
+// * @return AttributedCharacterIterator describing the formatted value.
+// * @exception NullPointerException if <code>arguments</code> is null.
+// * @exception IllegalArgumentException if an argument in the
+// * <code>arguments</code> array is not of the type
+// * expected by the format element(s) that use it.
+// */
+// public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
+// StringBuffer result = new StringBuffer();
+// ArrayList iterators = new ArrayList();
+//
+// if (arguments == null) {
+// throw new NullPointerException(
+// "formatToCharacterIterator must be passed non-null object");
+// }
+// subformat((Object[]) arguments, result, null, iterators);
+// if (iterators.size() == 0) {
+// return createAttributedCharacterIterator("");
+// }
+// return createAttributedCharacterIterator(
+// (AttributedCharacterIterator[])iterators.toArray(
+// new AttributedCharacterIterator[iterators.size()]));
+// }
+
+ /**
+ * Parses the string.
+ *
+ * <p>Caveats: The parse may fail in a number of circumstances.
+ * For example:
+ * <ul>
+ * <li>If one of the arguments does not occur in the pattern.
+ * <li>If the format of an argument loses information, such as
+ * with a choice format where a large number formats to "many".
+ * <li>Does not yet handle recursion (where
+ * the substituted strings contain {n} references.)
+ * <li>Will not always find a match (or the correct match)
+ * if some part of the parse is ambiguous.
+ * For example, if the pattern "{1},{2}" is used with the
+ * string arguments {"a,b", "c"}, it will format as "a,b,c".
+ * When the result is parsed, it will return {"a", "b,c"}.
+ * <li>If a single argument is parsed more than once in the string,
+ * then the later parse wins.
+ * </ul>
+ * When the parse fails, use ParsePosition.getErrorIndex() to find out
+ * where in the string did the parsing failed. The returned error
+ * index is the starting offset of the sub-patterns that the string
+ * is comparing with. For example, if the parsing string "AAA {0} BBB"
+ * is comparing against the pattern "AAD {0} BBB", the error index is
+ * 0. When an error occurs, the call to this method will return null.
+ * If the source is null, return an empty array.
+ * @stable ICU 3.0
+ */
+ public Object[] parse(String source, ParsePosition pos) {
+ if (source == null) {
+ Object[] empty = {};
+ return empty;
+ }
+
+ int maximumArgumentNumber = -1;
+ for (int i = 0; i <= maxOffset; i++) {
+ if (argumentNumbers[i] > maximumArgumentNumber) {
+ maximumArgumentNumber = argumentNumbers[i];
+ }
+ }
+ Object[] resultArray = new Object[maximumArgumentNumber + 1];
+
+ int patternOffset = 0;
+ int sourceOffset = pos.getIndex();
+ ParsePosition tempStatus = new ParsePosition(0);
+ for (int i = 0; i <= maxOffset; ++i) {
+ // match up to format
+ int len = offsets[i] - patternOffset;
+ if (len == 0 || pattern.regionMatches(patternOffset,
+ source, sourceOffset, len)) {
+ sourceOffset += len;
+ patternOffset += len;
+ } else {
+ pos.setErrorIndex(sourceOffset);
+ return null; // leave index as is to signal error
+ }
+
+ // now use format
+ if (formats[i] == null) { // string format
+ // if at end, use longest possible match
+ // otherwise uses first match to intervening string
+ // does NOT recursively try all possibilities
+ int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
+
+ int next;
+ if (patternOffset >= tempLength) {
+ next = source.length();
+ }else{
+ next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset);
+ }
+
+ if (next < 0) {
+ pos.setErrorIndex(sourceOffset);
+ return null; // leave index as is to signal error
+ } else {
+ String strValue= source.substring(sourceOffset,next);
+ if (!strValue.equals("{"+argumentNumbers[i]+"}"))
+ resultArray[argumentNumbers[i]]
+ = source.substring(sourceOffset,next);
+ sourceOffset = next;
+ }
+ } else {
+ tempStatus.setIndex(sourceOffset);
+ resultArray[argumentNumbers[i]]
+ = formats[i].parseObject(source,tempStatus);
+ if (tempStatus.getIndex() == sourceOffset) {
+ pos.setErrorIndex(sourceOffset);
+ return null; // leave index as is to signal error
+ }
+ sourceOffset = tempStatus.getIndex(); // update
+ }
+ }
+ int len = pattern.length() - patternOffset;
+ if (len == 0 || pattern.regionMatches(patternOffset,
+ source, sourceOffset, len)) {
+ pos.setIndex(sourceOffset + len);
+ } else {
+ pos.setErrorIndex(sourceOffset);
+ return null; // leave index as is to signal error
+ }
+ return resultArray;
+ }
+
+ /**
+ * Parses text from the beginning of the given string to produce an object
+ * array.
+ * The method may not use the entire text of the given string.
+ * <p>
+ * See the {@link #parse(String, ParsePosition)} method for more information
+ * on message parsing.
+ *
+ * @param source A <code>String</code> whose beginning should be parsed.
+ * @return An <code>Object</code> array parsed from the string.
+ * @exception ParseException if the beginning of the specified string
+ * cannot be parsed.
+ * @stable ICU 3.0
+ */
+ public Object[] parse(String source) throws ParseException {
+ ParsePosition pos = new ParsePosition(0);
+ Object[] result = parse(source, pos);
+ if (pos.getIndex() == 0) // unchanged, returned object is null
+ throw new ParseException("MessageFormat parse error!", pos.getErrorIndex());
+
+ return result;
+ }
+
+ /**
+ * Parses text from a string to produce an object array.
+ * <p>
+ * The method attempts to parse text starting at the index given by
+ * <code>pos</code>.
+ * If parsing succeeds, then the index of <code>pos</code> is updated
+ * to the index after the last character used (parsing does not necessarily
+ * use all characters up to the end of the string), and the parsed
+ * object array is returned. The updated <code>pos</code> can be used to
+ * indicate the starting point for the next call to this method.
+ * If an error occurs, then the index of <code>pos</code> is not
+ * changed, the error index of <code>pos</code> is set to the index of
+ * the character where the error occurred, and null is returned.
+ * <p>
+ * See the {@link #parse(String, ParsePosition)} method for more information
+ * on message parsing.
+ *
+ * @param source A <code>String</code>, part of which should be parsed.
+ * @param pos A <code>ParsePosition</code> object with index and error
+ * index information as described above.
+ * @return An <code>Object</code> array parsed from the string. In case of
+ * error, returns null.
+ * @exception NullPointerException if <code>pos</code> is null.
+ * @stable ICU 3.0
+ */
+ public Object parseObject(String source, ParsePosition pos) {
+ return parse(source, pos);
+ }
+
+ /**
+ * Creates and returns a copy of this object.
+ *
+ * @return a clone of this instance.
+ * @stable ICU 3.0
+ */
+ public Object clone() {
+ MessageFormat other = (MessageFormat) super.clone();
+
+ // clone arrays. Can't do with utility because of bug in Cloneable
+ other.formats = (Format[]) formats.clone(); // shallow clone
+ for (int i = 0; i < formats.length; ++i) {
+ if (formats[i] != null)
+ other.formats[i] = (Format)formats[i].clone();
+ }
+ // for primitives or immutables, shallow clone is enough
+ other.offsets = (int[]) offsets.clone();
+ other.argumentNumbers = (int[]) argumentNumbers.clone();
+
+ return other;
+ }
+
+ /**
+ * Equality comparison between two message format objects
+ * @stable ICU 3.0
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) // quick check
+ return true;
+ if (obj == null || getClass() != obj.getClass())
+ return false;
+ MessageFormat other = (MessageFormat) obj;
+ return (maxOffset == other.maxOffset
+ && pattern.equals(other.pattern)
+ && Utility.objectEquals(ulocale, other.ulocale) // does null check
+ && Utility.arrayEquals(offsets,other.offsets)
+ && Utility.arrayEquals(argumentNumbers,other.argumentNumbers)
+ && Utility.arrayEquals(formats,other.formats));
+ }
+
+ /**
+ * Generates a hash code for the message format object.
+ * @stable ICU 3.0
+ */
+ public int hashCode() {
+ return pattern.hashCode(); // enough for reasonable distribution
+ }
+
+// TODO Do not remove, this is API in JDK that we need to implement
+// /**
+// * Defines constants that are used as attribute keys in the
+// * <code>AttributedCharacterIterator</code> returned
+// * from <code>MessageFormat.formatToCharacterIterator</code>.
+// * @draft ICU 3.0
+// * @deprecated This is a draft API and might change in a future release of ICU.
+// */
+// public static class Field extends Format.Field {
+// /**
+// * Creates a Field with the specified name.
+// *
+// * @param name Name of the attribute
+// */
+// protected Field(String name) {
+// super(name);
+// }
+//
+// /**
+// * Resolves instances being deserialized to the predefined constants.
+// *
+// * @throws InvalidObjectException if the constant could not be
+// * resolved.
+// * @return resolved MessageFormat.Field constant
+// */
+// protected Object readResolve() throws InvalidObjectException {
+// if (this.getClass() != MessageFormat.Field.class) {
+// throw new InvalidObjectException("subclass didn't correctly implement readResolve");
+// }
+//
+// return ARGUMENT;
+// }
+//
+// //
+// // The constants
+// //
+//
+// /**
+// * Constant identifying a portion of a message that was generated
+// * from an argument passed into <code>formatToCharacterIterator</code>.
+// * The value associated with the key will be an <code>Integer</code>
+// * indicating the index in the <code>arguments</code> array of the
+// * argument from which the text was generated.
+// */
+// public final static Field ARGUMENT =
+// new Field("message argument field");
+// }
+
+ // ===========================privates============================
+
+ /**
+ * The locale to use for formatting numbers and dates.
+ * This is no longer used, and here only for serialization compatibility.
+ * @serial
+ */
+ private Locale locale;
+
+ /**
+ * The locale to use for formatting numbers and dates.
+ * @serial
+ */
+ private ULocale ulocale;
+
+ /**
+ * The string that the formatted values are to be plugged into. In other words, this
+ * is the pattern supplied on construction with all of the {} expressions taken out.
+ * @serial
+ */
+ private String pattern = "";
+
+ /** The initially expected number of subformats in the format */
+ private static final int INITIAL_FORMATS = 10;
+
+ /**
+ * An array of formatters, which are used to format the arguments.
+ * @serial
+ */
+ private Format[] formats = new Format[INITIAL_FORMATS];
+
+ /**
+ * The positions where the results of formatting each argument are to be inserted
+ * into the pattern.
+ * @serial
+ */
+ private int[] offsets = new int[INITIAL_FORMATS];
+
+ /**
+ * The argument numbers corresponding to each formatter. (The formatters are stored
+ * in the order they occur in the pattern, not in the order in which the arguments
+ * are specified.)
+ * @serial
+ */
+ private int[] argumentNumbers = new int[INITIAL_FORMATS];
+
+ /**
+ * One less than the number of entries in <code>offsets</code>. Can also be thought of
+ * as the index of the highest-numbered element in <code>offsets</code> that is being used.
+ * All of these arrays should have the same number of elements being used as <code>offsets</code>
+ * does, and so this variable suffices to tell us how many entries are in all of them.
+ * @serial
+ */
+ private int maxOffset = -1;
+
+ /**
+ * Internal routine used by format. If <code>characterIterators</code> is
+ * non-null, AttributedCharacterIterator will be created from the
+ * subformats as necessary. If <code>characterIterators</code> is null
+ * and <code>fp</code> is non-null and identifies
+ * <code>Field.MESSAGE_ARGUMENT</code>, the location of
+ * the first replaced argument will be set in it.
+ *
+ * @exception IllegalArgumentException if an argument in the
+ * <code>arguments</code> array is not of the type
+ * expected by the format element(s) that use it.
+ */
+ private StringBuffer subformat(Object[] arguments, StringBuffer result,
+ FieldPosition fp
+ /*, List characterIterators*/) {
+ // note: this implementation assumes a fast substring & index.
+ // if this is not true, would be better to append chars one by one.
+ int lastOffset = 0;
+ int last = result.length();
+ for (int i = 0; i <= maxOffset; ++i) {
+ result.append(pattern.substring(lastOffset, offsets[i]));
+ lastOffset = offsets[i];
+ int argumentNumber = argumentNumbers[i];
+ if (arguments == null || argumentNumber >= arguments.length) {
+ result.append("{" + argumentNumber + "}");
+ continue;
+ }
+ // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
+ if (false) { // if (argRecursion == 3){
+ // prevent loop!!!
+ result.append('\uFFFD');
+ } else {
+ Object obj = arguments[argumentNumber];
+ String arg = null;
+ Format subFormatter = null;
+ if (obj == null) {
+ arg = "null";
+ } else if (formats[i] != null) {
+ subFormatter = formats[i];
+ if (subFormatter instanceof ChoiceFormat) {
+ arg = formats[i].format(obj);
+ if (arg.indexOf('{') >= 0) {
+ subFormatter = new MessageFormat(arg, ulocale);
+ obj = arguments;
+ arg = null;
+ }
+ }
+ } else if (obj instanceof Number) {
+ // format number if can
+ subFormatter = NumberFormat.getInstance(ulocale);
+ } else if (obj instanceof Date) {
+ // format a Date if can
+ subFormatter = DateFormat.getDateTimeInstance(
+ DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix
+ } else if (obj instanceof String) {
+ arg = (String) obj;
+
+ } else {
+ arg = obj.toString();
+ if (arg == null) arg = "null";
+ }
+
+ // At this point we are in two states, either subFormatter
+ // is non-null indicating we should format obj using it,
+ // or arg is non-null and we should use it as the value.
+
+// TODO Do not remove, this is API in JDK that we need to implement
+// if (characterIterators != null) {
+// // If characterIterators is non-null, it indicates we need
+// // to get the CharacterIterator from the child formatter.
+// if (last != result.length()) {
+// characterIterators.add(
+// createAttributedCharacterIterator(result.substring
+// (last)));
+// last = result.length();
+// }
+// if (subFormatter != null) {
+// AttributedCharacterIterator subIterator =
+// subFormatter.formatToCharacterIterator(obj);
+//
+// append(result, subIterator);
+// if (last != result.length()) {
+// characterIterators.add(
+// createAttributedCharacterIterator(
+// subIterator, Field.ARGUMENT,
+// new Integer(argumentNumber)));
+// last = result.length();
+// }
+// arg = null;
+// }
+// if (arg != null && arg.length() > 0) {
+// result.append(arg);
+// characterIterators.add(
+// createAttributedCharacterIterator(
+// arg, Field.ARGUMENT,
+// new Integer(argumentNumber)));
+// last = result.length();
+// }
+// }
+// else
+ {
+ if (subFormatter != null) {
+ arg = subFormatter.format(obj);
+ }
+// last = result.length(); // Useless? [alan]
+ result.append(arg);
+// TODO Do not remove, this is JDK API we need to implement.
+// if (i == 0 && fp != null && Field.ARGUMENT.equals(
+// fp.getFieldAttribute())) {
+// fp.setBeginIndex(last);
+// fp.setEndIndex(result.length());
+// }
+ last = result.length();
+ }
+ }
+ }
+ result.append(pattern.substring(lastOffset, pattern.length()));
+// TODO Do not remove, this is JDK API we need to implement.
+// if (characterIterators != null && last != result.length()) {
+// characterIterators.add(createAttributedCharacterIterator(
+// result.substring(last)));
+// }
+ return result;
+ }
+
+// TODO Do not remove, this is JDK API we need to implement.
+// /**
+// * Convenience method to append all the characters in
+// * <code>iterator</code> to the StringBuffer <code>result</code>.
+// */
+// private void append(StringBuffer result, CharacterIterator iterator) {
+// if (iterator.first() != CharacterIterator.DONE) {
+// char aChar;
+//
+// result.append(iterator.first());
+// while ((aChar = iterator.next()) != CharacterIterator.DONE) {
+// result.append(aChar);
+// }
+// }
+// }
+
+ private static final String[] typeList =
+ {"", "number", "date", "time", "choice", "spellout", "ordinal", "duration"};
+ private static final int
+ TYPE_EMPTY = 0,
+ TYPE_NUMBER = 1,
+ TYPE_DATE = 2,
+ TYPE_TIME = 3,
+ TYPE_CHOICE = 4,
+ TYPE_SPELLOUT = 5,
+ TYPE_ORDINAL = 6,
+ TYPE_DURATION = 7;
+
+ private static final String[] modifierList =
+ {"", "currency", "percent", "integer"};
+ private static final int
+ MODIFIER_EMPTY = 0,
+ MODIFIER_CURRENCY = 1,
+ MODIFIER_PERCENT = 2,
+ MODIFIER_INTEGER = 3;
+
+ private static final String[] dateModifierList =
+ {"", "short", "medium", "long", "full"};
+ private static final int
+ DATE_MODIFIER_EMPTY = 0,
+ DATE_MODIFIER_SHORT = 1,
+ DATE_MODIFIER_MEDIUM = 2,
+ DATE_MODIFIER_LONG = 3,
+ DATE_MODIFIER_FULL = 4;
+
+ private void makeFormat(int position, int offsetNumber,
+ StringBuffer[] segments)
+ {
+ // get the argument number
+ int argumentNumber;
+ try {
+ argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("can't parse argument number " + segments[1]);
+ }
+ if (argumentNumber < 0) {
+ throw new IllegalArgumentException("negative argument number " + argumentNumber);
+ }
+
+ // resize format information arrays if necessary
+ if (offsetNumber >= formats.length) {
+ int newLength = formats.length * 2;
+ Format[] newFormats = new Format[newLength];
+ int[] newOffsets = new int[newLength];
+ int[] newArgumentNumbers = new int[newLength];
+ System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);
+ System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);
+ System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1);
+ formats = newFormats;
+ offsets = newOffsets;
+ argumentNumbers = newArgumentNumbers;
+ }
+ int oldMaxOffset = maxOffset;
+ maxOffset = offsetNumber;
+ offsets[offsetNumber] = segments[0].length();
+ argumentNumbers[offsetNumber] = argumentNumber;
+
+ // now get the format
+ Format newFormat = null;
+ switch (findKeyword(segments[2].toString(), typeList)) {
+ case TYPE_EMPTY:
+ break;
+ case TYPE_NUMBER:
+ switch (findKeyword(segments[3].toString(), modifierList)) {
+ case MODIFIER_EMPTY:
+ newFormat = NumberFormat.getInstance(ulocale);
+ break;
+ case MODIFIER_CURRENCY:
+ newFormat = NumberFormat.getCurrencyInstance(ulocale);
+ break;
+ case MODIFIER_PERCENT:
+ newFormat = NumberFormat.getPercentInstance(ulocale);
+ break;
+ case MODIFIER_INTEGER:
+ newFormat = NumberFormat.getIntegerInstance(ulocale);
+ break;
+ default: // pattern
+ newFormat = new DecimalFormat(segments[3].toString(), new DecimalFormatSymbols(ulocale));
+ break;
+ }
+ break;
+ case TYPE_DATE:
+ switch (findKeyword(segments[3].toString(), dateModifierList)) {
+ case DATE_MODIFIER_EMPTY:
+ newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
+ break;
+ case DATE_MODIFIER_SHORT:
+ newFormat = DateFormat.getDateInstance(DateFormat.SHORT, ulocale);
+ break;
+ case DATE_MODIFIER_MEDIUM:
+ newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
+ break;
+ case DATE_MODIFIER_LONG:
+ newFormat = DateFormat.getDateInstance(DateFormat.LONG, ulocale);
+ break;
+ case DATE_MODIFIER_FULL:
+ newFormat = DateFormat.getDateInstance(DateFormat.FULL, ulocale);
+ break;
+ default:
+ newFormat = new SimpleDateFormat(segments[3].toString(), ulocale);
+ break;
+ }
+ break;
+ case TYPE_TIME:
+ switch (findKeyword(segments[3].toString(), dateModifierList)) {
+ case DATE_MODIFIER_EMPTY:
+ newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
+ break;
+ case DATE_MODIFIER_SHORT:
+ newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, ulocale);
+ break;
+ case DATE_MODIFIER_MEDIUM:
+ newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
+ break;
+ case DATE_MODIFIER_LONG:
+ newFormat = DateFormat.getTimeInstance(DateFormat.LONG, ulocale);
+ break;
+ case DATE_MODIFIER_FULL:
+ newFormat = DateFormat.getTimeInstance(DateFormat.FULL, ulocale);
+ break;
+ default:
+ newFormat = new SimpleDateFormat(segments[3].toString(), ulocale);
+ break;
+ }
+ break;
+ case TYPE_CHOICE:
+ try {
+ newFormat = new ChoiceFormat(segments[3].toString());
+ } catch (Exception e) {
+ maxOffset = oldMaxOffset;
+ throw new IllegalArgumentException(
+ "Choice Pattern incorrect");
+ }
+ break;
+ case TYPE_SPELLOUT:
+ {
+ RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, RuleBasedNumberFormat.SPELLOUT);
+ String ruleset = segments[3].toString().trim();
+ if (ruleset.length() != 0) {
+ try {
+ rbnf.setDefaultRuleSet(ruleset);
+ }
+ catch (Exception e) {
+ // warn invalid ruleset
+ }
+ }
+ newFormat = rbnf;
+ }
+ break;
+ case TYPE_ORDINAL:
+ {
+ RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, RuleBasedNumberFormat.ORDINAL);
+ String ruleset = segments[3].toString().trim();
+ if (ruleset.length() != 0) {
+ try {
+ rbnf.setDefaultRuleSet(ruleset);
+ }
+ catch (Exception e) {
+ // warn invalid ruleset
+ }
+ }
+ newFormat = rbnf;
+ }
+ break;
+ case TYPE_DURATION:
+ {
+ RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, RuleBasedNumberFormat.DURATION);
+ String ruleset = segments[3].toString().trim();
+ if (ruleset.length() != 0) {
+ try {
+ rbnf.setDefaultRuleSet(ruleset);
+ }
+ catch (Exception e) {
+ // warn invalid ruleset
+ }
+ }
+ newFormat = rbnf;
+ }
+ break;
+ default:
+ maxOffset = oldMaxOffset;
+ throw new IllegalArgumentException("unknown format type at ");
+ }
+ formats[offsetNumber] = newFormat;
+ segments[1].setLength(0); // throw away other segments
+ segments[2].setLength(0);
+ segments[3].setLength(0);
+ }
+
+ private static final int findKeyword(String s, String[] list) {
+ s = s.trim().toLowerCase();
+ for (int i = 0; i < list.length; ++i) {
+ if (s.equals(list[i]))
+ return i;
+ }
+ return -1;
+ }
+
+ private static final void copyAndFixQuotes(String source, int start, int end, StringBuffer target) {
+ // added 'gotLB' logic from ICU4C - questionable [alan]
+ boolean gotLB = false;
+ for (int i = start; i < end; ++i) {
+ char ch = source.charAt(i);
+ if (ch == '{') {
+ target.append("'{'");
+ gotLB = true;
+ } else if (ch == '}') {
+ if (gotLB) {
+ target.append(ch);
+ gotLB = false;
+ } else {
+ target.append("'}'");
+ }
+ } else if (ch == '\'') {
+ target.append("''");
+ } else {
+ target.append(ch);
+ }
+ }
+ }
+
+ /**
+ * After reading an object from the input stream, do a simple verification
+ * to maintain class invariants.
+ * @throws InvalidObjectException if the objects read from the stream is invalid.
+ */
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ boolean isValid = maxOffset >= -1
+ && formats.length > maxOffset
+ && offsets.length > maxOffset
+ && argumentNumbers.length > maxOffset;
+ if (isValid) {
+ int lastOffset = pattern.length() + 1;
+ for (int i = maxOffset; i >= 0; --i) {
+ if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {
+ isValid = false;
+ break;
+ } else {
+ lastOffset = offsets[i];
+ }
+ }
+ }
+ if (!isValid) {
+ throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");
+ }
+ if (ulocale == null) {
+ ulocale = ULocale.forLocale(locale);
+ }
+ }
+
+ private static final char SINGLE_QUOTE = '\'';
+ private static final char CURLY_BRACE_LEFT = '{';
+ private static final char CURLY_BRACE_RIGHT = '}';
+
+ private static final int STATE_INITIAL = 0;
+ private static final int STATE_SINGLE_QUOTE = 1;
+ private static final int STATE_IN_QUOTE = 2;
+ private static final int STATE_MSG_ELEMENT = 3;
+
+ /**
+ * Convert an 'apostrophe-friendly' pattern into a standard
+ * pattern. Standard patterns treat all apostrophes as
+ * quotes, which is problematic in some languages, e.g.
+ * French, where apostrophe is commonly used. This utility
+ * assumes that only an unpaired apostrophe immediately before
+ * a brace is a true quote. Other unpaired apostrophes are paired,
+ * and the resulting standard pattern string is returned.
+ *
+ * <p><b>Note</b> it is not guaranteed that the returned pattern
+ * is indeed a valid pattern. The only effect is to convert
+ * between patterns having different quoting semantics.
+ *
+ * @param pattern the 'apostrophe-friendly' patttern to convert
+ * @return the standard equivalent of the original pattern
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static String autoQuoteApostrophe(String pattern) {
+ StringBuffer buf = new StringBuffer(pattern.length()*2);
+ int state = STATE_INITIAL;
+ int braceCount = 0;
+ for (int i = 0, j = pattern.length(); i < j; ++i) {
+ char c = pattern.charAt(i);
+ switch (state) {
+ case STATE_INITIAL:
+ switch (c) {
+ case SINGLE_QUOTE:
+ state = STATE_SINGLE_QUOTE;
+ break;
+ case CURLY_BRACE_LEFT:
+ state = STATE_MSG_ELEMENT;
+ ++braceCount;
+ break;
+ }
+ break;
+ case STATE_SINGLE_QUOTE:
+ switch (c) {
+ case SINGLE_QUOTE:
+ state = STATE_INITIAL;
+ break;
+ case CURLY_BRACE_LEFT:
+ case CURLY_BRACE_RIGHT:
+ state = STATE_IN_QUOTE;
+ break;
+ default:
+ buf.append(SINGLE_QUOTE);
+ state = STATE_INITIAL;
+ break;
+ }
+ break;
+ case STATE_IN_QUOTE:
+ switch (c) {
+ case SINGLE_QUOTE:
+ state = STATE_INITIAL;
+ break;
+ }
+ break;
+ case STATE_MSG_ELEMENT:
+ switch (c) {
+ case CURLY_BRACE_LEFT:
+ ++braceCount;
+ break;
+ case CURLY_BRACE_RIGHT:
+ if (--braceCount == 0) {
+ state = STATE_INITIAL;
+ }
+ break;
+ }
+ break;
+ default: // Never happens.
+ break;
+ }
+ buf.append(c);
+ }
+ // End of scan
+ if (state == STATE_SINGLE_QUOTE || state == STATE_IN_QUOTE) {
+ buf.append(SINGLE_QUOTE);
+ }
+ return new String(buf);
+ }
+}
diff --git a/src/com/ibm/icu/text/NumberFormatServiceShim.java b/src/com/ibm/icu/text/NumberFormatServiceShim.java
new file mode 100644
index 0000000..7e82299
--- /dev/null
+++ b/src/com/ibm/icu/text/NumberFormatServiceShim.java
@@ -0,0 +1,118 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2003-2006, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+
+package com.ibm.icu.text;
+
+import java.util.Locale;
+import java.util.Set;
+import java.util.MissingResourceException;
+
+//import com.ibm.icu.impl.ICULocaleData;
+import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.impl.ICUService;
+import com.ibm.icu.impl.ICUService.Factory;
+import com.ibm.icu.impl.ICUService.Key;
+import com.ibm.icu.impl.ICULocaleService;
+import com.ibm.icu.impl.ICULocaleService.LocaleKey;
+import com.ibm.icu.impl.ICULocaleService.LocaleKeyFactory;
+import com.ibm.icu.text.NumberFormat;
+import com.ibm.icu.text.NumberFormat.NumberFormatFactory;
+import com.ibm.icu.util.ULocale;
+import com.ibm.icu.util.UResourceBundle;
+
+class NumberFormatServiceShim extends NumberFormat.NumberFormatShim {
+
+ Locale[] getAvailableLocales() {
+ if (service.isDefault()) {
+ return ICUResourceBundle.getAvailableLocales(ICUResourceBundle.ICU_BASE_NAME);
+ }
+ return service.getAvailableLocales();
+ }
+
+ ULocale[] getAvailableULocales() {
+ if (service.isDefault()) {
+ return ICUResourceBundle.getAvailableULocales(ICUResourceBundle.ICU_BASE_NAME);
+ }
+ return service.getAvailableULocales();
+ }
+
+ private static final class NFFactory extends LocaleKeyFactory {
+ private NumberFormatFactory delegate;
+
+ NFFactory(NumberFormatFactory delegate) {
+ super(delegate.visible() ? VISIBLE : INVISIBLE);
+
+ this.delegate = delegate;
+ }
+
+ public Object create(Key key, ICUService service) {
+ if (handlesKey(key)) {
+ LocaleKey lkey = (LocaleKey)key;
+ ULocale loc = lkey.canonicalLocale();
+ int kind = lkey.kind();
+
+ Object result = delegate.createFormat(loc, kind);
+ if (result == null) {
+ result = service.getKey(key, null, this);
+ }
+ return result;
+ }
+ return null;
+ }
+
+ protected Set getSupportedIDs() {
+ return delegate.getSupportedLocaleNames();
+ }
+ }
+
+ Object registerFactory(NumberFormatFactory factory) {
+ return service.registerFactory(new NFFactory(factory));
+ }
+
+ boolean unregister(Object registryKey) {
+ return service.unregisterFactory((Factory)registryKey);
+ }
+
+ NumberFormat createInstance(ULocale desiredLocale, int choice) {
+
+ // use service cache
+// if (service.isDefault()) {
+// return NumberFormat.createInstance(desiredLocale, choice);
+// }
+
+ ULocale[] actualLoc = new ULocale[1];
+ if (desiredLocale.equals(ULocale.ROOT)) {
+ desiredLocale = ULocale.ROOT;
+ }
+ NumberFormat fmt = (NumberFormat)service.get(desiredLocale, choice,
+ actualLoc);
+ if (fmt == null) {
+ throw new MissingResourceException("Unable to construct NumberFormat", "", "");
+ }
+ fmt = (NumberFormat)fmt.clone();
+
+ ULocale uloc = actualLoc[0];
+ fmt.setLocale(uloc, uloc); // services make no distinction between actual & valid
+ return fmt;
+ }
+
+ private static class NFService extends ICULocaleService {
+ NFService() {
+ super("NumberFormat");
+
+ class RBNumberFormatFactory extends ICUResourceBundleFactory {
+ protected Object handleCreate(ULocale loc, int kind, ICUService service) {
+ return NumberFormat.createInstance(loc, kind);
+ }
+ }
+
+ this.registerFactory(new RBNumberFormatFactory());
+ markDefault();
+ }
+ }
+ private static ICULocaleService service = new NFService();
+}
diff --git a/src/com/ibm/icu/util/GlobalizationPreferences.java b/src/com/ibm/icu/util/GlobalizationPreferences.java
new file mode 100644
index 0000000..099a123
--- /dev/null
+++ b/src/com/ibm/icu/util/GlobalizationPreferences.java
@@ -0,0 +1,1100 @@
+//##header
+/*
+ *******************************************************************************
+ * Copyright (C) 2004-2006, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+*/
+package com.ibm.icu.util;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+//#ifndef FOUNDATION
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+//#endif
+
+import com.ibm.icu.impl.Utility;
+import com.ibm.icu.impl.ZoneMeta;
+import com.ibm.icu.text.Collator;
+import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.text.DecimalFormat;
+import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.NumberFormat;
+import com.ibm.icu.text.SimpleDateFormat;
+
+/**
+ * This convenience class provides a mechanism for bundling together different
+ * globalization preferences. It includes:
+ * <ul>
+ * <li>A list of locales/languages in preference order</li>
+ * <li>A territory</li>
+ * <li>A currency</li>
+ * <li>A timezone</li>
+ * <li>A calendar</li>
+ * <li>A collator (for language-sensitive sorting, searching, and matching).</li>
+ * <li>Explicit overrides for date/time formats, etc.</li>
+ * </ul>
+ * The class will heuristically compute implicit, heuristic values for the above
+ * based on available data if explicit values are not supplied. These implicit
+ * values can be presented to users for confirmation, or replacement if the
+ * values are incorrect.
+ * <p>
+ * To reset any explicit field so that it will get heuristic values, pass in
+ * null. For example, myPreferences.setLocale(null);
+ * <p>
+ * All of the heuristics can be customized by subclasses, by overriding
+ * getTerritory(), guessCollator(), etc.
+ * <p>
+ * The class also supplies display names for languages, scripts, territories,
+ * currencies, timezones, etc. These are computed according to the
+ * locale/language preference list. Thus, if the preference is Breton; French;
+ * English, then the display name for a language will be returned in Breton if
+ * available, otherwise in French if available, otherwise in English.
+ * <p>
+ * The codes used to reference territory, currency, etc. are as defined elsewhere in ICU,
+ * and are taken from CLDR (which reflects RFC 3066bis usage, ISO 4217, and the
+ * TZ Timezone database identifiers).
+ * <p>
+ * <b>This is at a prototype stage, and has not incorporated all the design
+ * changes that we would like yet; further feedback is welcome.</b></p>
+ * <p>
+ * TODO:<ul>
+ * <li>Separate out base class</li>
+ * <li>Add BreakIterator</li>
+ * <li>Add Holidays</li>
+ * <li>Add convenience to get/take Locale as well as ULocale.</li>
+ * <li>Add getResourceBundle(String baseName, ClassLoader loader);</li>
+ * <li>Add getFallbackLocales();</li>
+ * <li>Add Lenient datetime formatting when that is available.</li>
+ * <li>Should this be serializable?</li>
+ * <li>Other utilities?</li>
+ * </ul>
+ * Note:
+ * <ul>
+ * <li>to get the display name for the first day of the week, use the calendar +
+ * display names.</li>
+ * <li>to get the work days, ask the calendar (when that is available).</li>
+ * <li>to get papersize / measurement system/bidi-orientation, ask the locale
+ * (when that is available there)</li>
+ * <li>to get the field order in a date, and whether a time is 24hour or not,
+ * ask the DateFormat (when that is available there)</li>
+ * <li>it will support HOST locale when it becomes available (it is a special
+ * locale that will ask the services to use the host platform's values).</li>
+ * </ul>
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+public class GlobalizationPreferences implements Freezable {
+ /**
+ * Number Format types
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public static final int CURRENCY = 0, NUMBER = 1, INTEGER = 2, SCIENTIFIC = 3,
+ PERCENT = 4, NUMBER_LIMIT = 5;
+
+ /**
+ * Supplement to DateFormat.FULL, LONG, MEDIUM, SHORT. Indicates
+ * that no value for one of date or time is to be used.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public static final int NONE = 4;
+
+ /**
+ * For selecting a choice of display names
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public static final int
+ LOCALEID = 0, LANGUAGEID = 1, SCRIPTID = 2, TERRITORYID = 3, VARIANTID = 4,
+ KEYWORDID = 5, KEYWORD_VALUEID = 6,
+ CURRENCYID = 7, CURRENCY_SYMBOLID = 8, TIMEZONEID = 9, DISPLAYID_LIMIT = 10;
+
+ /**
+ * Sets the language/locale priority list. If other information is
+ * not (yet) available, this is used to to produce a default value
+ * for the appropriate territory, currency, timezone, etc. The
+ * user should be given the opportunity to correct those defaults
+ * in case they are incorrect.
+ * @param locales list of locales in priority order, eg {"be", "fr"}
+ * for Breton first, then French if that fails.
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setLocales(List locales) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ if (locales.size() == 0) {
+ this.locales = locales.get(0);
+ } else {
+ this.locales = new ArrayList(locales); // clone for safety
+ }
+ return this;
+ }
+
+ /**
+ * Get a copy of the language/locale priority list
+ * @return a copy of the language/locale priority list.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public List getLocales() {
+ List result = new ArrayList(); // clone for safety
+ if (locales == null) {
+ result = guessLocales();
+ } else if (locales instanceof ULocale) {
+ result.add(locales);
+ } else {
+ result.addAll((List)locales);
+ }
+ return result;
+ }
+
+ /**
+ * Convenience function for getting the locales in priority order
+ * @param index The index (0..n) of the desired item.
+ * @return desired item.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public ULocale getLocale(int index) {
+ if (locales == null) {
+ return (ULocale)guessLocales().get(index);
+ } else if (locales instanceof ULocale) {
+ if (index != 0) throw new IllegalArgumentException("Out of bounds: " + index);
+ return (ULocale)locales;
+ } else {
+ return (ULocale)((List)locales).get(index);
+ }
+ }
+
+ /**
+ * Convenience routine for setting the language/locale priority
+ * list from an array.
+ * @see #setLocales(List locales)
+ * @param uLocales list of locales in an array
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setLocales(ULocale[] uLocales) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ return setLocales(Arrays.asList(uLocales));
+ }
+ /**
+ * Convenience routine for setting the language/locale priority
+ * list from a single locale/language.
+ * @see #setLocales(List locales)
+ * @param uLocale single locale
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setLocale(ULocale uLocale) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ return setLocales(new ULocale[]{uLocale});
+ }
+
+//#ifndef FOUNDATION
+ /**
+ * Convenience routine for setting the locale priority list from
+ * an Accept-Language string.
+ * @see #setLocales(List locales)
+ * @param acceptLanguageString Accept-Language list, as defined by
+ * Section 14.4 of the RFC 2616 (HTTP 1.1)
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setLocales(String acceptLanguageString) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ /*
+ Accept-Language = "Accept-Language" ":" 1#( language-range [ ";" "q" "=" qvalue ] )
+ x matches x-...
+ */
+ // reorders in quality order
+ // don't care that it is not very efficient right now
+ Matcher acceptMatcher = Pattern.compile("\\s*([-_a-zA-Z]+)(;q=([.0-9]+))?\\s*").matcher("");
+ Map reorder = new TreeMap();
+ String[] pieces = acceptLanguageString.split(",");
+
+ for (int i = 0; i < pieces.length; ++i) {
+ Double qValue = new Double(1);
+ try {
+ if (!acceptMatcher.reset(pieces[i]).matches()) {
+ throw new IllegalArgumentException();
+ }
+ String qValueString = acceptMatcher.group(3);
+ if (qValueString != null) qValue = new Double(Double.parseDouble(qValueString));
+ } catch (Exception e) {
+ throw new IllegalArgumentException("element '" + pieces[i] +
+ "' is not of the form '<locale>{;q=<number>}");
+ }
+ List items = (List)reorder.get(qValue);
+ if (items == null) reorder.put(qValue, items = new LinkedList());
+ items.add(0, acceptMatcher.group(1)); // reverse order, will reverse again
+ }
+ // now read out in reverse order
+ List result = new ArrayList();
+ for (Iterator it = reorder.keySet().iterator(); it.hasNext();) {
+ Object key = it.next();
+ List items = (List)reorder.get(key);
+ for (Iterator it2 = items.iterator(); it2.hasNext();) {
+ result.add(0, new ULocale((String)it2.next()));
+ }
+ }
+ return setLocales(result);
+ }
+//#endif
+
+ /**
+ * Sets the territory, which is a valid territory according to for
+ * RFC 3066 (or successor). If not otherwise set, default
+ * currency and timezone values will be set from this. The user
+ * should be given the opportunity to correct those defaults in
+ * case they are incorrect.
+ * @param territory code
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setTerritory(String territory) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ this.territory = territory;
+ return this;
+ }
+
+ /**
+ * Gets the territory setting. If it wasn't explicitly set, it is
+ * computed from the general locale setting.
+ * @return territory code, explicit or implicit.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public String getTerritory() {
+ if (territory == null) return guessTerritory();
+ return territory; // immutable, so don't need to clone
+ }
+
+ /**
+ * Sets the currency code. If this has not been set, uses default for territory.
+ * @param currency Valid ISO 4217 currency code.
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setCurrency(Currency currency) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ this.currency = currency;
+ return this;
+ }
+
+ /**
+ * Get a copy of the currency computed according to the settings.
+ * @return currency code, explicit or implicit.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public Currency getCurrency() {
+ if (currency == null) return guessCurrency();
+ return currency; // immutable, so don't have to clone
+ }
+
+ /**
+ * Sets the calendar. If this has not been set, uses default for territory.
+ * @param calendar arbitrary calendar
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setCalendar(Calendar calendar) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ this.calendar = calendar;
+ return this;
+ }
+
+ /**
+ * Get a copy of the calendar according to the settings.
+ * @return calendar explicit or implicit.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public Calendar getCalendar() {
+ if (calendar == null) return guessCalendar();
+ Calendar temp = (Calendar) calendar.clone(); // clone for safety
+ temp.setTimeZone(getTimeZone());
+ return temp;
+ }
+
+ /**
+ * Sets the timezone ID. If this has not been set, uses default for territory.
+ * @param timezone a valid TZID (see UTS#35).
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setTimeZone(TimeZone timezone) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ this.timezone = timezone;
+ return this;
+ }
+
+ /**
+ * Get the timezone. It was either explicitly set, or is
+ * heuristically computed from other settings.
+ * @return timezone, either implicitly or explicitly set
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public TimeZone getTimeZone() {
+ if (timezone == null) return guessTimeZone();
+ return (TimeZone) timezone.clone(); // clone for safety
+ }
+
+ /**
+ * Get a copy of the collator according to the settings.
+ * @return collator explicit or implicit.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public Collator getCollator() {
+ if (collator == null) return guessCollator();
+ try {
+ return (Collator) collator.clone(); // clone for safety
+ } catch (CloneNotSupportedException e) {
+ throw new InternalError("Error in cloning collator");
+ }
+ }
+
+ /**
+ * Explicitly set the collator for this object.
+ * @param collator
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setCollator(Collator collator) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ this.collator = collator;
+ return this;
+ }
+
+ /**
+ * Set the date locale.
+ * @param dateLocale If not null, overrides the locale priority list for all the date formats.
+ * @return this, for chaining
+ */
+ public GlobalizationPreferences setDateLocale(ULocale dateLocale) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ this.dateLocale = dateLocale;
+ return this;
+ }
+
+ /**
+ * Gets the date locale, to be used in computing date formats. Overrides the general locale setting.
+ * @return date locale. Null if none was set explicitly.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public ULocale getDateLocale() {
+ return dateLocale != null ? dateLocale : getLocale(0);
+ }
+
+ /**
+ * Set the number locale.
+ * @param numberLocale If not null, overrides the locale priority list for all the date formats.
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setNumberLocale(ULocale numberLocale) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ this.numberLocale = numberLocale;
+ return this;
+ }
+
+ /**
+ * Get the current number locale setting used for getNumberFormat.
+ * @return number locale. Null if none was set explicitly.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public ULocale getNumberLocale() {
+ return numberLocale != null ? numberLocale : getLocale(0);
+ }
+
+ /**
+ * Get the display name for an ID: language, script, territory, currency, timezone...
+ * Uses the language priority list to do so.
+ * @param id language code, script code, ...
+ * @param type specifies the type of the ID: LANGUAGE, etc.
+ * @return the display name
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public String getDisplayName(String id, int type) {
+ String result = id;
+ for (Iterator it = getLocales().iterator(); it.hasNext();) {
+ ULocale locale = (ULocale) it.next();
+ switch (type) {
+ case LOCALEID:
+ result = ULocale.getDisplayName(id, locale);
+ break;
+ case LANGUAGEID:
+ result = ULocale.getDisplayLanguage(id, locale);
+ break;
+ case SCRIPTID:
+ result = ULocale.getDisplayScript("und-" + id, locale);
+ break;
+ case TERRITORYID:
+ result = ULocale.getDisplayCountry("und-" + id, locale);
+ break;
+ case VARIANTID:
+ // TODO fix variant parsing
+ result = ULocale.getDisplayVariant("und-QQ-" + id, locale);
+ break;
+ case KEYWORDID:
+ result = ULocale.getDisplayKeyword(id, locale);
+ break;
+ case KEYWORD_VALUEID:
+ String[] parts = new String[2];
+ Utility.split(id,'=',parts);
+ result = ULocale.getDisplayKeywordValue("und@"+id, parts[0], locale);
+ // TODO fix to tell when successful
+ if (result.equals(parts[1])) continue;
+ break;
+ case CURRENCY_SYMBOLID:
+ case CURRENCYID:
+ Currency temp = new Currency(id);
+ result =temp.getName(locale, type==CURRENCYID
+ ? Currency.LONG_NAME
+ : Currency.SYMBOL_NAME, new boolean[1]);
+ // TODO: have method that doesn't take parameter. Add
+ // function to determine whether string is choice
+ // format.
+ // TODO: have method that doesn't require us
+ // to create a currency
+ break;
+ case TIMEZONEID:
+ SimpleDateFormat dtf = new SimpleDateFormat("vvvv",locale);
+ dtf.setTimeZone(TimeZone.getTimeZone(id));
+ result = dtf.format(new Date());
+ // TODO, have method that doesn't require us to create a timezone
+ // fix other hacks
+ // hack for couldn't match
+ // note, compiling with FOUNDATION omits this check for now
+//#ifndef FOUNDATION
+ if (badTimeZone.reset(result).matches()) continue;
+//#endif
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
+ if (!id.equals(result)) return result;
+ // TODO need better way of seeing if we fell back to root!!
+ // This will not work at all for lots of stuff
+ }
+ return result;
+ }
+//#ifndef FOUNDATION
+ // TODO remove need for this
+ private static final Matcher badTimeZone = Pattern.compile("[A-Z]{2}|.*\\s\\([A-Z]{2}\\)").matcher("");
+//#endif
+
+
+ /**
+ * Set an explicit date format. Overrides both the date locale,
+ * and the locale priority list for a particular combination of
+ * dateStyle and timeStyle. NONE should be used if for the style,
+ * where only the date or time format individually is being set.
+ * @param dateStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
+ * @param timeStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
+ * @param format
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, DateFormat format) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ if (dateFormats == null) dateFormats = new Object[NONE+1][NONE+1];
+ dateFormats[dateStyle][timeStyle] = (DateFormat) format.clone(); // for safety
+ return this;
+ }
+
+ /**
+ * Set an explicit date format. Overrides both the date locale,
+ * and the locale priority list for a particular combination of
+ * dateStyle and timeStyle. NONE should be used if for the style,
+ * where only the date or time format individually is being set.
+ * @param dateStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
+ * @param timeStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
+ * @param formatPattern date pattern, eg "yyyy-MMM-dd"
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, String formatPattern) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ if (dateFormats == null) dateFormats = new Object[NONE+1][NONE+1];
+ // test the format to make sure it won't throw an error later
+ new SimpleDateFormat(formatPattern, getDateLocale());
+ dateFormats[dateStyle][timeStyle] = formatPattern; // for safety
+ return this;
+ }
+
+ /**
+ * Gets a date format according to the current settings. If there
+ * is an explicit (non-null) date/time format set, a copy of that
+ * is returned. Otherwise, if there is a non-null date locale,
+ * that is used. Otherwise, the language priority list is
+ * used. NONE should be used for the style, where only the date or
+ * time format individually is being gotten.
+ * @param dateStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
+ * @param timeStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
+ * @return a DateFormat, according to the above description
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public DateFormat getDateFormat(int dateStyle, int timeStyle) {
+ try {
+ DateFormat result = null;
+ if (dateFormats != null) { // and override can either be a string or a pattern
+ Object temp = dateFormats[dateStyle][timeStyle];
+ if (temp instanceof DateFormat) {
+ result = (DateFormat) temp;
+ } else {
+ result = new SimpleDateFormat((String)temp, getDateLocale());
+ }
+ }
+ if (result != null) {
+ result = (DateFormat) result.clone(); // clone for safety
+ result.setCalendar(getCalendar());
+ } else {
+ // In the case of date formats, we don't have to look at more than one
+ // locale. May be different for other cases
+ // TODO Make this one function.
+ if (timeStyle == NONE) {
+ result = DateFormat.getDateInstance(getCalendar(), dateStyle, getDateLocale());
+ } else if (dateStyle == NONE) {
+ result = DateFormat.getTimeInstance(getCalendar(), timeStyle, getDateLocale());
+ } else {
+ result = DateFormat.getDateTimeInstance(getCalendar(), dateStyle, timeStyle, getDateLocale());
+ }
+ }
+ return result;
+ } catch (RuntimeException e) {
+ IllegalArgumentException ex = new IllegalArgumentException("Cannot create DateFormat");
+//#ifndef FOUNDATION
+ ex.initCause(e);
+//#endif
+ throw ex;
+ }
+ }
+
+ /**
+ * Gets a number format according to the current settings. If
+ * there is an explicit (non-null) number format set, a copy of
+ * that is returned. Otherwise, if there is a non-null number
+ * locale, that is used. Otherwise, the language priority list is
+ * used. NONE should be used for the style, where only the date or
+ * time format individually is being gotten.
+ * @param style CURRENCY, NUMBER, INTEGER, SCIENTIFIC, PERCENT
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public NumberFormat getNumberFormat(int style) {
+ try {
+ NumberFormat result = null;
+ if (numberFormats != null) {
+ Object temp = numberFormats[style];
+ if (temp instanceof NumberFormat) {
+ result = (NumberFormat) temp;
+ } else {
+ result = new DecimalFormat((String)temp, new DecimalFormatSymbols(getDateLocale()));
+ }
+ }
+ if (result != null) {
+ result = (NumberFormat) result.clone(); // clone for safety (later optimize)
+ if (style == CURRENCY) {
+ result.setCurrency(getCurrency());
+ }
+ return result;
+ }
+ // In the case of date formats, we don't have to look at more than one
+ // locale. May be different for other cases
+ switch (style) {
+ case NUMBER: return NumberFormat.getInstance(getNumberLocale());
+ case SCIENTIFIC: return NumberFormat.getScientificInstance(getNumberLocale());
+ case INTEGER: return NumberFormat.getIntegerInstance(getNumberLocale());
+ case PERCENT: return NumberFormat.getPercentInstance(getNumberLocale());
+ case CURRENCY: result = NumberFormat.getCurrencyInstance(getNumberLocale());
+ result.setCurrency(getCurrency());
+ return result;
+ }
+ } catch (RuntimeException e) {}
+ throw new IllegalArgumentException(); // fix later
+ }
+
+ /**
+ * Sets a number format explicitly. Overrides the number locale
+ * and the general locale settings.
+ * @param style CURRENCY, NUMBER, INTEGER, SCIENTIFIC, PERCENT
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setNumberFormat(int style, DateFormat format) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ if (numberFormats == null) numberFormats = new Object[NUMBER_LIMIT];
+ numberFormats[style] = (NumberFormat) format.clone(); // for safety
+ return this;
+ }
+
+ /**
+ * Sets a number format explicitly. Overrides the number locale
+ * and the general locale settings.
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences setNumberFormat(int style, String formatPattern) {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ if (numberFormats == null) numberFormats = new Object[NUMBER_LIMIT];
+ // check to make sure it compiles
+ new DecimalFormat((String)formatPattern, new DecimalFormatSymbols(getDateLocale()));
+ numberFormats[style] = formatPattern; // for safety
+ return this;
+ }
+
+ /**
+ * Restore the object to the initial state.
+ * @return this, for chaining
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public GlobalizationPreferences reset() {
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify immutable object");
+ }
+ territory = null;
+ calendar = null;
+ collator = null;
+ timezone = null;
+ currency = null;
+ dateFormats = null;
+ numberFormats = null;
+ dateLocale = null;
+ numberLocale = null;
+ locales = null;
+ return this;
+ }
+
+ /**
+ * This function can be overridden by subclasses to use different heuristics.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected String guessTerritory() {
+ String result;
+ // pass through locales to see if there is a territory.
+ for (Iterator it = getLocales().iterator(); it.hasNext();) {
+ ULocale locale = (ULocale)it.next();
+ result = locale.getCountry();
+ if (result.length() != 0) {
+ return result;
+ }
+ }
+ // if not, guess from the first language tag, or maybe from
+ // intersection of languages, eg nl + fr => BE
+ // TODO: fix using real data
+ // for now, just use fixed values
+ ULocale firstLocale = getLocale(0);
+ String language = firstLocale.getLanguage();
+ String script = firstLocale.getScript();
+ result = null;
+ if (script.length() != 0) {
+ result = (String) language_territory_hack_map.get(language + "_" + script);
+ }
+ if (result == null) result = (String) language_territory_hack_map.get(language);
+ if (result == null) result = "US"; // need *some* default
+ return result;
+ }
+
+ /**
+ * This function can be overridden by subclasses to use different heuristics
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected Currency guessCurrency() {
+ return Currency.getInstance(new ULocale("und-" + getTerritory()));
+ }
+
+ /**
+ * This function can be overridden by subclasses to use different heuristics
+ * <b>It MUST return a 'safe' value,
+ * one whose modification will not affect this object.</b>
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected List guessLocales() {
+ List result = new ArrayList(0);
+ result.add(ULocale.getDefault());
+ return result;
+ }
+
+ /**
+ * This function can be overridden by subclasses to use different heuristics.
+ * <b>It MUST return a 'safe' value,
+ * one whose modification will not affect this object.</b>
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected Collator guessCollator() {
+ return Collator.getInstance(getLocale(0));
+ }
+
+ /**
+ * This function can be overridden by subclasses to use different heuristics.
+ * <b>It MUST return a 'safe' value,
+ * one whose modification will not affect this object.</b>
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected TimeZone guessTimeZone() {
+ // TODO fix using real data
+ // for single-zone countries, pick that zone
+ // for others, pick the most populous zone
+ // for now, just use fixed value
+ // NOTE: in a few cases can do better by looking at language.
+ // Eg haw+US should go to Pacific/Honolulu
+ // fr+CA should go to America/Montreal
+ String timezoneString = (String) territory_tzid_hack_map.get(getTerritory());
+ if (timezoneString == null) {
+ String[] attempt = ZoneMeta.getAvailableIDs(getTerritory());
+ if (attempt.length == 0) {
+ timezoneString = "Etc/GMT"; // gotta do something
+ } else {
+ int i;
+ // this all needs to be fixed to use real data. But for now, do slightly better by skipping cruft
+ for (i = 0; i < attempt.length; ++i) {
+ if (attempt[i].indexOf("/") >= 0) break;
+ }
+ if (i > attempt.length) i = 0;
+ timezoneString = attempt[i];
+ }
+ }
+ return TimeZone.getTimeZone(timezoneString);
+ }
+
+ /**
+ * This function can be overridden by subclasses to use different heuristics.
+ * <b>It MUST return a 'safe' value,
+ * one whose modification will not affect this object.</b>
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected Calendar guessCalendar() {
+ // TODO add better API
+ return Calendar.getInstance(new ULocale("und-" + getTerritory()));
+ }
+
+ // PRIVATES
+
+ private Object locales;
+ private String territory;
+ private Currency currency;
+ private TimeZone timezone;
+ private Calendar calendar;
+ private Collator collator;
+
+ private ULocale dateLocale;
+ private Object[][] dateFormats;
+ private ULocale numberLocale;
+ private Object[] numberFormats;
+
+ {
+ reset();
+ }
+
+ /** WARNING: All of this data is temporary, until we start importing from CLDR!!!
+ *
+ */
+ private static final Map language_territory_hack_map = new HashMap();
+ private static final String[][] language_territory_hack = {
+ {"af", "ZA"},
+ {"am", "ET"},
+ {"ar", "SA"},
+ {"as", "IN"},
+ {"ay", "PE"},
+ {"az", "AZ"},
+ {"bal", "PK"},
+ {"be", "BY"},
+ {"bg", "BG"},
+ {"bn", "IN"},
+ {"bs", "BA"},
+ {"ca", "ES"},
+ {"ch", "MP"},
+ {"cpe", "SL"},
+ {"cs", "CZ"},
+ {"cy", "GB"},
+ {"da", "DK"},
+ {"de", "DE"},
+ {"dv", "MV"},
+ {"dz", "BT"},
+ {"el", "GR"},
+ {"en", "US"},
+ {"es", "ES"},
+ {"et", "EE"},
+ {"eu", "ES"},
+ {"fa", "IR"},
+ {"fi", "FI"},
+ {"fil", "PH"},
+ {"fj", "FJ"},
+ {"fo", "FO"},
+ {"fr", "FR"},
+ {"ga", "IE"},
+ {"gd", "GB"},
+ {"gl", "ES"},
+ {"gn", "PY"},
+ {"gu", "IN"},
+ {"gv", "GB"},
+ {"ha", "NG"},
+ {"he", "IL"},
+ {"hi", "IN"},
+ {"ho", "PG"},
+ {"hr", "HR"},
+ {"ht", "HT"},
+ {"hu", "HU"},
+ {"hy", "AM"},
+ {"id", "ID"},
+ {"is", "IS"},
+ {"it", "IT"},
+ {"ja", "JP"},
+ {"ka", "GE"},
+ {"kk", "KZ"},
+ {"kl", "GL"},
+ {"km", "KH"},
+ {"kn", "IN"},
+ {"ko", "KR"},
+ {"kok", "IN"},
+ {"ks", "IN"},
+ {"ku", "TR"},
+ {"ky", "KG"},
+ {"la", "VA"},
+ {"lb", "LU"},
+ {"ln", "CG"},
+ {"lo", "LA"},
+ {"lt", "LT"},
+ {"lv", "LV"},
+ {"mai", "IN"},
+ {"men", "GN"},
+ {"mg", "MG"},
+ {"mh", "MH"},
+ {"mk", "MK"},
+ {"ml", "IN"},
+ {"mn", "MN"},
+ {"mni", "IN"},
+ {"mo", "MD"},
+ {"mr", "IN"},
+ {"ms", "MY"},
+ {"mt", "MT"},
+ {"my", "MM"},
+ {"na", "NR"},
+ {"nb", "NO"},
+ {"nd", "ZA"},
+ {"ne", "NP"},
+ {"niu", "NU"},
+ {"nl", "NL"},
+ {"nn", "NO"},
+ {"no", "NO"},
+ {"nr", "ZA"},
+ {"nso", "ZA"},
+ {"ny", "MW"},
+ {"om", "KE"},
+ {"or", "IN"},
+ {"pa", "IN"},
+ {"pau", "PW"},
+ {"pl", "PL"},
+ {"ps", "PK"},
+ {"pt", "BR"},
+ {"qu", "PE"},
+ {"rn", "BI"},
+ {"ro", "RO"},
+ {"ru", "RU"},
+ {"rw", "RW"},
+ {"sd", "IN"},
+ {"sg", "CF"},
+ {"si", "LK"},
+ {"sk", "SK"},
+ {"sl", "SI"},
+ {"sm", "WS"},
+ {"so", "DJ"},
+ {"sq", "CS"},
+ {"sr", "CS"},
+ {"ss", "ZA"},
+ {"st", "ZA"},
+ {"sv", "SE"},
+ {"sw", "KE"},
+ {"ta", "IN"},
+ {"te", "IN"},
+ {"tem", "SL"},
+ {"tet", "TL"},
+ {"th", "TH"},
+ {"ti", "ET"},
+ {"tg", "TJ"},
+ {"tk", "TM"},
+ {"tkl", "TK"},
+ {"tvl", "TV"},
+ {"tl", "PH"},
+ {"tn", "ZA"},
+ {"to", "TO"},
+ {"tpi", "PG"},
+ {"tr", "TR"},
+ {"ts", "ZA"},
+ {"uk", "UA"},
+ {"ur", "IN"},
+ {"uz", "UZ"},
+ {"ve", "ZA"},
+ {"vi", "VN"},
+ {"wo", "SN"},
+ {"xh", "ZA"},
+ {"zh", "CN"},
+ {"zh_Hant", "TW"},
+ {"zu", "ZA"},
+ {"aa", "ET"},
+ {"byn", "ER"},
+ {"eo", "DE"},
+ {"gez", "ET"},
+ {"haw", "US"},
+ {"iu", "CA"},
+ {"kw", "GB"},
+ {"sa", "IN"},
+ {"sh", "HR"},
+ {"sid", "ET"},
+ {"syr", "SY"},
+ {"tig", "ER"},
+ {"tt", "RU"},
+ {"wal", "ET"}, };
+ static {
+ for (int i = 0; i < language_territory_hack.length; ++i) {
+ language_territory_hack_map.put(language_territory_hack[i][0],language_territory_hack[i][1]);
+ }
+ }
+
+ static final Map territory_tzid_hack_map = new HashMap();
+ static final String[][] territory_tzid_hack = {
+ {"AQ", "Antarctica/McMurdo"},
+ {"AR", "America/Buenos_Aires"},
+ {"AU", "Australia/Sydney"},
+ {"BR", "America/Sao_Paulo"},
+ {"CA", "America/Toronto"},
+ {"CD", "Africa/Kinshasa"},
+ {"CL", "America/Santiago"},
+ {"CN", "Asia/Shanghai"},
+ {"EC", "America/Guayaquil"},
+ {"ES", "Europe/Madrid"},
+ {"GB", "Europe/London"},
+ {"GL", "America/Godthab"},
+ {"ID", "Asia/Jakarta"},
+ {"ML", "Africa/Bamako"},
+ {"MX", "America/Mexico_City"},
+ {"MY", "Asia/Kuala_Lumpur"},
+ {"NZ", "Pacific/Auckland"},
+ {"PT", "Europe/Lisbon"},
+ {"RU", "Europe/Moscow"},
+ {"UA", "Europe/Kiev"},
+ {"US", "America/New_York"},
+ {"UZ", "Asia/Tashkent"},
+ {"PF", "Pacific/Tahiti"},
+ {"FM", "Pacific/Kosrae"},
+ {"KI", "Pacific/Tarawa"},
+ {"KZ", "Asia/Almaty"},
+ {"MH", "Pacific/Majuro"},
+ {"MN", "Asia/Ulaanbaatar"},
+ {"SJ", "Arctic/Longyearbyen"},
+ {"UM", "Pacific/Midway"},
+ };
+ static {
+ for (int i = 0; i < territory_tzid_hack.length; ++i) {
+ territory_tzid_hack_map.put(territory_tzid_hack[i][0],territory_tzid_hack[i][1]);
+ }
+ }
+
+ private boolean frozen;
+
+ /**
+ * @inheritDocs
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public boolean isFrozen() {
+ return frozen;
+ }
+
+ /**
+ * @inheritDocs
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public Object freeze() {
+ frozen = true;
+ return this;
+ }
+
+ /**
+ * @inheritDocs
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ public Object cloneAsThawed() {
+ try {
+ GlobalizationPreferences result = (GlobalizationPreferences) clone();
+ result.frozen = false;
+ return result;
+ } catch (CloneNotSupportedException e) {
+ // will always work
+ return null;
+ }
+ }
+}
+
diff --git a/src/com/ibm/icu/util/LocaleData.java b/src/com/ibm/icu/util/LocaleData.java
new file mode 100644
index 0000000..8eaf729
--- /dev/null
+++ b/src/com/ibm/icu/util/LocaleData.java
@@ -0,0 +1,311 @@
+/*
+ *******************************************************************************
+ * Copyright (C) 2004-2006, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+*/
+package com.ibm.icu.util;
+
+import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.text.UnicodeSet;
+
+/**
+ * A class for accessing miscelleneous data in the locale bundles
+ * @author ram
+ * @stable ICU 2.8
+ */
+public final class LocaleData {
+
+ private static final String EXEMPLAR_CHARS = "ExemplarCharacters";
+ private static final String MEASUREMENT_SYSTEM = "MeasurementSystem";
+ private static final String PAPER_SIZE = "PaperSize";
+ private boolean noSubstitute;
+ private ICUResourceBundle bundle;
+
+ /**
+ * EXType for {@link #getExemplarSet(int, int)}.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final int ES_STANDARD = 0;
+
+ /**
+ * EXType for {@link #getExemplarSet(int, int)}.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final int ES_AUXILIARY = 1;
+
+ /**
+ * Count of EXTypes for {@link #getExemplarSet(int, int)}.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final int ES_COUNT = 2;
+
+ /**
+ * Delimiter type for {@link #getDelimiter(int)}.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final int QUOTATION_START = 0;
+
+ /**
+ * Delimiter type for {@link #getDelimiter(int)}.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final int QUOTATION_END = 1;
+
+ /**
+ * Delimiter type for {@link #getDelimiter(int)}.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final int ALT_QUOTATION_START = 2;
+
+ /**
+ * Delimiter type for {@link #getDelimiter(int)}.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final int ALT_QUOTATION_END = 3;
+
+ /**
+ * Count of delimiter types for {@link #getDelimiter(int)}.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final int DELIMITER_COUNT = 4;
+
+ // private constructor to prevent default construction
+ ///CLOVER:OFF
+ private LocaleData(){}
+ ///CLOVER:ON
+
+ /**
+ * Returns the set of exemplar characters for a locale.
+ *
+ * @param locale Locale for which the exemplar character set
+ * is to be retrieved.
+ * @param options Bitmask for options to apply to the exemplar pattern.
+ * Specify zero to retrieve the exemplar set as it is
+ * defined in the locale data. Specify
+ * UnicodeSet.CASE to retrieve a case-folded exemplar
+ * set. See {@link UnicodeSet#applyPattern(String,
+ * int)} for a complete list of valid options. The
+ * IGNORE_SPACE bit is always set, regardless of the
+ * value of 'options'.
+ * @return The set of exemplar characters for the given locale.
+ * @draft ICU 3.0
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static UnicodeSet getExemplarSet(ULocale locale, int options) {
+ ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
+ String pattern = bundle.getString(EXEMPLAR_CHARS);
+ return new UnicodeSet(pattern, UnicodeSet.IGNORE_SPACE | options);
+ }
+
+ /**
+ * Returns the set of exemplar characters for a locale.
+ *
+ * @param options Bitmask for options to apply to the exemplar pattern.
+ * Specify zero to retrieve the exemplar set as it is
+ * defined in the locale data. Specify
+ * UnicodeSet.CASE to retrieve a case-folded exemplar
+ * set. See {@link UnicodeSet#applyPattern(String,
+ * int)} for a complete list of valid options. The
+ * IGNORE_SPACE bit is always set, regardless of the
+ * value of 'options'.
+ * @param extype The type of exemplar set to be retrieved,
+ * ES_STANDARD or ES_AUXILIARY
+ * @return The set of exemplar characters for the given locale.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public UnicodeSet getExemplarSet(int options, int extype) {
+ String [] exemplarSetTypes = { "ExemplarCharacters", "AuxExemplarCharacters" };
+ ICUResourceBundle stringBundle = bundle.get(exemplarSetTypes[extype]);
+
+ if ( noSubstitute && (stringBundle.getLoadingStatus() == ICUResourceBundle.FROM_ROOT) )
+ return null;
+
+ return new UnicodeSet(stringBundle.getString(), UnicodeSet.IGNORE_SPACE | options);
+ }
+
+ /**
+ * Gets the LocaleData object associated with the ULocale specified in locale
+ *
+ * @param locale Locale with thich the locale data object is associated.
+ * @return A locale data object.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final LocaleData getInstance(ULocale locale) {
+ LocaleData ld = new LocaleData();
+ ld.bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale );
+ ld.noSubstitute = false;
+ return ld;
+ }
+
+ /**
+ * Gets the LocaleData object associated with the default locale
+ *
+ * @return A locale data object.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final LocaleData getInstance() {
+ return LocaleData.getInstance(ULocale.getDefault());
+ }
+
+ /**
+ * Sets the "no substitute" behavior of this locale data object.
+ *
+ * @param setting Value for the no substitute behavior. If TRUE,
+ * methods of this locale data object will return
+ * an error when no data is available for that method,
+ * given the locale ID supplied to the constructor.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public void setNoSubstitute(boolean setting) {
+ noSubstitute = setting;
+ }
+
+ /**
+ * Gets the "no substitute" behavior of this locale data object.
+ *
+ * @return Value for the no substitute behavior. If TRUE,
+ * methods of this locale data object will return
+ * an error when no data is available for that method,
+ * given the locale ID supplied to the constructor.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public boolean getNoSubstitute() {
+ return noSubstitute;
+ }
+
+ /**
+ * Retrieves a delimiter string from the locale data.
+ *
+ * @param type The type of delimiter string desired. Currently,
+ * the valid choices are QUOTATION_START, QUOTATION_END,
+ * ALT_QUOTATION_START, or ALT_QUOTATION_END.
+ * @return The desired delimiter string.
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public String getDelimiter(int type) {
+ String [] delimiterTypes = { "quotationStart",
+ "quotationEnd",
+ "alternateQuotationStart",
+ "alternateQuotationEnd" };
+
+ ICUResourceBundle stringBundle = bundle.get("delimiters").get(delimiterTypes[type]);
+
+ if ( noSubstitute && (stringBundle.getLoadingStatus() == ICUResourceBundle.FROM_ROOT) )
+ return null;
+
+ return new String (stringBundle.getString());
+ }
+
+ /**
+ * Enumeration for representing the measurement systems.
+ * @stable ICU 2.8
+ */
+ public static final class MeasurementSystem{
+ /**
+ * Measurement system specified by Le Système International d'Unités (SI)
+ * otherwise known as Metric system.
+ * @stable ICU 2.8
+ */
+ public static final MeasurementSystem SI = new MeasurementSystem(0);
+
+ /**
+ * Measurement system followed in the United States of America.
+ * @stable ICU 2.8
+ */
+ public static final MeasurementSystem US = new MeasurementSystem(1);
+
+ private int systemID;
+ private MeasurementSystem(int id){
+ systemID = id;
+ }
+
+ private boolean equals(int id){
+ return systemID == id;
+ }
+ }
+
+ /**
+ * Returns the measurement system used in the locale specified by the locale.
+ *
+ * @param locale The locale for which the measurement system to be retrieved.
+ * @return MeasurementSystem the measurement system used in the locale.
+ * @draft ICU 3.0
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final MeasurementSystem getMeasurementSystem(ULocale locale){
+ ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
+ ICUResourceBundle sysBundle = bundle.get(MEASUREMENT_SYSTEM);
+
+ int system = sysBundle.getInt();
+ if(MeasurementSystem.US.equals(system)){
+ return MeasurementSystem.US;
+ }
+ if(MeasurementSystem.SI.equals(system)){
+ return MeasurementSystem.SI;
+ }
+ // return null if the object is null or is not an instance
+ // of integer indicating an error
+ return null;
+ }
+
+ /**
+ * A class that represents the size of letter head
+ * used in the country
+ * @stable ICU 2.8
+ */
+ public static final class PaperSize{
+ private int height;
+ private int width;
+
+ private PaperSize(int h, int w){
+ height = h;
+ width = w;
+ }
+ /**
+ * Retruns the height of the paper
+ * @return the height
+ * @stable ICU 2.8
+ */
+ public int getHeight(){
+ return height;
+ }
+ /**
+ * Returns the width of hte paper
+ * @return the width
+ * @stable ICU 2.8
+ */
+ public int getWidth(){
+ return width;
+ }
+ }
+
+ /**
+ * Returns the size of paper used in the locale. The paper sizes returned are always in
+ * <em> milli-meters<em>.
+ * @param locale The locale for which the measurement system to be retrieved.
+ * @return The paper size used in the locale
+ * @draft ICU 3.0
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final PaperSize getPaperSize(ULocale locale){
+ ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
+ ICUResourceBundle obj = bundle.get(PAPER_SIZE);
+ int[] size = obj.getIntVector();
+ return new PaperSize(size[0], size[1]);
+ }
+}
diff --git a/src/com/ibm/icu/util/ULocale.java b/src/com/ibm/icu/util/ULocale.java
new file mode 100644
index 0000000..ff5d11d
--- /dev/null
+++ b/src/com/ibm/icu/util/ULocale.java
@@ -0,0 +1,2810 @@
+/*
+******************************************************************************
+* Copyright (C) 2003-2006, International Business Machines Corporation and *
+* others. All Rights Reserved. *
+******************************************************************************
+*/
+
+package com.ibm.icu.util;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.TreeMap;
+
+import com.ibm.icu.impl.LocaleUtility;
+import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.lang.UCharacter;
+
+/**
+ * A class analogous to {@link java.util.Locale} that provides additional
+ * support for ICU protocol. In ICU 3.0 this class is enhanced to support
+ * RFC 3066 language identifiers.
+ *
+ * <p>Many classes and services in ICU follow a factory idiom, in
+ * which a factory method or object responds to a client request with
+ * an object. The request includes a locale (the <i>requested</i>
+ * locale), and the returned object is constructed using data for that
+ * locale. The system may lack data for the requested locale, in
+ * which case the locale fallback mechanism will be invoked until a
+ * populated locale is found (the <i>valid</i> locale). Furthermore,
+ * even when a populated locale is found (the <i>valid</i> locale),
+ * further fallback may be required to reach a locale containing the
+ * specific data required by the service (the <i>actual</i> locale).
+ *
+ * <p>ULocale performs <b>'normalization'</b> and <b>'canonicalization'</b> of locale ids.
+ * Normalization 'cleans up' ICU locale ids as follows:
+ * <ul>
+ * <li>language, script, country, variant, and keywords are properly cased<br>
+ * (lower, title, upper, upper, and lower case respectively)</li>
+ * <li>hyphens used as separators are converted to underscores</li>
+ * <li>three-letter language and country ids are converted to two-letter
+ * equivalents where available</li>
+ * <li>surrounding spaces are removed from keywords and values</li>
+ * <li>if there are multiple keywords, they are put in sorted order</li>
+ * </ul>
+ * Canonicalization additionally performs the following:
+ * <ul>
+ * <li>POSIX ids are converted to ICU format IDs</li>
+ * <li>'grandfathered' 3066 ids are converted to ICU standard form</li>
+ * <li>'PREEURO' and 'EURO' variants are converted to currency keyword form, with the currency
+ * id appropriate to the country of the locale (for PREEURO) or EUR (for EURO).
+ * </ul>
+ * All ULocale constructors automatically normalize the locale id. To handle
+ * POSIX ids, <code>canonicalize</code> can be called to convert the id
+ * to canonical form, or the <code>canonicalInstance</code> factory method
+ * can be called.</p>
+ *
+ * <p>This class provides selectors {@link #VALID_LOCALE} and {@link
+ * #ACTUAL_LOCALE} intended for use in methods named
+ * <tt>getLocale()</tt>. These methods exist in several ICU classes,
+ * including {@link com.ibm.icu.util.Calendar}, {@link
+ * com.ibm.icu.util.Currency}, {@link com.ibm.icu.text.UFormat},
+ * {@link com.ibm.icu.text.BreakIterator}, {@link
+ * com.ibm.icu.text.Collator}, {@link
+ * com.ibm.icu.text.DateFormatSymbols}, and {@link
+ * com.ibm.icu.text.DecimalFormatSymbols} and their subclasses, if
+ * any. Once an object of one of these classes has been created,
+ * <tt>getLocale()</tt> may be called on it to determine the valid and
+ * actual locale arrived at during the object's construction.
+ *
+ * <p>Note: The <tt>getLocale()</tt> method will be implemented in ICU
+ * 3.0; ICU 2.8 contains a partial preview implementation. The
+ * <i>actual</i> locale is returned correctly, but the <i>valid</i>
+ * locale is not, in most cases.
+ *
+ * @see java.util.Locale
+ * @author weiv
+ * @author Alan Liu
+ * @author Ram Viswanadha
+ * @stable ICU 2.8
+ */
+public final class ULocale implements Serializable {
+ // using serialver from jdk1.4.2_05
+ private static final long serialVersionUID = 3715177670352309217L;
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale ENGLISH = new ULocale("en", Locale.ENGLISH);
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale FRENCH = new ULocale("fr", Locale.FRENCH);
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale GERMAN = new ULocale("de", Locale.GERMAN);
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale ITALIAN = new ULocale("it", Locale.ITALIAN);
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale JAPANESE = new ULocale("ja", Locale.JAPANESE);
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale KOREAN = new ULocale("ko", Locale.KOREAN);
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale CHINESE = new ULocale("zh", Locale.CHINESE);
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale SIMPLIFIED_CHINESE = new ULocale("zh_Hans", Locale.CHINESE);
+
+ /**
+ * Useful constant for language.
+ * @stable ICU 3.0
+ */
+ public static final ULocale TRADITIONAL_CHINESE = new ULocale("zh_Hant", Locale.CHINESE);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale FRANCE = new ULocale("fr_FR", Locale.FRANCE);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale GERMANY = new ULocale("de_DE", Locale.GERMANY);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale ITALY = new ULocale("it_IT", Locale.ITALY);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale JAPAN = new ULocale("ja_JP", Locale.JAPAN);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale KOREA = new ULocale("ko_KR", Locale.KOREA);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale CHINA = new ULocale("zh_Hans_CN", Locale.CHINA);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale PRC = CHINA;
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale TAIWAN = new ULocale("zh_Hant_TW", Locale.TAIWAN);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale UK = new ULocale("en_GB", Locale.UK);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale US = new ULocale("en_US", Locale.US);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale CANADA = new ULocale("en_CA", Locale.CANADA);
+
+ /**
+ * Useful constant for country/region.
+ * @stable ICU 3.0
+ */
+ public static final ULocale CANADA_FRENCH = new ULocale("fr_CA", Locale.CANADA_FRENCH);
+
+ /**
+ * Handy constant.
+ */
+ private static final String EMPTY_STRING = "";
+
+ // Used in both ULocale and IDParser, so moved up here.
+ private static final char UNDERSCORE = '_';
+
+ /**
+ * The root ULocale.
+ * @stable ICU 2.8
+ */
+ private static final Locale EMPTY_LOCALE = new Locale("", "");
+ public static final ULocale ROOT = new ULocale(EMPTY_STRING, EMPTY_LOCALE);
+
+ private static final HashMap CACHE = new HashMap(20);
+ static {
+ CACHE.put(EMPTY_LOCALE, ROOT);
+ CACHE.put(Locale.ENGLISH, ENGLISH);
+ CACHE.put(Locale.FRENCH, FRENCH);
+ CACHE.put(Locale.GERMAN, GERMAN);
+ CACHE.put(Locale.ITALIAN, ITALIAN);
+ CACHE.put(Locale.JAPANESE, JAPANESE);
+ CACHE.put(Locale.KOREAN, KOREAN);
+ CACHE.put(Locale.CHINESE, CHINESE);
+ CACHE.put(Locale.SIMPLIFIED_CHINESE, SIMPLIFIED_CHINESE);
+ CACHE.put(Locale.TRADITIONAL_CHINESE, TRADITIONAL_CHINESE);
+ CACHE.put(Locale.FRANCE, FRANCE);
+ CACHE.put(Locale.GERMANY, GERMANY);
+ CACHE.put(Locale.ITALY, ITALY);
+ CACHE.put(Locale.JAPAN, JAPAN);
+ CACHE.put(Locale.KOREA, KOREA);
+ CACHE.put(Locale.CHINA, CHINA);
+ CACHE.put(Locale.TAIWAN, TAIWAN);
+ CACHE.put(Locale.UK, UK);
+ CACHE.put(Locale.US, US);
+ CACHE.put(Locale.CANADA, CANADA);
+ CACHE.put(Locale.CANADA_FRENCH, CANADA_FRENCH);
+ }
+
+ /**
+ * Cache the locale.
+ */
+ private transient Locale locale;
+
+ /**
+ * The raw localeID that we were passed in.
+ */
+ private String localeID;
+
+ /**
+ * Tables used in normalizing portions of the id.
+ */
+ /* tables updated per http://lcweb.loc.gov/standards/iso639-2/
+ to include the revisions up to 2001/7/27 *CWB*/
+ /* The 3 character codes are the terminology codes like RFC 3066.
+ This is compatible with prior ICU codes */
+ /* "in" "iw" "ji" "jw" & "sh" have been withdrawn but are still in
+ the table but now at the end of the table because
+ 3 character codes are duplicates. This avoids bad searches
+ going from 3 to 2 character codes.*/
+ /* The range qaa-qtz is reserved for local use. */
+
+ private static String[] _languages;
+ private static String[] _replacementLanguages;
+ private static String[] _obsoleteLanguages;
+ private static String[] _languages3;
+ private static String[] _obsoleteLanguages3;
+
+ // Avoid initializing languages tables unless we have to.
+ private static void initLanguageTables() {
+ if (_languages == null) {
+
+ /* This list MUST be in sorted order, and MUST contain the two-letter codes
+ if one exists otherwise use the three letter code */
+ String[] tempLanguages = {
+ "aa", "ab", "ace", "ach", "ada", "ady", "ae", "af", "afa",
+ "afh", "ak", "akk", "ale", "alg", "am", "an", "ang", "apa",
+ "ar", "arc", "arn", "arp", "art", "arw", "as", "ast",
+ "ath", "aus", "av", "awa", "ay", "az", "ba", "bad",
+ "bai", "bal", "ban", "bas", "bat", "be", "bej",
+ "bem", "ber", "bg", "bh", "bho", "bi", "bik", "bin",
+ "bla", "bm", "bn", "bnt", "bo", "br", "bra", "bs",
+ "btk", "bua", "bug", "byn", "ca", "cad", "cai", "car", "cau",
+ "ce", "ceb", "cel", "ch", "chb", "chg", "chk", "chm",
+ "chn", "cho", "chp", "chr", "chy", "cmc", "co", "cop",
+ "cpe", "cpf", "cpp", "cr", "crh", "crp", "cs", "csb", "cu", "cus",
+ "cv", "cy", "da", "dak", "dar", "day", "de", "del", "den",
+ "dgr", "din", "doi", "dra", "dsb", "dua", "dum", "dv", "dyu",
+ "dz", "ee", "efi", "egy", "eka", "el", "elx", "en",
+ "enm", "eo", "es", "et", "eu", "ewo", "fa",
+ "fan", "fat", "ff", "fi", "fiu", "fj", "fo", "fon",
+ "fr", "frm", "fro", "fur", "fy", "ga", "gaa", "gay",
+ "gba", "gd", "gem", "gez", "gil", "gl", "gmh", "gn",
+ "goh", "gon", "gor", "got", "grb", "grc", "gu", "gv",
+ "gwi", "ha", "hai", "haw", "he", "hi", "hil", "him",
+ "hit", "hmn", "ho", "hr", "hsb", "ht", "hu", "hup", "hy", "hz",
+ "ia", "iba", "id", "ie", "ig", "ii", "ijo", "ik",
+ "ilo", "inc", "ine", "inh", "io", "ira", "iro", "is", "it",
+ "iu", "ja", "jbo", "jpr", "jrb", "jv", "ka", "kaa", "kab",
+ "kac", "kam", "kar", "kaw", "kbd", "kg", "kha", "khi",
+ "kho", "ki", "kj", "kk", "kl", "km", "kmb", "kn",
+ "ko", "kok", "kos", "kpe", "kr", "krc", "kro", "kru", "ks",
+ "ku", "kum", "kut", "kv", "kw", "ky", "la", "lad",
+ "lah", "lam", "lb", "lez", "lg", "li", "ln", "lo", "lol",
+ "loz", "lt", "lu", "lua", "lui", "lun", "luo", "lus",
+ "lv", "mad", "mag", "mai", "mak", "man", "map", "mas",
+ "mdf", "mdr", "men", "mg", "mga", "mh", "mi", "mic", "min",
+ "mis", "mk", "mkh", "ml", "mn", "mnc", "mni", "mno",
+ "mo", "moh", "mos", "mr", "ms", "mt", "mul", "mun",
+ "mus", "mwr", "my", "myn", "myv", "na", "nah", "nai", "nap",
+ "nb", "nd", "nds", "ne", "new", "ng", "nia", "nic",
+ "niu", "nl", "nn", "no", "nog", "non", "nr", "nso", "nub",
+ "nv", "nwc", "ny", "nym", "nyn", "nyo", "nzi", "oc", "oj",
+ "om", "or", "os", "osa", "ota", "oto", "pa", "paa",
+ "pag", "pal", "pam", "pap", "pau", "peo", "phi", "phn",
+ "pi", "pl", "pon", "pra", "pro", "ps", "pt", "qu",
+ "raj", "rap", "rar", "rm", "rn", "ro", "roa", "rom",
+ "ru", "rup", "rw", "sa", "sad", "sah", "sai", "sal", "sam",
+ "sas", "sat", "sc", "sco", "sd", "se", "sel", "sem",
+ "sg", "sga", "sgn", "shn", "si", "sid", "sio", "sit",
+ "sk", "sl", "sla", "sm", "sma", "smi", "smj", "smn",
+ "sms", "sn", "snk", "so", "sog", "son", "sq", "sr",
+ "srr", "ss", "ssa", "st", "su", "suk", "sus", "sux",
+ "sv", "sw", "syr", "ta", "tai", "te", "tem", "ter",
+ "tet", "tg", "th", "ti", "tig", "tiv", "tk", "tkl",
+ "tl", "tlh", "tli", "tmh", "tn", "to", "tog", "tpi", "tr",
+ "ts", "tsi", "tt", "tum", "tup", "tut", "tvl", "tw",
+ "ty", "tyv", "udm", "ug", "uga", "uk", "umb", "und", "ur",
+ "uz", "vai", "ve", "vi", "vo", "vot", "wa", "wak",
+ "wal", "war", "was", "wen", "wo", "xal", "xh", "yao", "yap",
+ "yi", "yo", "ypk", "za", "zap", "zen", "zh", "znd",
+ "zu", "zun",
+ };
+
+ String[] tempReplacementLanguages = {
+ "id", "he", "yi", "jv", "sr", "nb",/* replacement language codes */
+ };
+
+ String[] tempObsoleteLanguages = {
+ "in", "iw", "ji", "jw", "sh", "no", /* obsolete language codes */
+ };
+
+ /* This list MUST contain a three-letter code for every two-letter code in the
+ list above, and they MUST ne in the same order (i.e., the same language must
+ be in the same place in both lists)! */
+ String[] tempLanguages3 = {
+ /*"aa", "ab", "ace", "ach", "ada", "ady", "ae", "af", "afa", */
+ "aar", "abk", "ace", "ach", "ada", "ady", "ave", "afr", "afa",
+ /*"afh", "ak", "akk", "ale", "alg", "am", "an", "ang", "apa", */
+ "afh", "aka", "akk", "ale", "alg", "amh", "arg", "ang", "apa",
+ /*"ar", "arc", "arn", "arp", "art", "arw", "as", "ast", */
+ "ara", "arc", "arn", "arp", "art", "arw", "asm", "ast",
+ /*"ath", "aus", "av", "awa", "ay", "az", "ba", "bad", */
+ "ath", "aus", "ava", "awa", "aym", "aze", "bak", "bad",
+ /*"bai", "bal", "ban", "bas", "bat", "be", "bej", */
+ "bai", "bal", "ban", "bas", "bat", "bel", "bej",
+ /*"bem", "ber", "bg", "bh", "bho", "bi", "bik", "bin", */
+ "bem", "ber", "bul", "bih", "bho", "bis", "bik", "bin",
+ /*"bla", "bm", "bn", "bnt", "bo", "br", "bra", "bs", */
+ "bla", "bam", "ben", "bnt", "bod", "bre", "bra", "bos",
+ /*"btk", "bua", "bug", "byn", "ca", "cad", "cai", "car", "cau", */
+ "btk", "bua", "bug", "byn", "cat", "cad", "cai", "car", "cau",
+ /*"ce", "ceb", "cel", "ch", "chb", "chg", "chk", "chm", */
+ "che", "ceb", "cel", "cha", "chb", "chg", "chk", "chm",
+ /*"chn", "cho", "chp", "chr", "chy", "cmc", "co", "cop", */
+ "chn", "cho", "chp", "chr", "chy", "cmc", "cos", "cop",
+ /*"cpe", "cpf", "cpp", "cr", "crh", "crp", "cs", "csb", "cu", "cus", */
+ "cpe", "cpf", "cpp", "cre", "crh", "crp", "ces", "csb", "chu", "cus",
+ /*"cv", "cy", "da", "dak", "dar", "day", "de", "del", "den", */
+ "chv", "cym", "dan", "dak", "dar", "day", "deu", "del", "den",
+ /*"dgr", "din", "doi", "dra", "dsb", "dua", "dum", "dv", "dyu", */
+ "dgr", "din", "doi", "dra", "dsb", "dua", "dum", "div", "dyu",
+ /*"dz", "ee", "efi", "egy", "eka", "el", "elx", "en", */
+ "dzo", "ewe", "efi", "egy", "eka", "ell", "elx", "eng",
+ /*"enm", "eo", "es", "et", "eu", "ewo", "fa", */
+ "enm", "epo", "spa", "est", "eus", "ewo", "fas",
+ /*"fan", "fat", "ff", "fi", "fiu", "fj", "fo", "fon", */
+ "fan", "fat", "ful", "fin", "fiu", "fij", "fao", "fon",
+ /*"fr", "frm", "fro", "fur", "fy", "ga", "gaa", "gay", */
+ "fra", "frm", "fro", "fur", "fry", "gle", "gaa", "gay",
+ /*"gba", "gd", "gem", "gez", "gil", "gl", "gmh", "gn", */
+ "gba", "gla", "gem", "gez", "gil", "glg", "gmh", "grn",
+ /*"goh", "gon", "gor", "got", "grb", "grc", "gu", "gv", */
+ "goh", "gon", "gor", "got", "grb", "grc", "guj", "glv",
+ /*"gwi", "ha", "hai", "haw", "he", "hi", "hil", "him", */
+ "gwi", "hau", "hai", "haw", "heb", "hin", "hil", "him",
+ /*"hit", "hmn", "ho", "hr", "hsb", "ht", "hu", "hup", "hy", "hz", */
+ "hit", "hmn", "hmo", "hrv", "hsb", "hat", "hun", "hup", "hye", "her",
+ /*"ia", "iba", "id", "ie", "ig", "ii", "ijo", "ik", */
+ "ina", "iba", "ind", "ile", "ibo", "iii", "ijo", "ipk",
+ /*"ilo", "inc", "ine", "inh", "io", "ira", "iro", "is", "it", */
+ "ilo", "inc", "ine", "inh", "ido", "ira", "iro", "isl", "ita",
+ /*"iu", "ja", "jbo", "jpr", "jrb", "jv", "ka", "kaa", "kab", */
+ "iku", "jpn", "jbo", "jpr", "jrb", "jaw", "kat", "kaa", "kab",
+ /*"kac", "kam", "kar", "kaw", "kbd", "kg", "kha", "khi", */
+ "kac", "kam", "kar", "kaw", "kbd", "kon", "kha", "khi",
+ /*"kho", "ki", "kj", "kk", "kl", "km", "kmb", "kn", */
+ "kho", "kik", "kua", "kaz", "kal", "khm", "kmb", "kan",
+ /*"ko", "kok", "kos", "kpe", "kr", "krc", "kro", "kru", "ks", */
+ "kor", "kok", "kos", "kpe", "kau", "krc", "kro", "kru", "kas",
+ /*"ku", "kum", "kut", "kv", "kw", "ky", "la", "lad", */
+ "kur", "kum", "kut", "kom", "cor", "kir", "lat", "lad",
+ /*"lah", "lam", "lb", "lez", "lg", "li", "ln", "lo", "lol", */
+ "lah", "lam", "ltz", "lez", "lug", "lim", "lin", "lao", "lol",
+ /*"loz", "lt", "lu", "lua", "lui", "lun", "luo", "lus", */
+ "loz", "lit", "lub", "lua", "lui", "lun", "luo", "lus",
+ /*"lv", "mad", "mag", "mai", "mak", "man", "map", "mas", */
+ "lav", "mad", "mag", "mai", "mak", "man", "map", "mas",
+ /*"mdf", "mdr", "men", "mg", "mga", "mh", "mi", "mic", "min", */
+ "mdf", "mdr", "men", "mlg", "mga", "mah", "mri", "mic", "min",
+ /*"mis", "mk", "mkh", "ml", "mn", "mnc", "mni", "mno", */
+ "mis", "mkd", "mkh", "mal", "mon", "mnc", "mni", "mno",
+ /*"mo", "moh", "mos", "mr", "ms", "mt", "mul", "mun", */
+ "mol", "moh", "mos", "mar", "msa", "mlt", "mul", "mun",
+ /*"mus", "mwr", "my", "myn", "myv", "na", "nah", "nai", "nap", */
+ "mus", "mwr", "mya", "myn", "myv", "nau", "nah", "nai", "nap",
+ /*"nb", "nd", "nds", "ne", "new", "ng", "nia", "nic", */
+ "nob", "nde", "nds", "nep", "new", "ndo", "nia", "nic",
+ /*"niu", "nl", "nn", "no", "nog", "non", "nr", "nso", "nub", */
+ "niu", "nld", "nno", "nor", "nog", "non", "nbl", "nso", "nub",
+ /*"nv", "nwc", "ny", "nym", "nyn", "nyo", "nzi", "oc", "oj", */
+ "nav", "nwc", "nya", "nym", "nyn", "nyo", "nzi", "oci", "oji",
+ /*"om", "or", "os", "osa", "ota", "oto", "pa", "paa", */
+ "orm", "ori", "oss", "osa", "ota", "oto", "pan", "paa",
+ /*"pag", "pal", "pam", "pap", "pau", "peo", "phi", "phn", */
+ "pag", "pal", "pam", "pap", "pau", "peo", "phi", "phn",
+ /*"pi", "pl", "pon", "pra", "pro", "ps", "pt", "qu", */
+ "pli", "pol", "pon", "pra", "pro", "pus", "por", "que",
+ /*"raj", "rap", "rar", "rm", "rn", "ro", "roa", "rom", */
+ "raj", "rap", "rar", "roh", "run", "ron", "roa", "rom",
+ /*"ru", "rup", "rw", "sa", "sad", "sah", "sai", "sal", "sam", */
+ "rus", "rup", "kin", "san", "sad", "sah", "sai", "sal", "sam",
+ /*"sas", "sat", "sc", "sco", "sd", "se", "sel", "sem", */
+ "sas", "sat", "srd", "sco", "snd", "sme", "sel", "sem",
+ /*"sg", "sga", "sgn", "shn", "si", "sid", "sio", "sit", */
+ "sag", "sga", "sgn", "shn", "sin", "sid", "sio", "sit",
+ /*"sk", "sl", "sla", "sm", "sma", "smi", "smj", "smn", */
+ "slk", "slv", "sla", "smo", "sma", "smi", "smj", "smn",
+ /*"sms", "sn", "snk", "so", "sog", "son", "sq", "sr", */
+ "sms", "sna", "snk", "som", "sog", "son", "sqi", "srp",
+ /*"srr", "ss", "ssa", "st", "su", "suk", "sus", "sux", */
+ "srr", "ssw", "ssa", "sot", "sun", "suk", "sus", "sux",
+ /*"sv", "sw", "syr", "ta", "tai", "te", "tem", "ter", */
+ "swe", "swa", "syr", "tam", "tai", "tel", "tem", "ter",
+ /*"tet", "tg", "th", "ti", "tig", "tiv", "tk", "tkl", */
+ "tet", "tgk", "tha", "tir", "tig", "tiv", "tuk", "tkl",
+ /*"tl", "tlh", "tli", "tmh", "tn", "to", "tog", "tpi", "tr", */
+ "tgl", "tlh", "tli", "tmh", "tsn", "ton", "tog", "tpi", "tur",
+ /*"ts", "tsi", "tt", "tum", "tup", "tut", "tvl", "tw", */
+ "tso", "tsi", "tat", "tum", "tup", "tut", "tvl", "twi",
+ /*"ty", "tyv", "udm", "ug", "uga", "uk", "umb", "und", "ur", */
+ "tah", "tyv", "udm", "uig", "uga", "ukr", "umb", "und", "urd",
+ /*"uz", "vai", "ve", "vi", "vo", "vot", "wa", "wak", */
+ "uzb", "vai", "ven", "vie", "vol", "vot", "wln", "wak",
+ /*"wal", "war", "was", "wen", "wo", "xal", "xh", "yao", "yap", */
+ "wal", "war", "was", "wen", "wol", "xal", "xho", "yao", "yap",
+ /*"yi", "yo", "ypk", "za", "zap", "zen", "zh", "znd", */
+ "yid", "yor", "ypk", "zha", "zap", "zen", "zho", "znd",
+ /*"zu", "zun", */
+ "zul", "zun",
+ };
+
+ String[] tempObsoleteLanguages3 = {
+ /* "in", "iw", "ji", "jw", "sh", */
+ "ind", "heb", "yid", "jaw", "srp",
+ };
+
+ synchronized (ULocale.class) {
+ if (_languages == null) {
+ _languages = tempLanguages;
+ _replacementLanguages = tempReplacementLanguages;
+ _obsoleteLanguages = tempObsoleteLanguages;
+ _languages3 = tempLanguages3;
+ _obsoleteLanguages3 = tempObsoleteLanguages3;
+ }
+ }
+ }
+ }
+
+ private static String[] _countries;
+ private static String[] _deprecatedCountries;
+ private static String[] _replacementCountries;
+ private static String[] _obsoleteCountries;
+ private static String[] _countries3;
+ private static String[] _obsoleteCountries3;
+
+ // Avoid initializing country tables unless we have to.
+ private static void initCountryTables() {
+ if (_countries == null) {
+ /* ZR(ZAR) is now CD(COD) and FX(FXX) is PS(PSE) as per
+ http://www.evertype.com/standards/iso3166/iso3166-1-en.html
+ added new codes keeping the old ones for compatibility
+ updated to include 1999/12/03 revisions *CWB*/
+
+ /* RO(ROM) is now RO(ROU) according to
+ http://www.iso.org/iso/en/prods-services/iso3166ma/03updates-on-iso-3166/nlv3e-rou.html
+ */
+
+ /* This list MUST be in sorted order, and MUST contain only two-letter codes! */
+ String[] tempCountries = {
+ "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN",
+ "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AZ",
+ "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI",
+ "BJ", "BM", "BN", "BO", "BR", "BS", "BT", "BV",
+ "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG",
+ "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR",
+ "CU", "CV", "CX", "CY", "CZ", "DE", "DJ", "DK",
+ "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER",
+ "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR",
+ "GA", "GB", "GD", "GE", "GF", "GH", "GI", "GL",
+ "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU",
+ "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU",
+ "ID", "IE", "IL", "IN", "IO", "IQ", "IR", "IS",
+ "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KI",
+ "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA",
+ "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU",
+ "LV", "LY", "MA", "MC", "MD", "MG", "MH", "MK",
+ "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS",
+ "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA",
+ "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP",
+ "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG",
+ "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT",
+ "PW", "PY", "QA", "RE", "RO", "RU", "RW", "SA",
+ "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ",
+ "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV",
+ "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ",
+ "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV",
+ "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ",
+ "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF",
+ "WS", "YE", "YT", "YU", "ZA", "ZM", "ZW",
+ };
+
+ /* this table is used for 3 letter codes */
+ String[] tempObsoleteCountries = {
+ "FX", "RO", "TP", "ZR", /* obsolete country codes */
+ };
+
+ String[] tempDeprecatedCountries = {
+ "BU", "DY", "FX", "HV", "NH", "RH", "TP", "YU", "ZR" /* deprecated country list */
+ };
+ String[] tempReplacementCountries = {
+ /* "BU", "DY", "FX", "HV", "NH", "RH", "TP", "YU", "ZR" */
+ "MM", "BJ", "FR", "BF", "VU", "ZW", "TL", "CS", "CD", /* replacement country codes */
+ };
+
+ /* This list MUST contain a three-letter code for every two-letter code in
+ the above list, and they MUST be listed in the same order! */
+ String[] tempCountries3 = {
+ /*"AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", */
+ "AND", "ARE", "AFG", "ATG", "AIA", "ALB", "ARM", "ANT",
+ /*"AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AZ", */
+ "AGO", "ATA", "ARG", "ASM", "AUT", "AUS", "ABW", "AZE",
+ /*"BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", */
+ "BIH", "BRB", "BGD", "BEL", "BFA", "BGR", "BHR", "BDI",
+ /*"BJ", "BM", "BN", "BO", "BR", "BS", "BT", "BV", */
+ "BEN", "BMU", "BRN", "BOL", "BRA", "BHS", "BTN", "BVT",
+ /*"BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", */
+ "BWA", "BLR", "BLZ", "CAN", "CCK", "COD", "CAF", "COG",
+ /*"CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", */
+ "CHE", "CIV", "COK", "CHL", "CMR", "CHN", "COL", "CRI",
+ /*"CU", "CV", "CX", "CY", "CZ", "DE", "DJ", "DK", */
+ "CUB", "CPV", "CXR", "CYP", "CZE", "DEU", "DJI", "DNK",
+ /*"DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", */
+ "DMA", "DOM", "DZA", "ECU", "EST", "EGY", "ESH", "ERI",
+ /*"ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", */
+ "ESP", "ETH", "FIN", "FJI", "FLK", "FSM", "FRO", "FRA",
+ /*"GA", "GB", "GD", "GE", "GF", "GH", "GI", "GL", */
+ "GAB", "GBR", "GRD", "GEO", "GUF", "GHA", "GIB", "GRL",
+ /*"GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", */
+ "GMB", "GIN", "GLP", "GNQ", "GRC", "SGS", "GTM", "GUM",
+ /*"GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", */
+ "GNB", "GUY", "HKG", "HMD", "HND", "HRV", "HTI", "HUN",
+ /*"ID", "IE", "IL", "IN", "IO", "IQ", "IR", "IS", */
+ "IDN", "IRL", "ISR", "IND", "IOT", "IRQ", "IRN", "ISL",
+ /*"IT", "JM", "JO", "JP", "KE", "KG", "KH", "KI", */
+ "ITA", "JAM", "JOR", "JPN", "KEN", "KGZ", "KHM", "KIR",
+ /*"KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", */
+ "COM", "KNA", "PRK", "KOR", "KWT", "CYM", "KAZ", "LAO",
+ /*"LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", */
+ "LBN", "LCA", "LIE", "LKA", "LBR", "LSO", "LTU", "LUX",
+ /*"LV", "LY", "MA", "MC", "MD", "MG", "MH", "MK", */
+ "LVA", "LBY", "MAR", "MCO", "MDA", "MDG", "MHL", "MKD",
+ /*"ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", */
+ "MLI", "MMR", "MNG", "MAC", "MNP", "MTQ", "MRT", "MSR",
+ /*"MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", */
+ "MLT", "MUS", "MDV", "MWI", "MEX", "MYS", "MOZ", "NAM",
+ /*"NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", */
+ "NCL", "NER", "NFK", "NGA", "NIC", "NLD", "NOR", "NPL",
+ /*"NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", */
+ "NRU", "NIU", "NZL", "OMN", "PAN", "PER", "PYF", "PNG",
+ /*"PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", */
+ "PHL", "PAK", "POL", "SPM", "PCN", "PRI", "PSE", "PRT",
+ /*"PW", "PY", "QA", "RE", "RO", "RU", "RW", "SA", */
+ "PLW", "PRY", "QAT", "REU", "ROU", "RUS", "RWA", "SAU",
+ /*"SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", */
+ "SLB", "SYC", "SDN", "SWE", "SGP", "SHN", "SVN", "SJM",
+ /*"SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", */
+ "SVK", "SLE", "SMR", "SEN", "SOM", "SUR", "STP", "SLV",
+ /*"SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", */
+ "SYR", "SWZ", "TCA", "TCD", "ATF", "TGO", "THA", "TJK",
+ /*"TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", */
+ "TKL", "TLS", "TKM", "TUN", "TON", "TUR", "TTO", "TUV",
+ /*"TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", */
+ "TWN", "TZA", "UKR", "UGA", "UMI", "USA", "URY", "UZB",
+ /*"VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", */
+ "VAT", "VCT", "VEN", "VGB", "VIR", "VNM", "VUT", "WLF",
+ /*"WS", "YE", "YT", "YU", "ZA", "ZM", "ZW", */
+ "WSM", "YEM", "MYT", "YUG", "ZAF", "ZMB", "ZWE",
+ };
+
+ String[] tempObsoleteCountries3 = {
+ /*"FX", "RO", "TP", "ZR", */
+ "FXX", "ROM", "TMP", "ZAR",
+ };
+
+ synchronized (ULocale.class) {
+ if (_countries == null) {
+ _countries = tempCountries;
+ _deprecatedCountries = tempDeprecatedCountries;
+ _replacementCountries = tempReplacementCountries;
+ _obsoleteCountries = tempObsoleteCountries;
+ _countries3 = tempCountries3;
+ _obsoleteCountries3 = tempObsoleteCountries3;
+ }
+ }
+ }
+ }
+
+ private static String[][] _variantsToKeywords;
+
+ private static void initVariantsTable() {
+ if (_variantsToKeywords == null) {
+ /**
+ * This table lists pairs of locale ids for canonicalization. The
+ * The first item is the normalized id, the second item is the
+ * canonicalized id.
+ */
+ String[][] tempVariantsToKeywords = {
+// { EMPTY_STRING, "en_US_POSIX", null, null }, /* .NET name */
+ { "C", "en_US_POSIX", null, null }, /* POSIX name */
+ { "art_LOJBAN", "jbo", null, null }, /* registered name */
+ { "az_AZ_CYRL", "az_Cyrl_AZ", null, null }, /* .NET name */
+ { "az_AZ_LATN", "az_Latn_AZ", null, null }, /* .NET name */
+ { "ca_ES_PREEURO", "ca_ES", "currency", "ESP" },
+ { "cel_GAULISH", "cel__GAULISH", null, null }, /* registered name */
+ { "de_1901", "de__1901", null, null }, /* registered name */
+ { "de_1906", "de__1906", null, null }, /* registered name */
+ { "de__PHONEBOOK", "de", "collation", "phonebook" },
+ { "de_AT_PREEURO", "de_AT", "currency", "ATS" },
+ { "de_DE_PREEURO", "de_DE", "currency", "DEM" },
+ { "de_LU_PREEURO", "de_LU", "currency", "EUR" },
+ { "el_GR_PREEURO", "el_GR", "currency", "GRD" },
+ { "en_BOONT", "en__BOONT", null, null }, /* registered name */
+ { "en_SCOUSE", "en__SCOUSE", null, null }, /* registered name */
+ { "en_BE_PREEURO", "en_BE", "currency", "BEF" },
+ { "en_IE_PREEURO", "en_IE", "currency", "IEP" },
+ { "es__TRADITIONAL", "es", "collation", "traditional" },
+ { "es_ES_PREEURO", "es_ES", "currency", "ESP" },
+ { "eu_ES_PREEURO", "eu_ES", "currency", "ESP" },
+ { "fi_FI_PREEURO", "fi_FI", "currency", "FIM" },
+ { "fr_BE_PREEURO", "fr_BE", "currency", "BEF" },
+ { "fr_FR_PREEURO", "fr_FR", "currency", "FRF" },
+ { "fr_LU_PREEURO", "fr_LU", "currency", "LUF" },
+ { "ga_IE_PREEURO", "ga_IE", "currency", "IEP" },
+ { "gl_ES_PREEURO", "gl_ES", "currency", "ESP" },
+ { "hi__DIRECT", "hi", "collation", "direct" },
+ { "it_IT_PREEURO", "it_IT", "currency", "ITL" },
+ { "ja_JP_TRADITIONAL", "ja_JP", "calendar", "japanese" },
+// { "nb_NO_NY", "nn_NO", null, null },
+ { "nl_BE_PREEURO", "nl_BE", "currency", "BEF" },
+ { "nl_NL_PREEURO", "nl_NL", "currency", "NLG" },
+ { "pt_PT_PREEURO", "pt_PT", "currency", "PTE" },
+ { "sl_ROZAJ", "sl__ROZAJ", null, null }, /* registered name */
+ { "sr_SP_CYRL", "sr_Cyrl_CS", null, null }, /* .NET name */
+ { "sr_SP_LATN", "sr_Latn_CS", null, null }, /* .NET name */
+ { "sr_YU_CYRILLIC", "sr_Cyrl_CS", null, null }, /* Linux name */
+ { "uz_UZ_CYRILLIC", "uz_Cyrl_UZ", null, null }, /* Linux name */
+ { "uz_UZ_CYRL", "uz_Cyrl_UZ", null, null }, /* .NET name */
+ { "uz_UZ_LATN", "uz_Latn_UZ", null, null }, /* .NET name */
+ { "zh_CHS", "zh_Hans", null, null }, /* .NET name */
+ { "zh_CHT", "zh_Hant", null, null }, /* .NET name */
+ { "zh_GAN", "zh__GAN", null, null }, /* registered name */
+ { "zh_GUOYU", "zh", null, null }, /* registered name */
+ { "zh_HAKKA", "zh__HAKKA", null, null }, /* registered name */
+ { "zh_MIN", "zh__MIN", null, null }, /* registered name */
+ { "zh_MIN_NAN", "zh__MINNAN", null, null }, /* registered name */
+ { "zh_WUU", "zh__WUU", null, null }, /* registered name */
+ { "zh_XIANG", "zh__XIANG", null, null }, /* registered name */
+ { "zh_YUE", "zh__YUE", null, null }, /* registered name */
+ { "th_TH_TRADITIONAL", "th_TH", "calendar", "buddhist" },
+ { "zh_TW_STROKE", "zh_TW", "collation", "stroke" },
+ { "zh__PINYIN", "zh", "collation", "pinyin" }
+ };
+
+ synchronized (ULocale.class) {
+ if (_variantsToKeywords == null) {
+ _variantsToKeywords = tempVariantsToKeywords;
+ }
+ }
+ }
+ }
+
+ /**
+ * Private constructor used by static initializers.
+ */
+ private ULocale(String localeID, Locale locale) {
+ this.localeID = localeID;
+ this.locale = locale;
+ }
+
+ /**
+ * Construct a ULocale object from a {@link java.util.Locale}.
+ * @param loc a JDK locale
+ * @stable ICU 2.8
+ * @internal
+ */
+ private ULocale(Locale loc) {
+ this.localeID = getName(loc.toString());
+ this.locale = loc;
+ }
+
+ /**
+ * Return a ULocale object for a {@link java.util.Locale}.
+ * The ULocale is canonicalized.
+ * @param loc a JDK locale
+ * @stable ICU 3.2
+ */
+ public static ULocale forLocale(Locale loc) {
+ if (loc == null) {
+ return null;
+ }
+ ULocale result = (ULocale)CACHE.get(loc);
+ if (result == null && defaultULocale != null && loc == defaultULocale.locale) {
+ result = defaultULocale;
+ } else {
+ result = new ULocale(loc.toString(), loc);
+ }
+ return result;
+ }
+
+ /**
+ * Construct a ULocale from a RFC 3066 locale ID. The locale ID consists
+ * of optional language, script, country, and variant fields in that order,
+ * separated by underscores, followed by an optional keyword list. The
+ * script, if present, is four characters long-- this distinguishes it
+ * from a country code, which is two characters long. Other fields
+ * are distinguished by position as indicated by the underscores. The
+ * start of the keyword list is indicated by '@', and consists of one
+ * or more keyword/value pairs separated by commas.
+ * <p>
+ * This constructor does not canonicalize the localeID.
+ *
+ * @param localeID string representation of the locale, e.g:
+ * "en_US", "sy_Cyrl_YU", "zh__pinyin", "es_ES@currency=EUR,collation=traditional"
+ * @stable ICU 2.8
+ */
+ public ULocale(String localeID) {
+ this.localeID = getName(localeID);
+ }
+
+ /**
+ * Convenience overload of ULocale(String, String, String) for
+ * compatibility with java.util.Locale.
+ * @see #ULocale(String, String, String)
+ * @stable ICU 3.4
+ */
+ public ULocale(String a, String b) {
+ this(a, b, null);
+ }
+
+ /**
+ * Construct a ULocale from a localeID constructed from the three 'fields' a, b, and c. These
+ * fields are concatenated using underscores to form a localeID of
+ * the form a_b_c, which is then handled like the localeID passed
+ * to <code>ULocale(String localeID)</code>.
+ *
+ * <p>Java locale strings consisting of language, country, and
+ * variant will be handled by this form, since the country code
+ * (being shorter than four letters long) will not be interpreted
+ * as a script code. If a script code is present, the final
+ * argument ('c') will be interpreted as the country code. It is
+ * recommended that this constructor only be used to ease porting,
+ * and that clients instead use the single-argument constructor
+ * when constructing a ULocale from a localeID.
+ * @param a first component of the locale id
+ * @param b second component of the locale id
+ * @param c third component of the locale id
+ * @see #ULocale(String)
+ * @stable ICU 3.0
+ */
+ public ULocale(String a, String b, String c) {
+ localeID = getName(lscvToID(a, b, c, EMPTY_STRING));
+ }
+
+ /**
+ * Create a ULocale from the id by first canonicalizing the id.
+ * @param nonCanonicalID the locale id to canonicalize
+ * @return the locale created from the canonical version of the ID.
+ * @stable ICU 3.0
+ */
+ public static ULocale createCanonical(String nonCanonicalID) {
+ return new ULocale(canonicalize(nonCanonicalID), (Locale)null);
+ }
+
+ private static String lscvToID(String lang, String script, String country, String variant) {
+ StringBuffer buf = new StringBuffer();
+
+ if (lang != null && lang.length() > 0) {
+ buf.append(lang);
+ }
+ if (script != null && script.length() > 0) {
+ buf.append(UNDERSCORE);
+ buf.append(script);
+ }
+ if (country != null && country.length() > 0) {
+ buf.append(UNDERSCORE);
+ buf.append(country);
+ }
+ if (variant != null && variant.length() > 0) {
+ if (country == null || country.length() == 0) {
+ buf.append(UNDERSCORE);
+ }
+ buf.append(UNDERSCORE);
+ buf.append(variant);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Convert this ULocale object to a {@link java.util.Locale}.
+ * @return a JDK locale that either exactly represents this object
+ * or is the closest approximation.
+ * @stable ICU 2.8
+ */
+ public Locale toLocale() {
+ if (locale == null) {
+ String[] names = new IDParser(localeID).getLanguageScriptCountryVariant();
+ locale = new Locale(names[0], names[2], names[3]);
+ }
+ return locale;
+ }
+
+ /**
+ * Keep our own default ULocale.
+ */
+ private static ULocale defaultULocale;
+
+ /**
+ * Returns the current default ULocale.
+ * @stable ICU 2.8
+ */
+ public static ULocale getDefault() {
+ synchronized (ULocale.class) {
+ Locale defaultLocale = Locale.getDefault();
+ if (defaultULocale == null || defaultULocale.toLocale() != defaultLocale) {
+ defaultULocale = new ULocale(defaultLocale);
+ }
+ return defaultULocale;
+ }
+ }
+
+ /**
+ * Sets the default ULocale. This also sets the default Locale.
+ * If the caller does not have write permission to the
+ * user.language property, a security exception will be thrown,
+ * and the default ULocale will remain unchanged.
+ * @param newLocale the new default locale
+ * @throws SecurityException
+ * if a security manager exists and its
+ * <code>checkPermission</code> method doesn't allow the operation.
+ * @throws NullPointerException if <code>newLocale</code> is null
+ * @see SecurityManager#checkPermission
+ * @see java.util.PropertyPermission
+ * @stable ICU 3.0
+ */
+ public static synchronized void setDefault(ULocale newLocale){
+ Locale.setDefault(newLocale.toLocale());
+ defaultULocale = newLocale;
+ }
+
+ /**
+ * This is for compatibility with Locale-- in actuality, since ULocale is
+ * immutable, there is no reason to clone it, so this API returns 'this'.
+ * @stable ICU 3.0
+ */
+ public Object clone() {
+ return this;
+ }
+
+ /**
+ * Returns the hashCode.
+ * @stable ICU 3.0
+ */
+ public int hashCode() {
+ return localeID.hashCode();
+ }
+
+ /**
+ * Returns true if the other object is another ULocale with the
+ * same full name, or is a String localeID that matches the full name.
+ * Note that since names are not canonicalized, two ULocales that
+ * function identically might not compare equal.
+ *
+ * @return true if this Locale is equal to the specified object.
+ * @stable ICU 3.0
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof String) {
+ return localeID.equals((String)obj);
+ }
+ if (obj instanceof ULocale) {
+ return localeID.equals(((ULocale)obj).localeID);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a list of all installed locales.
+ * @stable ICU 3.0
+ */
+ public static ULocale[] getAvailableLocales() {
+ return ICUResourceBundle.getAvailableULocales();
+ }
+
+ /**
+ * Returns a list of all 2-letter country codes defined in ISO 3166.
+ * Can be used to create Locales.
+ * @stable ICU 3.0
+ */
+ public static String[] getISOCountries() {
+ initCountryTables();
+ return (String[])_countries.clone();
+ }
+
+ /**
+ * Returns a list of all 2-letter language codes defined in ISO 639.
+ * Can be used to create Locales.
+ * [NOTE: ISO 639 is not a stable standard-- some languages' codes have changed.
+ * The list this function returns includes both the new and the old codes for the
+ * languages whose codes have changed.]
+ * @stable ICU 3.0
+ */
+ public static String[] getISOLanguages() {
+ initLanguageTables();
+ return (String[])_languages.clone();
+ }
+
+ /**
+ * Returns the language code for this locale, which will either be the empty string
+ * or a lowercase ISO 639 code.
+ * @see #getDisplayLanguage
+ * @stable ICU 3.0
+ */
+ public String getLanguage() {
+ return getLanguage(localeID);
+ }
+
+ /**
+ * Returns the language code for the locale ID,
+ * which will either be the empty string
+ * or a lowercase ISO 639 code.
+ * @see #getDisplayLanguage
+ * @stable ICU 3.0
+ */
+ public static String getLanguage(String localeID) {
+ return new IDParser(localeID).getLanguage();
+ }
+
+ /**
+ * Returns the script code for this locale, which might be the empty string.
+ * @see #getDisplayScript
+ * @stable ICU 3.0
+ */
+ public String getScript() {
+ return getScript(localeID);
+ }
+
+ /**
+ * Returns the script code for the specified locale, which might be the empty string.
+ * @see #getDisplayScript
+ * @stable ICU 3.0
+ */
+ public static String getScript(String localeID) {
+ return new IDParser(localeID).getScript();
+ }
+
+ /**
+ * Returns the country/region code for this locale, which will either be the empty string
+ * or an uppercase ISO 3166 2-letter code.
+ * @see #getDisplayCountry
+ * @stable ICU 3.0
+ */
+ public String getCountry() {
+ return getCountry(localeID);
+ }
+
+ /**
+ * Returns the country/region code for this locale, which will either be the empty string
+ * or an uppercase ISO 3166 2-letter code.
+ * @param localeID
+ * @see #getDisplayCountry
+ * @stable ICU 3.0
+ */
+ public static String getCountry(String localeID) {
+ return new IDParser(localeID).getCountry();
+ }
+
+ /**
+ * Returns the variant code for this locale, which might be the empty string.
+ * @see #getDisplayVariant
+ * @stable ICU 3.0
+ */
+ public String getVariant() {
+ return getVariant(localeID);
+ }
+
+ /**
+ * Returns the variant code for the specified locale, which might be the empty string.
+ * @see #getDisplayVariant
+ * @stable ICU 3.0
+ */
+ public static String getVariant(String localeID) {
+ return new IDParser(localeID).getVariant();
+ }
+
+ /**
+ * Returns the fallback locale for the specified locale, which might be the empty string.
+ * @stable ICU 3.2
+ */
+ public static String getFallback(String localeID) {
+ return getFallbackString(getName(localeID));
+ }
+
+ /**
+ * Returns the fallback locale for this locale. If this locale is root, returns null.
+ * @stable ICU 3.2
+ */
+ public ULocale getFallback() {
+ if (localeID.length() == 0 || localeID.charAt(0) == '@') {
+ return null;
+ }
+ return new ULocale(getFallbackString(localeID), (Locale)null);
+ }
+
+ /**
+ * Return the given (canonical) locale id minus the last part before the tags.
+ */
+ private static String getFallbackString(String fallback) {
+ int limit = fallback.indexOf('@');
+ if (limit == -1) {
+ limit = fallback.length();
+ }
+ int start = fallback.lastIndexOf('_', limit);
+ if (start == -1) {
+ start = 0;
+ }
+ return fallback.substring(0, start) + fallback.substring(limit);
+ }
+
+ /**
+ * Returns the (normalized) base name for this locale.
+ * @return the base name as a String.
+ * @stable ICU 3.0
+ */
+ public String getBaseName() {
+ return getBaseName(localeID);
+ }
+
+ /**
+ * Returns the (normalized) base name for the specified locale.
+ * @param localeID the locale ID as a string
+ * @return the base name as a String.
+ * @stable ICU 3.0
+ */
+ public static String getBaseName(String localeID){
+ return new IDParser(localeID).getBaseName();
+ }
+
+ /**
+ * Returns the (normalized) full name for this locale.
+ *
+ * @return String the full name of the localeID
+ * @stable ICU 3.0
+ */
+ public String getName() {
+ return localeID; // always normalized
+ }
+
+ /**
+ * Returns the (normalized) full name for the specified locale.
+ *
+ * @param localeID the localeID as a string
+ * @return String the full name of the localeID
+ * @stable ICU 3.0
+ */
+ public static String getName(String localeID){
+ return new IDParser(localeID).getName();
+ }
+
+ /**
+ * Returns a string representation of this object.
+ * @stable ICU 3.0
+ */
+ public String toString() {
+ return localeID;
+ }
+
+ /**
+ * Returns an iterator over keywords for this locale. If there
+ * are no keywords, returns null.
+ * @return iterator over keywords, or null if there are no keywords.
+ * @stable ICU 3.0
+ */
+ public Iterator getKeywords() {
+ return getKeywords(localeID);
+ }
+
+ /**
+ * Returns an iterator over keywords for the specified locale. If there
+ * are no keywords, returns null.
+ * @return an iterator over the keywords in the specified locale, or null
+ * if there are no keywords.
+ * @stable ICU 3.0
+ */
+ public static Iterator getKeywords(String localeID){
+ return new IDParser(localeID).getKeywords();
+ }
+
+ /**
+ * Returns the value for a keyword in this locale. If the keyword is not defined, returns null.
+ * @param keywordName name of the keyword whose value is desired. Case insensitive.
+ * @return the value of the keyword, or null.
+ * @stable ICU 3.0
+ */
+ public String getKeywordValue(String keywordName){
+ return getKeywordValue(localeID, keywordName);
+ }
+
+ /**
+ * Returns the value for a keyword in the specified locale. If the keyword is not defined, returns null.
+ * The locale name does not need to be normalized.
+ * @param keywordName name of the keyword whose value is desired. Case insensitive.
+ * @return String the value of the keyword as a string
+ * @stable ICU 3.0
+ */
+ public static String getKeywordValue(String localeID, String keywordName) {
+ return new IDParser(localeID).getKeywordValue(keywordName);
+ }
+
+ /**
+ * Utility class to parse and normalize locale ids (including POSIX style)
+ */
+ private static final class IDParser {
+ private char[] id;
+ private int index;
+ private char[] buffer;
+ private int blen;
+ // um, don't handle POSIX ids unless we request it. why not? well... because.
+ private boolean canonicalize;
+ private boolean hadCountry;
+
+ // used when canonicalizing
+ Map keywords;
+ String baseName;
+
+ /**
+ * Parsing constants.
+ */
+ private static final char KEYWORD_SEPARATOR = '@';
+ private static final char HYPHEN = '-';
+ private static final char KEYWORD_ASSIGN = '=';
+ private static final char COMMA = ',';
+ private static final char ITEM_SEPARATOR = ';';
+ private static final char DOT = '.';
+
+ private IDParser(String localeID) {
+ this(localeID, false);
+ }
+
+ private IDParser(String localeID, boolean canonicalize) {
+ id = localeID.toCharArray();
+ index = 0;
+ buffer = new char[id.length + 5];
+ blen = 0;
+ this.canonicalize = canonicalize;
+ }
+
+ private void reset() {
+ index = blen = 0;
+ }
+
+ // utilities for working on text in the buffer
+
+ /**
+ * Append c to the buffer.
+ */
+ private void append(char c) {
+ try {
+ buffer[blen] = c;
+ }
+ catch (IndexOutOfBoundsException e) {
+ if (buffer.length > 512) {
+ // something is seriously wrong, let this go
+ throw e;
+ }
+ char[] nbuffer = new char[buffer.length * 2];
+ System.arraycopy(buffer, 0, nbuffer, 0, buffer.length);
+ nbuffer[blen] = c;
+ buffer = nbuffer;
+ }
+ ++blen;
+ }
+
+ private void addSeparator() {
+ append(UNDERSCORE);
+ }
+
+ /**
+ * Returns the text in the buffer from start to blen as a String.
+ */
+ private String getString(int start) {
+ if (start == blen) {
+ return EMPTY_STRING;
+ }
+ return new String(buffer, start, blen-start);
+ }
+
+ /**
+ * Set the length of the buffer to pos, then append the string.
+ */
+ private void set(int pos, String s) {
+ this.blen = pos; // no safety
+ append(s);
+ }
+
+ /**
+ * Append the string to the buffer.
+ */
+ private void append(String s) {
+ for (int i = 0; i < s.length(); ++i) {
+ append(s.charAt(i));
+ }
+ }
+
+ // utilities for parsing text out of the id
+
+ /**
+ * Character to indicate no more text is available in the id.
+ */
+ private static final char DONE = '\uffff';
+
+ /**
+ * Returns the character at index in the id, and advance index. The returned character
+ * is DONE if index was at the limit of the buffer. The index is advanced regardless
+ * so that decrementing the index will always 'unget' the last character returned.
+ */
+ private char next() {
+ if (index == id.length) {
+ index++;
+ return DONE;
+ }
+
+ return id[index++];
+ }
+
+ /**
+ * Advance index until the next terminator or id separator, and leave it there.
+ */
+ private void skipUntilTerminatorOrIDSeparator() {
+ while (!isTerminatorOrIDSeparator(next())) {
+ }
+ --index;
+ }
+
+ /**
+ * Returns true if the character at index in the id is a terminator.
+ */
+ private boolean atTerminator() {
+ return index >= id.length || isTerminator(id[index]);
+ }
+
+ /**
+ * Returns true if the character is an id separator (underscore or hyphen).
+ */
+ private boolean isIDSeparator(char c) {
+ return c == UNDERSCORE || c == HYPHEN;
+ }
+
+ /**
+ * Returns true if the character is a terminator (keyword separator, dot, or DONE).
+ * Dot is a terminator because of the POSIX form, where dot precedes the codepage.
+ */
+ private boolean isTerminator(char c) {
+ // always terminate at DOT, even if not handling POSIX. It's an error...
+ return c == KEYWORD_SEPARATOR || c == DONE || c == DOT;
+ }
+
+ /**
+ * Returns true if the character is a terminator or id separator.
+ */
+ private boolean isTerminatorOrIDSeparator(char c) {
+ return c == KEYWORD_SEPARATOR || c == UNDERSCORE || c == HYPHEN ||
+ c == DONE || c == DOT;
+ }
+
+ /**
+ * Returns true if the start of the buffer has an experimental or private language
+ * prefix, the pattern '[ixIX][-_].' shows the syntax checked.
+ */
+ private boolean haveExperimentalLanguagePrefix() {
+ if (id.length > 2) {
+ char c = id[1];
+ if (c == HYPHEN || c == UNDERSCORE) {
+ c = id[0];
+ return c == 'x' || c == 'X' || c == 'i' || c == 'I';
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if a value separator occurs at or after index.
+ */
+ private boolean haveKeywordAssign() {
+ // assume it is safe to start from index
+ for (int i = index; i < id.length; ++i) {
+ if (id[i] == KEYWORD_ASSIGN) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Advance index past language, and accumulate normalized language code in buffer.
+ * Index must be at 0 when this is called. Index is left at a terminator or id
+ * separator. Returns the start of the language code in the buffer.
+ */
+ private int parseLanguage() {
+ if (haveExperimentalLanguagePrefix()) {
+ append(Character.toLowerCase(id[0]));
+ append(HYPHEN);
+ index = 2;
+ }
+
+ char c;
+ while(!isTerminatorOrIDSeparator(c = next())) {
+ append(Character.toLowerCase(c));
+ }
+ --index; // unget
+
+ if (blen == 3) {
+ initLanguageTables();
+
+ /* convert 3 character code to 2 character code if possible *CWB*/
+ String lang = getString(0);
+ int offset = findIndex(_languages3, lang);
+ if (offset >= 0) {
+ set(0, _languages[offset]);
+ } else {
+ offset = findIndex(_obsoleteLanguages3, lang);
+ if (offset >= 0) {
+ set(0, _obsoleteLanguages[offset]);
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Advance index past language. Index must be at 0 when this is called. Index
+ * is left at a terminator or id separator.
+ */
+ private void skipLanguage() {
+ if (haveExperimentalLanguagePrefix()) {
+ index = 2;
+ }
+ skipUntilTerminatorOrIDSeparator();
+ }
+
+ /**
+ * Advance index past script, and accumulate normalized script in buffer.
+ * Index must be immediately after the language.
+ * If the item at this position is not a script (is not four characters
+ * long) leave index and buffer unchanged. Otherwise index is left at
+ * a terminator or id separator. Returns the start of the script code
+ * in the buffer (this may be equal to the buffer length, if there is no
+ * script).
+ */
+ private int parseScript() {
+ if (!atTerminator()) {
+ int oldIndex = index; // save original index
+ ++index;
+
+ int oldBlen = blen; // get before append hyphen, if we truncate everything is undone
+ char c;
+ while(!isTerminatorOrIDSeparator(c = next())) {
+ if (blen == oldBlen) { // first pass
+ addSeparator();
+ append(Character.toUpperCase(c));
+ } else {
+ append(Character.toLowerCase(c));
+ }
+ }
+ --index; // unget
+
+ /* If it's not exactly 4 characters long, then it's not a script. */
+ if (index - oldIndex != 5) { // +1 to account for separator
+ index = oldIndex;
+ blen = oldBlen;
+ } else {
+ oldBlen++; // index past hyphen, for clients who want to extract just the script
+ }
+
+ return oldBlen;
+ }
+ return blen;
+ }
+
+ /**
+ * Advance index past script.
+ * Index must be immediately after the language and IDSeparator.
+ * If the item at this position is not a script (is not four characters
+ * long) leave index. Otherwise index is left at a terminator or
+ * id separator.
+ */
+ private void skipScript() {
+ if (!atTerminator()) {
+ int oldIndex = index;
+ ++index;
+
+ skipUntilTerminatorOrIDSeparator();
+ if (index - oldIndex != 5) { // +1 to account for separator
+ index = oldIndex;
+ }
+ }
+ }
+
+ /**
+ * Advance index past country, and accumulate normalized country in buffer.
+ * Index must be immediately after the script (if there is one, else language)
+ * and IDSeparator. Return the start of the country code in the buffer.
+ */
+ private int parseCountry() {
+ if (!atTerminator()) {
+ ++index;
+
+ int oldBlen = blen;
+ char c;
+ while (!isTerminatorOrIDSeparator(c = next())) {
+ if (oldBlen == blen) { // first, add hyphen
+ hadCountry = true; // we have a country, let variant parsing know
+ addSeparator();
+ ++oldBlen; // increment past hyphen
+ }
+ append(Character.toUpperCase(c));
+ }
+ --index; // unget
+
+ if (blen - oldBlen == 3) {
+ initCountryTables();
+
+ /* convert 3 character code to 2 character code if possible *CWB*/
+ int offset = findIndex(_countries3, getString(oldBlen));
+ if (offset >= 0) {
+ set(oldBlen, _countries[offset]);
+ } else {
+ offset = findIndex(_obsoleteCountries3, getString(oldBlen));
+ if (offset >= 0) {
+ set(oldBlen, _obsoleteCountries[offset]);
+ }
+ }
+ }
+
+ return oldBlen;
+ }
+
+ return blen;
+ }
+
+ /**
+ * Advance index past country.
+ * Index must be immediately after the script (if there is one, else language)
+ * and IDSeparator.
+ */
+ private void skipCountry() {
+ if (!atTerminator()) {
+ ++index;
+ skipUntilTerminatorOrIDSeparator();
+ }
+ }
+
+ /**
+ * Advance index past variant, and accumulate normalized variant in buffer. This ignores
+ * the codepage information from POSIX ids. Index must be immediately after the country
+ * or script. Index is left at the keyword separator or at the end of the text. Return
+ * the start of the variant code in the buffer.
+ *
+ * In standard form, we can have the following forms:
+ * ll__VVVV
+ * ll_CC_VVVV
+ * ll_Ssss_VVVV
+ * ll_Ssss_CC_VVVV
+ *
+ * This also handles POSIX ids, which can have the following forms (pppp is code page id):
+ * ll_CC.pppp --> ll_CC
+ * ll_CC.pppp@VVVV --> ll_CC_VVVV
+ * ll_CC@VVVV --> ll_CC_VVVV
+ *
+ * We identify this use of '@' in POSIX ids by looking for an '=' following
+ * the '@'. If there is one, we consider '@' to start a keyword list, instead of
+ * being part of a POSIX id.
+ *
+ * Note: since it was decided that we want an option to not handle POSIX ids, this
+ * becomes a bit more complex.
+ */
+ private int parseVariant() {
+ int oldBlen = blen;
+
+ boolean start = true;
+ boolean needSeparator = true;
+ boolean skipping = false;
+ char c;
+ while ((c = next()) != DONE) {
+ if (c == DOT) {
+ start = false;
+ skipping = true;
+ } else if (c == KEYWORD_SEPARATOR) {
+ if (haveKeywordAssign()) {
+ break;
+ }
+ skipping = false;
+ start = false;
+ needSeparator = true; // add another underscore if we have more text
+ } else if (start) {
+ start = false;
+ } else if (!skipping) {
+ if (needSeparator) {
+ boolean incOldBlen = blen == oldBlen; // need to skip separators
+ needSeparator = false;
+ if (incOldBlen && !hadCountry) { // no country, we'll need two
+ addSeparator();
+ ++oldBlen; // for sure
+ }
+ addSeparator();
+ if (incOldBlen) { // only for the first separator
+ ++oldBlen;
+ }
+ }
+ c = Character.toUpperCase(c);
+ if (c == HYPHEN || c == COMMA) {
+ c = UNDERSCORE;
+ }
+ append(c);
+ }
+ }
+ --index; // unget
+
+ return oldBlen;
+ }
+
+ // no need for skipvariant, to get the keywords we'll just scan directly for
+ // the keyword separator
+
+ /**
+ * Returns the normalized language id, or the empty string.
+ */
+ public String getLanguage() {
+ reset();
+ return getString(parseLanguage());
+ }
+
+ /**
+ * Returns the normalized script id, or the empty string.
+ */
+ public String getScript() {
+ reset();
+ skipLanguage();
+ return getString(parseScript());
+ }
+
+ /**
+ * return the normalized country id, or the empty string.
+ */
+ public String getCountry() {
+ reset();
+ skipLanguage();
+ skipScript();
+ return getString(parseCountry());
+ }
+
+ /**
+ * Returns the normalized variant id, or the empty string.
+ */
+ public String getVariant() {
+ reset();
+ skipLanguage();
+ skipScript();
+ skipCountry();
+ return getString(parseVariant());
+ }
+
+ /**
+ * Returns the language, script, country, and variant as separate strings.
+ */
+ public String[] getLanguageScriptCountryVariant() {
+ reset();
+ return new String[] {
+ getString(parseLanguage()),
+ getString(parseScript()),
+ getString(parseCountry()),
+ getString(parseVariant())
+ };
+ }
+
+ public void setBaseName(String baseName) {
+ this.baseName = baseName;
+ }
+
+ public void parseBaseName() {
+ if (baseName != null) {
+ set(0, baseName);
+ } else {
+ reset();
+ parseLanguage();
+ parseScript();
+ parseCountry();
+ parseVariant();
+
+ // catch unwanted trailing underscore after country if there was no variant
+ if (blen > 1 && buffer[blen-1] == UNDERSCORE) {
+ --blen;
+ }
+ }
+ }
+
+ /**
+ * Returns the normalized base form of the locale id. The base
+ * form does not include keywords.
+ */
+ public String getBaseName() {
+ if (baseName != null) {
+ return baseName;
+ }
+ parseBaseName();
+ return getString(0);
+ }
+
+ /**
+ * Returns the normalized full form of the locale id. The full
+ * form includes keywords if they are present.
+ */
+ public String getName() {
+ parseBaseName();
+ parseKeywords();
+ return getString(0);
+ }
+
+ // keyword utilities
+
+ /**
+ * If we have keywords, advance index to the start of the keywords and return true,
+ * otherwise return false.
+ */
+ private boolean setToKeywordStart() {
+ for (int i = index; i < id.length; ++i) {
+ if (id[i] == KEYWORD_SEPARATOR) {
+ if (canonicalize) {
+ for (int j = ++i; j < id.length; ++j) { // increment i past separator for return
+ if (id[j] == KEYWORD_ASSIGN) {
+ index = i;
+ return true;
+ }
+ }
+ } else {
+ if (++i < id.length) {
+ index = i;
+ return true;
+ }
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isDoneOrKeywordAssign(char c) {
+ return c == DONE || c == KEYWORD_ASSIGN;
+ }
+
+ private static boolean isDoneOrItemSeparator(char c) {
+ return c == DONE || c == ITEM_SEPARATOR;
+ }
+
+ private String getKeyword() {
+ int start = index;
+ while (!isDoneOrKeywordAssign(next())) {
+ }
+ --index;
+ return new String(id, start, index-start).trim().toLowerCase();
+ }
+
+ private String getValue() {
+ int start = index;
+ while (!isDoneOrItemSeparator(next())) {
+ }
+ --index;
+ return new String(id, start, index-start).trim(); // leave case alone
+ }
+
+ private Comparator getKeyComparator() {
+ final Comparator comp = new Comparator() {
+ public int compare(Object lhs, Object rhs) {
+ return ((String)lhs).compareTo((String)rhs);
+ }
+ };
+ return comp;
+ }
+
+ /**
+ * Returns a map of the keywords and values, or null if there are none.
+ */
+ private Map getKeywordMap() {
+ if (keywords == null) {
+ TreeMap m = null;
+ if (setToKeywordStart()) {
+ // trim spaces and convert to lower case, both keywords and values.
+ do {
+ String key = getKeyword();
+ if (key.length() == 0) {
+ break;
+ }
+ char c = next();
+ if (c != KEYWORD_ASSIGN) {
+ // throw new IllegalArgumentException("key '" + key + "' missing a value.");
+ if (c == DONE) {
+ break;
+ } else {
+ continue;
+ }
+ }
+ String value = getValue();
+ if (value.length() == 0) {
+ // throw new IllegalArgumentException("key '" + key + "' missing a value.");
+ continue;
+ }
+ if (m == null) {
+ m = new TreeMap(getKeyComparator());
+ } else if (m.containsKey(key)) {
+ // throw new IllegalArgumentException("key '" + key + "' already has a value.");
+ continue;
+ }
+ m.put(key, value);
+ } while (next() == ITEM_SEPARATOR);
+ }
+ keywords = m != null ? m : Collections.EMPTY_MAP;
+ }
+
+ return keywords;
+ }
+
+ /**
+ * Parse the keywords and return start of the string in the buffer.
+ */
+ private int parseKeywords() {
+ int oldBlen = blen;
+ Map m = getKeywordMap();
+ if (!m.isEmpty()) {
+ Iterator iter = m.entrySet().iterator();
+ boolean first = true;
+ while (iter.hasNext()) {
+ append(first ? KEYWORD_SEPARATOR : ITEM_SEPARATOR);
+ first = false;
+ Map.Entry e = (Map.Entry)iter.next();
+ append((String)e.getKey());
+ append(KEYWORD_ASSIGN);
+ append((String)e.getValue());
+ }
+ if (blen != oldBlen) {
+ ++oldBlen;
+ }
+ }
+ return oldBlen;
+ }
+
+ /**
+ * Returns an iterator over the keywords, or null if we have an empty map.
+ */
+ public Iterator getKeywords() {
+ Map m = getKeywordMap();
+ return m.isEmpty() ? null : m.keySet().iterator();
+ }
+
+ /**
+ * Returns the value for the named keyword, or null if the keyword is not
+ * present.
+ */
+ public String getKeywordValue(String keywordName) {
+ Map m = getKeywordMap();
+ return m.isEmpty() ? null : (String)m.get(keywordName.trim().toLowerCase());
+ }
+
+ /**
+ * Set the keyword value only if it is not already set to something else.
+ */
+ public void defaultKeywordValue(String keywordName, String value) {
+ setKeywordValue(keywordName, value, false);
+ }
+
+ /**
+ * Set the value for the named keyword, or unset it if value is null. If
+ * keywordName itself is null, unset all keywords. If keywordName is not null,
+ * value must not be null.
+ */
+ public void setKeywordValue(String keywordName, String value) {
+ setKeywordValue(keywordName, value, true);
+ }
+
+ /**
+ * Set the value for the named keyword, or unset it if value is null. If
+ * keywordName itself is null, unset all keywords. If keywordName is not null,
+ * value must not be null. If reset is true, ignore any previous value for
+ * the keyword, otherwise do not change the keyword (including removal of
+ * one or all keywords).
+ */
+ private void setKeywordValue(String keywordName, String value, boolean reset) {
+ if (keywordName == null) {
+ if (reset) {
+ // force new map, ignore value
+ keywords = Collections.EMPTY_MAP;
+ }
+ } else {
+ keywordName = keywordName.trim().toLowerCase();
+ if (keywordName.length() == 0) {
+ throw new IllegalArgumentException("keyword must not be empty");
+ }
+ if (value != null) {
+ value = value.trim();
+ if (value.length() == 0) {
+ throw new IllegalArgumentException("value must not be empty");
+ }
+ }
+ Map m = getKeywordMap();
+ if (m.isEmpty()) { // it is EMPTY_MAP
+ if (value != null) {
+ // force new map
+ keywords = new TreeMap(getKeyComparator());
+ keywords.put(keywordName, value.trim());
+ }
+ } else {
+ if (reset || !m.containsKey(keywordName)) {
+ if (value != null) {
+ m.put(keywordName, value);
+ } else {
+ m.remove(keywordName);
+ if (m.isEmpty()) {
+ // force new map
+ keywords = Collections.EMPTY_MAP;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * linear search of the string array. the arrays are unfortunately ordered by the
+ * two-letter target code, not the three-letter search code, which seems backwards.
+ */
+ private static int findIndex(String[] array, String target){
+ for (int i = 0; i < array.length; i++) {
+ if (target.equals(array[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the canonical name for the specified locale ID. This is used to convert POSIX
+ * and other grandfathered IDs to standard ICU form.
+ * @param localeID the locale id
+ * @return the canonicalized id
+ * @stable ICU 3.0
+ */
+ public static String canonicalize(String localeID){
+ IDParser parser = new IDParser(localeID, true);
+ String baseName = parser.getBaseName();
+ boolean foundVariant = false;
+
+ // formerly, we always set to en_US_POSIX if the basename was empty, but
+ // now we require that the entire id be empty, so that "@foo=bar"
+ // will pass through unchanged.
+ // {dlf} I'd rather keep "" unchanged.
+ if (localeID.equals("")) {
+ return "";
+// return "en_US_POSIX";
+ }
+
+ // we have an ID in the form xx_Yyyy_ZZ_KKKKK
+
+ initVariantsTable();
+
+ /* See if this is an already known locale */
+ for (int i = 0; i < _variantsToKeywords.length; i++) {
+ if (_variantsToKeywords[i][0].equals(baseName)) {
+ foundVariant = true;
+
+ String[] vals = _variantsToKeywords[i];
+ parser.setBaseName(vals[1]);
+ if (vals[2] != null) {
+ parser.defaultKeywordValue(vals[2], vals[3]);
+ }
+ break;
+ }
+ }
+
+ /* convert the Euro variant to appropriate ID */
+ if (!foundVariant) {
+ int idx = baseName.indexOf("_EURO");
+ if (idx > -1) {
+ parser.setBaseName(baseName.substring(0, idx));
+ parser.defaultKeywordValue("currency", "EUR");
+ }
+ }
+
+ /* total mondo hack for Norwegian, fortunately the main NY case is handled earlier */
+ if (!foundVariant) {
+ if (parser.getLanguage().equals("nb") && parser.getVariant().equals("NY")) {
+ parser.setBaseName(lscvToID("nn", parser.getScript(), parser.getCountry(), null));
+ }
+ }
+
+ return parser.getName();
+ }
+
+ /**
+ * Given a keyword and a value, return a new locale with an updated
+ * keyword and value. If keyword is null, this removes all keywords from the locale id.
+ * Otherwise, if the value is null, this removes the value for this keyword from the
+ * locale id. Otherwise, this adds/replaces the value for this keyword in the locale id.
+ * The keyword and value must not be empty.
+ * @param keyword the keyword to add/remove, or null to remove all keywords.
+ * @param value the value to add/set, or null to remove this particular keyword.
+ * @return the updated locale
+ * @stable ICU 3.2
+ */
+ public ULocale setKeywordValue(String keyword, String value) {
+ return new ULocale(setKeywordValue(localeID, keyword, value), (Locale)null);
+ }
+
+ /**
+ * Given a locale id, a keyword, and a value, return a new locale id with an updated
+ * keyword and value. If keyword is null, this removes all keywords from the locale id.
+ * Otherwise, if the value is null, this removes the value for this keyword from the
+ * locale id. Otherwise, this adds/replaces the value for this keyword in the locale id.
+ * The keyword and value must not be empty.
+ * @param localeID the locale id to modify
+ * @param keyword the keyword to add/remove, or null to remove all keywords.
+ * @param value the value to add/set, or null to remove this particular keyword.
+ * @return the updated locale id
+ * @stable ICU 3.2
+ */
+ public static String setKeywordValue(String localeID, String keyword, String value) {
+ IDParser parser = new IDParser(localeID);
+ parser.setKeywordValue(keyword, value);
+ return parser.getName();
+ }
+
+ /**
+ * Given a locale id, a keyword, and a value, return a new locale id with an updated
+ * keyword and value, if the keyword does not already have a value. The keyword and
+ * value must not be null or empty.
+ * @param localeID the locale id to modify
+ * @param keyword the keyword to add, if not already present
+ * @param value the value to add, if not already present
+ * @return the updated locale id
+ * @internal
+ */
+ private static String defaultKeywordValue(String localeID, String keyword, String value) {
+ IDParser parser = new IDParser(localeID);
+ parser.defaultKeywordValue(keyword, value);
+ return parser.getName();
+ }
+
+ /**
+ * Returns a three-letter abbreviation for this locale's language. If the locale
+ * doesn't specify a language, returns the empty string. Otherwise, returns
+ * a lowercase ISO 639-2/T language code.
+ * The ISO 639-2 language codes can be found on-line at
+ * <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
+ * @exception MissingResourceException Throws MissingResourceException if the
+ * three-letter language abbreviation is not available for this locale.
+ * @stable ICU 3.0
+ */
+ public String getISO3Language(){
+ return getISO3Language(localeID);
+ }
+
+ /**
+ * Returns a three-letter abbreviation for this locale's language. If the locale
+ * doesn't specify a language, returns the empty string. Otherwise, returns
+ * a lowercase ISO 639-2/T language code.
+ * The ISO 639-2 language codes can be found on-line at
+ * <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
+ * @exception MissingResourceException Throws MissingResourceException if the
+ * three-letter language abbreviation is not available for this locale.
+ * @stable ICU 3.0
+ */
+ public static String getISO3Language(String localeID){
+ initLanguageTables();
+
+ String language = getLanguage(localeID);
+ int offset = findIndex(_languages, language);
+ if(offset>=0){
+ return _languages3[offset];
+ } else {
+ offset = findIndex(_obsoleteLanguages, language);
+ if (offset >= 0) {
+ return _obsoleteLanguages3[offset];
+ }
+ }
+ return EMPTY_STRING;
+ }
+
+ /**
+ * Returns a three-letter abbreviation for this locale's country/region. If the locale
+ * doesn't specify a country, returns the empty string. Otherwise, returns
+ * an uppercase ISO 3166 3-letter country code.
+ * @exception MissingResourceException Throws MissingResourceException if the
+ * three-letter country abbreviation is not available for this locale.
+ * @stable ICU 3.0
+ */
+ public String getISO3Country(){
+ return getISO3Country(localeID);
+ }
+ /**
+ * Returns a three-letter abbreviation for this locale's country/region. If the locale
+ * doesn't specify a country, returns the empty string. Otherwise, returns
+ * an uppercase ISO 3166 3-letter country code.
+ * @exception MissingResourceException Throws MissingResourceException if the
+ * three-letter country abbreviation is not available for this locale.
+ * @stable ICU 3.0
+ */
+ public static String getISO3Country(String localeID){
+ initCountryTables();
+
+ String country = getCountry(localeID);
+ int offset = findIndex(_countries, country);
+ if(offset>=0){
+ return _countries3[offset];
+ }else{
+ offset = findIndex(_obsoleteCountries, country);
+ if(offset>=0){
+ return _obsoleteCountries3[offset];
+ }
+ }
+ return EMPTY_STRING;
+ }
+
+ // display names
+
+ /**
+ * Utility to fetch locale display data from resource bundle tables.
+ */
+ private static String getTableString(String tableName, String subtableName, String item, String displayLocaleID) {
+ if (item.length() > 0) {
+ try {
+ ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.
+ getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, displayLocaleID);
+ return getTableString(tableName, subtableName, item, bundle);
+ } catch (Exception e) {
+// System.out.println("gtsu: " + e.getMessage());
+ }
+ }
+ return item;
+ }
+
+ /**
+ * Utility to fetch locale display data from resource bundle tables.
+ */
+ private static String getTableString(String tableName, String subtableName, String item, ICUResourceBundle bundle) {
+// System.out.println("gts table: " + tableName +
+// " subtable: " + subtableName +
+// " item: " + item +
+// " bundle: " + bundle.getULocale());
+ try {
+ for (;;) {
+ // special case currency
+ if ("currency".equals(subtableName)) {
+ ICUResourceBundle table = bundle.getWithFallback("Currencies");
+ table = table.getWithFallback(item);
+ return table.getString(1);
+ } else {
+ ICUResourceBundle table = bundle.getWithFallback(tableName);
+ try {
+ if (subtableName != null) {
+ table = table.getWithFallback(subtableName);
+ }
+ return table.getStringWithFallback(item);
+ }
+ catch (MissingResourceException e) {
+
+ if(subtableName==null){
+ try{
+ // may be a deprecated code
+ String currentName = null;
+ if(tableName.equals("Countries")){
+ currentName = getCurrentCountryID(item);
+ }else if(tableName.equals("Languages")){
+ currentName = getCurrentLanguageID(item);
+ }
+ return table.getStringWithFallback(currentName);
+ }catch (MissingResourceException ex){/* fall through*/}
+ }
+
+ // still can't figure out ?.. try the fallback mechanism
+ String fallbackLocale = table.getWithFallback("Fallback").getString();
+ if (fallbackLocale.length() == 0) {
+ fallbackLocale = "root";
+ }
+// System.out.println("bundle: " + bundle.getULocale() + " fallback: " + fallbackLocale);
+ if(fallbackLocale.equals(table.getULocale().localeID)){
+ return item;
+ }
+ bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
+ fallbackLocale);
+// System.out.println("fallback from " + table.getULocale() + " to " + fallbackLocale +
+// ", got bundle " + bundle.getULocale());
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+// System.out.println("gtsi: " + e.getMessage());
+ }
+ return item;
+ }
+
+ /**
+ * Returns this locale's language localized for display in the default locale.
+ * @return the localized language name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayLanguage() {
+ return getDisplayLanguageInternal(localeID, getDefault().localeID);
+ }
+
+ /**
+ * Returns this locale's language localized for display in the provided locale.
+ * @param displayLocale the locale in which to display the name.
+ * @return the localized language name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayLanguage(ULocale displayLocale) {
+ return getDisplayLanguageInternal(localeID, displayLocale.localeID);
+ }
+
+ /**
+ * Returns a locale's language localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose language will be displayed
+ * @param displayLocaleID the id of the locale in which to display the name.
+ * @return the localized language name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayLanguage(String localeID, String displayLocaleID) {
+ return getDisplayLanguageInternal(localeID, getName(displayLocaleID));
+ }
+
+ /**
+ * Returns a locale's language localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose language will be displayed.
+ * @param displayLocale the locale in which to display the name.
+ * @return the localized language name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayLanguage(String localeID, ULocale displayLocale) {
+ return getDisplayLanguageInternal(localeID, displayLocale.localeID);
+ }
+
+ static String getCurrentCountryID(String oldID){
+ initCountryTables();
+ int offset = findIndex(_deprecatedCountries, oldID);
+ if (offset >= 0) {
+ return _replacementCountries[offset];
+ }
+ return oldID;
+ }
+ static String getCurrentLanguageID(String oldID){
+ initLanguageTables();
+ int offset = findIndex(_obsoleteLanguages, oldID);
+ if (offset >= 0) {
+ return _replacementLanguages[offset];
+ }
+ return oldID;
+ }
+
+
+ // displayLocaleID is canonical, localeID need not be since parsing will fix this.
+ private static String getDisplayLanguageInternal(String localeID, String displayLocaleID) {
+ return getTableString("Languages", null, new IDParser(localeID).getLanguage(), displayLocaleID);
+ }
+
+ /**
+ * Returns this locale's script localized for display in the default locale.
+ * @return the localized script name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayScript() {
+ return getDisplayScriptInternal(localeID, getDefault().localeID);
+ }
+
+ /**
+ * Returns this locale's script localized for display in the provided locale.
+ * @param displayLocale the locale in which to display the name.
+ * @return the localized script name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayScript(ULocale displayLocale) {
+ return getDisplayScriptInternal(localeID, displayLocale.localeID);
+ }
+
+ /**
+ * Returns a locale's script localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose script will be displayed
+ * @param displayLocaleID the id of the locale in which to display the name.
+ * @return the localized script name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayScript(String localeID, String displayLocaleID) {
+ return getDisplayScriptInternal(localeID, getName(displayLocaleID));
+ }
+
+ /**
+ * Returns a locale's script localized for display in the provided locale.
+ * @param localeID the id of the locale whose script will be displayed.
+ * @param displayLocale the locale in which to display the name.
+ * @return the localized script name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayScript(String localeID, ULocale displayLocale) {
+ return getDisplayScriptInternal(localeID, displayLocale.localeID);
+ }
+
+ // displayLocaleID is canonical, localeID need not be since parsing will fix this.
+ private static String getDisplayScriptInternal(String localeID, String displayLocaleID) {
+ return getTableString("Scripts", null, new IDParser(localeID).getScript(), displayLocaleID);
+ }
+
+ /**
+ * Returns this locale's country localized for display in the default locale.
+ * @return the localized country name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayCountry() {
+ return getDisplayCountryInternal(localeID, getDefault().localeID);
+ }
+
+ /**
+ * Returns this locale's country localized for display in the provided locale.
+ * @param displayLocale the locale in which to display the name.
+ * @return the localized country name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayCountry(ULocale displayLocale){
+ return getDisplayCountryInternal(localeID, displayLocale.localeID);
+ }
+
+ /**
+ * Returns a locale's country localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose country will be displayed
+ * @param displayLocaleID the id of the locale in which to display the name.
+ * @return the localized country name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayCountry(String localeID, String displayLocaleID) {
+ return getDisplayCountryInternal(localeID, getName(displayLocaleID));
+ }
+
+ /**
+ * Returns a locale's country localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose country will be displayed.
+ * @param displayLocale the locale in which to display the name.
+ * @return the localized country name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayCountry(String localeID, ULocale displayLocale) {
+ return getDisplayCountryInternal(localeID, displayLocale.localeID);
+ }
+
+ // displayLocaleID is canonical, localeID need not be since parsing will fix this.
+ private static String getDisplayCountryInternal(String localeID, String displayLocaleID) {
+ return getTableString("Countries", null, new IDParser(localeID).getCountry(), displayLocaleID);
+ }
+
+ /**
+ * Returns this locale's variant localized for display in the default locale.
+ * @return the localized variant name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayVariant() {
+ return getDisplayVariantInternal(localeID, getDefault().localeID);
+ }
+
+ /**
+ * Returns this locale's variant localized for display in the provided locale.
+ * @param displayLocale the locale in which to display the name.
+ * @return the localized variant name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayVariant(ULocale displayLocale) {
+ return getDisplayVariantInternal(localeID, displayLocale.localeID);
+ }
+
+ /**
+ * Returns a locale's variant localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose variant will be displayed
+ * @param displayLocaleID the id of the locale in which to display the name.
+ * @return the localized variant name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayVariant(String localeID, String displayLocaleID){
+ return getDisplayVariantInternal(localeID, getName(displayLocaleID));
+ }
+
+ /**
+ * Returns a locale's variant localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose variant will be displayed.
+ * @param displayLocale the locale in which to display the name.
+ * @return the localized variant name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayVariant(String localeID, ULocale displayLocale) {
+ return getDisplayVariantInternal(localeID, displayLocale.localeID);
+ }
+
+ // displayLocaleID is canonical, localeID need not be since parsing will fix this.
+ private static String getDisplayVariantInternal(String localeID, String displayLocaleID) {
+ return getTableString("Variants", null, new IDParser(localeID).getVariant(), displayLocaleID);
+ }
+
+ /**
+ * Returns a keyword localized for display in the default locale.
+ * @param keyword the keyword to be displayed.
+ * @return the localized keyword name.
+ * @see #getKeywords
+ * @stable ICU 3.0
+ */
+ public static String getDisplayKeyword(String keyword) {
+ return getDisplayKeywordInternal(keyword, getDefault().localeID);
+ }
+
+ /**
+ * Returns a keyword localized for display in the specified locale.
+ * @param keyword the keyword to be displayed.
+ * @param displayLocaleID the id of the locale in which to display the keyword.
+ * @return the localized keyword name.
+ * @see #getKeywords
+ * @stable ICU 3.0
+ */
+ public static String getDisplayKeyword(String keyword, String displayLocaleID) {
+ return getDisplayKeywordInternal(keyword, getName(displayLocaleID));
+ }
+
+ /**
+ * Returns a keyword localized for display in the specified locale.
+ * @param keyword the keyword to be displayed.
+ * @param displayLocale the locale in which to display the keyword.
+ * @return the localized keyword name.
+ * @see #getKeywords
+ * @stable ICU 3.0
+ */
+ public static String getDisplayKeyword(String keyword, ULocale displayLocale) {
+ return getDisplayKeywordInternal(keyword, displayLocale.localeID);
+ }
+
+ // displayLocaleID is canonical, localeID need not be since parsing will fix this.
+ private static String getDisplayKeywordInternal(String keyword, String displayLocaleID) {
+ return getTableString("Keys", null, keyword.trim().toLowerCase(), displayLocaleID);
+ }
+
+ /**
+ * Returns a keyword value localized for display in the default locale.
+ * @param keyword the keyword whose value is to be displayed.
+ * @return the localized value name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayKeywordValue(String keyword) {
+ return getDisplayKeywordValueInternal(localeID, keyword, getDefault().localeID);
+ }
+
+ /**
+ * Returns a keyword value localized for display in the specified locale.
+ * @param keyword the keyword whose value is to be displayed.
+ * @param displayLocale the locale in which to display the value.
+ * @return the localized value name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayKeywordValue(String keyword, ULocale displayLocale) {
+ return getDisplayKeywordValueInternal(localeID, keyword, displayLocale.localeID);
+ }
+
+ /**
+ * Returns a keyword value localized for display in the specified locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose keyword value is to be displayed.
+ * @param keyword the keyword whose value is to be displayed.
+ * @param displayLocaleID the id of the locale in which to display the value.
+ * @return the localized value name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayKeywordValue(String localeID, String keyword, String displayLocaleID) {
+ return getDisplayKeywordValueInternal(localeID, keyword, getName(displayLocaleID));
+ }
+
+ /**
+ * Returns a keyword value localized for display in the specified locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the id of the locale whose keyword value is to be displayed.
+ * @param keyword the keyword whose value is to be displayed.
+ * @param displayLocale the id of the locale in which to display the value.
+ * @return the localized value name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayKeywordValue(String localeID, String keyword, ULocale displayLocale) {
+ return getDisplayKeywordValueInternal(localeID, keyword, displayLocale.localeID);
+ }
+
+ // displayLocaleID is canonical, localeID need not be since parsing will fix this.
+ private static String getDisplayKeywordValueInternal(String localeID, String keyword, String displayLocaleID) {
+ keyword = keyword.trim().toLowerCase();
+ String value = new IDParser(localeID).getKeywordValue(keyword);
+ return getTableString("Types", keyword, value, displayLocaleID);
+ }
+
+ /**
+ * Returns this locale name localized for display in the default locale.
+ * @return the localized locale name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayName() {
+ return getDisplayNameInternal(localeID, getDefault().localeID);
+ }
+
+ /**
+ * Returns this locale name localized for display in the provided locale.
+ * @param displayLocale the locale in which to display the locale name.
+ * @return the localized locale name.
+ * @stable ICU 3.0
+ */
+ public String getDisplayName(ULocale displayLocale) {
+ return getDisplayNameInternal(localeID, displayLocale.localeID);
+ }
+
+ /**
+ * Returns the locale ID localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the locale whose name is to be displayed.
+ * @param displayLocaleID the id of the locale in which to display the locale name.
+ * @return the localized locale name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayName(String localeID, String displayLocaleID) {
+ return getDisplayNameInternal(localeID, getName(displayLocaleID));
+ }
+
+ /**
+ * Returns the locale ID localized for display in the provided locale.
+ * This is a cover for the ICU4C API.
+ * @param localeID the locale whose name is to be displayed.
+ * @param displayLocale the locale in which to display the locale name.
+ * @return the localized locale name.
+ * @stable ICU 3.0
+ */
+ public static String getDisplayName(String localeID, ULocale displayLocale) {
+ return getDisplayNameInternal(localeID, displayLocale.localeID);
+ }
+
+ // displayLocaleID is canonical, localeID need not be since parsing will fix this.
+ private static String getDisplayNameInternal(String localeID, String displayLocaleID) {
+ // lang
+ // lang (script, country, variant, keyword=value, ...)
+ // script, country, variant, keyword=value, ...
+
+ final String[] tableNames = { "Languages", "Scripts", "Countries", "Variants" };
+
+ ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, displayLocaleID);
+
+ StringBuffer buf = new StringBuffer();
+
+ IDParser parser = new IDParser(localeID);
+ String[] names = parser.getLanguageScriptCountryVariant();
+
+ boolean haveLanguage = names[0].length() > 0;
+ boolean openParen = false;
+ for (int i = 0; i < names.length; ++i) {
+ String name = names[i];
+ if (name.length() > 0) {
+ name = getTableString(tableNames[i], null, name, bundle);
+ if (buf.length() > 0) { // need a separator
+ if (haveLanguage & !openParen) {
+ buf.append(" (");
+ openParen = true;
+ } else {
+ buf.append(", ");
+ }
+ }
+ buf.append(name);
+ }
+ }
+
+ Map m = parser.getKeywordMap();
+ if (!m.isEmpty()) {
+ Iterator keys = m.entrySet().iterator();
+ while (keys.hasNext()) {
+ if (buf.length() > 0) {
+ if (haveLanguage & !openParen) {
+ buf.append(" (");
+ openParen = true;
+ } else {
+ buf.append(", ");
+ }
+ }
+ Map.Entry e = (Map.Entry)keys.next();
+ String key = (String)e.getKey();
+ String val = (String)e.getValue();
+ buf.append(getTableString("Keys", null, key, bundle));
+ buf.append("=");
+ buf.append(getTableString("Types", key, val, bundle));
+ }
+ }
+
+ if (openParen) {
+ buf.append(")");
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Selector for <tt>getLocale()</tt> indicating the locale of the
+ * resource containing the data. This is always at or above the
+ * valid locale. If the valid locale does not contain the
+ * specific data being requested, then the actual locale will be
+ * above the valid locale. If the object was not constructed from
+ * locale data, then the valid locale is <i>null</i>.
+ *
+ * @draft ICU 2.8 (retain)
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static Type ACTUAL_LOCALE = new Type(0);
+
+ /**
+ * Selector for <tt>getLocale()</tt> indicating the most specific
+ * locale for which any data exists. This is always at or above
+ * the requested locale, and at or below the actual locale. If
+ * the requested locale does not correspond to any resource data,
+ * then the valid locale will be above the requested locale. If
+ * the object was not constructed from locale data, then the
+ * actual locale is <i>null</i>.
+ *
+ * <p>Note: The valid locale will be returned correctly in ICU
+ * 3.0 or later. In ICU 2.8, it is not returned correctly.
+ * @draft ICU 2.8 (retain)
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static Type VALID_LOCALE = new Type(1);
+
+ /**
+ * Opaque selector enum for <tt>getLocale()</tt>.
+ * @see com.ibm.icu.util.ULocale
+ * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE
+ * @see com.ibm.icu.util.ULocale#VALID_LOCALE
+ * @draft ICU 2.8 (retainAll)
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+ public static final class Type {
+ private int localeType;
+ private Type(int type) { localeType = type; }
+ }
+
+
+ /**
+ * Based on a HTTP formatted list of acceptable locales, determine an available locale for the user.
+ * NullPointerException is thrown if acceptLanguageList or availableLocales is
+ * null. If fallback is non-null, it will contain true if a fallback locale (one
+ * not in the acceptLanguageList) was returned. The value on entry is ignored.
+ * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if
+ * if a ROOT locale was used as a fallback (because nothing else in
+ * availableLocales matched). No ULocale array element should be null; behavior
+ * is undefined if this is the case.
+ * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
+ * @param availableLocales list of available locales. One of these will be returned.
+ * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status
+ * @return one of the locales from the availableLocales list, or null if none match
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+
+ public static ULocale acceptLanguage(String acceptLanguageList, ULocale[] availableLocales,
+ boolean[] fallback) {
+ /**
+ * @internal ICU 3.4
+ */
+ class ULocaleAcceptLanguageQ implements Comparable {
+ private double q;
+ private double serial;
+ public ULocaleAcceptLanguageQ(double theq, int theserial) {
+ q = theq;
+ serial = theserial;
+ }
+ public int compareTo(Object o) {
+ ULocaleAcceptLanguageQ other = (ULocaleAcceptLanguageQ) o;
+ if(q > other.q) { // reverse - to sort in descending order
+ return -1;
+ } else if(q < other.q) {
+ return 1;
+ }
+ if(serial < other.serial) {
+ return -1;
+ } else if(serial > other.serial) {
+ return 1;
+ } else {
+ return 0; // same object
+ }
+ }
+ }
+
+ // 1st: parse out the acceptLanguageList into an array
+
+ TreeMap map = new TreeMap();
+
+ final int l = acceptLanguageList.length();
+ int n;
+ int last=-1;
+ for(n=0;n<l;n++) {
+ int itemEnd = acceptLanguageList.indexOf(',',n);
+ if(itemEnd == -1) {
+ itemEnd = l;
+ }
+ int paramEnd = acceptLanguageList.indexOf(';',n);
+ double q = 1.0;
+
+ if((paramEnd != -1) && (paramEnd < itemEnd)) {
+ /* semicolon (;) is closer than end (,) */
+ int t = paramEnd + 1;
+ while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
+ t++;
+ }
+ if(acceptLanguageList.charAt(t)=='q') {
+ t++;
+ }
+ while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
+ t++;
+ }
+ if(acceptLanguageList.charAt(t)=='=') {
+ t++;
+ }
+ while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
+ t++;
+ }
+ try {
+ String val = acceptLanguageList.substring(t,itemEnd).trim();
+ q = Double.parseDouble(val);
+ } catch (NumberFormatException nfe) {
+ q = 1.0;
+ }
+ } else {
+ q = 1.0; //default
+ paramEnd = itemEnd;
+ }
+
+ String loc = acceptLanguageList.substring(n,paramEnd).trim();
+ int serial = map.size();
+ ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q,serial);
+ map.put(entry, new ULocale(canonicalize(loc))); // sort in reverse order.. 1.0, 0.9, 0.8 .. etc
+ n = itemEnd; // get next item. (n++ will skip over delimiter)
+ }
+
+ // 2. pull out the map
+ ULocale acceptList[] = (ULocale[])map.values().toArray(new ULocale[map.size()]);
+
+ // 3. call the real function
+ return acceptLanguage(acceptList, availableLocales, fallback);
+ }
+
+ /**
+ * Based on a list of acceptable locales, determine an available locale for the user.
+ * NullPointerException is thrown if acceptLanguageList or availableLocales is
+ * null. If fallback is non-null, it will contain true if a fallback locale (one
+ * not in the acceptLanguageList) was returned. The value on entry is ignored.
+ * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if
+ * if a ROOT locale was used as a fallback (because nothing else in
+ * availableLocales matched). No ULocale array element should be null; behavior
+ * is undefined if this is the case.
+ * @param acceptLanguageList list of acceptable locales
+ * @param availableLocales list of available locales. One of these will be returned.
+ * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status
+ * @return one of the locales from the availableLocales list, or null if none match
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+
+ public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[]
+ availableLocales, boolean[] fallback) {
+ // fallbacklist
+ int i,j;
+ if(fallback != null) {
+ fallback[0]=true;
+ }
+ for(i=0;i<acceptLanguageList.length;i++) {
+ ULocale aLocale = acceptLanguageList[i];
+ boolean[] setFallback = fallback;
+ do {
+ for(j=0;j<availableLocales.length;j++) {
+ if(availableLocales[j].equals(aLocale)) {
+ if(setFallback != null) {
+ setFallback[0]=false; // first time with this locale - not a fallback.
+ }
+ return availableLocales[j];
+ }
+ }
+ Locale loc = aLocale.toLocale();
+ Locale parent = LocaleUtility.fallback(loc);
+ if(parent != null) {
+ aLocale = new ULocale(parent);
+ } else {
+ aLocale = null;
+ }
+ setFallback = null; // Do not set fallback in later iterations
+ } while (aLocale != null);
+ }
+ return null;
+ }
+
+ /**
+ * Based on a HTTP formatted list of acceptable locales, determine an available locale for the user.
+ * NullPointerException is thrown if acceptLanguageList or availableLocales is
+ * null. If fallback is non-null, it will contain true if a fallback locale (one
+ * not in the acceptLanguageList) was returned. The value on entry is ignored.
+ * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if
+ * if a ROOT locale was used as a fallback (because nothing else in
+ * availableLocales matched). No ULocale array element should be null; behavior
+ * is undefined if this is the case.
+ * This function will choose a locale from the ULocale.getAvailableLocales() list as available.
+ * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
+ * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status
+ * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+
+ public static ULocale acceptLanguage(String acceptLanguageList, boolean[] fallback) {
+ return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
+ fallback);
+ }
+
+ /**
+ * Based on an ordered array of acceptable locales, determine an available locale for the user.
+ * NullPointerException is thrown if acceptLanguageList or availableLocales is
+ * null. If fallback is non-null, it will contain true if a fallback locale (one
+ * not in the acceptLanguageList) was returned. The value on entry is ignored.
+ * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if
+ * if a ROOT locale was used as a fallback (because nothing else in
+ * availableLocales matched). No ULocale array element should be null; behavior
+ * is undefined if this is the case.
+ * This function will choose a locale from the ULocale.getAvailableLocales() list as available.
+ * @param acceptLanguageList ordered array of acceptable locales (preferred are listed first)
+ * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status
+ * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match
+ * @draft ICU 3.4
+ * @deprecated This is a draft API and might change in a future release of ICU.
+ */
+
+ public static ULocale acceptLanguage(ULocale[] acceptLanguageList, boolean[]
+ fallback) {
+ return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
+ fallback);
+ }
+
+}