• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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