/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /* ******************************************************************************* * Copyright (C) 2014-2016, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ package android.icu.impl; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.Iterator; import java.util.LinkedList; import java.util.MissingResourceException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import android.icu.impl.TextTrieMap.ResultHandler; import android.icu.text.TimeZoneNames; import android.icu.util.ULocale; import android.icu.util.UResourceBundle; /** * Yet another TimeZoneNames implementation based on the tz database. * This implementation contains only tz abbreviations (short standard * and daylight names) for each metazone. * * The data file $ICU4C_ROOT/source/data/zone/tzdbNames.txt contains * the metazone - abbreviations mapping data (manually edited). * * Note: The abbreviations in the tz database are not necessarily * unique. For example, parsing abbreviation "IST" is ambiguous * (can be parsed as India Standard Time or Israel Standard Time). * The data file (tzdbNames.txt) contains regional mapping, and * the locale in the constructor is used as a hint for resolving * these ambiguous names. * @hide Only a subset of ICU is exposed in Android */ public class TZDBTimeZoneNames extends TimeZoneNames { private static final long serialVersionUID = 1L; private static final ConcurrentHashMap TZDB_NAMES_MAP = new ConcurrentHashMap(); private static volatile TextTrieMap TZDB_NAMES_TRIE = null; private static final ICUResourceBundle ZONESTRINGS; static { UResourceBundle bundle = ICUResourceBundle .getBundleInstance(ICUData.ICU_ZONE_BASE_NAME, "tzdbNames"); ZONESTRINGS = (ICUResourceBundle)bundle.get("zoneStrings"); } private ULocale _locale; private transient volatile String _region; public TZDBTimeZoneNames(ULocale loc) { _locale = loc; } /* (non-Javadoc) * @see android.icu.text.TimeZoneNames#getAvailableMetaZoneIDs() */ @Override public Set getAvailableMetaZoneIDs() { return TimeZoneNamesImpl._getAvailableMetaZoneIDs(); } /* (non-Javadoc) * @see android.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String) */ @Override public Set getAvailableMetaZoneIDs(String tzID) { return TimeZoneNamesImpl._getAvailableMetaZoneIDs(tzID); } /* (non-Javadoc) * @see android.icu.text.TimeZoneNames#getMetaZoneID(java.lang.String, long) */ @Override public String getMetaZoneID(String tzID, long date) { return TimeZoneNamesImpl._getMetaZoneID(tzID, date); } /* (non-Javadoc) * @see android.icu.text.TimeZoneNames#getReferenceZoneID(java.lang.String, java.lang.String) */ @Override public String getReferenceZoneID(String mzID, String region) { return TimeZoneNamesImpl._getReferenceZoneID(mzID, region); } /* (non-Javadoc) * @see android.icu.text.TimeZoneNames#getMetaZoneDisplayName(java.lang.String, * android.icu.text.TimeZoneNames.NameType) */ @Override public String getMetaZoneDisplayName(String mzID, NameType type) { if (mzID == null || mzID.length() == 0 || (type != NameType.SHORT_STANDARD && type != NameType.SHORT_DAYLIGHT)) { return null; } return getMetaZoneNames(mzID).getName(type); } /* (non-Javadoc) * @see android.icu.text.TimeZoneNames#getTimeZoneDisplayName(java.lang.String, * android.icu.text.TimeZoneNames.NameType) */ @Override public String getTimeZoneDisplayName(String tzID, NameType type) { // No abbreviations associated a zone directly for now. return null; } // /* (non-Javadoc) // * @see android.icu.text.TimeZoneNames#getExemplarLocationName(java.lang.String) // */ // public String getExemplarLocationName(String tzID) { // return super.getExemplarLocationName(tzID); // } /* (non-Javadoc) * @see android.icu.text.TimeZoneNames#find(java.lang.CharSequence, int, java.util.EnumSet) */ @Override public Collection find(CharSequence text, int start, EnumSet nameTypes) { if (text == null || text.length() == 0 || start < 0 || start >= text.length()) { throw new IllegalArgumentException("bad input text or range"); } prepareFind(); TZDBNameSearchHandler handler = new TZDBNameSearchHandler(nameTypes, getTargetRegion()); TZDB_NAMES_TRIE.find(text, start, handler); return handler.getMatches(); } private static class TZDBNames { public static final TZDBNames EMPTY_TZDBNAMES = new TZDBNames(null, null); private String[] _names; private String[] _parseRegions; private static final String[] KEYS = {"ss", "sd"}; private TZDBNames(String[] names, String[] parseRegions) { _names = names; _parseRegions = parseRegions; } static TZDBNames getInstance(ICUResourceBundle zoneStrings, String key) { if (zoneStrings == null || key == null || key.length() == 0) { return EMPTY_TZDBNAMES; } ICUResourceBundle table = null; try { table = (ICUResourceBundle)zoneStrings.get(key); } catch (MissingResourceException e) { return EMPTY_TZDBNAMES; } boolean isEmpty = true; String[] names = new String[KEYS.length]; for (int i = 0; i < names.length; i++) { try { names[i] = table.getString(KEYS[i]); isEmpty = false; } catch (MissingResourceException e) { names[i] = null; } } if (isEmpty) { return EMPTY_TZDBNAMES; } String[] parseRegions = null; try { ICUResourceBundle regionsRes = (ICUResourceBundle)table.get("parseRegions"); if (regionsRes.getType() == UResourceBundle.STRING) { parseRegions = new String[1]; parseRegions[0] = regionsRes.getString(); } else if (regionsRes.getType() == UResourceBundle.ARRAY) { parseRegions = regionsRes.getStringArray(); } } catch (MissingResourceException e) { // fall through } return new TZDBNames(names, parseRegions); } String getName(NameType type) { if (_names == null) { return null; } String name = null; switch (type) { case SHORT_STANDARD: name = _names[0]; break; case SHORT_DAYLIGHT: name = _names[1]; break; default: // No names for all other types handled by // this class. break; } return name; } String[] getParseRegions() { return _parseRegions; } } private static class TZDBNameInfo { final String mzID; final NameType type; final boolean ambiguousType; final String[] parseRegions; TZDBNameInfo(String mzID, NameType type, boolean ambiguousType, String[] parseRegions) { this.mzID = mzID; this.type = type; this.ambiguousType = ambiguousType; this.parseRegions = parseRegions; } } private static class TZDBNameSearchHandler implements ResultHandler { private EnumSet _nameTypes; private Collection _matches; private String _region; TZDBNameSearchHandler(EnumSet nameTypes, String region) { _nameTypes = nameTypes; assert region != null; _region = region; } /* (non-Javadoc) * @see android.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, * java.util.Iterator) */ @Override public boolean handlePrefixMatch(int matchLength, Iterator values) { TZDBNameInfo match = null; TZDBNameInfo defaultRegionMatch = null; while (values.hasNext()) { TZDBNameInfo ninfo = values.next(); if (_nameTypes != null && !_nameTypes.contains(ninfo.type)) { continue; } // Some tz database abbreviations are ambiguous. For example, // CST means either Central Standard Time or China Standard Time. // Unlike CLDR time zone display names, this implementation // does not use unique names. And TimeZoneFormat does not expect // multiple results returned for the same time zone type. // For this reason, this implementation resolve one among same // zone type with a same name at this level. if (ninfo.parseRegions == null) { // parseRegions == null means this is the default metazone // mapping for the abbreviation. if (defaultRegionMatch == null) { match = defaultRegionMatch = ninfo; } } else { boolean matchRegion = false; // non-default metazone mapping for an abbreviation // comes with applicable regions. For example, the default // metazone mapping for "CST" is America_Central, // but if region is one of CN/MO/TW, "CST" is parsed // as metazone China (China Standard Time). for (String region : ninfo.parseRegions) { if (_region.equals(region)) { match = ninfo; matchRegion = true; break; } } if (matchRegion) { break; } if (match == null) { match = ninfo; } } } if (match != null) { NameType ntype = match.type; // Note: Workaround for duplicated standard/daylight names // The tz database contains a few zones sharing a // same name for both standard time and daylight saving // time. For example, Australia/Sydney observes DST, // but "EST" is used for both standard and daylight. // When both SHORT_STANDARD and SHORT_DAYLIGHT are included // in the find operation, we cannot tell which one was // actually matched. // TimeZoneFormat#parse returns a matched name type (standard // or daylight) and DateFormat implementation uses the info to // to adjust actual time. To avoid false type information, // this implementation replaces the name type with SHORT_GENERIC. if (match.ambiguousType && (ntype == NameType.SHORT_STANDARD || ntype == NameType.SHORT_DAYLIGHT) && _nameTypes.contains(NameType.SHORT_STANDARD) && _nameTypes.contains(NameType.SHORT_DAYLIGHT)) { ntype = NameType.SHORT_GENERIC; } MatchInfo minfo = new MatchInfo(ntype, null, match.mzID, matchLength); if (_matches == null) { _matches = new LinkedList(); } _matches.add(minfo); } return true; } /** * Returns the match results * @return the match results */ public Collection getMatches() { if (_matches == null) { return Collections.emptyList(); } return _matches; } } private static TZDBNames getMetaZoneNames(String mzID) { TZDBNames names = TZDB_NAMES_MAP.get(mzID); if (names == null) { names = TZDBNames.getInstance(ZONESTRINGS, "meta:" + mzID); mzID = mzID.intern(); TZDBNames tmpNames = TZDB_NAMES_MAP.putIfAbsent(mzID, names); names = (tmpNames == null) ? names : tmpNames; } return names; } private static void prepareFind() { if (TZDB_NAMES_TRIE == null) { synchronized(TZDBTimeZoneNames.class) { if (TZDB_NAMES_TRIE == null) { // loading all names into trie TextTrieMap trie = new TextTrieMap(true); Set mzIDs = TimeZoneNamesImpl._getAvailableMetaZoneIDs(); for (String mzID : mzIDs) { TZDBNames names = getMetaZoneNames(mzID); String std = names.getName(NameType.SHORT_STANDARD); String dst = names.getName(NameType.SHORT_DAYLIGHT); if (std == null && dst == null) { continue; } String[] parseRegions = names.getParseRegions(); mzID = mzID.intern(); // The tz database contains a few zones sharing a // same name for both standard time and daylight saving // time. For example, Australia/Sydney observes DST, // but "EST" is used for both standard and daylight. // we need to store the information for later processing. boolean ambiguousType = (std != null && dst != null && std.equals(dst)); if (std != null) { TZDBNameInfo stdInf = new TZDBNameInfo(mzID, NameType.SHORT_STANDARD, ambiguousType, parseRegions); trie.put(std, stdInf); } if (dst != null) { TZDBNameInfo dstInf = new TZDBNameInfo(mzID, NameType.SHORT_DAYLIGHT, ambiguousType, parseRegions); trie.put(dst, dstInf); } } TZDB_NAMES_TRIE = trie; } } } } private String getTargetRegion() { if (_region == null) { String region = _locale.getCountry(); if (region.length() == 0) { ULocale tmp = ULocale.addLikelySubtags(_locale); region = tmp.getCountry(); if (region.length() == 0) { region = "001"; } } _region = region; } return _region; } }