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