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 */ 10package android.icu.impl; 11 12import java.util.Collection; 13import java.util.Collections; 14import java.util.EnumSet; 15import java.util.Iterator; 16import java.util.LinkedList; 17import java.util.MissingResourceException; 18import java.util.Set; 19import java.util.concurrent.ConcurrentHashMap; 20 21import android.icu.impl.TextTrieMap.ResultHandler; 22import android.icu.text.TimeZoneNames; 23import android.icu.util.ULocale; 24import 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 */ 42public 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 } 209 210 return name; 211 } 212 213 String[] getParseRegions() { 214 return _parseRegions; 215 } 216 } 217 218 private static class TZDBNameInfo { 219 final String mzID; 220 final NameType type; 221 final boolean ambiguousType; 222 final String[] parseRegions; 223 224 TZDBNameInfo(String mzID, NameType type, boolean ambiguousType, String[] parseRegions) { 225 this.mzID = mzID; 226 this.type = type; 227 this.ambiguousType = ambiguousType; 228 this.parseRegions = parseRegions; 229 } 230 } 231 232 private static class TZDBNameSearchHandler implements ResultHandler<TZDBNameInfo> { 233 private EnumSet<NameType> _nameTypes; 234 private Collection<MatchInfo> _matches; 235 private String _region; 236 237 TZDBNameSearchHandler(EnumSet<NameType> nameTypes, String region) { 238 _nameTypes = nameTypes; 239 assert region != null; 240 _region = region; 241 } 242 243 /* (non-Javadoc) 244 * @see android.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, 245 * java.util.Iterator) 246 */ 247 @Override 248 public boolean handlePrefixMatch(int matchLength, Iterator<TZDBNameInfo> values) { 249 TZDBNameInfo match = null; 250 TZDBNameInfo defaultRegionMatch = null; 251 252 while (values.hasNext()) { 253 TZDBNameInfo ninfo = values.next(); 254 255 if (_nameTypes != null && !_nameTypes.contains(ninfo.type)) { 256 continue; 257 } 258 259 // Some tz database abbreviations are ambiguous. For example, 260 // CST means either Central Standard Time or China Standard Time. 261 // Unlike CLDR time zone display names, this implementation 262 // does not use unique names. And TimeZoneFormat does not expect 263 // multiple results returned for the same time zone type. 264 // For this reason, this implementation resolve one among same 265 // zone type with a same name at this level. 266 if (ninfo.parseRegions == null) { 267 // parseRegions == null means this is the default metazone 268 // mapping for the abbreviation. 269 if (defaultRegionMatch == null) { 270 match = defaultRegionMatch = ninfo; 271 } 272 } else { 273 boolean matchRegion = false; 274 // non-default metazone mapping for an abbreviation 275 // comes with applicable regions. For example, the default 276 // metazone mapping for "CST" is America_Central, 277 // but if region is one of CN/MO/TW, "CST" is parsed 278 // as metazone China (China Standard Time). 279 for (String region : ninfo.parseRegions) { 280 if (_region.equals(region)) { 281 match = ninfo; 282 matchRegion = true; 283 break; 284 } 285 } 286 if (matchRegion) { 287 break; 288 } 289 if (match == null) { 290 match = ninfo; 291 } 292 } 293 } 294 295 if (match != null) { 296 NameType ntype = match.type; 297 // Note: Workaround for duplicated standard/daylight names 298 // The tz database contains a few zones sharing a 299 // same name for both standard time and daylight saving 300 // time. For example, Australia/Sydney observes DST, 301 // but "EST" is used for both standard and daylight. 302 // When both SHORT_STANDARD and SHORT_DAYLIGHT are included 303 // in the find operation, we cannot tell which one was 304 // actually matched. 305 // TimeZoneFormat#parse returns a matched name type (standard 306 // or daylight) and DateFormat implementation uses the info to 307 // to adjust actual time. To avoid false type information, 308 // this implementation replaces the name type with SHORT_GENERIC. 309 if (match.ambiguousType 310 && (ntype == NameType.SHORT_STANDARD || ntype == NameType.SHORT_DAYLIGHT) 311 && _nameTypes.contains(NameType.SHORT_STANDARD) 312 && _nameTypes.contains(NameType.SHORT_DAYLIGHT)) { 313 ntype = NameType.SHORT_GENERIC; 314 } 315 MatchInfo minfo = new MatchInfo(ntype, null, match.mzID, matchLength); 316 if (_matches == null) { 317 _matches = new LinkedList<MatchInfo>(); 318 } 319 _matches.add(minfo); 320 } 321 322 return true; 323 } 324 325 /** 326 * Returns the match results 327 * @return the match results 328 */ 329 public Collection<MatchInfo> getMatches() { 330 if (_matches == null) { 331 return Collections.emptyList(); 332 } 333 return _matches; 334 } 335 } 336 337 private static TZDBNames getMetaZoneNames(String mzID) { 338 TZDBNames names = TZDB_NAMES_MAP.get(mzID); 339 if (names == null) { 340 names = TZDBNames.getInstance(ZONESTRINGS, "meta:" + mzID); 341 mzID = mzID.intern(); 342 TZDBNames tmpNames = TZDB_NAMES_MAP.putIfAbsent(mzID, names); 343 names = (tmpNames == null) ? names : tmpNames; 344 } 345 return names; 346 } 347 348 private static void prepareFind() { 349 if (TZDB_NAMES_TRIE == null) { 350 synchronized(TZDBTimeZoneNames.class) { 351 if (TZDB_NAMES_TRIE == null) { 352 // loading all names into trie 353 TextTrieMap<TZDBNameInfo> trie = new TextTrieMap<TZDBNameInfo>(true); 354 Set<String> mzIDs = TimeZoneNamesImpl._getAvailableMetaZoneIDs(); 355 for (String mzID : mzIDs) { 356 TZDBNames names = getMetaZoneNames(mzID); 357 String std = names.getName(NameType.SHORT_STANDARD); 358 String dst = names.getName(NameType.SHORT_DAYLIGHT); 359 if (std == null && dst == null) { 360 continue; 361 } 362 String[] parseRegions = names.getParseRegions(); 363 mzID = mzID.intern(); 364 365 // The tz database contains a few zones sharing a 366 // same name for both standard time and daylight saving 367 // time. For example, Australia/Sydney observes DST, 368 // but "EST" is used for both standard and daylight. 369 // we need to store the information for later processing. 370 boolean ambiguousType = (std != null && dst != null && std.equals(dst)); 371 372 if (std != null) { 373 TZDBNameInfo stdInf = new TZDBNameInfo(mzID, 374 NameType.SHORT_STANDARD, 375 ambiguousType, 376 parseRegions); 377 trie.put(std, stdInf); 378 } 379 if (dst != null) { 380 TZDBNameInfo dstInf = new TZDBNameInfo(mzID, 381 NameType.SHORT_DAYLIGHT, 382 ambiguousType, 383 parseRegions); 384 trie.put(dst, dstInf); 385 } 386 } 387 TZDB_NAMES_TRIE = trie; 388 } 389 } 390 } 391 } 392 393 private String getTargetRegion() { 394 if (_region == null) { 395 String region = _locale.getCountry(); 396 if (region.length() == 0) { 397 ULocale tmp = ULocale.addLikelySubtags(_locale); 398 region = tmp.getCountry(); 399 if (region.length() == 0) { 400 region = "001"; 401 } 402 } 403 _region = region; 404 } 405 return _region; 406 } 407} 408