Home | History | Annotate | Download | only in util
      1 package org.unicode.cldr.util;
      2 
      3 import java.text.ParseException;
      4 import java.util.ArrayList;
      5 import java.util.Date;
      6 import java.util.HashMap;
      7 import java.util.List;
      8 import java.util.Map;
      9 import java.util.regex.Matcher;
     10 
     11 import org.unicode.cldr.util.CLDRFile.Status;
     12 import org.unicode.cldr.util.DayPeriodInfo.DayPeriod;
     13 import org.unicode.cldr.util.SupplementalDataInfo.CurrencyNumberInfo;
     14 
     15 import com.ibm.icu.text.DateFormat;
     16 import com.ibm.icu.text.DateFormatSymbols;
     17 import com.ibm.icu.text.DecimalFormat;
     18 import com.ibm.icu.text.DecimalFormatSymbols;
     19 import com.ibm.icu.text.MessageFormat;
     20 import com.ibm.icu.text.NumberFormat;
     21 import com.ibm.icu.text.RuleBasedCollator;
     22 import com.ibm.icu.text.SimpleDateFormat;
     23 import com.ibm.icu.text.UTF16;
     24 import com.ibm.icu.text.UnicodeSet;
     25 import com.ibm.icu.util.Calendar;
     26 import com.ibm.icu.util.Currency;
     27 import com.ibm.icu.util.Output;
     28 import com.ibm.icu.util.TimeZone;
     29 import com.ibm.icu.util.ULocale;
     30 
     31 public class ICUServiceBuilder {
     32     public static Currency NO_CURRENCY = Currency.getInstance("XXX");
     33     private CLDRFile cldrFile;
     34     private CLDRFile collationFile;
     35     private static Map<CLDRLocale, ICUServiceBuilder> ISBMap = new HashMap<CLDRLocale, ICUServiceBuilder>();
     36 
     37     private static TimeZone utc = TimeZone.getTimeZone("GMT");
     38     private static DateFormat iso = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", ULocale.ENGLISH);
     39     static {
     40         iso.setTimeZone(utc);
     41     }
     42 
     43     static public String isoDateFormat(Date date) {
     44         return iso.format(date);
     45     }
     46 
     47     public static String isoDateFormat(long value) {
     48         // TODO Auto-generated method stub
     49         return iso.format(new Date(value));
     50     }
     51 
     52     static public Date isoDateParse(String date) throws ParseException {
     53         return iso.parse(date);
     54     }
     55 
     56     private Map<String, SimpleDateFormat> cacheDateFormats = new HashMap<String, SimpleDateFormat>();
     57     private Map<String, DateFormatSymbols> cacheDateFormatSymbols = new HashMap<String, DateFormatSymbols>();
     58     private Map<String, NumberFormat> cacheNumberFormats = new HashMap<String, NumberFormat>();
     59     private Map<String, DecimalFormatSymbols> cacheDecimalFormatSymbols = new HashMap<String, DecimalFormatSymbols>();
     60     private SupplementalDataInfo supplementalData;
     61 
     62     // private Factory cldrFactory;
     63     // public ICUServiceBuilder setCLDRFactory(Factory cldrFactory) {
     64     // this.cldrFactory = cldrFactory;
     65     // dateFormatCache.clear();
     66     // return this; // for chaining
     67     // }
     68 
     69     private static int[] DateFormatValues = { -1, DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL };
     70     private static String[] DateFormatNames = { "none", "short", "medium", "long", "full" };
     71 
     72     public static String getDateNames(int i) {
     73         return DateFormatNames[i];
     74     }
     75 
     76     public static int LIMIT_DATE_FORMAT_INDEX = DateFormatValues.length;
     77 
     78     private static final String[] Days = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
     79 
     80     // public SimpleDateFormat getDateFormat(CLDRFile cldrFile, int dateIndex, int timeIndex) {
     81     // //CLDRFile cldrFile = cldrFactory.make(localeID.toString(), true);
     82     // return getDateFormat(dateIndex, timeIndex);
     83     // }
     84 
     85     public CLDRFile getCldrFile() {
     86         return cldrFile;
     87     }
     88 
     89     public CLDRFile getCollationFile() {
     90         return collationFile;
     91     }
     92 
     93     public ICUServiceBuilder setCldrFile(CLDRFile cldrFile) {
     94         if (!cldrFile.isResolved()) throw new IllegalArgumentException("CLDRFile must be resolved");
     95         this.cldrFile = cldrFile;
     96         supplementalData = CLDRConfig.getInstance().getSupplementalDataInfo();
     97         // SupplementalDataInfo.getInstance(this.cldrFile.getSupplementalDirectory());
     98         cacheDateFormats.clear();
     99         cacheNumberFormats.clear();
    100         cacheDateFormatSymbols.clear();
    101         cacheDecimalFormatSymbols.clear();
    102         return this;
    103     }
    104 
    105     public static ICUServiceBuilder forLocale(CLDRLocale locale) {
    106 
    107         ICUServiceBuilder result = ISBMap.get(locale);
    108 
    109         if (result == null) {
    110             result = new ICUServiceBuilder();
    111 
    112             if (locale != null) {
    113                 result.cldrFile = Factory.make(CLDRPaths.MAIN_DIRECTORY, ".*").make(locale.getBaseName(), true);
    114                 result.collationFile = Factory.make(CLDRPaths.COLLATION_DIRECTORY, ".*").makeWithFallback(locale.getBaseName());
    115             }
    116             result.supplementalData = SupplementalDataInfo.getInstance(CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
    117             result.cacheDateFormats.clear();
    118             result.cacheNumberFormats.clear();
    119             result.cacheDateFormatSymbols.clear();
    120             result.cacheDecimalFormatSymbols.clear();
    121 
    122             ISBMap.put(locale, result);
    123         }
    124         return result;
    125     }
    126 
    127     public RuleBasedCollator getRuleBasedCollator(String type) throws Exception {
    128         String rules = "";
    129         String collationType;
    130         if ("default".equals(type)) {
    131             String path = "//ldml/collations/defaultCollation";
    132             collationType = collationFile.getWinningValueWithBailey(path);
    133         } else {
    134             collationType = type;
    135         }
    136         String path = "";
    137         String importPath = "//ldml/collations/collation[@visibility=\"external\"][@type=\"" + collationType + "\"]/import[@type=\"standard\"]";
    138         if (collationFile.isHere(importPath)) {
    139             String fullPath = collationFile.getFullXPath(importPath);
    140             XPathParts xpp = new XPathParts();
    141             xpp.set(fullPath);
    142             String importSource = xpp.getAttributeValue(-1, "source");
    143             String importType = xpp.getAttributeValue(-1, "type");
    144             CLDRLocale importLocale = CLDRLocale.getInstance(importSource);
    145             CLDRFile importCollationFile = Factory.make(CLDRPaths.COLLATION_DIRECTORY, ".*").makeWithFallback(importLocale.getBaseName());
    146             path = "//ldml/collations/collation[@type=\"" + importType + "\"]/cr";
    147             rules = importCollationFile.getStringValue(path);
    148 
    149         } else {
    150             path = "//ldml/collations/collation[@type=\"" + collationType + "\"]/cr";
    151             rules = collationFile.getStringValue(path);
    152         }
    153         RuleBasedCollator col;
    154         if (rules != null && rules.length() > 0)
    155             col = new RuleBasedCollator(rules);
    156         else
    157             col = (RuleBasedCollator) RuleBasedCollator.getInstance();
    158 
    159         return col;
    160     }
    161 
    162     public RuleBasedCollator getRuleBasedCollator() throws Exception {
    163         return getRuleBasedCollator("default");
    164     }
    165 
    166     public SimpleDateFormat getDateFormat(String calendar, int dateIndex, int timeIndex) {
    167         return getDateFormat(calendar, dateIndex, timeIndex, null);
    168     }
    169 
    170     public SimpleDateFormat getDateFormat(String calendar, int dateIndex, int timeIndex, String numbersOverride) {
    171         String key = cldrFile.getLocaleID() + "," + calendar + "," + dateIndex + "," + timeIndex;
    172         SimpleDateFormat result = (SimpleDateFormat) cacheDateFormats.get(key);
    173         if (result != null) return (SimpleDateFormat) result.clone();
    174 
    175         String pattern = getPattern(calendar, dateIndex, timeIndex);
    176 
    177         result = getFullFormat(calendar, pattern, numbersOverride);
    178         cacheDateFormats.put(key, result);
    179         // System.out.println("created " + key);
    180         return (SimpleDateFormat) result.clone();
    181     }
    182 
    183     public SimpleDateFormat getDateFormat(String calendar, String pattern, String numbersOverride) {
    184         String key = cldrFile.getLocaleID() + "," + calendar + ",," + pattern + ",,," + numbersOverride;
    185         SimpleDateFormat result = (SimpleDateFormat) cacheDateFormats.get(key);
    186         if (result != null) return (SimpleDateFormat) result.clone();
    187         result = getFullFormat(calendar, pattern, numbersOverride);
    188         cacheDateFormats.put(key, result);
    189         // System.out.println("created " + key);
    190         return (SimpleDateFormat) result.clone();
    191     }
    192 
    193     public SimpleDateFormat getDateFormat(String calendar, String pattern) {
    194         return getDateFormat(calendar, pattern, null);
    195     }
    196 
    197     private SimpleDateFormat getFullFormat(String calendar, String pattern, String numbersOverride) {
    198         ULocale curLocaleWithCalendar = new ULocale(cldrFile.getLocaleID() + "@calendar=" + calendar);
    199         SimpleDateFormat result = new SimpleDateFormat(pattern, numbersOverride, curLocaleWithCalendar); // formatData
    200         // TODO Serious Hack, until ICU #4915 is fixed. => It *was* fixed in ICU 3.8, so now use current locale.(?)
    201         Calendar cal = Calendar.getInstance(curLocaleWithCalendar);
    202         // TODO look these up and set them
    203         // cal.setFirstDayOfWeek()
    204         // cal.setMinimalDaysInFirstWeek()
    205         cal.setTimeZone(utc);
    206         result.setCalendar(cal);
    207 
    208         result.setDateFormatSymbols((DateFormatSymbols) _getDateFormatSymbols(calendar).clone());
    209 
    210         // formatData.setZoneStrings();
    211 
    212         NumberFormat numberFormat = result.getNumberFormat();
    213         if (numberFormat instanceof DecimalFormat) {
    214             DecimalFormat df = (DecimalFormat) numberFormat;
    215             df.setGroupingUsed(false);
    216             df.setDecimalSeparatorAlwaysShown(false);
    217             df.setParseIntegerOnly(true); /* So that dd.MM.yy can be parsed */
    218             df.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
    219         }
    220         result.setNumberFormat((NumberFormat) numberFormat.clone());
    221         // Need to put the field specific number format override formatters back in place, since
    222         // the previous result.setNumberFormat above nukes them.
    223         if (numbersOverride != null && numbersOverride.indexOf("=") != -1) {
    224             String[] overrides = numbersOverride.split(",");
    225             for (String override : overrides) {
    226                 String[] fields = override.split("=", 2);
    227                 if (fields.length == 2) {
    228                     String overrideField = fields[0].substring(0, 1);
    229                     ULocale curLocaleWithNumbers = new ULocale(cldrFile.getLocaleID() + "@numbers=" + fields[1]);
    230                     NumberFormat onf = NumberFormat.getInstance(curLocaleWithNumbers, NumberFormat.NUMBERSTYLE);
    231                     if (onf instanceof DecimalFormat) {
    232                         DecimalFormat df = (DecimalFormat) onf;
    233                         df.setGroupingUsed(false);
    234                         df.setDecimalSeparatorAlwaysShown(false);
    235                         df.setParseIntegerOnly(true); /* So that dd.MM.yy can be parsed */
    236                         df.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
    237                     }
    238                     result.setNumberFormat(overrideField, onf);
    239                 }
    240             }
    241         }
    242         return result;
    243     }
    244 
    245     private DateFormatSymbols _getDateFormatSymbols(String calendar) {
    246         String key = cldrFile.getLocaleID() + "," + calendar;
    247         DateFormatSymbols result = cacheDateFormatSymbols.get(key);
    248         if (result != null) return (DateFormatSymbols) result.clone();
    249 
    250         String[] last;
    251         // TODO We would also like to be able to set the new symbols leapMonthPatterns & shortYearNames
    252         // (related to Chinese calendar) to their currently-winning values. Until we have the necessary
    253         // setters (per ICU ticket #9385) we can't do that. However, we can at least use the values
    254         // that ICU has for the current locale, instead of using the values that ICU has for root.
    255         ULocale curLocaleWithCalendar = new ULocale(cldrFile.getLocaleID() + "@calendar=" + calendar);
    256         DateFormatSymbols formatData = new DateFormatSymbols(curLocaleWithCalendar);
    257 
    258         String prefix = "//ldml/dates/calendars/calendar[@type=\"" + calendar + "\"]/";
    259 
    260         formatData.setAmPmStrings(last = getArrayOfWinningValues(new String[] {
    261             getDayPeriods(prefix, "format", "wide", "am"),
    262             getDayPeriods(prefix, "format", "wide", "pm") }));
    263         checkFound(last);
    264         // if (last[0] == null && notGregorian) {
    265         // if (gregorianBackup == null) gregorianBackup = _getDateFormatSymbols("gregorian");
    266         // formatData.setAmPmStrings(last = gregorianBackup.getAmPmStrings());
    267         // }
    268 
    269         int minEras = (calendar.equals("chinese") || calendar.equals("dangi")) ? 0 : 1;
    270 
    271         List<String> temp = getArray(prefix + "eras/eraAbbr/era[@type=\"", 0, null, "\"]", minEras);
    272         formatData.setEras(last = (String[]) temp.toArray(new String[temp.size()]));
    273         if (minEras != 0) checkFound(last);
    274         // if (temp.size() < 2 && notGregorian) {
    275         // if (gregorianBackup == null) gregorianBackup = _getDateFormatSymbols("gregorian");
    276         // formatData.setEras(last = gregorianBackup.getEras());
    277         // }
    278 
    279         temp = getArray(prefix + "eras/eraNames/era[@type=\"", 0, null, "\"]", minEras);
    280         formatData.setEraNames(last = (String[]) temp.toArray(new String[temp.size()]));
    281         if (minEras != 0) checkFound(last);
    282         // if (temp.size() < 2 && notGregorian) {
    283         // if (gregorianBackup == null) gregorianBackup = _getDateFormatSymbols("gregorian");
    284         // formatData.setEraNames(last = gregorianBackup.getEraNames());
    285         // }
    286 
    287         formatData.setMonths(getArray(prefix, "month", "format", "wide"), DateFormatSymbols.FORMAT,
    288             DateFormatSymbols.WIDE);
    289         formatData.setMonths(getArray(prefix, "month", "format", "abbreviated"), DateFormatSymbols.FORMAT,
    290             DateFormatSymbols.ABBREVIATED);
    291         formatData.setMonths(getArray(prefix, "month", "format", "narrow"), DateFormatSymbols.FORMAT,
    292             DateFormatSymbols.NARROW);
    293 
    294         formatData.setMonths(getArray(prefix, "month", "stand-alone", "wide"), DateFormatSymbols.STANDALONE,
    295             DateFormatSymbols.WIDE);
    296         formatData.setMonths(getArray(prefix, "month", "stand-alone", "abbreviated"), DateFormatSymbols.STANDALONE,
    297             DateFormatSymbols.ABBREVIATED);
    298         formatData.setMonths(getArray(prefix, "month", "stand-alone", "narrow"), DateFormatSymbols.STANDALONE,
    299             DateFormatSymbols.NARROW);
    300 
    301         // formatData.setWeekdays(getArray(prefix, "day", "format", "wide"));
    302         // if (last == null && notGregorian) {
    303         // if (gregorianBackup == null) gregorianBackup = _getDateFormatSymbols("gregorian");
    304         // formatData.setWeekdays(gregorianBackup.getWeekdays());
    305         // }
    306 
    307         formatData.setWeekdays(getArray(prefix, "day", "format", "wide"), DateFormatSymbols.FORMAT,
    308             DateFormatSymbols.WIDE);
    309         formatData.setWeekdays(getArray(prefix, "day", "format", "abbreviated"), DateFormatSymbols.FORMAT,
    310             DateFormatSymbols.ABBREVIATED);
    311         formatData.setWeekdays(getArray(prefix, "day", "format", "narrow"), DateFormatSymbols.FORMAT,
    312             DateFormatSymbols.NARROW);
    313 
    314         formatData.setWeekdays(getArray(prefix, "day", "stand-alone", "wide"), DateFormatSymbols.STANDALONE,
    315             DateFormatSymbols.WIDE);
    316         formatData.setWeekdays(getArray(prefix, "day", "stand-alone", "abbreviated"), DateFormatSymbols.STANDALONE,
    317             DateFormatSymbols.ABBREVIATED);
    318         formatData.setWeekdays(getArray(prefix, "day", "stand-alone", "narrow"), DateFormatSymbols.STANDALONE,
    319             DateFormatSymbols.NARROW);
    320 
    321         // quarters
    322 
    323         formatData.setQuarters(getArray(prefix, "quarter", "format", "wide"), DateFormatSymbols.FORMAT,
    324             DateFormatSymbols.WIDE);
    325         formatData.setQuarters(getArray(prefix, "quarter", "format", "abbreviated"), DateFormatSymbols.FORMAT,
    326             DateFormatSymbols.ABBREVIATED);
    327         formatData.setQuarters(getArray(prefix, "quarter", "format", "narrow"), DateFormatSymbols.FORMAT,
    328             DateFormatSymbols.NARROW);
    329 
    330         formatData.setQuarters(getArray(prefix, "quarter", "stand-alone", "wide"), DateFormatSymbols.STANDALONE,
    331             DateFormatSymbols.WIDE);
    332         formatData.setQuarters(getArray(prefix, "quarter", "stand-alone", "abbreviated"), DateFormatSymbols.STANDALONE,
    333             DateFormatSymbols.ABBREVIATED);
    334         formatData.setQuarters(getArray(prefix, "quarter", "stand-alone", "narrow"), DateFormatSymbols.STANDALONE,
    335             DateFormatSymbols.NARROW);
    336 
    337         cacheDateFormatSymbols.put(key, formatData);
    338         return formatData;
    339     }
    340 
    341     /**
    342      * Example from en.xml
    343      * <dayPeriods>
    344      * <dayPeriodContext type="format">
    345      * <dayPeriodWidth type="wide">
    346      * <dayPeriod type="am">AM</dayPeriod>
    347      * <dayPeriod type="am" alt="variant">a.m.</dayPeriod>
    348      * <dayPeriod type="pm">PM</dayPeriod>
    349      * <dayPeriod type="pm" alt="variant">p.m.</dayPeriod>
    350      * </dayPeriodWidth>
    351      * </dayPeriodContext>
    352      * </dayPeriods>
    353      */
    354     private String getDayPeriods(String prefix, String context, String width, String type) {
    355         return prefix + "dayPeriods/dayPeriodContext[@type=\"" + context + "\"]/dayPeriodWidth[@type=\"" +
    356             width + "\"]/dayPeriod[@type=\"" + type + "\"]";
    357     }
    358 
    359     private String[] getArrayOfWinningValues(String[] xpaths) {
    360         String result[] = new String[xpaths.length];
    361         for (int i = 0; i < xpaths.length; i++) {
    362             result[i] = cldrFile.getWinningValueWithBailey(xpaths[i]);
    363         }
    364         checkFound(result, xpaths);
    365         return result;
    366     }
    367 
    368     private void checkFound(String[] last) {
    369         if (last == null || last.length == 0 || last[0] == null) {
    370             throw new IllegalArgumentException("Failed to load array");
    371         }
    372     }
    373 
    374     private void checkFound(String[] last, String[] xpaths) {
    375         if (last == null || last.length == 0 || last[0] == null) {
    376             throw new IllegalArgumentException("Failed to load array {" + xpaths[0] + ",...}");
    377         }
    378     }
    379 
    380     private String getPattern(String calendar, int dateIndex, int timeIndex) {
    381         String pattern;
    382         if (DateFormatValues[timeIndex] == -1)
    383             pattern = getDateTimePattern(calendar, "date", DateFormatNames[dateIndex]);
    384         else if (DateFormatValues[dateIndex] == -1)
    385             pattern = getDateTimePattern(calendar, "time", DateFormatNames[timeIndex]);
    386         else {
    387             String p0 = getDateTimePattern(calendar, "time", DateFormatNames[timeIndex]);
    388             String p1 = getDateTimePattern(calendar, "date", DateFormatNames[dateIndex]);
    389             String datetimePat = getDateTimePattern(calendar, "dateTime", DateFormatNames[dateIndex]);
    390             pattern = MessageFormat.format(datetimePat, (Object[]) new String[] { p0, p1 });
    391         }
    392         return pattern;
    393     }
    394 
    395     /**
    396      * @param calendar
    397      *            TODO
    398      *
    399      */
    400     private String getDateTimePattern(String calendar, String dateOrTime, String type) {
    401         type = "[@type=\"" + type + "\"]";
    402         String key = "//ldml/dates/calendars/calendar[@type=\"" + calendar + "\"]/"
    403             + dateOrTime + "Formats/"
    404             + dateOrTime + "FormatLength"
    405             + type + "/" + dateOrTime + "Format[@type=\"standard\"]/pattern[@type=\"standard\"]";
    406         // change standard to a choice
    407 
    408         String value = cldrFile.getWinningValueWithBailey(key);
    409         if (value == null)
    410             throw new IllegalArgumentException("locale: " + cldrFile.getLocaleID() + "\tpath: " + key
    411                 + CldrUtility.LINE_SEPARATOR + "value: " + value);
    412         return value;
    413     }
    414 
    415     // enum ArrayType {day, month, quarter};
    416 
    417     private String[] getArray(String key, String type, String context, String width) {
    418         String prefix = key + type + "s/"
    419             + type + "Context[@type=\"" + context + "\"]/"
    420             + type + "Width[@type=\"" + width + "\"]/"
    421             + type + "[@type=\"";
    422         String postfix = "\"]";
    423         boolean isDay = type.equals("day");
    424         final int arrayCount = isDay ? 7 : type.equals("month") ? 12 : 4;
    425         List<String> temp = getArray(prefix, isDay ? 0 : 1, isDay ? Days : null, postfix, arrayCount);
    426         if (isDay) temp.add(0, "");
    427         String[] result = (String[]) temp.toArray(new String[temp.size()]);
    428         checkFound(result);
    429         return result;
    430     }
    431 
    432     static final Matcher gregorianMonthsMatcher = PatternCache.get(".*gregorian.*months.*").matcher("");
    433 
    434     private List<String> getArray(String prefix, int firstIndex, String[] itemNames, String postfix, int minimumSize) {
    435         List<String> result = new ArrayList<String>();
    436         String lastType;
    437         for (int i = firstIndex;; ++i) {
    438             lastType = itemNames != null && i < itemNames.length ? itemNames[i] : String.valueOf(i);
    439             String item = cldrFile.getWinningValueWithBailey(prefix + lastType + postfix);
    440             if (item == null) break;
    441             result.add(item);
    442         }
    443         // the following code didn't do anything, so I'm wondering what it was there for?
    444         // it's to catch errors
    445         if (result.size() < minimumSize) {
    446             throw new RuntimeException("Internal Error: ICUServiceBuilder.getArray():" + cldrFile.getLocaleID() + " "
    447                 + prefix + lastType + postfix + " - result.size=" + result.size() + ", less than acceptable minimum "
    448                 + minimumSize);
    449             // Collection s = CollectionUtilities.addAll(cldrFile.iterator(prefix), new
    450             // TreeSet());//cldrFile.keySet(".*gregorian.*months.*", );
    451             // String item = cldrFile.getWinningValueWithBailey(prefix + lastType + postfix);
    452             // throw new IllegalArgumentException("Can't find enough items");
    453         }
    454         /*
    455          * <months>
    456          * <monthContext type="format">
    457          * <monthWidth type="abbreviated">
    458          * <month type="1">1</month>
    459          */
    460         return result;
    461     }
    462 
    463     private static String[] NumberNames = { "integer", "decimal", "percent", "scientific" }; // // "standard", , "INR",
    464 
    465     public String getNumberNames(int i) {
    466         return NumberNames[i];
    467     }
    468 
    469     public static int LIMIT_NUMBER_INDEX = NumberNames.length;
    470 
    471     private static class MyCurrency extends Currency {
    472         String symbol;
    473         String displayName;
    474         int fractDigits;
    475         double roundingIncrement;
    476 
    477         MyCurrency(String code, String symbol, String displayName, CurrencyNumberInfo currencyNumberInfo) {
    478             super(code);
    479             this.symbol = symbol == null ? code : symbol;
    480             this.displayName = displayName == null ? code : displayName;
    481             this.fractDigits = currencyNumberInfo.getDigits();
    482             this.roundingIncrement = currencyNumberInfo.getRoundingIncrement();
    483         }
    484 
    485         public String getName(ULocale locale,
    486             int nameStyle,
    487             boolean[] isChoiceFormat) {
    488 
    489             String result = nameStyle == 0 ? this.symbol
    490                 : nameStyle == 1 ? getCurrencyCode()
    491                     : nameStyle == 2 ? displayName
    492                         : null;
    493             if (result == null) throw new IllegalArgumentException();
    494             // snagged from currency
    495             if (isChoiceFormat != null) {
    496                 isChoiceFormat[0] = false;
    497             }
    498             int i = 0;
    499             while (i < result.length() && result.charAt(i) == '=' && i < 2) {
    500                 ++i;
    501             }
    502             if (isChoiceFormat != null) {
    503                 isChoiceFormat[0] = (i == 1);
    504             }
    505             if (i != 0) {
    506                 // Skip over first mark
    507                 result = result.substring(1);
    508             }
    509             return result;
    510         }
    511 
    512         /**
    513          * Returns the rounding increment for this currency, or 0.0 if no
    514          * rounding is done by this currency.
    515          *
    516          * @return the non-negative rounding increment, or 0.0 if none
    517          * @stable ICU 2.2
    518          */
    519         public double getRoundingIncrement() {
    520             return roundingIncrement;
    521         }
    522 
    523         public int getDefaultFractionDigits() {
    524             return fractDigits;
    525         }
    526 
    527         public boolean equals(Object other) {
    528             MyCurrency that = (MyCurrency) other;
    529             return roundingIncrement == that.roundingIncrement
    530                 && fractDigits == that.fractDigits
    531                 && symbol.equals(that.symbol)
    532                 && displayName.equals(that.displayName);
    533         }
    534 
    535 //        public int hashCode(Object other) {
    536 //            MyCurrency that = (MyCurrency) other;
    537 //            return (((int) (roundingIncrement * 37) + fractDigits) * 37 + symbol.hashCode()) * 37
    538 //                + displayName.hashCode();
    539 //        }
    540     }
    541 
    542     static int CURRENCY = 0, OTHER_KEY = 1, PATTERN = 2;
    543 
    544     public DecimalFormat getCurrencyFormat(String currency) {
    545         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    546         return _getNumberFormat(currency, CURRENCY, null, null);
    547     }
    548 
    549     public DecimalFormat getCurrencyFormat(String currency, String currencySymbol) {
    550         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    551         return _getNumberFormat(currency, CURRENCY, currencySymbol, null);
    552     }
    553 
    554     public DecimalFormat getCurrencyFormat(String currency, String currencySymbol, String numberSystem) {
    555         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    556         return _getNumberFormat(currency, CURRENCY, currencySymbol, numberSystem);
    557     }
    558 
    559     public DecimalFormat getLongCurrencyFormat(String currency) {
    560         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    561         return _getNumberFormat(currency, CURRENCY, null, null);
    562     }
    563 
    564     public DecimalFormat getNumberFormat(int index) {
    565         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    566         return _getNumberFormat(NumberNames[index], OTHER_KEY, null, null);
    567     }
    568 
    569     public DecimalFormat getNumberFormat(int index, String numberSystem) {
    570         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    571         return _getNumberFormat(NumberNames[index], OTHER_KEY, null, numberSystem);
    572     }
    573 
    574     public NumberFormat getGenericNumberFormat(String ns) {
    575         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    576         NumberFormat result = (NumberFormat) cacheNumberFormats.get(cldrFile.getLocaleID() + "@numbers=" + ns);
    577         if (result != null) return result;
    578         ULocale ulocale = new ULocale(cldrFile.getLocaleID() + "@numbers=" + ns);
    579         result = NumberFormat.getInstance(ulocale);
    580         cacheNumberFormats.put(cldrFile.getLocaleID() + "@numbers=" + ns, result);
    581         return result;
    582     }
    583 
    584     public DecimalFormat getNumberFormat(String pattern) {
    585         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    586         return _getNumberFormat(pattern, PATTERN, null, null);
    587     }
    588 
    589     public DecimalFormat getNumberFormat(String pattern, String numberSystem) {
    590         // CLDRFile cldrFile = cldrFactory.make(localeID, true);
    591         return _getNumberFormat(pattern, PATTERN, null, numberSystem);
    592     }
    593 
    594     private DecimalFormat _getNumberFormat(String key1, int kind, String currencySymbol, String numberSystem) {
    595         String localeIDString = (numberSystem == null) ? cldrFile.getLocaleID() : cldrFile.getLocaleID() + "@numbers="
    596             + numberSystem;
    597         ULocale ulocale = new ULocale(localeIDString);
    598         String key = (currencySymbol == null) ? ulocale + "/" + key1 + "/" + kind : ulocale + "/" + key1 + "/" + kind
    599             + "/" + currencySymbol;
    600         DecimalFormat result = (DecimalFormat) cacheNumberFormats.get(key);
    601         if (result != null) {
    602             return result;
    603         }
    604 
    605         String pattern = kind == PATTERN ? key1 : getPattern(key1, kind);
    606 
    607         DecimalFormatSymbols symbols = _getDecimalFormatSymbols(numberSystem);
    608         /*
    609          * currencySymbol.equals(other.currencySymbol) &&
    610          * intlCurrencySymbol.equals(other.intlCurrencySymbol) &&
    611          * padEscape == other.padEscape && // [NEW]
    612          * monetarySeparator == other.monetarySeparator);
    613          */
    614         MyCurrency mc = null;
    615         if (kind == CURRENCY) {
    616             // in this case numberSystem is null and symbols are for the default system
    617             // ^^^^^ NO, that is not true.
    618 
    619             String prefix = "//ldml/numbers/currencies/currency[@type=\"" + key1 + "\"]/";
    620             // /ldml/numbers/currencies/currency[@type="GBP"]/symbol
    621             // /ldml/numbers/currencies/currency[@type="GBP"]
    622 
    623             if (currencySymbol == null) {
    624                 currencySymbol = cldrFile.getWinningValueWithBailey(prefix + "symbol");
    625             }
    626             String currencyDecimal = cldrFile.getWinningValueWithBailey(prefix + "decimal");
    627             if (currencyDecimal != null) {
    628                 (symbols = cloneIfNeeded(symbols)).setMonetaryDecimalSeparator(currencyDecimal.charAt(0));
    629             }
    630             String currencyPattern = cldrFile.getWinningValueWithBailey(prefix + "pattern");
    631             if (currencyPattern != null) {
    632                 pattern = currencyPattern;
    633             }
    634 
    635             String currencyGrouping = cldrFile.getWinningValueWithBailey(prefix + "grouping");
    636             if (currencyGrouping != null) {
    637                 (symbols = cloneIfNeeded(symbols)).setMonetaryGroupingSeparator(currencyGrouping.charAt(0));
    638             }
    639 
    640             // <decimal>,</decimal>
    641             // <group>.</group>
    642 
    643             // TODO This is a hack for now, since I am ignoring the possibility of quoted text next to the symbol
    644             if (pattern.contains(";")) { // multi pattern
    645                 String[] pieces = pattern.split(";");
    646                 for (int i = 0; i < pieces.length; ++i) {
    647                     pieces[i] = fixCurrencySpacing(pieces[i], currencySymbol);
    648                 }
    649                 pattern = org.unicode.cldr.util.CldrUtility.join(pieces, ";");
    650             } else {
    651                 pattern = fixCurrencySpacing(pattern, currencySymbol);
    652             }
    653 
    654             CurrencyNumberInfo info = supplementalData.getCurrencyNumberInfo(key1);
    655 
    656             mc = new MyCurrency(key1,
    657                 currencySymbol,
    658                 cldrFile.getWinningValueWithBailey(prefix + "displayName"),
    659                 info);
    660 
    661             // String possible = null;
    662             // possible = cldrFile.getWinningValueWithBailey(prefix + "decimal");
    663             // symbols.setMonetaryDecimalSeparator(possible != null ? possible.charAt(0) :
    664             // symbols.getDecimalSeparator());
    665             // if ((possible = cldrFile.getWinningValueWithBailey(prefix + "pattern")) != null) pattern = possible;
    666             // if ((possible = cldrFile.getWinningValueWithBailey(prefix + "group")) != null)
    667             // symbols.setGroupingSeparator(possible.charAt(0));
    668             // ;
    669         }
    670         result = new DecimalFormat(pattern, symbols);
    671         if (mc != null) {
    672             result.setCurrency(mc);
    673             result.setMaximumFractionDigits(mc.getDefaultFractionDigits());
    674             result.setMinimumFractionDigits(mc.getDefaultFractionDigits());
    675         } else {
    676             result.setCurrency(NO_CURRENCY);
    677         }
    678 
    679         if (false) {
    680             System.out.println("creating " + ulocale + "\tkey: " + key + "\tpattern "
    681                 + pattern + "\tresult: " + result.toPattern() + "\t0=>" + result.format(0));
    682             DecimalFormat n2 = (DecimalFormat) NumberFormat.getScientificInstance(ulocale);
    683             System.out.println("\tresult: " + n2.toPattern() + "\t0=>" + n2.format(0));
    684         }
    685         if (kind == OTHER_KEY && key1.equals("integer")) {
    686             result.setMaximumFractionDigits(0);
    687             result.setDecimalSeparatorAlwaysShown(false);
    688             result.setParseIntegerOnly(true);
    689         }
    690         cacheNumberFormats.put(key, result);
    691         return result;
    692     }
    693 
    694     private String fixCurrencySpacing(String pattern, String symbol) {
    695         int startPos = pattern.indexOf('\u00a4');
    696         if (startPos > 0
    697             && beforeCurrencyMatch.contains(UTF16.charAt(symbol, 0))) {
    698             int ch = UTF16.charAt(pattern, startPos - 1);
    699             if (ch == '#') ch = '0';// fix pattern
    700             if (beforeSurroundingMatch.contains(ch)) {
    701                 pattern = pattern.substring(0, startPos) + beforeInsertBetween + pattern.substring(startPos);
    702             }
    703         }
    704         int endPos = pattern.lastIndexOf('\u00a4') + 1;
    705         if (endPos < pattern.length()
    706             && afterCurrencyMatch.contains(UTF16.charAt(symbol, symbol.length() - 1))) {
    707             int ch = UTF16.charAt(pattern, endPos);
    708             if (ch == '#') ch = '0';// fix pattern
    709             if (afterSurroundingMatch.contains(ch)) {
    710                 pattern = pattern.substring(0, endPos) + afterInsertBetween + pattern.substring(endPos);
    711             }
    712         }
    713         return pattern;
    714     }
    715 
    716     private DecimalFormatSymbols cloneIfNeeded(DecimalFormatSymbols symbols) {
    717         if (symbols == _getDecimalFormatSymbols(null)) {
    718             return (DecimalFormatSymbols) symbols.clone();
    719         }
    720         return symbols;
    721     }
    722 
    723     public DecimalFormatSymbols getDecimalFormatSymbols(String numberSystem) {
    724         return (DecimalFormatSymbols) _getDecimalFormatSymbols(numberSystem).clone();
    725     }
    726 
    727     private DecimalFormatSymbols _getDecimalFormatSymbols(String numberSystem) {
    728         String key = (numberSystem == null) ? cldrFile.getLocaleID() : cldrFile.getLocaleID() + "@numbers="
    729             + numberSystem;
    730         DecimalFormatSymbols symbols = (DecimalFormatSymbols) cacheDecimalFormatSymbols.get(key);
    731         if (symbols != null) return symbols;
    732 
    733         symbols = new DecimalFormatSymbols();
    734         if (numberSystem == null) {
    735             numberSystem = cldrFile.getWinningValueWithBailey("//ldml/numbers/defaultNumberingSystem");
    736         }
    737 
    738         // currently constants
    739         // symbols.setPadEscape(cldrFile.getWinningValueWithBailey("//ldml/numbers/symbols/xxx"));
    740         // symbols.setSignificantDigit(cldrFile.getWinningValueWithBailey("//ldml/numbers/symbols/patternDigit"));
    741 
    742         symbols.setDecimalSeparator(getSymbolCharacter("decimal", numberSystem));
    743         // symbols.setDigit(getSymbolCharacter("patternDigit", numberSystem));
    744         symbols.setExponentSeparator(getSymbolString("exponential", numberSystem));
    745         symbols.setGroupingSeparator(getSymbolCharacter("group", numberSystem));
    746         symbols.setInfinity(getSymbolString("infinity", numberSystem));
    747         symbols.setMinusSignString(getSymbolString("minusSign", numberSystem));
    748         symbols.setNaN(getSymbolString("nan", numberSystem));
    749         symbols.setPatternSeparator(getSymbolCharacter("list", numberSystem));
    750         symbols.setPercentString(getSymbolString("percentSign", numberSystem));
    751         symbols.setPerMill(getSymbolCharacter("perMille", numberSystem));
    752         symbols.setPlusSignString(getSymbolString("plusSign", numberSystem));
    753         // symbols.setZeroDigit(getSymbolCharacter("nativeZeroDigit", numberSystem));
    754         String digits = supplementalData.getDigits(numberSystem);
    755         if (digits != null && digits.length() == 10) {
    756             symbols.setZeroDigit(digits.charAt(0));
    757         }
    758 
    759         try {
    760             symbols.setMonetaryDecimalSeparator(getSymbolCharacter("currencyDecimal", numberSystem));
    761         } catch (IllegalArgumentException e) {
    762             symbols.setMonetaryDecimalSeparator(symbols.getDecimalSeparator());
    763         }
    764 
    765         try {
    766             symbols.setMonetaryGroupingSeparator(getSymbolCharacter("currencyGroup", numberSystem));
    767         } catch (IllegalArgumentException e) {
    768             symbols.setMonetaryGroupingSeparator(symbols.getGroupingSeparator());
    769         }
    770 
    771         String prefix = "//ldml/numbers/currencyFormats/currencySpacing/beforeCurrency/";
    772         beforeCurrencyMatch = new UnicodeSet(cldrFile.getWinningValueWithBailey(prefix + "currencyMatch"));
    773         beforeSurroundingMatch = new UnicodeSet(cldrFile.getWinningValueWithBailey(prefix + "surroundingMatch"));
    774         beforeInsertBetween = cldrFile.getWinningValueWithBailey(prefix + "insertBetween");
    775         prefix = "//ldml/numbers/currencyFormats/currencySpacing/afterCurrency/";
    776         afterCurrencyMatch = new UnicodeSet(cldrFile.getWinningValueWithBailey(prefix + "currencyMatch"));
    777         afterSurroundingMatch = new UnicodeSet(cldrFile.getWinningValueWithBailey(prefix + "surroundingMatch"));
    778         afterInsertBetween = cldrFile.getWinningValueWithBailey(prefix + "insertBetween");
    779 
    780         cacheDecimalFormatSymbols.put(key, symbols);
    781 
    782         return symbols;
    783     }
    784 
    785     private char getSymbolCharacter(String key, String numsys) {
    786         // numsys should not be null (previously resolved to defaultNumberingSystem if necessary)
    787         return getSymbolString(key, numsys).charAt(0);
    788     }
    789 
    790     // TODO no longer used now that http://bugs.icu-project.org/trac/ticket/10368 is done.
    791     private char getHackSymbolCharacter(String key, String numsys) {
    792         String minusString = getSymbolString(key, numsys);
    793         char minusSign = (minusString.length() > 1 && isBidiMark(minusString.charAt(0))) ? minusString.charAt(1) : minusString.charAt(0);
    794         return minusSign;
    795     }
    796 
    797     private static boolean isBidiMark(char c) {
    798         return (c == '\u200E' || c == '\u200F' || c == '\u061C');
    799     }
    800 
    801     private String getSymbolString(String key, String numsys) {
    802         // numsys should not be null (previously resolved to defaultNumberingSystem if necessary)
    803         String value = null;
    804         try {
    805             value = cldrFile.getWinningValueWithBailey("//ldml/numbers/symbols[@numberSystem=\"" + numsys + "\"]/" + key);
    806             if (value == null || value.length() < 1) {
    807                 throw new RuntimeException();
    808             }
    809             return value;
    810         } catch (RuntimeException e) {
    811             throw new IllegalArgumentException("Illegal value <" + value + "> at "
    812                 + "//ldml/numbers/symbols[@numberSystem='" + numsys + "']/" + key);
    813         }
    814     }
    815 
    816     UnicodeSet beforeCurrencyMatch;
    817     UnicodeSet beforeSurroundingMatch;
    818     String beforeInsertBetween;
    819     UnicodeSet afterCurrencyMatch;
    820     UnicodeSet afterSurroundingMatch;
    821     String afterInsertBetween;
    822 
    823     private String getPattern(String key1, int isCurrency) {
    824         String prefix = "//ldml/numbers/";
    825         String type = key1;
    826         if (isCurrency == CURRENCY)
    827             type = "currency";
    828         else if (key1.equals("integer")) type = "decimal";
    829         String path = prefix
    830             + type + "Formats/"
    831             + type + "FormatLength/"
    832             + type + "Format[@type=\"standard\"]/pattern[@type=\"standard\"]";
    833 
    834         String pattern = cldrFile.getWinningValueWithBailey(path);
    835         if (pattern == null)
    836             throw new IllegalArgumentException("locale: " + cldrFile.getLocaleID() + "\tpath: " + path);
    837         return pattern;
    838     }
    839 
    840     public enum Width {
    841         wide, abbreviated, narrow
    842     }
    843 
    844     public enum Context {
    845         format, stand_alone;
    846         public String toString() {
    847             return name().replace('_', '-');
    848         }
    849     }
    850 
    851     /**
    852      * Format a dayPeriod string. The dayPeriodOverride, if null, will be fetched from the file.
    853      * @param timeInDay
    854      * @param dayPeriodString
    855      * @return
    856      */
    857     public String formatDayPeriod(int timeInDay, Context context, Width width) {
    858         DayPeriodInfo dayPeriodInfo = supplementalData.getDayPeriods(DayPeriodInfo.Type.format, cldrFile.getLocaleID());
    859         DayPeriod period = dayPeriodInfo.getDayPeriod(timeInDay);
    860         String dayPeriodFormatString = getDayPeriodValue(getDayPeriodPath(period, context, width), "", null);
    861         String result = formatDayPeriod(timeInDay, dayPeriodFormatString);
    862         return result;
    863     }
    864 
    865     public String getDayPeriodValue(String path, String fallback, Output<Boolean> real) {
    866         String dayPeriodFormatString = cldrFile.getStringValue(path);
    867         if (dayPeriodFormatString == null) {
    868             dayPeriodFormatString = fallback;
    869         }
    870         if (real != null) {
    871             Status status = new Status();
    872             String locale = cldrFile.getSourceLocaleID(path, status);
    873             real.value = status.pathWhereFound.equals(path) && cldrFile.getLocaleID().equals(locale);
    874         }
    875         return dayPeriodFormatString;
    876     }
    877 
    878     public static String getDayPeriodPath(DayPeriod period, Context context, Width width) {
    879         String path = "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\""
    880             + context
    881             + "\"]/dayPeriodWidth[@type=\""
    882             + width
    883             + "\"]/dayPeriod[@type=\""
    884             + period
    885             + "\"]";
    886         return path;
    887     }
    888 
    889     static final String SHORT_PATH = "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/timeFormats/timeFormatLength[@type=\"short\"]/timeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
    890     static final String HM_PATH = "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"hm\"]";
    891     static final String BHM_PATH = "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"Bhm\"]";
    892 
    893     public String formatDayPeriod(int timeInDay, String dayPeriodFormatString) {
    894         String pattern = null;
    895         if ((timeInDay % 6) != 0) { // need a better way to test for this
    896             // dayPeriods other than am, pm, noon, midnight (want patterns with B)
    897             pattern = cldrFile.getStringValue(BHM_PATH);
    898             if (pattern != null) {
    899                 pattern = pattern.replace('B', '\uE000');
    900             }
    901         }
    902         if (pattern == null) {
    903             // dayPeriods am, pm, noon, midnight (want patterns with a)
    904             pattern = cldrFile.getStringValue(HM_PATH);
    905             if (pattern != null) {
    906                 pattern = pattern.replace('a', '\uE000');
    907             }
    908         }
    909         if (pattern == null) {
    910             pattern = "h:mm \uE000";
    911         }
    912         SimpleDateFormat df = getDateFormat("gregorian", pattern);
    913         String formatted = df.format(timeInDay);
    914         String result = formatted.replace("\uE000", dayPeriodFormatString);
    915         return result;
    916     }
    917 
    918     public String getMinusSign(String numberSystem) {
    919         return _getDecimalFormatSymbols(numberSystem).getMinusSignString();
    920     }
    921 }
    922