Home | History | Annotate | Download | only in impl
      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) 2014-2016, International Business Machines Corporation and
      7  * others. All Rights Reserved.
      8  *******************************************************************************
      9  */
     10 package android.icu.impl;
     11 
     12 import java.util.Collection;
     13 import java.util.Collections;
     14 import java.util.EnumSet;
     15 import java.util.Iterator;
     16 import java.util.LinkedList;
     17 import java.util.MissingResourceException;
     18 import java.util.Set;
     19 import java.util.concurrent.ConcurrentHashMap;
     20 
     21 import android.icu.impl.TextTrieMap.ResultHandler;
     22 import android.icu.text.TimeZoneNames;
     23 import android.icu.util.ULocale;
     24 import android.icu.util.UResourceBundle;
     25 
     26 /**
     27  * Yet another TimeZoneNames implementation based on the tz database.
     28  * This implementation contains only tz abbreviations (short standard
     29  * and daylight names) for each metazone.
     30  *
     31  * The data file $ICU4C_ROOT/source/data/zone/tzdbNames.txt contains
     32  * the metazone - abbreviations mapping data (manually edited).
     33  *
     34  * Note: The abbreviations in the tz database are not necessarily
     35  * unique. For example, parsing abbreviation "IST" is ambiguous
     36  * (can be parsed as India Standard Time or Israel Standard Time).
     37  * The data file (tzdbNames.txt) contains regional mapping, and
     38  * the locale in the constructor is used as a hint for resolving
     39  * these ambiguous names.
     40  * @hide Only a subset of ICU is exposed in Android
     41  */
     42 public class TZDBTimeZoneNames extends TimeZoneNames {
     43     private static final long serialVersionUID = 1L;
     44 
     45     private static final ConcurrentHashMap<String, TZDBNames> TZDB_NAMES_MAP =
     46             new ConcurrentHashMap<String, TZDBNames>();
     47 
     48     private static volatile TextTrieMap<TZDBNameInfo> TZDB_NAMES_TRIE = null;
     49 
     50     private static final ICUResourceBundle ZONESTRINGS;
     51     static {
     52         UResourceBundle bundle = ICUResourceBundle
     53                 .getBundleInstance(ICUData.ICU_ZONE_BASE_NAME, "tzdbNames");
     54         ZONESTRINGS = (ICUResourceBundle)bundle.get("zoneStrings");
     55     }
     56 
     57     private ULocale _locale;
     58     private transient volatile String _region;
     59 
     60     public TZDBTimeZoneNames(ULocale loc) {
     61         _locale = loc;
     62     }
     63 
     64     /* (non-Javadoc)
     65      * @see android.icu.text.TimeZoneNames#getAvailableMetaZoneIDs()
     66      */
     67     @Override
     68     public Set<String> getAvailableMetaZoneIDs() {
     69         return TimeZoneNamesImpl._getAvailableMetaZoneIDs();
     70     }
     71 
     72     /* (non-Javadoc)
     73      * @see android.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String)
     74      */
     75     @Override
     76     public Set<String> getAvailableMetaZoneIDs(String tzID) {
     77         return TimeZoneNamesImpl._getAvailableMetaZoneIDs(tzID);
     78     }
     79 
     80     /* (non-Javadoc)
     81      * @see android.icu.text.TimeZoneNames#getMetaZoneID(java.lang.String, long)
     82      */
     83     @Override
     84     public String getMetaZoneID(String tzID, long date) {
     85         return TimeZoneNamesImpl._getMetaZoneID(tzID, date);
     86     }
     87 
     88     /* (non-Javadoc)
     89      * @see android.icu.text.TimeZoneNames#getReferenceZoneID(java.lang.String, java.lang.String)
     90      */
     91     @Override
     92     public String getReferenceZoneID(String mzID, String region) {
     93         return TimeZoneNamesImpl._getReferenceZoneID(mzID, region);
     94     }
     95 
     96     /* (non-Javadoc)
     97      * @see android.icu.text.TimeZoneNames#getMetaZoneDisplayName(java.lang.String,
     98      *      android.icu.text.TimeZoneNames.NameType)
     99      */
    100     @Override
    101     public String getMetaZoneDisplayName(String mzID, NameType type) {
    102         if (mzID == null || mzID.length() == 0 ||
    103                 (type != NameType.SHORT_STANDARD && type != NameType.SHORT_DAYLIGHT)) {
    104             return null;
    105         }
    106         return getMetaZoneNames(mzID).getName(type);
    107     }
    108 
    109     /* (non-Javadoc)
    110      * @see android.icu.text.TimeZoneNames#getTimeZoneDisplayName(java.lang.String,
    111      *      android.icu.text.TimeZoneNames.NameType)
    112      */
    113     @Override
    114     public String getTimeZoneDisplayName(String tzID, NameType type) {
    115         // No abbreviations associated a zone directly for now.
    116         return null;
    117     }
    118 
    119 //    /* (non-Javadoc)
    120 //     * @see android.icu.text.TimeZoneNames#getExemplarLocationName(java.lang.String)
    121 //     */
    122 //    public String getExemplarLocationName(String tzID) {
    123 //        return super.getExemplarLocationName(tzID);
    124 //    }
    125 
    126     /* (non-Javadoc)
    127      * @see android.icu.text.TimeZoneNames#find(java.lang.CharSequence, int, java.util.EnumSet)
    128      */
    129     @Override
    130     public Collection<MatchInfo> find(CharSequence text, int start, EnumSet<NameType> nameTypes) {
    131         if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
    132             throw new IllegalArgumentException("bad input text or range");
    133         }
    134 
    135         prepareFind();
    136         TZDBNameSearchHandler handler = new TZDBNameSearchHandler(nameTypes, getTargetRegion());
    137         TZDB_NAMES_TRIE.find(text, start, handler);
    138         return handler.getMatches();
    139     }
    140 
    141     private static class TZDBNames {
    142         public static final TZDBNames EMPTY_TZDBNAMES = new TZDBNames(null, null);
    143 
    144         private String[] _names;
    145         private String[] _parseRegions;
    146         private static final String[] KEYS = {"ss", "sd"};
    147 
    148         private TZDBNames(String[] names, String[] parseRegions) {
    149             _names = names;
    150             _parseRegions = parseRegions;
    151         }
    152 
    153         static TZDBNames getInstance(ICUResourceBundle zoneStrings, String key) {
    154             if (zoneStrings == null || key == null || key.length() == 0) {
    155                 return EMPTY_TZDBNAMES;
    156             }
    157 
    158             ICUResourceBundle table = null;
    159             try {
    160                 table = (ICUResourceBundle)zoneStrings.get(key);
    161             } catch (MissingResourceException e) {
    162                 return EMPTY_TZDBNAMES;
    163             }
    164 
    165             boolean isEmpty = true;
    166             String[] names = new String[KEYS.length];
    167             for (int i = 0; i < names.length; i++) {
    168                 try {
    169                     names[i] = table.getString(KEYS[i]);
    170                     isEmpty = false;
    171                 } catch (MissingResourceException e) {
    172                     names[i] = null;
    173                 }
    174             }
    175 
    176             if (isEmpty) {
    177                 return EMPTY_TZDBNAMES;
    178             }
    179 
    180             String[] parseRegions = null;
    181             try {
    182                 ICUResourceBundle regionsRes = (ICUResourceBundle)table.get("parseRegions");
    183                 if (regionsRes.getType() == UResourceBundle.STRING) {
    184                     parseRegions = new String[1];
    185                     parseRegions[0] = regionsRes.getString();
    186                 } else if (regionsRes.getType() == UResourceBundle.ARRAY) {
    187                     parseRegions = regionsRes.getStringArray();
    188                 }
    189             } catch (MissingResourceException e) {
    190                 // fall through
    191             }
    192 
    193             return new TZDBNames(names, parseRegions);
    194         }
    195 
    196         String getName(NameType type) {
    197             if (_names == null) {
    198                 return null;
    199             }
    200             String name = null;
    201             switch (type) {
    202             case SHORT_STANDARD:
    203                 name = _names[0];
    204                 break;
    205             case SHORT_DAYLIGHT:
    206                 name = _names[1];
    207                 break;
    208             default:
    209                 // No names for all other types handled by
    210                 // this class.
    211                 break;
    212             }
    213 
    214             return name;
    215         }
    216 
    217         String[] getParseRegions() {
    218             return _parseRegions;
    219         }
    220     }
    221 
    222     private static class TZDBNameInfo {
    223         final String mzID;
    224         final NameType type;
    225         final boolean ambiguousType;
    226         final String[] parseRegions;
    227 
    228         TZDBNameInfo(String mzID, NameType type, boolean ambiguousType, String[] parseRegions) {
    229             this.mzID = mzID;
    230             this.type = type;
    231             this.ambiguousType = ambiguousType;
    232             this.parseRegions = parseRegions;
    233         }
    234     }
    235 
    236     private static class TZDBNameSearchHandler implements ResultHandler<TZDBNameInfo> {
    237         private EnumSet<NameType> _nameTypes;
    238         private Collection<MatchInfo> _matches;
    239         private String _region;
    240 
    241         TZDBNameSearchHandler(EnumSet<NameType> nameTypes, String region) {
    242             _nameTypes = nameTypes;
    243             assert region != null;
    244             _region = region;
    245         }
    246 
    247         /* (non-Javadoc)
    248          * @see android.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int,
    249          *      java.util.Iterator)
    250          */
    251         @Override
    252         public boolean handlePrefixMatch(int matchLength, Iterator<TZDBNameInfo> values) {
    253             TZDBNameInfo match = null;
    254             TZDBNameInfo defaultRegionMatch = null;
    255 
    256             while (values.hasNext()) {
    257                 TZDBNameInfo ninfo = values.next();
    258 
    259                 if (_nameTypes != null && !_nameTypes.contains(ninfo.type)) {
    260                     continue;
    261                 }
    262 
    263                 // Some tz database abbreviations are ambiguous. For example,
    264                 // CST means either Central Standard Time or China Standard Time.
    265                 // Unlike CLDR time zone display names, this implementation
    266                 // does not use unique names. And TimeZoneFormat does not expect
    267                 // multiple results returned for the same time zone type.
    268                 // For this reason, this implementation resolve one among same
    269                 // zone type with a same name at this level.
    270                 if (ninfo.parseRegions == null) {
    271                     // parseRegions == null means this is the default metazone
    272                     // mapping for the abbreviation.
    273                     if (defaultRegionMatch == null) {
    274                         match = defaultRegionMatch = ninfo;
    275                     }
    276                 } else {
    277                     boolean matchRegion = false;
    278                     // non-default metazone mapping for an abbreviation
    279                     // comes with applicable regions. For example, the default
    280                     // metazone mapping for "CST" is America_Central,
    281                     // but if region is one of CN/MO/TW, "CST" is parsed
    282                     // as metazone China (China Standard Time).
    283                     for (String region : ninfo.parseRegions) {
    284                         if (_region.equals(region)) {
    285                             match = ninfo;
    286                             matchRegion = true;
    287                             break;
    288                         }
    289                     }
    290                     if (matchRegion) {
    291                         break;
    292                     }
    293                     if (match == null) {
    294                         match = ninfo;
    295                     }
    296                 }
    297             }
    298 
    299             if (match != null) {
    300                 NameType ntype = match.type;
    301                 // Note: Workaround for duplicated standard/daylight names
    302                 // The tz database contains a few zones sharing a
    303                 // same name for both standard time and daylight saving
    304                 // time. For example, Australia/Sydney observes DST,
    305                 // but "EST" is used for both standard and daylight.
    306                 // When both SHORT_STANDARD and SHORT_DAYLIGHT are included
    307                 // in the find operation, we cannot tell which one was
    308                 // actually matched.
    309                 // TimeZoneFormat#parse returns a matched name type (standard
    310                 // or daylight) and DateFormat implementation uses the info to
    311                 // to adjust actual time. To avoid false type information,
    312                 // this implementation replaces the name type with SHORT_GENERIC.
    313                 if (match.ambiguousType
    314                         && (ntype == NameType.SHORT_STANDARD || ntype == NameType.SHORT_DAYLIGHT)
    315                         && _nameTypes.contains(NameType.SHORT_STANDARD)
    316                         && _nameTypes.contains(NameType.SHORT_DAYLIGHT)) {
    317                     ntype = NameType.SHORT_GENERIC;
    318                 }
    319                 MatchInfo minfo = new MatchInfo(ntype, null, match.mzID, matchLength);
    320                 if (_matches == null) {
    321                     _matches = new LinkedList<MatchInfo>();
    322                 }
    323                 _matches.add(minfo);
    324             }
    325 
    326             return true;
    327         }
    328 
    329         /**
    330          * Returns the match results
    331          * @return the match results
    332          */
    333         public Collection<MatchInfo> getMatches() {
    334             if (_matches == null) {
    335                 return Collections.emptyList();
    336             }
    337             return _matches;
    338         }
    339     }
    340 
    341     private static TZDBNames getMetaZoneNames(String mzID) {
    342         TZDBNames names = TZDB_NAMES_MAP.get(mzID);
    343         if (names == null) {
    344             names = TZDBNames.getInstance(ZONESTRINGS, "meta:" + mzID);
    345             mzID = mzID.intern();
    346             TZDBNames tmpNames = TZDB_NAMES_MAP.putIfAbsent(mzID, names);
    347             names = (tmpNames == null) ? names : tmpNames;
    348         }
    349         return names;
    350     }
    351 
    352     private static void prepareFind() {
    353         if (TZDB_NAMES_TRIE == null) {
    354             synchronized(TZDBTimeZoneNames.class) {
    355                 if (TZDB_NAMES_TRIE == null) {
    356                     // loading all names into trie
    357                     TextTrieMap<TZDBNameInfo> trie = new TextTrieMap<TZDBNameInfo>(true);
    358                     Set<String> mzIDs = TimeZoneNamesImpl._getAvailableMetaZoneIDs();
    359                     for (String mzID : mzIDs) {
    360                         TZDBNames names = getMetaZoneNames(mzID);
    361                         String std = names.getName(NameType.SHORT_STANDARD);
    362                         String dst = names.getName(NameType.SHORT_DAYLIGHT);
    363                         if (std == null && dst == null) {
    364                             continue;
    365                         }
    366                         String[] parseRegions = names.getParseRegions();
    367                         mzID = mzID.intern();
    368 
    369                         // The tz database contains a few zones sharing a
    370                         // same name for both standard time and daylight saving
    371                         // time. For example, Australia/Sydney observes DST,
    372                         // but "EST" is used for both standard and daylight.
    373                         // we need to store the information for later processing.
    374                         boolean ambiguousType = (std != null && dst != null && std.equals(dst));
    375 
    376                         if (std != null) {
    377                             TZDBNameInfo stdInf = new TZDBNameInfo(mzID,
    378                                     NameType.SHORT_STANDARD,
    379                                     ambiguousType,
    380                                     parseRegions);
    381                             trie.put(std, stdInf);
    382                         }
    383                         if (dst != null) {
    384                             TZDBNameInfo dstInf = new TZDBNameInfo(mzID,
    385                                     NameType.SHORT_DAYLIGHT,
    386                                     ambiguousType,
    387                                     parseRegions);
    388                             trie.put(dst, dstInf);
    389                         }
    390                     }
    391                     TZDB_NAMES_TRIE = trie;
    392                 }
    393             }
    394         }
    395     }
    396 
    397     private String getTargetRegion() {
    398         if (_region == null) {
    399             String region = _locale.getCountry();
    400             if (region.length() == 0) {
    401                 ULocale tmp = ULocale.addLikelySubtags(_locale);
    402                 region = tmp.getCountry();
    403                 if (region.length() == 0) {
    404                     region = "001";
    405                 }
    406             }
    407             _region = region;
    408         }
    409         return _region;
    410     }
    411 }
    412