• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
isoDateFormat(Date date)43     static public String isoDateFormat(Date date) {
44         return iso.format(date);
45     }
46 
isoDateFormat(long value)47     public static String isoDateFormat(long value) {
48         // TODO Auto-generated method stub
49         return iso.format(new Date(value));
50     }
51 
isoDateParse(String date)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 
getDateNames(int i)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 
getCldrFile()85     public CLDRFile getCldrFile() {
86         return cldrFile;
87     }
88 
getCollationFile()89     public CLDRFile getCollationFile() {
90         return collationFile;
91     }
92 
setCldrFile(CLDRFile cldrFile)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 
forLocale(CLDRLocale locale)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 
getRuleBasedCollator(String type)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 
getRuleBasedCollator()162     public RuleBasedCollator getRuleBasedCollator() throws Exception {
163         return getRuleBasedCollator("default");
164     }
165 
getDateFormat(String calendar, int dateIndex, int timeIndex)166     public SimpleDateFormat getDateFormat(String calendar, int dateIndex, int timeIndex) {
167         return getDateFormat(calendar, dateIndex, timeIndex, null);
168     }
169 
getDateFormat(String calendar, int dateIndex, int timeIndex, String numbersOverride)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 
getDateFormat(String calendar, String pattern, String numbersOverride)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 
getDateFormat(String calendar, String pattern)193     public SimpleDateFormat getDateFormat(String calendar, String pattern) {
194         return getDateFormat(calendar, pattern, null);
195     }
196 
getFullFormat(String calendar, String pattern, String numbersOverride)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 
_getDateFormatSymbols(String calendar)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      */
getDayPeriods(String prefix, String context, String width, String type)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 
getArrayOfWinningValues(String[] xpaths)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 
checkFound(String[] last)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 
checkFound(String[] last, String[] xpaths)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 
getPattern(String calendar, int dateIndex, int timeIndex)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      */
getDateTimePattern(String calendar, String dateOrTime, String type)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 
getArray(String key, String type, String context, String width)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 
getArray(String prefix, int firstIndex, String[] itemNames, String postfix, int minimumSize)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