1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 /* 3 ******************************************************************************* 4 * Copyright (C) 2011-2015, International Business Machines Corporation and 5 * others. All Rights Reserved. 6 ******************************************************************************* 7 */ 8 package android.icu.impl; 9 10 import java.io.IOException; 11 import java.io.ObjectInputStream; 12 import java.io.ObjectOutputStream; 13 import java.util.ArrayList; 14 import java.util.Collection; 15 import java.util.Collections; 16 import java.util.EnumSet; 17 import java.util.HashMap; 18 import java.util.HashSet; 19 import java.util.Iterator; 20 import java.util.LinkedList; 21 import java.util.List; 22 import java.util.Map; 23 import java.util.MissingResourceException; 24 import java.util.Set; 25 import java.util.concurrent.ConcurrentHashMap; 26 import java.util.regex.Pattern; 27 28 import android.icu.impl.TextTrieMap.ResultHandler; 29 import android.icu.impl.UResource.TableSink; 30 import android.icu.text.TimeZoneNames; 31 import android.icu.util.TimeZone; 32 import android.icu.util.TimeZone.SystemTimeZoneType; 33 import android.icu.util.ULocale; 34 import android.icu.util.UResourceBundle; 35 36 /** 37 * The standard ICU implementation of TimeZoneNames 38 * @hide Only a subset of ICU is exposed in Android 39 */ 40 public class TimeZoneNamesImpl extends TimeZoneNames { 41 42 private static final long serialVersionUID = -2179814848495897472L; 43 44 private static final String ZONE_STRINGS_BUNDLE = "zoneStrings"; 45 private static final String MZ_PREFIX = "meta:"; 46 private static final NameType[] NAME_TYPE_VALUES = NameType.values(); 47 48 private static volatile Set<String> METAZONE_IDS; 49 private static final TZ2MZsCache TZ_TO_MZS_CACHE = new TZ2MZsCache(); 50 private static final MZ2TZsCache MZ_TO_TZS_CACHE = new MZ2TZsCache(); 51 52 private transient ICUResourceBundle _zoneStrings; 53 54 55 // These are hard cache. We create only one TimeZoneNamesImpl per locale 56 // and it's stored in SoftCache, so we do not need to worry about the 57 // footprint much. 58 private transient ConcurrentHashMap<String, ZNames> _mzNamesMap; 59 private transient ConcurrentHashMap<String, ZNames> _tzNamesMap; 60 private transient boolean _namesFullyLoaded; 61 62 private transient TextTrieMap<NameInfo> _namesTrie; 63 private transient boolean _namesTrieFullyLoaded; 64 TimeZoneNamesImpl(ULocale locale)65 public TimeZoneNamesImpl(ULocale locale) { 66 initialize(locale); 67 } 68 69 /* (non-Javadoc) 70 * @see android.icu.text.TimeZoneNames#getAvailableMetaZoneIDs() 71 */ 72 @Override getAvailableMetaZoneIDs()73 public Set<String> getAvailableMetaZoneIDs() { 74 return _getAvailableMetaZoneIDs(); 75 } 76 _getAvailableMetaZoneIDs()77 static Set<String> _getAvailableMetaZoneIDs() { 78 if (METAZONE_IDS == null) { 79 synchronized (TimeZoneNamesImpl.class) { 80 if (METAZONE_IDS == null) { 81 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones"); 82 UResourceBundle mapTimezones = bundle.get("mapTimezones"); 83 Set<String> keys = mapTimezones.keySet(); 84 METAZONE_IDS = Collections.unmodifiableSet(keys); 85 } 86 } 87 } 88 return METAZONE_IDS; 89 } 90 91 /* (non-Javadoc) 92 * @see android.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String) 93 */ 94 @Override getAvailableMetaZoneIDs(String tzID)95 public Set<String> getAvailableMetaZoneIDs(String tzID) { 96 return _getAvailableMetaZoneIDs(tzID); 97 } 98 _getAvailableMetaZoneIDs(String tzID)99 static Set<String> _getAvailableMetaZoneIDs(String tzID) { 100 if (tzID == null || tzID.length() == 0) { 101 return Collections.emptySet(); 102 } 103 List<MZMapEntry> maps = TZ_TO_MZS_CACHE.getInstance(tzID, tzID); 104 if (maps.isEmpty()) { 105 return Collections.emptySet(); 106 } 107 Set<String> mzIDs = new HashSet<String>(maps.size()); 108 for (MZMapEntry map : maps) { 109 mzIDs.add(map.mzID()); 110 } 111 // make it unmodifiable because of the API contract. We may cache the results in futre. 112 return Collections.unmodifiableSet(mzIDs); 113 } 114 115 /* (non-Javadoc) 116 * @see android.icu.text.TimeZoneNames#getMetaZoneID(java.lang.String, long) 117 */ 118 @Override getMetaZoneID(String tzID, long date)119 public String getMetaZoneID(String tzID, long date) { 120 return _getMetaZoneID(tzID, date); 121 } 122 _getMetaZoneID(String tzID, long date)123 static String _getMetaZoneID(String tzID, long date) { 124 if (tzID == null || tzID.length() == 0) { 125 return null; 126 } 127 String mzID = null; 128 List<MZMapEntry> maps = TZ_TO_MZS_CACHE.getInstance(tzID, tzID); 129 for (MZMapEntry map : maps) { 130 if (date >= map.from() && date < map.to()) { 131 mzID = map.mzID(); 132 break; 133 } 134 } 135 return mzID; 136 } 137 138 /* (non-Javadoc) 139 * @see android.icu.text.TimeZoneNames#getReferenceZoneID(java.lang.String, java.lang.String) 140 */ 141 @Override getReferenceZoneID(String mzID, String region)142 public String getReferenceZoneID(String mzID, String region) { 143 return _getReferenceZoneID(mzID, region); 144 } 145 _getReferenceZoneID(String mzID, String region)146 static String _getReferenceZoneID(String mzID, String region) { 147 if (mzID == null || mzID.length() == 0) { 148 return null; 149 } 150 String refID = null; 151 Map<String, String> regionTzMap = MZ_TO_TZS_CACHE.getInstance(mzID, mzID); 152 if (!regionTzMap.isEmpty()) { 153 refID = regionTzMap.get(region); 154 if (refID == null) { 155 refID = regionTzMap.get("001"); 156 } 157 } 158 return refID; 159 } 160 161 /* 162 * (non-Javadoc) 163 * @see android.icu.text.TimeZoneNames#getMetaZoneDisplayName(java.lang.String, android.icu.text.TimeZoneNames.NameType) 164 */ 165 @Override getMetaZoneDisplayName(String mzID, NameType type)166 public String getMetaZoneDisplayName(String mzID, NameType type) { 167 if (mzID == null || mzID.length() == 0) { 168 return null; 169 } 170 return loadMetaZoneNames(null, mzID).getName(type); 171 } 172 173 /* 174 * (non-Javadoc) 175 * @see android.icu.text.TimeZoneNames#getTimeZoneDisplayName(java.lang.String, android.icu.text.TimeZoneNames.NameType) 176 */ 177 @Override getTimeZoneDisplayName(String tzID, NameType type)178 public String getTimeZoneDisplayName(String tzID, NameType type) { 179 if (tzID == null || tzID.length() == 0) { 180 return null; 181 } 182 return loadTimeZoneNames(null, tzID).getName(type); 183 } 184 185 /* (non-Javadoc) 186 * @see android.icu.text.TimeZoneNames#getExemplarLocationName(java.lang.String) 187 */ 188 @Override getExemplarLocationName(String tzID)189 public String getExemplarLocationName(String tzID) { 190 if (tzID == null || tzID.length() == 0) { 191 return null; 192 } 193 String locName = loadTimeZoneNames(null, tzID).getName(NameType.EXEMPLAR_LOCATION); 194 return locName; 195 } 196 197 /* (non-Javadoc) 198 * @see android.icu.text.TimeZoneNames#find(java.lang.CharSequence, int, java.util.Set) 199 */ 200 @Override find(CharSequence text, int start, EnumSet<NameType> nameTypes)201 public synchronized Collection<MatchInfo> find(CharSequence text, int start, EnumSet<NameType> nameTypes) { 202 if (text == null || text.length() == 0 || start < 0 || start >= text.length()) { 203 throw new IllegalArgumentException("bad input text or range"); 204 } 205 NameSearchHandler handler = new NameSearchHandler(nameTypes); 206 _namesTrie.find(text, start, handler); 207 if (handler.getMaxMatchLen() == (text.length() - start) || _namesTrieFullyLoaded) { 208 // perfect match, or no more names available 209 return handler.getMatches(); 210 } 211 212 // All names are not yet loaded into the trie. 213 // We may have loaded names for formatting several time zones, 214 // and might be parsing one of those. 215 // Populate the parsing trie from all of the already-loaded names. 216 addAllNamesIntoTrie(); 217 handler.resetResults(); 218 _namesTrie.find(text, start, handler); 219 if (handler.getMaxMatchLen() == (text.length() - start)) { 220 // perfect match 221 return handler.getMatches(); 222 } 223 224 // Still no match, load all names. 225 internalLoadAllDisplayNames(); 226 addAllNamesIntoTrie(); 227 228 // Set default time zone location names 229 // for time zones without explicit display names. 230 Set<String> tzIDs = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null); 231 for (String tzID : tzIDs) { 232 if (!_tzNamesMap.containsKey(tzID)) { 233 tzID = tzID.intern(); 234 ZNames tznames = ZNames.getInstance(null, tzID); 235 tznames.addNamesIntoTrie(null, tzID, _namesTrie); 236 _tzNamesMap.put(tzID, tznames); 237 } 238 } 239 _namesTrieFullyLoaded = true; 240 241 // now, try it again 242 handler.resetResults(); 243 _namesTrie.find(text, start, handler); 244 return handler.getMatches(); 245 } 246 247 @Override loadAllDisplayNames()248 public synchronized void loadAllDisplayNames() { 249 internalLoadAllDisplayNames(); 250 } 251 252 @Override getDisplayNames(String tzID, NameType[] types, long date, String[] dest, int destOffset)253 public void getDisplayNames(String tzID, NameType[] types, long date, 254 String[] dest, int destOffset) { 255 if (tzID == null || tzID.length() == 0) { 256 return; 257 } 258 ZNames tzNames = loadTimeZoneNames(null, tzID); 259 ZNames mzNames = null; 260 for (int i = 0; i < types.length; ++i) { 261 NameType type = types[i]; 262 String name = tzNames.getName(type); 263 if (name == null) { 264 if (mzNames == null) { 265 String mzID = getMetaZoneID(tzID, date); 266 if (mzID == null || mzID.length() == 0) { 267 mzNames = ZNames.EMPTY_ZNAMES; 268 } else { 269 mzNames = loadMetaZoneNames(null, mzID); 270 } 271 } 272 name = mzNames.getName(type); 273 } 274 dest[destOffset + i] = name; 275 } 276 } 277 278 /** Caller must synchronize. */ internalLoadAllDisplayNames()279 private void internalLoadAllDisplayNames() { 280 if (!_namesFullyLoaded) { 281 new ZoneStringsLoader().load(); 282 _namesFullyLoaded = true; 283 } 284 } 285 286 /** Caller must synchronize. */ addAllNamesIntoTrie()287 private void addAllNamesIntoTrie() { 288 for (Map.Entry<String, ZNames> entry : _tzNamesMap.entrySet()) { 289 entry.getValue().addNamesIntoTrie(null, entry.getKey(), _namesTrie); 290 } 291 for (Map.Entry<String, ZNames> entry : _mzNamesMap.entrySet()) { 292 entry.getValue().addNamesIntoTrie(entry.getKey(), null, _namesTrie); 293 } 294 } 295 296 /** 297 * Loads all meta zone and time zone names for this TimeZoneNames' locale. 298 */ 299 private final class ZoneStringsLoader extends UResource.TableSink { 300 /** 301 * Prepare for several hundred time zones and meta zones. 302 * _zoneStrings.getSize() is ineffective in a sparsely populated locale like en-GB. 303 */ 304 private static final int INITIAL_NUM_ZONES = 300; 305 private HashMap<UResource.Key, ZNamesLoader> keyToLoader = 306 new HashMap<UResource.Key, ZNamesLoader>(INITIAL_NUM_ZONES); 307 private StringBuilder sb = new StringBuilder(32); 308 309 /** Caller must synchronize. */ load()310 void load() { 311 _zoneStrings.getAllTableItemsWithFallback("", this); 312 for (Map.Entry<UResource.Key, ZNamesLoader> entry : keyToLoader.entrySet()) { 313 UResource.Key key = entry.getKey(); 314 ZNamesLoader loader = entry.getValue(); 315 if (loader == ZNamesLoader.DUMMY_LOADER) { 316 // skip 317 } else if (key.startsWith(MZ_PREFIX)) { 318 String mzID = mzIDFromKey(key).intern(); 319 ZNames mzNames = ZNames.getInstance(loader.getNames(), null); 320 _mzNamesMap.put(mzID, mzNames); 321 } else { 322 String tzID = tzIDFromKey(key).intern(); 323 ZNames tzNames = ZNames.getInstance(loader.getNames(), tzID); 324 _tzNamesMap.put(tzID, tzNames); 325 } 326 } 327 } 328 329 @Override getOrCreateTableSink(UResource.Key key, int initialSize)330 public TableSink getOrCreateTableSink(UResource.Key key, int initialSize) { 331 ZNamesLoader loader = keyToLoader.get(key); 332 if (loader != null) { 333 if (loader == ZNamesLoader.DUMMY_LOADER) { 334 return null; 335 } 336 return loader; 337 } 338 ZNamesLoader result = null; 339 if (key.startsWith(MZ_PREFIX)) { 340 String mzID = mzIDFromKey(key); 341 if (_mzNamesMap.containsKey(mzID)) { 342 // We have already loaded the names for this meta zone. 343 loader = ZNamesLoader.DUMMY_LOADER; 344 } else { 345 result = loader = ZNamesLoader.forMetaZoneNames(); 346 } 347 } else { 348 String tzID = tzIDFromKey(key); 349 if (_tzNamesMap.containsKey(tzID)) { 350 // We have already loaded the names for this time zone. 351 loader = ZNamesLoader.DUMMY_LOADER; 352 } else { 353 result = loader = ZNamesLoader.forTimeZoneNames(); 354 } 355 } 356 keyToLoader.put(key.clone(), loader); 357 return result; 358 } 359 360 @Override putNoFallback(UResource.Key key)361 public void putNoFallback(UResource.Key key) { 362 if (!keyToLoader.containsKey(key)) { 363 keyToLoader.put(key.clone(), ZNamesLoader.DUMMY_LOADER); 364 } 365 } 366 367 /** 368 * Equivalent to key.substring(MZ_PREFIX.length()) 369 * except reuses our StringBuilder. 370 */ mzIDFromKey(UResource.Key key)371 private String mzIDFromKey(UResource.Key key) { 372 sb.setLength(0); 373 for (int i = MZ_PREFIX.length(); i < key.length(); ++i) { 374 sb.append(key.charAt(i)); 375 } 376 return sb.toString(); 377 } 378 tzIDFromKey(UResource.Key key)379 private String tzIDFromKey(UResource.Key key) { 380 sb.setLength(0); 381 for (int i = 0; i < key.length(); ++i) { 382 char c = key.charAt(i); 383 if (c == ':') { 384 c = '/'; 385 } 386 sb.append(c); 387 } 388 return sb.toString(); 389 } 390 } 391 392 /** 393 * Initialize the transient fields, called from the constructor and 394 * readObject. 395 * 396 * @param locale The locale 397 */ initialize(ULocale locale)398 private void initialize(ULocale locale) { 399 ICUResourceBundle bundle = (ICUResourceBundle)ICUResourceBundle.getBundleInstance( 400 ICUResourceBundle.ICU_ZONE_BASE_NAME, locale); 401 _zoneStrings = (ICUResourceBundle)bundle.get(ZONE_STRINGS_BUNDLE); 402 403 // TODO: Access is synchronized, can we use a non-concurrent map? 404 _tzNamesMap = new ConcurrentHashMap<String, ZNames>(); 405 _mzNamesMap = new ConcurrentHashMap<String, ZNames>(); 406 _namesFullyLoaded = false; 407 408 _namesTrie = new TextTrieMap<NameInfo>(true); 409 _namesTrieFullyLoaded = false; 410 411 // Preload zone strings for the default time zone 412 TimeZone tz = TimeZone.getDefault(); 413 String tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz); 414 if (tzCanonicalID != null) { 415 loadStrings(tzCanonicalID); 416 } 417 } 418 419 /** 420 * Load all strings used by the specified time zone. 421 * This is called from the initializer to load default zone's 422 * strings. 423 * @param tzCanonicalID the canonical time zone ID 424 */ loadStrings(String tzCanonicalID)425 private synchronized void loadStrings(String tzCanonicalID) { 426 if (tzCanonicalID == null || tzCanonicalID.length() == 0) { 427 return; 428 } 429 loadTimeZoneNames(null, tzCanonicalID); 430 431 ZNamesLoader loader = ZNamesLoader.forMetaZoneNames(); 432 Set<String> mzIDs = getAvailableMetaZoneIDs(tzCanonicalID); 433 for (String mzID : mzIDs) { 434 loadMetaZoneNames(loader, mzID); 435 } 436 addAllNamesIntoTrie(); 437 } 438 439 /* 440 * The custom serialization method. 441 * This implementation only preserve locale object used for the names. 442 */ writeObject(ObjectOutputStream out)443 private void writeObject(ObjectOutputStream out) throws IOException { 444 ULocale locale = _zoneStrings.getULocale(); 445 out.writeObject(locale); 446 } 447 448 /* 449 * The custom deserialization method. 450 * This implementation only read locale object used by the object. 451 */ readObject(ObjectInputStream in)452 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 453 ULocale locale = (ULocale)in.readObject(); 454 initialize(locale); 455 } 456 457 /** 458 * Returns a set of names for the given meta zone ID. This method loads 459 * the set of names into the internal map and trie for future references. 460 * @param mzID the meta zone ID 461 * @return An instance of ZNames that includes a set of meta zone display names. 462 */ loadMetaZoneNames(ZNamesLoader loader, String mzID)463 private synchronized ZNames loadMetaZoneNames(ZNamesLoader loader, String mzID) { 464 ZNames znames = _mzNamesMap.get(mzID); 465 if (znames == null) { 466 if (loader == null) { 467 loader = ZNamesLoader.forMetaZoneNames(); 468 } 469 znames = ZNames.getInstance(loader, _zoneStrings, MZ_PREFIX + mzID, null); 470 mzID = mzID.intern(); 471 if (_namesTrieFullyLoaded) { 472 znames.addNamesIntoTrie(mzID, null, _namesTrie); 473 } 474 _mzNamesMap.put(mzID, znames); 475 } 476 return znames; 477 } 478 479 /** 480 * Returns a set of names for the given time zone ID. This method loads 481 * the set of names into the internal map and trie for future references. 482 * @param tzID the canonical time zone ID 483 * @return An instance of TZNames that includes a set of time zone display names. 484 */ loadTimeZoneNames(ZNamesLoader loader, String tzID)485 private synchronized ZNames loadTimeZoneNames(ZNamesLoader loader, String tzID) { 486 ZNames tznames = _tzNamesMap.get(tzID); 487 if (tznames == null) { 488 if (loader == null) { 489 loader = ZNamesLoader.forTimeZoneNames(); 490 } 491 tznames = ZNames.getInstance(loader, _zoneStrings, tzID.replace('/', ':'), tzID); 492 tzID = tzID.intern(); 493 if (_namesTrieFullyLoaded) { 494 tznames.addNamesIntoTrie(null, tzID, _namesTrie); 495 } 496 _tzNamesMap.put(tzID, tznames); 497 } 498 return tznames; 499 } 500 501 /** 502 * An instance of NameInfo is stored in the zone names trie. 503 */ 504 private static class NameInfo { 505 String tzID; 506 String mzID; 507 NameType type; 508 } 509 510 /** 511 * NameSearchHandler is used for collecting name matches. 512 */ 513 private static class NameSearchHandler implements ResultHandler<NameInfo> { 514 private EnumSet<NameType> _nameTypes; 515 private Collection<MatchInfo> _matches; 516 private int _maxMatchLen; 517 NameSearchHandler(EnumSet<NameType> nameTypes)518 NameSearchHandler(EnumSet<NameType> nameTypes) { 519 _nameTypes = nameTypes; 520 } 521 522 /* (non-Javadoc) 523 * @see android.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, java.util.Iterator) 524 */ handlePrefixMatch(int matchLength, Iterator<NameInfo> values)525 public boolean handlePrefixMatch(int matchLength, Iterator<NameInfo> values) { 526 while (values.hasNext()) { 527 NameInfo ninfo = values.next(); 528 if (_nameTypes != null && !_nameTypes.contains(ninfo.type)) { 529 continue; 530 } 531 MatchInfo minfo; 532 if (ninfo.tzID != null) { 533 minfo = new MatchInfo(ninfo.type, ninfo.tzID, null, matchLength); 534 } else { 535 assert(ninfo.mzID != null); 536 minfo = new MatchInfo(ninfo.type, null, ninfo.mzID, matchLength); 537 } 538 if (_matches == null) { 539 _matches = new LinkedList<MatchInfo>(); 540 } 541 _matches.add(minfo); 542 if (matchLength > _maxMatchLen) { 543 _maxMatchLen = matchLength; 544 } 545 } 546 return true; 547 } 548 549 /** 550 * Returns the match results 551 * @return the match results 552 */ getMatches()553 public Collection<MatchInfo> getMatches() { 554 if (_matches == null) { 555 return Collections.emptyList(); 556 } 557 return _matches; 558 } 559 560 /** 561 * Returns the maximum match length, or 0 if no match was found 562 * @return the maximum match length 563 */ getMaxMatchLen()564 public int getMaxMatchLen() { 565 return _maxMatchLen; 566 } 567 568 /** 569 * Resets the match results 570 */ resetResults()571 public void resetResults() { 572 _matches = null; 573 _maxMatchLen = 0; 574 } 575 } 576 577 private static final class ZNamesLoader extends UResource.TableSink { 578 private static int NUM_META_ZONE_NAMES = 6; 579 private static int NUM_TIME_ZONE_NAMES = 7; // incl. EXEMPLAR_LOCATION 580 581 private static String NO_NAME = ""; 582 583 /** 584 * Does not load any names, for no-fallback handling. 585 */ 586 private static ZNamesLoader DUMMY_LOADER = new ZNamesLoader(0); 587 588 private String[] names; 589 private int numNames; 590 ZNamesLoader(int numNames)591 private ZNamesLoader(int numNames) { 592 this.numNames = numNames; 593 } 594 forMetaZoneNames()595 static ZNamesLoader forMetaZoneNames() { 596 return new ZNamesLoader(NUM_META_ZONE_NAMES); 597 } 598 forTimeZoneNames()599 static ZNamesLoader forTimeZoneNames() { 600 return new ZNamesLoader(NUM_TIME_ZONE_NAMES); 601 } 602 load(ICUResourceBundle zoneStrings, String key)603 String[] load(ICUResourceBundle zoneStrings, String key) { 604 if (zoneStrings == null || key == null || key.length() == 0) { 605 return null; 606 } 607 608 try { 609 zoneStrings.getAllTableItemsWithFallback(key, this); 610 } catch (MissingResourceException e) { 611 return null; 612 } 613 614 return getNames(); 615 } 616 nameTypeFromKey(UResource.Key key)617 private static NameType nameTypeFromKey(UResource.Key key) { 618 // Avoid key.toString() object creation. 619 if (key.length() != 2) { 620 return null; 621 } 622 char c0 = key.charAt(0); 623 char c1 = key.charAt(1); 624 if (c0 == 'l') { 625 return c1 == 'g' ? NameType.LONG_GENERIC : 626 c1 == 's' ? NameType.LONG_STANDARD : 627 c1 == 'd' ? NameType.LONG_DAYLIGHT : null; 628 } else if (c0 == 's') { 629 return c1 == 'g' ? NameType.SHORT_GENERIC : 630 c1 == 's' ? NameType.SHORT_STANDARD : 631 c1 == 'd' ? NameType.SHORT_DAYLIGHT : null; 632 } else if (c0 == 'e' && c1 == 'c') { 633 return NameType.EXEMPLAR_LOCATION; 634 } 635 return null; 636 } 637 638 @Override put(UResource.Key key, UResource.Value value)639 public void put(UResource.Key key, UResource.Value value) { 640 if (value.getType() == UResourceBundle.STRING) { 641 if (names == null) { 642 names = new String[numNames]; 643 } 644 NameType type = nameTypeFromKey(key); 645 if (type != null && type.ordinal() < numNames && names[type.ordinal()] == null) { 646 names[type.ordinal()] = value.getString(); 647 } 648 } 649 } 650 651 @Override putNoFallback(UResource.Key key)652 public void putNoFallback(UResource.Key key) { 653 if (names == null) { 654 names = new String[numNames]; 655 } 656 NameType type = nameTypeFromKey(key); 657 if (type != null && type.ordinal() < numNames && names[type.ordinal()] == null) { 658 names[type.ordinal()] = NO_NAME; 659 } 660 } 661 getNames()662 private String[] getNames() { 663 if (names == null) { 664 return null; 665 } 666 int length = 0; 667 for (int i = 0; i < numNames; ++i) { 668 String name = names[i]; 669 if (name != null) { 670 if (name == NO_NAME) { 671 names[i] = null; 672 } else { 673 length = i + 1; 674 } 675 } 676 } 677 if (length == 0) { 678 return null; 679 } 680 if (length == numNames || numNames == NUM_TIME_ZONE_NAMES) { 681 // Return the full array if the last name is set. 682 // Also return the full *time* zone names array, 683 // so that the exemplar location can be set. 684 String[] result = names; 685 names = null; 686 return result; 687 } 688 // Return a shorter array for permanent storage. 689 // *Move* all names into a minimal array. 690 String[] result = new String[length]; 691 do { 692 --length; 693 result[length] = names[length]; 694 names[length] = null; // Reset for loading another set of names. 695 } while (length > 0); 696 return result; 697 } 698 } 699 700 /** 701 * This class stores name data for a meta zone or time zone. 702 */ 703 private static class ZNames { 704 private static final ZNames EMPTY_ZNAMES = new ZNames(null); 705 // A meta zone names instance never has an exemplar location string. 706 private static final int EX_LOC_INDEX = NameType.EXEMPLAR_LOCATION.ordinal(); 707 708 private String[] _names; 709 private boolean didAddIntoTrie; 710 ZNames(String[] names)711 protected ZNames(String[] names) { 712 _names = names; 713 didAddIntoTrie = names == null; 714 } 715 getInstance(String[] names, String tzID)716 public static ZNames getInstance(String[] names, String tzID) { 717 if (tzID != null && (names == null || names[EX_LOC_INDEX] == null)) { 718 String locationName = getDefaultExemplarLocationName(tzID); 719 if (locationName != null) { 720 if (names == null) { 721 names = new String[EX_LOC_INDEX + 1]; 722 } 723 names[EX_LOC_INDEX] = locationName; 724 } 725 } 726 727 if (names == null) { 728 return EMPTY_ZNAMES; 729 } 730 return new ZNames(names); 731 } 732 getInstance(ZNamesLoader loader, ICUResourceBundle zoneStrings, String key, String tzID)733 public static ZNames getInstance(ZNamesLoader loader, 734 ICUResourceBundle zoneStrings, String key, String tzID) { 735 return getInstance(loader.load(zoneStrings, key), tzID); 736 } 737 getName(NameType type)738 public String getName(NameType type) { 739 if (_names != null && type.ordinal() < _names.length) { 740 return _names[type.ordinal()]; 741 } else { 742 return null; 743 } 744 } 745 addNamesIntoTrie(String mzID, String tzID, TextTrieMap<NameInfo> trie)746 public void addNamesIntoTrie(String mzID, String tzID, TextTrieMap<NameInfo> trie) { 747 if (_names == null || didAddIntoTrie) { 748 return; 749 } 750 for (int i = 0; i < _names.length; ++ i) { 751 String name = _names[i]; 752 if (name != null) { 753 NameInfo info = new NameInfo(); 754 info.mzID = mzID; 755 info.tzID = tzID; 756 info.type = NAME_TYPE_VALUES[i]; 757 trie.put(name, info); 758 } 759 } 760 didAddIntoTrie = true; 761 } 762 } 763 764 // 765 // Canonical time zone ID -> meta zone ID 766 // 767 768 private static class MZMapEntry { 769 private String _mzID; 770 private long _from; 771 private long _to; 772 MZMapEntry(String mzID, long from, long to)773 MZMapEntry(String mzID, long from, long to) { 774 _mzID = mzID; 775 _from = from; 776 _to = to; 777 } 778 mzID()779 String mzID() { 780 return _mzID; 781 } 782 from()783 long from() { 784 return _from; 785 } 786 to()787 long to() { 788 return _to; 789 } 790 } 791 792 private static class TZ2MZsCache extends SoftCache<String, List<MZMapEntry>, String> { 793 /* (non-Javadoc) 794 * @see android.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object) 795 */ 796 @Override createInstance(String key, String data)797 protected List<MZMapEntry> createInstance(String key, String data) { 798 List<MZMapEntry> mzMaps = null; 799 800 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones"); 801 UResourceBundle metazoneInfoBundle = bundle.get("metazoneInfo"); 802 803 String tzkey = data.replace('/', ':'); 804 try { 805 UResourceBundle zoneBundle = metazoneInfoBundle.get(tzkey); 806 807 mzMaps = new ArrayList<MZMapEntry>(zoneBundle.getSize()); 808 for (int idx = 0; idx < zoneBundle.getSize(); idx++) { 809 UResourceBundle mz = zoneBundle.get(idx); 810 String mzid = mz.getString(0); 811 String fromStr = "1970-01-01 00:00"; 812 String toStr = "9999-12-31 23:59"; 813 if (mz.getSize() == 3) { 814 fromStr = mz.getString(1); 815 toStr = mz.getString(2); 816 } 817 long from, to; 818 from = parseDate(fromStr); 819 to = parseDate(toStr); 820 mzMaps.add(new MZMapEntry(mzid, from, to)); 821 } 822 823 } catch (MissingResourceException mre) { 824 mzMaps = Collections.emptyList(); 825 } 826 return mzMaps; 827 } 828 829 /** 830 * Private static method parsing the date text used by meta zone to 831 * time zone mapping data in locale resource. 832 * 833 * @param text the UTC date text in the format of "yyyy-MM-dd HH:mm", 834 * for example - "1970-01-01 00:00" 835 * @return the date 836 */ parseDate(String text)837 private static long parseDate (String text) { 838 int year = 0, month = 0, day = 0, hour = 0, min = 0; 839 int idx; 840 int n; 841 842 // "yyyy" (0 - 3) 843 for (idx = 0; idx <= 3; idx++) { 844 n = text.charAt(idx) - '0'; 845 if (n >= 0 && n < 10) { 846 year = 10*year + n; 847 } else { 848 throw new IllegalArgumentException("Bad year"); 849 } 850 } 851 // "MM" (5 - 6) 852 for (idx = 5; idx <= 6; idx++) { 853 n = text.charAt(idx) - '0'; 854 if (n >= 0 && n < 10) { 855 month = 10*month + n; 856 } else { 857 throw new IllegalArgumentException("Bad month"); 858 } 859 } 860 // "dd" (8 - 9) 861 for (idx = 8; idx <= 9; idx++) { 862 n = text.charAt(idx) - '0'; 863 if (n >= 0 && n < 10) { 864 day = 10*day + n; 865 } else { 866 throw new IllegalArgumentException("Bad day"); 867 } 868 } 869 // "HH" (11 - 12) 870 for (idx = 11; idx <= 12; idx++) { 871 n = text.charAt(idx) - '0'; 872 if (n >= 0 && n < 10) { 873 hour = 10*hour + n; 874 } else { 875 throw new IllegalArgumentException("Bad hour"); 876 } 877 } 878 // "mm" (14 - 15) 879 for (idx = 14; idx <= 15; idx++) { 880 n = text.charAt(idx) - '0'; 881 if (n >= 0 && n < 10) { 882 min = 10*min + n; 883 } else { 884 throw new IllegalArgumentException("Bad minute"); 885 } 886 } 887 888 long date = Grego.fieldsToDay(year, month - 1, day) * Grego.MILLIS_PER_DAY 889 + (long)hour * Grego.MILLIS_PER_HOUR + (long)min * Grego.MILLIS_PER_MINUTE; 890 return date; 891 } 892 } 893 894 // 895 // Meta zone ID -> time zone ID 896 // 897 898 private static class MZ2TZsCache extends SoftCache<String, Map<String, String>, String> { 899 900 /* (non-Javadoc) 901 * @see android.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object) 902 */ 903 @Override createInstance(String key, String data)904 protected Map<String, String> createInstance(String key, String data) { 905 Map<String, String> map = null; 906 907 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones"); 908 UResourceBundle mapTimezones = bundle.get("mapTimezones"); 909 910 try { 911 UResourceBundle regionMap = mapTimezones.get(key); 912 913 Set<String> regions = regionMap.keySet(); 914 map = new HashMap<String, String>(regions.size()); 915 916 for (String region : regions) { 917 String tzID = regionMap.getString(region).intern(); 918 map.put(region.intern(), tzID); 919 } 920 } catch (MissingResourceException e) { 921 map = Collections.emptyMap(); 922 } 923 return map; 924 } 925 } 926 927 private static final Pattern LOC_EXCLUSION_PATTERN = Pattern.compile("Etc/.*|SystemV/.*|.*/Riyadh8[7-9]"); 928 929 /** 930 * Default exemplar location name based on time zone ID 931 * @param tzID the time zone ID 932 * @return the exemplar location name or null if location is not available. 933 */ getDefaultExemplarLocationName(String tzID)934 public static String getDefaultExemplarLocationName(String tzID) { 935 if (tzID == null || tzID.length() == 0 || LOC_EXCLUSION_PATTERN.matcher(tzID).matches()) { 936 return null; 937 } 938 939 String location = null; 940 int sep = tzID.lastIndexOf('/'); 941 if (sep > 0 && sep + 1 < tzID.length()) { 942 location = tzID.substring(sep + 1).replace('_', ' '); 943 } 944 945 return location; 946 } 947 } 948