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