17935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
27935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
37935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Copyright (C) 2011-2014, International Business Machines Corporation and    *
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * others. All Rights Reserved.                                                *
57935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.impl;
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.IOException;
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ObjectInputStream;
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.Serializable;
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.lang.ref.WeakReference;
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.MessageFormat;
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Collection;
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.EnumSet;
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Iterator;
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.LinkedList;
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.MissingResourceException;
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Set;
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.concurrent.ConcurrentHashMap;
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.TextTrieMap.ResultHandler;
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.LocaleDisplayNames;
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.TimeZoneFormat.TimeType;
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.TimeZoneNames;
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.TimeZoneNames.MatchInfo;
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.TimeZoneNames.NameType;
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.BasicTimeZone;
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.Freezable;
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.Output;
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.TimeZone;
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.TimeZone.SystemTimeZoneType;
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.TimeZoneTransition;
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ULocale;
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/**
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * This class interact with TimeZoneNames and LocaleDisplayNames
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * to format and parse time zone's generic display names.
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * It is not recommended to use this class directly, instead
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * use com.ibm.icu.text.TimeZoneFormat.
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic class TimeZoneGenericNames implements Serializable, Freezable<TimeZoneGenericNames> {
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Note: This class implements Serializable, but we no longer serialize instance of
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // TimeZoneGenericNames in ICU 49. ICU 4.8 com.ibm.icu.text.TimeZoneFormat used to
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // serialize TimeZoneGenericNames field. TimeZoneFormat no longer read TimeZoneGenericNames
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // field, we have to keep TimeZoneGenericNames Serializable. Otherwise it fails to read
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // (unused) TimeZoneGenericNames serialized data.
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final long serialVersionUID = 2729910342063468417L;
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Generic name type enum
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public enum GenericNameType {
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        LOCATION ("LONG", "SHORT"),
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        LONG (),
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        SHORT ();
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String[] _fallbackTypeOf;
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        GenericNameType(String... fallbackTypeOf) {
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _fallbackTypeOf = fallbackTypeOf;
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean isFallbackTypeOf(GenericNameType type) {
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String typeStr = type.toString();
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String t : _fallbackTypeOf) {
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (t.equals(typeStr)) {
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return true;
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return false;
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Format pattern enum used for composing location and partial location names
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public enum Pattern {
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // The format pattern such as "{0} Time", where {0} is the country or city.
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        REGION_FORMAT("regionFormat", "({0})"),
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Note: FALLBACK_REGION_FORMAT is no longer used since ICU 50/CLDR 22.1
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // The format pattern such as "{1} Time ({0})", where {1} is the country and {0} is a city.
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        //FALLBACK_REGION_FORMAT("fallbackRegionFormat", "{1} ({0})"),
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // The format pattern such as "{1} ({0})", where {1} is the metazone, and {0} is the country or city.
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FALLBACK_FORMAT("fallbackFormat", "{1} ({0})");
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String _key;
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String _defaultVal;
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Pattern(String key, String defaultVal) {
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _key = key;
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _defaultVal = defaultVal;
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String key() {
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return _key;
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String defaultValue() {
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return _defaultVal;
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private ULocale _locale;
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private TimeZoneNames _tznames;
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient volatile boolean _frozen;
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient String _region;
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient WeakReference<LocaleDisplayNames> _localeDisplayNamesRef;
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient MessageFormat[] _patternFormatters;
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient ConcurrentHashMap<String, String> _genericLocationNamesMap;
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient ConcurrentHashMap<String, String> _genericPartialLocationNamesMap;
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient TextTrieMap<NameInfo> _gnamesTrie;
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient boolean _gnamesTrieFullyLoaded;
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static Cache GENERIC_NAMES_CACHE = new Cache();
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Window size used for DST check for a zone in a metazone (about a half year)
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final long DST_CHECK_RANGE = 184L*(24*60*60*1000);
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final NameType[] GENERIC_NON_LOCATION_TYPES =
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                {NameType.LONG_GENERIC, NameType.SHORT_GENERIC};
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Constructs a <code>TimeZoneGenericNames</code> with the given locale
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and the <code>TimeZoneNames</code>.
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param tznames the TimeZoneNames
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public TimeZoneGenericNames(ULocale locale, TimeZoneNames tznames) {
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _locale = locale;
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _tznames = tznames;
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        init();
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Private method initializing the instance of <code>TimeZoneGenericName</code>.
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This method should be called from a constructor and readObject.
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void init() {
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_tznames == null) {
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _tznames = TimeZoneNames.getInstance(_locale);
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _genericLocationNamesMap = new ConcurrentHashMap<String, String>();
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _genericPartialLocationNamesMap = new ConcurrentHashMap<String, String>();
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _gnamesTrie = new TextTrieMap<NameInfo>(true);
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _gnamesTrieFullyLoaded = false;
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Preload zone strings for the default time zone
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TimeZone tz = TimeZone.getDefault();
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (tzCanonicalID != null) {
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            loadStrings(tzCanonicalID);
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Constructs a <code>TimeZoneGenericNames</code> with the given locale.
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This constructor is private and called from {@link #getInstance(ULocale)}.
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private TimeZoneGenericNames(ULocale locale) {
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this(locale, null);
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The factory method of <code>TimeZoneGenericNames</code>. This static method
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * returns a frozen instance of cached <code>TimeZoneGenericNames</code>.
1757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return A frozen <code>TimeZoneGenericNames</code>.
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static TimeZoneGenericNames getInstance(ULocale locale) {
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String key = locale.getBaseName();
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return GENERIC_NAMES_CACHE.getInstance(key, locale);
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the display name of the time zone for the given name type
1857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * at the given date, or null if the display name is not available.
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param tz the time zone
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param type the generic name type - see {@link GenericNameType}
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param date the date
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the display name of the time zone for the given name type
1917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * at the given date, or null.
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String getDisplayName(TimeZone tz, GenericNameType type, long date) {
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String name = null;
1957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String tzCanonicalID = null;
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        switch (type) {
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case LOCATION:
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
1997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (tzCanonicalID != null) {
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                name = getGenericLocationName(tzCanonicalID);
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case LONG:
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case SHORT:
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            name = formatGenericNonLocationName(tz, type, date);
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (name == null) {
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (tzCanonicalID != null) {
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    name = getGenericLocationName(tzCanonicalID);
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
2137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return name;
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the generic location name for the given canonical time zone ID.
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param canonicalTzID the canonical time zone ID
2217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the generic location name for the given canonical time zone ID.
2227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String getGenericLocationName(String canonicalTzID) {
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (canonicalTzID == null || canonicalTzID.length() == 0) {
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return null;
2267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String name = _genericLocationNamesMap.get(canonicalTzID);
2287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (name != null) {
2297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (name.length() == 0) {
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // empty string to indicate the name is not available
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return null;
2327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return name;
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Output<Boolean> isPrimary = new Output<Boolean>();
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String countryCode = ZoneMeta.getCanonicalCountry(canonicalTzID, isPrimary);
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (countryCode != null) {
2397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (isPrimary.value) {
2407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // If this is only the single zone in the country, use the country name
2417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String country = getLocaleDisplayNames().regionDisplayName(countryCode);
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                name = formatPattern(Pattern.REGION_FORMAT, country);
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // If there are multiple zones including this in the country,
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // use the exemplar city name
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // getExemplarLocationName should return non-empty String
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // if the time zone is associated with a location
2497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String city = _tznames.getExemplarLocationName(canonicalTzID);
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                name = formatPattern(Pattern.REGION_FORMAT, city);
2517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (name == null) {
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _genericLocationNamesMap.putIfAbsent(canonicalTzID.intern(), "");
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            synchronized (this) {   // we have to sync the name map and the trie
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                canonicalTzID = canonicalTzID.intern();
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String tmp = _genericLocationNamesMap.putIfAbsent(canonicalTzID, name.intern());
2607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (tmp == null) {
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // Also put the name info the to trie
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    NameInfo info = new NameInfo();
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    info.tzID = canonicalTzID;
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    info.type = GenericNameType.LOCATION;
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _gnamesTrie.put(name, info);
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    name = tmp;
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return name;
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the pattern string for the pattern type.
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Note: This method is designed for CLDR ST - not for common use.
2777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param patType the pattern type
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param patStr the pattern string
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return this object.
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public TimeZoneGenericNames setFormatPattern(Pattern patType, String patStr) {
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (isFrozen()) {
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new UnsupportedOperationException("Attempt to modify frozen object");
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Changing pattern will invalidates cached names
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!_genericLocationNamesMap.isEmpty()) {
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _genericLocationNamesMap = new ConcurrentHashMap<String, String>();
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!_genericPartialLocationNamesMap.isEmpty()) {
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _genericPartialLocationNamesMap = new ConcurrentHashMap<String, String>();
2927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _gnamesTrie = null;
2947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _gnamesTrieFullyLoaded = false;
2957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_patternFormatters == null) {
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _patternFormatters = new MessageFormat[Pattern.values().length];
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _patternFormatters[patType.ordinal()] = new MessageFormat(patStr);
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Private method to get a generic string, with fallback logics involved,
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * that is,
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * 1. If a generic non-location string is available for the zone, return it.
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * 2. If a generic non-location string is associated with a meta zone and
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    the zone never use daylight time around the given date, use the standard
3107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    string (if available).
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * 3. If a generic non-location string is associated with a meta zone and
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    the offset at the given time is different from the preferred zone for the
3137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    current locale, then return the generic partial location string (if available)
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * 4. If a generic non-location string is not available, use generic location
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    string.
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param tz the requested time zone
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param date the date
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param type the generic name type, either LONG or SHORT
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the name used for a generic name type, which could be the
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * generic name, or the standard name (if the zone does not observes DST
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * around the date), or the partial location name.
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String formatGenericNonLocationName(TimeZone tz, GenericNameType type, long date) {
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assert(type == GenericNameType.LONG || type == GenericNameType.SHORT);
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String tzID = ZoneMeta.getCanonicalCLDRID(tz);
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (tzID == null) {
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return null;
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Try to get a name from time zone first
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        NameType nameType = (type == GenericNameType.LONG) ? NameType.LONG_GENERIC : NameType.SHORT_GENERIC;
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String name = _tznames.getTimeZoneDisplayName(tzID, nameType);
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (name != null) {
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return name;
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Try meta zone
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String mzID = _tznames.getMetaZoneID(tzID, date);
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (mzID != null) {
3437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            boolean useStandard = false;
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int[] offsets = {0, 0};
3457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            tz.getOffset(date, false, offsets);
3467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (offsets[1] == 0) {
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                useStandard = true;
3497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Check if the zone actually uses daylight saving time around the time
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (tz instanceof BasicTimeZone) {
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    BasicTimeZone btz = (BasicTimeZone)tz;
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    TimeZoneTransition before = btz.getPreviousTransition(date, true);
3537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (before != null
3547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            && (date - before.getTime() < DST_CHECK_RANGE)
3557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            && before.getFrom().getDSTSavings() != 0) {
3567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        useStandard = false;
3577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else {
3587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        TimeZoneTransition after = btz.getNextTransition(date, false);
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (after != null
3607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                && (after.getTime() - date < DST_CHECK_RANGE)
3617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                && after.getTo().getDSTSavings() != 0) {
3627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            useStandard = false;
3637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
3647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // If not BasicTimeZone... only if the instance is not an ICU's implementation.
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // We may get a wrong answer in edge case, but it should practically work OK.
3687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    int[] tmpOffsets = new int[2];
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    tz.getOffset(date - DST_CHECK_RANGE, false, tmpOffsets);
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (tmpOffsets[1] != 0) {
3717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        useStandard = false;
3727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else {
3737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        tz.getOffset(date + DST_CHECK_RANGE, false, tmpOffsets);
3747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (tmpOffsets[1] != 0){
3757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            useStandard = false;
3767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
3777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
3787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (useStandard) {
3817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                NameType stdNameType = (nameType == NameType.LONG_GENERIC) ?
3827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        NameType.LONG_STANDARD : NameType.SHORT_STANDARD;
3837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String stdName = _tznames.getDisplayName(tzID, stdNameType, date);
3847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (stdName != null) {
3857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    name = stdName;
3867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // TODO: revisit this issue later
3887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // In CLDR, a same display name is used for both generic and standard
3897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // for some meta zones in some locales.  This looks like a data bugs.
3907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // For now, we check if the standard name is different from its generic
3917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // name below.
3927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String mzGenericName = _tznames.getMetaZoneDisplayName(mzID, nameType);
3937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (stdName.equalsIgnoreCase(mzGenericName)) {
3947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        name = null;
3957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
3967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (name == null) {
4007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Get a name from meta zone
4017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String mzName = _tznames.getMetaZoneDisplayName(mzID, nameType);
4027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (mzName != null) {
4037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // Check if we need to use a partial location format.
4047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // This check is done by comparing offset with the meta zone's
4057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // golden zone at the given date.
4067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String goldenID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
4077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (goldenID != null && !goldenID.equals(tzID)) {
4087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        TimeZone goldenZone = TimeZone.getFrozenTimeZone(goldenID);
4097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        int[] offsets1 = {0, 0};
4107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // Check offset in the golden zone with wall time.
4127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // With getOffset(date, false, offsets1),
4137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // you may get incorrect results because of time overlap at DST->STD
4147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // transition.
4157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        goldenZone.getOffset(date + offsets[0] + offsets[1], true, offsets1);
4167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (offsets[0] != offsets1[0] || offsets[1] != offsets1[1]) {
4187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            // Now we need to use a partial location format.
4197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            name = getPartialLocationName(tzID, mzID, (nameType == NameType.LONG_GENERIC), mzName);
4207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        } else {
4217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            name = mzName;
4227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
4237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else {
4247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        name = mzName;
4257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
4267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
4277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return name;
4307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Private simple pattern formatter used for formatting generic location names
4347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and partial location names. We intentionally use JDK MessageFormat
4357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * for performance reason.
4367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pat the message pattern enum
4387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param args the format argument(s)
4397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the formatted string
4407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private synchronized String formatPattern(Pattern pat, String... args) {
4427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_patternFormatters == null) {
4437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _patternFormatters = new MessageFormat[Pattern.values().length];
4447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int idx = pat.ordinal();
4477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_patternFormatters[idx] == null) {
4487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String patText;
4497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
4507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(
4517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    ICUResourceBundle.ICU_ZONE_BASE_NAME, _locale);
4527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                patText = bundle.getStringWithFallback("zoneStrings/" + pat.key());
4537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch (MissingResourceException e) {
4547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                patText = pat.defaultValue();
4557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _patternFormatters[idx] = new MessageFormat(patText);
4587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return _patternFormatters[idx].format(args);
4607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Private method returning LocaleDisplayNames instance for the locale of this
4647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * instance. Because LocaleDisplayNames is only used for generic
4657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * location formant and partial location format, the LocaleDisplayNames
4667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * is instantiated lazily.
4677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the instance of LocaleDisplayNames for the locale of this object.
4697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private synchronized LocaleDisplayNames getLocaleDisplayNames() {
4717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        LocaleDisplayNames locNames = null;
4727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_localeDisplayNamesRef != null) {
4737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            locNames = _localeDisplayNamesRef.get();
4747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (locNames == null) {
4767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            locNames = LocaleDisplayNames.getInstance(_locale);
4777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _localeDisplayNamesRef = new WeakReference<LocaleDisplayNames>(locNames);
4787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return locNames;
4807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private synchronized void loadStrings(String tzCanonicalID) {
4837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (tzCanonicalID == null || tzCanonicalID.length() == 0) {
4847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return;
4857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // getGenericLocationName() formats a name and put it into the trie
4877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        getGenericLocationName(tzCanonicalID);
4887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Generic partial location format
4907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Set<String> mzIDs = _tznames.getAvailableMetaZoneIDs(tzCanonicalID);
4917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (String mzID : mzIDs) {
4927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // if this time zone is not the golden zone of the meta zone,
4937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // partial location name (such as "PT (Los Angeles)") might be
4947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // available.
4957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String goldenID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
4967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!tzCanonicalID.equals(goldenID)) {
4977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (NameType genNonLocType : GENERIC_NON_LOCATION_TYPES) {
4987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String mzGenName = _tznames.getMetaZoneDisplayName(mzID, genNonLocType);
4997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (mzGenName != null) {
5007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // getPartialLocationName() formats a name and put it into the trie
5017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        getPartialLocationName(tzCanonicalID, mzID, (genNonLocType == NameType.LONG_GENERIC), mzGenName);
5027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
5037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
5047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Private method returning the target region. The target regions is determined by
5107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the locale of this instance. When a generic name is coming from
5117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * a meta zone, this region is used for checking if the time zone
5127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * is a reference zone of the meta zone.
5137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the target region
5157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private synchronized String getTargetRegion() {
5177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_region == null) {
5187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _region = _locale.getCountry();
5197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (_region.length() == 0) {
5207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ULocale tmp = ULocale.addLikelySubtags(_locale);
5217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _region = tmp.getCountry();
5227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (_region.length() == 0) {
5237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _region = "001";
5247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
5257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return _region;
5287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Private method for formatting partial location names. This format
5327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * is used when a generic name of a meta zone is available, but the given
5337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * time zone is not a reference zone (golden zone) of the meta zone.
5347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param tzID the canonical time zone ID
5367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param mzID the meta zone ID
5377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param isLong true when long generic name
5387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param mzDisplayName the meta zone generic display name
5397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the partial location format string
5407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String getPartialLocationName(String tzID, String mzID, boolean isLong, String mzDisplayName) {
5427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String letter = isLong ? "L" : "S";
5437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String key = tzID + "&" + mzID + "#" + letter;
5447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String name = _genericPartialLocationNamesMap.get(key);
5457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (name != null) {
5467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return name;
5477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String location = null;
5497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String countryCode = ZoneMeta.getCanonicalCountry(tzID);
5507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (countryCode != null) {
5517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Is this the golden zone for the region?
5527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String regionalGolden = _tznames.getReferenceZoneID(mzID, countryCode);
5537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (tzID.equals(regionalGolden)) {
5547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Use country name
5557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                location = getLocaleDisplayNames().regionDisplayName(countryCode);
5567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
5577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Otherwise, use exemplar city name
5587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                location = _tznames.getExemplarLocationName(tzID);
5597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
5617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            location = _tznames.getExemplarLocationName(tzID);
5627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (location == null) {
5637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // This could happen when the time zone is not associated with a country,
5647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // and its ID is not hierarchical, for example, CST6CDT.
5657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // We use the canonical ID itself as the location for this case.
5667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                location = tzID;
5677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        name = formatPattern(Pattern.FALLBACK_FORMAT, location, mzDisplayName);
5707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        synchronized (this) {   // we have to sync the name map and the trie
5717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String tmp = _genericPartialLocationNamesMap.putIfAbsent(key.intern(), name.intern());
5727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (tmp == null) {
5737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                NameInfo info = new NameInfo();
5747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                info.tzID = tzID.intern();
5757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                info.type = isLong ? GenericNameType.LONG : GenericNameType.SHORT;
5767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _gnamesTrie.put(name, info);
5777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
5787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                name = tmp;
5797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return name;
5827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * A private class used for storing the name information in the local trie.
5867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class NameInfo {
5887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String tzID;
5897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        GenericNameType type;
5907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * A class used for returning the name search result used by
5947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@link TimeZoneGenericNames#find(String, int, EnumSet)}.
5957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static class GenericMatchInfo {
5977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        GenericNameType nameType;
5987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String tzID;
5997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int matchLength;
6007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TimeType timeType = TimeType.UNKNOWN;
6017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public GenericNameType nameType() {
6037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return nameType;
6047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public String tzID() {
6077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return tzID;
6087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public TimeType timeType() {
6117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return timeType;
6127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int matchLength() {
6157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return matchLength;
6167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * A private class implementing the search callback interface in
6217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>TextTrieMap</code> for collecting match results.
6227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class GenericNameSearchHandler implements ResultHandler<NameInfo> {
6247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private EnumSet<GenericNameType> _types;
6257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private Collection<GenericMatchInfo> _matches;
6267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int _maxMatchLen;
6277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        GenericNameSearchHandler(EnumSet<GenericNameType> types) {
6297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _types = types;
6307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /* (non-Javadoc)
6337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @see com.ibm.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, java.util.Iterator)
6347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
6357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean handlePrefixMatch(int matchLength, Iterator<NameInfo> values) {
6367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            while (values.hasNext()) {
6377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                NameInfo info = values.next();
6387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (_types != null && !_types.contains(info.type)) {
6397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    continue;
6407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                GenericMatchInfo matchInfo = new GenericMatchInfo();
6427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                matchInfo.tzID = info.tzID;
6437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                matchInfo.nameType = info.type;
6447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                matchInfo.matchLength = matchLength;
6457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                //matchInfo.timeType = TimeType.UNKNOWN;
6467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (_matches == null) {
6477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _matches = new LinkedList<GenericMatchInfo>();
6487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _matches.add(matchInfo);
6507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (matchLength > _maxMatchLen) {
6517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _maxMatchLen = matchLength;
6527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return true;
6557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
6587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Returns the match results
6597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return the match results
6607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
6617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public Collection<GenericMatchInfo> getMatches() {
6627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return _matches;
6637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
6667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Returns the maximum match length, or 0 if no match was found
6677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return the maximum match length
6687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
6697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int getMaxMatchLen() {
6707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return _maxMatchLen;
6717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
6747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Resets the match results
6757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
6767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void resetResults() {
6777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _matches = null;
6787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _maxMatchLen = 0;
6797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the best match of time zone display name for the specified types in the
6847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * given text at the given offset.
6857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param text the text
6867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param start the start offset in the text
6877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param genericTypes the set of name types.
6887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the best matching name info.
6897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public GenericMatchInfo findBestMatch(String text, int start, EnumSet<GenericNameType> genericTypes) {
6917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
6927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException("bad input text or range");
6937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        GenericMatchInfo bestMatch = null;
6957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Find matches in the TimeZoneNames first
6977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Collection<MatchInfo> tznamesMatches = findTimeZoneNames(text, start, genericTypes);
6987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (tznamesMatches != null) {
6997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            MatchInfo longestMatch = null;
7007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (MatchInfo match : tznamesMatches) {
7017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (longestMatch == null || match.matchLength() > longestMatch.matchLength()) {
7027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    longestMatch = match;
7037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
7047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (longestMatch != null) {
7067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                bestMatch = createGenericMatchInfo(longestMatch);
7077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (bestMatch.matchLength() == (text.length() - start)) {
7087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // Full match
7097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    //return bestMatch;
7107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // TODO Some time zone uses a same name for the long standard name
7127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // and the location name. When the match is a long standard name,
7137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // then we need to check if the name is same with the location name.
7147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // This is probably a data error or a design bug.
7157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert//                    if (bestMatch.nameType != GenericNameType.LONG || bestMatch.timeType != TimeType.STANDARD) {
7167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert//                        return bestMatch;
7177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert//                    }
7187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // TODO The deprecation of commonlyUsed flag introduced the name
7207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // conflict not only for long standard names, but short standard names too.
7217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // These short names (found in zh_Hant) should be gone once we clean
7227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // up CLDR time zone display name data. Once the short name conflict
7237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // problem (with location name) is resolved, we should change the condition
7247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // below back to the original one above. -Yoshito (2011-09-14)
7257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (bestMatch.timeType != TimeType.STANDARD) {
7267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        return bestMatch;
7277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
7287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
7297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Find matches in the local trie
7337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Collection<GenericMatchInfo> localMatches = findLocal(text, start, genericTypes);
7347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (localMatches != null) {
7357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (GenericMatchInfo match : localMatches) {
7367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // TODO See the above TODO. We use match.matchLength() >= bestMatch.matcheLength()
7377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // for the reason described above.
7387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                //if (bestMatch == null || match.matchLength() > bestMatch.matchLength()) {
7397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (bestMatch == null || match.matchLength() >= bestMatch.matchLength()) {
7407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    bestMatch = match;
7417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
7427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return bestMatch;
7467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns a collection of time zone display name matches for the specified types in the
7507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * given text at the given offset.
7517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param text the text
7527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param start the start offset in the text
7537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param genericTypes the set of name types.
7547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return A collection of match info.
7557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
7567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Collection<GenericMatchInfo> find(String text, int start, EnumSet<GenericNameType> genericTypes) {
7577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
7587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException("bad input text or range");
7597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Find matches in the local trie
7617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Collection<GenericMatchInfo> results = findLocal(text, start, genericTypes);
7627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Also find matches in the TimeZoneNames
7647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Collection<MatchInfo> tznamesMatches = findTimeZoneNames(text, start, genericTypes);
7657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (tznamesMatches != null) {
7667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // transform matches and append them to local matches
7677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (MatchInfo match : tznamesMatches) {
7687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (results == null) {
7697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    results = new LinkedList<GenericMatchInfo>();
7707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
7717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                results.add(createGenericMatchInfo(match));
7727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return results;
7757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns a <code>GenericMatchInfo</code> for the given <code>MatchInfo</code>.
7797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param matchInfo the MatchInfo
7807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return A GenericMatchInfo
7817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
7827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private GenericMatchInfo createGenericMatchInfo(MatchInfo matchInfo) {
7837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        GenericNameType nameType = null;
7847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TimeType timeType = TimeType.UNKNOWN;
7857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        switch (matchInfo.nameType()) {
7867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case LONG_STANDARD:
7877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            nameType = GenericNameType.LONG;
7887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            timeType = TimeType.STANDARD;
7897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
7907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case LONG_GENERIC:
7917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            nameType = GenericNameType.LONG;
7927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
7937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case SHORT_STANDARD:
7947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            nameType = GenericNameType.SHORT;
7957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            timeType = TimeType.STANDARD;
7967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
7977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case SHORT_GENERIC:
7987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            nameType = GenericNameType.SHORT;
7997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
8007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        default:
8017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException("Unexpected MatchInfo name type - " + matchInfo.nameType());
8027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String tzID = matchInfo.tzID();
8057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (tzID == null) {
8067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String mzID = matchInfo.mzID();
8077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assert(mzID != null);
8087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            tzID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
8097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assert(tzID != null);
8117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        GenericMatchInfo gmatch = new GenericMatchInfo();
8137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        gmatch.nameType = nameType;
8147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        gmatch.tzID = tzID;
8157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        gmatch.matchLength = matchInfo.matchLength();
8167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        gmatch.timeType = timeType;
8177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return gmatch;
8197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
8227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns a collection of time zone display name matches for the specified types in the
8237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * given text at the given offset. This method only finds matches from the TimeZoneNames
8247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * used by this object.
8257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param text the text
8267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param start the start offset in the text
8277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param types the set of name types.
8287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return A collection of match info.
8297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
8307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private Collection<MatchInfo> findTimeZoneNames(String text, int start, EnumSet<GenericNameType> types) {
8317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Collection<MatchInfo> tznamesMatches = null;
8327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Check if the target name type is really in the TimeZoneNames
8347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        EnumSet<NameType> nameTypes = EnumSet.noneOf(NameType.class);
8357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (types.contains(GenericNameType.LONG)) {
8367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            nameTypes.add(NameType.LONG_GENERIC);
8377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            nameTypes.add(NameType.LONG_STANDARD);
8387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (types.contains(GenericNameType.SHORT)) {
8407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            nameTypes.add(NameType.SHORT_GENERIC);
8417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            nameTypes.add(NameType.SHORT_STANDARD);
8427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!nameTypes.isEmpty()) {
8457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Find matches in the TimeZoneNames
8467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            tznamesMatches = _tznames.find(text, start, nameTypes);
8477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return tznamesMatches;
8497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
8527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns a collection of time zone display name matches for the specified types in the
8537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * given text at the given offset. This method only finds matches from the local trie,
8547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * that contains 1) generic location names and 2) long/short generic partial location names,
8557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * used by this object.
8567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param text the text
8577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param start the start offset in the text
8587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param types the set of name types.
8597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return A collection of match info.
8607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
8617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private synchronized Collection<GenericMatchInfo> findLocal(String text, int start, EnumSet<GenericNameType> types) {
8627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        GenericNameSearchHandler handler = new GenericNameSearchHandler(types);
8637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _gnamesTrie.find(text, start, handler);
8647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (handler.getMaxMatchLen() == (text.length() - start) || _gnamesTrieFullyLoaded) {
8657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // perfect match
8667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return handler.getMatches();
8677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // All names are not yet loaded into the local trie.
8707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Load all available names into the trie. This could be very heavy.
8717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Set<String> tzIDs = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
8737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (String tzID : tzIDs) {
8747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            loadStrings(tzID);
8757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _gnamesTrieFullyLoaded = true;
8777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // now, try it again
8797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        handler.resetResults();
8807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _gnamesTrie.find(text, start, handler);
8817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return handler.getMatches();
8827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
8857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>TimeZoneGenericNames</code> cache implementation.
8867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
8877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static class Cache extends SoftCache<String, TimeZoneGenericNames, ULocale> {
8887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /* (non-Javadoc)
8907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
8917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
8927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        @Override
8937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        protected TimeZoneGenericNames createInstance(String key, ULocale data) {
8947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return new TimeZoneGenericNames(data).freeze();
8957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
9007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The custom deserialization method.
9017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This implementation only read locale used by the object.
9027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
9037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
9047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        in.defaultReadObject();
9057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        init();
9067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
9097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
9107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
9117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public boolean isFrozen() {
9127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return _frozen;
9137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
9167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
9177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
9187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public TimeZoneGenericNames freeze() {
9197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _frozen = true;
9207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
9217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
9247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
9257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
9267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public TimeZoneGenericNames cloneAsThawed() {
9277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TimeZoneGenericNames copy = null;
9287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
9297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            copy = (TimeZoneGenericNames)super.clone();
9307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            copy._frozen = false;
9317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (Throwable t) {
9327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // This should never happen
9337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return copy;
9357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
937