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