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&lt;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&#x00E8;me International d'Unit&#x00E9;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);
+    }    
+    
+}