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