blob: 7ddf1b00d5145f1d0ecd3a6a3459c2a8d6cdefd7 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2011-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.util.Collection;
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 com.ibm.icu.impl.TextTrieMap.ResultHandler;
import com.ibm.icu.text.LocaleDisplayNames;
import com.ibm.icu.text.TimeZoneFormat.TimeType;
import com.ibm.icu.text.TimeZoneNames;
import com.ibm.icu.text.TimeZoneNames.MatchInfo;
import com.ibm.icu.text.TimeZoneNames.NameType;
import com.ibm.icu.util.BasicTimeZone;
import com.ibm.icu.util.Freezable;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.TimeZone.SystemTimeZoneType;
import com.ibm.icu.util.TimeZoneTransition;
import com.ibm.icu.util.ULocale;
/**
* This class interact with TimeZoneNames and LocaleDisplayNames
* to format and parse time zone's generic display names.
* It is not recommended to use this class directly, instead
* use com.ibm.icu.text.TimeZoneFormat.
*/
public class TimeZoneGenericNames implements Serializable, Freezable<TimeZoneGenericNames> {
// Note: This class implements Serializable, but we no longer serialize instance of
// TimeZoneGenericNames in ICU 49. ICU 4.8 com.ibm.icu.text.TimeZoneFormat used to
// serialize TimeZoneGenericNames field. TimeZoneFormat no longer read TimeZoneGenericNames
// field, we have to keep TimeZoneGenericNames Serializable. Otherwise it fails to read
// (unused) TimeZoneGenericNames serialized data.
private static final long serialVersionUID = 2729910342063468417L;
/**
* Generic name type enum
*/
public enum GenericNameType {
LOCATION ("LONG", "SHORT"),
LONG (),
SHORT ();
String[] _fallbackTypeOf;
GenericNameType(String... fallbackTypeOf) {
_fallbackTypeOf = fallbackTypeOf;
}
public boolean isFallbackTypeOf(GenericNameType type) {
String typeStr = type.toString();
for (String t : _fallbackTypeOf) {
if (t.equals(typeStr)) {
return true;
}
}
return false;
}
}
/**
* Format pattern enum used for composing location and partial location names
*/
public enum Pattern {
// The format pattern such as "{0} Time", where {0} is the country or city.
REGION_FORMAT("regionFormat", "({0})"),
// Note: FALLBACK_REGION_FORMAT is no longer used since ICU 50/CLDR 22.1
// The format pattern such as "{1} Time ({0})", where {1} is the country and {0} is a city.
//FALLBACK_REGION_FORMAT("fallbackRegionFormat", "{1} ({0})"),
// The format pattern such as "{1} ({0})", where {1} is the metazone, and {0} is the country or city.
FALLBACK_FORMAT("fallbackFormat", "{1} ({0})");
String _key;
String _defaultVal;
Pattern(String key, String defaultVal) {
_key = key;
_defaultVal = defaultVal;
}
String key() {
return _key;
}
String defaultValue() {
return _defaultVal;
}
}
private ULocale _locale;
private TimeZoneNames _tznames;
private transient volatile boolean _frozen;
private transient String _region;
private transient WeakReference<LocaleDisplayNames> _localeDisplayNamesRef;
private transient MessageFormat[] _patternFormatters;
private transient ConcurrentHashMap<String, String> _genericLocationNamesMap;
private transient ConcurrentHashMap<String, String> _genericPartialLocationNamesMap;
private transient TextTrieMap<NameInfo> _gnamesTrie;
private transient boolean _gnamesTrieFullyLoaded;
private static Cache GENERIC_NAMES_CACHE = new Cache();
// Window size used for DST check for a zone in a metazone (about a half year)
private static final long DST_CHECK_RANGE = 184L*(24*60*60*1000);
private static final NameType[] GENERIC_NON_LOCATION_TYPES =
{NameType.LONG_GENERIC, NameType.SHORT_GENERIC};
/**
* Constructs a <code>TimeZoneGenericNames</code> with the given locale
* and the <code>TimeZoneNames</code>.
* @param locale the locale
* @param tznames the TimeZoneNames
*/
public TimeZoneGenericNames(ULocale locale, TimeZoneNames tznames) {
_locale = locale;
_tznames = tznames;
init();
}
/**
* Private method initializing the instance of <code>TimeZoneGenericName</code>.
* This method should be called from a constructor and readObject.
*/
private void init() {
if (_tznames == null) {
_tznames = TimeZoneNames.getInstance(_locale);
}
_genericLocationNamesMap = new ConcurrentHashMap<String, String>();
_genericPartialLocationNamesMap = new ConcurrentHashMap<String, String>();
_gnamesTrie = new TextTrieMap<NameInfo>(true);
_gnamesTrieFullyLoaded = false;
// Preload zone strings for the default time zone
TimeZone tz = TimeZone.getDefault();
String tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzCanonicalID != null) {
loadStrings(tzCanonicalID);
}
}
/**
* Constructs a <code>TimeZoneGenericNames</code> with the given locale.
* This constructor is private and called from {@link #getInstance(ULocale)}.
* @param locale the locale
*/
private TimeZoneGenericNames(ULocale locale) {
this(locale, null);
}
/**
* The factory method of <code>TimeZoneGenericNames</code>. This static method
* returns a frozen instance of cached <code>TimeZoneGenericNames</code>.
* @param locale the locale
* @return A frozen <code>TimeZoneGenericNames</code>.
*/
public static TimeZoneGenericNames getInstance(ULocale locale) {
String key = locale.getBaseName();
return GENERIC_NAMES_CACHE.getInstance(key, locale);
}
/**
* Returns the display name of the time zone for the given name type
* at the given date, or null if the display name is not available.
*
* @param tz the time zone
* @param type the generic name type - see {@link GenericNameType}
* @param date the date
* @return the display name of the time zone for the given name type
* at the given date, or null.
*/
public String getDisplayName(TimeZone tz, GenericNameType type, long date) {
String name = null;
String tzCanonicalID = null;
switch (type) {
case LOCATION:
tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzCanonicalID != null) {
name = getGenericLocationName(tzCanonicalID);
}
break;
case LONG:
case SHORT:
name = formatGenericNonLocationName(tz, type, date);
if (name == null) {
tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzCanonicalID != null) {
name = getGenericLocationName(tzCanonicalID);
}
}
break;
}
return name;
}
/**
* Returns the generic location name for the given canonical time zone ID.
*
* @param canonicalTzID the canonical time zone ID
* @return the generic location name for the given canonical time zone ID.
*/
public String getGenericLocationName(String canonicalTzID) {
if (canonicalTzID == null || canonicalTzID.length() == 0) {
return null;
}
String name = _genericLocationNamesMap.get(canonicalTzID);
if (name != null) {
if (name.length() == 0) {
// empty string to indicate the name is not available
return null;
}
return name;
}
Output<Boolean> isPrimary = new Output<Boolean>();
String countryCode = ZoneMeta.getCanonicalCountry(canonicalTzID, isPrimary);
if (countryCode != null) {
if (isPrimary.value) {
// If this is only the single zone in the country, use the country name
String country = getLocaleDisplayNames().regionDisplayName(countryCode);
name = formatPattern(Pattern.REGION_FORMAT, country);
} else {
// If there are multiple zones including this in the country,
// use the exemplar city name
// getExemplarLocationName should return non-empty String
// if the time zone is associated with a location
String city = _tznames.getExemplarLocationName(canonicalTzID);
name = formatPattern(Pattern.REGION_FORMAT, city);
}
}
if (name == null) {
_genericLocationNamesMap.putIfAbsent(canonicalTzID.intern(), "");
} else {
synchronized (this) { // we have to sync the name map and the trie
canonicalTzID = canonicalTzID.intern();
String tmp = _genericLocationNamesMap.putIfAbsent(canonicalTzID, name.intern());
if (tmp == null) {
// Also put the name info the to trie
NameInfo info = new NameInfo();
info.tzID = canonicalTzID;
info.type = GenericNameType.LOCATION;
_gnamesTrie.put(name, info);
} else {
name = tmp;
}
}
}
return name;
}
/**
* Sets the pattern string for the pattern type.
* Note: This method is designed for CLDR ST - not for common use.
* @param patType the pattern type
* @param patStr the pattern string
* @return this object.
*/
public TimeZoneGenericNames setFormatPattern(Pattern patType, String patStr) {
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify frozen object");
}
// Changing pattern will invalidates cached names
if (!_genericLocationNamesMap.isEmpty()) {
_genericLocationNamesMap = new ConcurrentHashMap<String, String>();
}
if (!_genericPartialLocationNamesMap.isEmpty()) {
_genericPartialLocationNamesMap = new ConcurrentHashMap<String, String>();
}
_gnamesTrie = null;
_gnamesTrieFullyLoaded = false;
if (_patternFormatters == null) {
_patternFormatters = new MessageFormat[Pattern.values().length];
}
_patternFormatters[patType.ordinal()] = new MessageFormat(patStr);
return this;
}
/**
* Private method to get a generic string, with fallback logics involved,
* that is,
*
* 1. If a generic non-location string is available for the zone, return it.
* 2. If a generic non-location string is associated with a meta zone and
* the zone never use daylight time around the given date, use the standard
* string (if available).
* 3. If a generic non-location string is associated with a meta zone and
* the offset at the given time is different from the preferred zone for the
* current locale, then return the generic partial location string (if available)
* 4. If a generic non-location string is not available, use generic location
* string.
*
* @param tz the requested time zone
* @param date the date
* @param type the generic name type, either LONG or SHORT
* @return the name used for a generic name type, which could be the
* generic name, or the standard name (if the zone does not observes DST
* around the date), or the partial location name.
*/
private String formatGenericNonLocationName(TimeZone tz, GenericNameType type, long date) {
assert(type == GenericNameType.LONG || type == GenericNameType.SHORT);
String tzID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzID == null) {
return null;
}
// Try to get a name from time zone first
NameType nameType = (type == GenericNameType.LONG) ? NameType.LONG_GENERIC : NameType.SHORT_GENERIC;
String name = _tznames.getTimeZoneDisplayName(tzID, nameType);
if (name != null) {
return name;
}
// Try meta zone
String mzID = _tznames.getMetaZoneID(tzID, date);
if (mzID != null) {
boolean useStandard = false;
int[] offsets = {0, 0};
tz.getOffset(date, false, offsets);
if (offsets[1] == 0) {
useStandard = true;
// Check if the zone actually uses daylight saving time around the time
if (tz instanceof BasicTimeZone) {
BasicTimeZone btz = (BasicTimeZone)tz;
TimeZoneTransition before = btz.getPreviousTransition(date, true);
if (before != null
&& (date - before.getTime() < DST_CHECK_RANGE)
&& before.getFrom().getDSTSavings() != 0) {
useStandard = false;
} else {
TimeZoneTransition after = btz.getNextTransition(date, false);
if (after != null
&& (after.getTime() - date < DST_CHECK_RANGE)
&& after.getTo().getDSTSavings() != 0) {
useStandard = false;
}
}
} else {
// If not BasicTimeZone... only if the instance is not an ICU's implementation.
// We may get a wrong answer in edge case, but it should practically work OK.
int[] tmpOffsets = new int[2];
tz.getOffset(date - DST_CHECK_RANGE, false, tmpOffsets);
if (tmpOffsets[1] != 0) {
useStandard = false;
} else {
tz.getOffset(date + DST_CHECK_RANGE, false, tmpOffsets);
if (tmpOffsets[1] != 0){
useStandard = false;
}
}
}
}
if (useStandard) {
NameType stdNameType = (nameType == NameType.LONG_GENERIC) ?
NameType.LONG_STANDARD : NameType.SHORT_STANDARD;
String stdName = _tznames.getDisplayName(tzID, stdNameType, date);
if (stdName != null) {
name = stdName;
// TODO: revisit this issue later
// In CLDR, a same display name is used for both generic and standard
// for some meta zones in some locales. This looks like a data bugs.
// For now, we check if the standard name is different from its generic
// name below.
String mzGenericName = _tznames.getMetaZoneDisplayName(mzID, nameType);
if (stdName.equalsIgnoreCase(mzGenericName)) {
name = null;
}
}
}
if (name == null) {
// Get a name from meta zone
String mzName = _tznames.getMetaZoneDisplayName(mzID, nameType);
if (mzName != null) {
// Check if we need to use a partial location format.
// This check is done by comparing offset with the meta zone's
// golden zone at the given date.
String goldenID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
if (goldenID != null && !goldenID.equals(tzID)) {
TimeZone goldenZone = TimeZone.getFrozenTimeZone(goldenID);
int[] offsets1 = {0, 0};
// Check offset in the golden zone with wall time.
// With getOffset(date, false, offsets1),
// you may get incorrect results because of time overlap at DST->STD
// transition.
goldenZone.getOffset(date + offsets[0] + offsets[1], true, offsets1);
if (offsets[0] != offsets1[0] || offsets[1] != offsets1[1]) {
// Now we need to use a partial location format.
name = getPartialLocationName(tzID, mzID, (nameType == NameType.LONG_GENERIC), mzName);
} else {
name = mzName;
}
} else {
name = mzName;
}
}
}
}
return name;
}
/**
* Private simple pattern formatter used for formatting generic location names
* and partial location names. We intentionally use JDK MessageFormat
* for performance reason.
*
* @param pat the message pattern enum
* @param args the format argument(s)
* @return the formatted string
*/
private synchronized String formatPattern(Pattern pat, String... args) {
if (_patternFormatters == null) {
_patternFormatters = new MessageFormat[Pattern.values().length];
}
int idx = pat.ordinal();
if (_patternFormatters[idx] == null) {
String patText;
try {
ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(
ICUResourceBundle.ICU_ZONE_BASE_NAME, _locale);
patText = bundle.getStringWithFallback("zoneStrings/" + pat.key());
} catch (MissingResourceException e) {
patText = pat.defaultValue();
}
_patternFormatters[idx] = new MessageFormat(patText);
}
return _patternFormatters[idx].format(args);
}
/**
* Private method returning LocaleDisplayNames instance for the locale of this
* instance. Because LocaleDisplayNames is only used for generic
* location formant and partial location format, the LocaleDisplayNames
* is instantiated lazily.
*
* @return the instance of LocaleDisplayNames for the locale of this object.
*/
private synchronized LocaleDisplayNames getLocaleDisplayNames() {
LocaleDisplayNames locNames = null;
if (_localeDisplayNamesRef != null) {
locNames = _localeDisplayNamesRef.get();
}
if (locNames == null) {
locNames = LocaleDisplayNames.getInstance(_locale);
_localeDisplayNamesRef = new WeakReference<LocaleDisplayNames>(locNames);
}
return locNames;
}
private synchronized void loadStrings(String tzCanonicalID) {
if (tzCanonicalID == null || tzCanonicalID.length() == 0) {
return;
}
// getGenericLocationName() formats a name and put it into the trie
getGenericLocationName(tzCanonicalID);
// Generic partial location format
Set<String> mzIDs = _tznames.getAvailableMetaZoneIDs(tzCanonicalID);
for (String mzID : mzIDs) {
// if this time zone is not the golden zone of the meta zone,
// partial location name (such as "PT (Los Angeles)") might be
// available.
String goldenID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
if (!tzCanonicalID.equals(goldenID)) {
for (NameType genNonLocType : GENERIC_NON_LOCATION_TYPES) {
String mzGenName = _tznames.getMetaZoneDisplayName(mzID, genNonLocType);
if (mzGenName != null) {
// getPartialLocationName() formats a name and put it into the trie
getPartialLocationName(tzCanonicalID, mzID, (genNonLocType == NameType.LONG_GENERIC), mzGenName);
}
}
}
}
}
/**
* Private method returning the target region. The target regions is determined by
* the locale of this instance. When a generic name is coming from
* a meta zone, this region is used for checking if the time zone
* is a reference zone of the meta zone.
*
* @return the target region
*/
private synchronized String getTargetRegion() {
if (_region == null) {
_region = _locale.getCountry();
if (_region.length() == 0) {
ULocale tmp = ULocale.addLikelySubtags(_locale);
_region = tmp.getCountry();
if (_region.length() == 0) {
_region = "001";
}
}
}
return _region;
}
/**
* Private method for formatting partial location names. This format
* is used when a generic name of a meta zone is available, but the given
* time zone is not a reference zone (golden zone) of the meta zone.
*
* @param tzID the canonical time zone ID
* @param mzID the meta zone ID
* @param isLong true when long generic name
* @param mzDisplayName the meta zone generic display name
* @return the partial location format string
*/
private String getPartialLocationName(String tzID, String mzID, boolean isLong, String mzDisplayName) {
String letter = isLong ? "L" : "S";
String key = tzID + "&" + mzID + "#" + letter;
String name = _genericPartialLocationNamesMap.get(key);
if (name != null) {
return name;
}
String location = null;
String countryCode = ZoneMeta.getCanonicalCountry(tzID);
if (countryCode != null) {
// Is this the golden zone for the region?
String regionalGolden = _tznames.getReferenceZoneID(mzID, countryCode);
if (tzID.equals(regionalGolden)) {
// Use country name
location = getLocaleDisplayNames().regionDisplayName(countryCode);
} else {
// Otherwise, use exemplar city name
location = _tznames.getExemplarLocationName(tzID);
}
} else {
location = _tznames.getExemplarLocationName(tzID);
if (location == null) {
// This could happen when the time zone is not associated with a country,
// and its ID is not hierarchical, for example, CST6CDT.
// We use the canonical ID itself as the location for this case.
location = tzID;
}
}
name = formatPattern(Pattern.FALLBACK_FORMAT, location, mzDisplayName);
synchronized (this) { // we have to sync the name map and the trie
String tmp = _genericPartialLocationNamesMap.putIfAbsent(key.intern(), name.intern());
if (tmp == null) {
NameInfo info = new NameInfo();
info.tzID = tzID.intern();
info.type = isLong ? GenericNameType.LONG : GenericNameType.SHORT;
_gnamesTrie.put(name, info);
} else {
name = tmp;
}
}
return name;
}
/**
* A private class used for storing the name information in the local trie.
*/
private static class NameInfo {
String tzID;
GenericNameType type;
}
/**
* A class used for returning the name search result used by
* {@link TimeZoneGenericNames#find(String, int, EnumSet)}.
*/
public static class GenericMatchInfo {
GenericNameType nameType;
String tzID;
int matchLength;
TimeType timeType = TimeType.UNKNOWN;
public GenericNameType nameType() {
return nameType;
}
public String tzID() {
return tzID;
}
public TimeType timeType() {
return timeType;
}
public int matchLength() {
return matchLength;
}
}
/**
* A private class implementing the search callback interface in
* <code>TextTrieMap</code> for collecting match results.
*/
private static class GenericNameSearchHandler implements ResultHandler<NameInfo> {
private EnumSet<GenericNameType> _types;
private Collection<GenericMatchInfo> _matches;
private int _maxMatchLen;
GenericNameSearchHandler(EnumSet<GenericNameType> types) {
_types = types;
}
/* (non-Javadoc)
* @see com.ibm.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, java.util.Iterator)
*/
public boolean handlePrefixMatch(int matchLength, Iterator<NameInfo> values) {
while (values.hasNext()) {
NameInfo info = values.next();
if (_types != null && !_types.contains(info.type)) {
continue;
}
GenericMatchInfo matchInfo = new GenericMatchInfo();
matchInfo.tzID = info.tzID;
matchInfo.nameType = info.type;
matchInfo.matchLength = matchLength;
//matchInfo.timeType = TimeType.UNKNOWN;
if (_matches == null) {
_matches = new LinkedList<GenericMatchInfo>();
}
_matches.add(matchInfo);
if (matchLength > _maxMatchLen) {
_maxMatchLen = matchLength;
}
}
return true;
}
/**
* Returns the match results
* @return the match results
*/
public Collection<GenericMatchInfo> getMatches() {
return _matches;
}
/**
* Returns the maximum match length, or 0 if no match was found
* @return the maximum match length
*/
public int getMaxMatchLen() {
return _maxMatchLen;
}
/**
* Resets the match results
*/
public void resetResults() {
_matches = null;
_maxMatchLen = 0;
}
}
/**
* Returns the best match of time zone display name for the specified types in the
* given text at the given offset.
* @param text the text
* @param start the start offset in the text
* @param genericTypes the set of name types.
* @return the best matching name info.
*/
public GenericMatchInfo findBestMatch(String text, int start, EnumSet<GenericNameType> genericTypes) {
if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
throw new IllegalArgumentException("bad input text or range");
}
GenericMatchInfo bestMatch = null;
// Find matches in the TimeZoneNames first
Collection<MatchInfo> tznamesMatches = findTimeZoneNames(text, start, genericTypes);
if (tznamesMatches != null) {
MatchInfo longestMatch = null;
for (MatchInfo match : tznamesMatches) {
if (longestMatch == null || match.matchLength() > longestMatch.matchLength()) {
longestMatch = match;
}
}
if (longestMatch != null) {
bestMatch = createGenericMatchInfo(longestMatch);
if (bestMatch.matchLength() == (text.length() - start)) {
// Full match
//return bestMatch;
// TODO Some time zone uses a same name for the long standard name
// and the location name. When the match is a long standard name,
// then we need to check if the name is same with the location name.
// This is probably a data error or a design bug.
// if (bestMatch.nameType != GenericNameType.LONG || bestMatch.timeType != TimeType.STANDARD) {
// return bestMatch;
// }
// TODO The deprecation of commonlyUsed flag introduced the name
// conflict not only for long standard names, but short standard names too.
// These short names (found in zh_Hant) should be gone once we clean
// up CLDR time zone display name data. Once the short name conflict
// problem (with location name) is resolved, we should change the condition
// below back to the original one above. -Yoshito (2011-09-14)
if (bestMatch.timeType != TimeType.STANDARD) {
return bestMatch;
}
}
}
}
// Find matches in the local trie
Collection<GenericMatchInfo> localMatches = findLocal(text, start, genericTypes);
if (localMatches != null) {
for (GenericMatchInfo match : localMatches) {
// TODO See the above TODO. We use match.matchLength() >= bestMatch.matcheLength()
// for the reason described above.
//if (bestMatch == null || match.matchLength() > bestMatch.matchLength()) {
if (bestMatch == null || match.matchLength() >= bestMatch.matchLength()) {
bestMatch = match;
}
}
}
return bestMatch;
}
/**
* Returns a collection of time zone display name matches for the specified types in the
* given text at the given offset.
* @param text the text
* @param start the start offset in the text
* @param genericTypes the set of name types.
* @return A collection of match info.
*/
public Collection<GenericMatchInfo> find(String text, int start, EnumSet<GenericNameType> genericTypes) {
if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
throw new IllegalArgumentException("bad input text or range");
}
// Find matches in the local trie
Collection<GenericMatchInfo> results = findLocal(text, start, genericTypes);
// Also find matches in the TimeZoneNames
Collection<MatchInfo> tznamesMatches = findTimeZoneNames(text, start, genericTypes);
if (tznamesMatches != null) {
// transform matches and append them to local matches
for (MatchInfo match : tznamesMatches) {
if (results == null) {
results = new LinkedList<GenericMatchInfo>();
}
results.add(createGenericMatchInfo(match));
}
}
return results;
}
/**
* Returns a <code>GenericMatchInfo</code> for the given <code>MatchInfo</code>.
* @param matchInfo the MatchInfo
* @return A GenericMatchInfo
*/
private GenericMatchInfo createGenericMatchInfo(MatchInfo matchInfo) {
GenericNameType nameType = null;
TimeType timeType = TimeType.UNKNOWN;
switch (matchInfo.nameType()) {
case LONG_STANDARD:
nameType = GenericNameType.LONG;
timeType = TimeType.STANDARD;
break;
case LONG_GENERIC:
nameType = GenericNameType.LONG;
break;
case SHORT_STANDARD:
nameType = GenericNameType.SHORT;
timeType = TimeType.STANDARD;
break;
case SHORT_GENERIC:
nameType = GenericNameType.SHORT;
break;
default:
throw new IllegalArgumentException("Unexpected MatchInfo name type - " + matchInfo.nameType());
}
String tzID = matchInfo.tzID();
if (tzID == null) {
String mzID = matchInfo.mzID();
assert(mzID != null);
tzID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
}
assert(tzID != null);
GenericMatchInfo gmatch = new GenericMatchInfo();
gmatch.nameType = nameType;
gmatch.tzID = tzID;
gmatch.matchLength = matchInfo.matchLength();
gmatch.timeType = timeType;
return gmatch;
}
/**
* Returns a collection of time zone display name matches for the specified types in the
* given text at the given offset. This method only finds matches from the TimeZoneNames
* used by this object.
* @param text the text
* @param start the start offset in the text
* @param types the set of name types.
* @return A collection of match info.
*/
private Collection<MatchInfo> findTimeZoneNames(String text, int start, EnumSet<GenericNameType> types) {
Collection<MatchInfo> tznamesMatches = null;
// Check if the target name type is really in the TimeZoneNames
EnumSet<NameType> nameTypes = EnumSet.noneOf(NameType.class);
if (types.contains(GenericNameType.LONG)) {
nameTypes.add(NameType.LONG_GENERIC);
nameTypes.add(NameType.LONG_STANDARD);
}
if (types.contains(GenericNameType.SHORT)) {
nameTypes.add(NameType.SHORT_GENERIC);
nameTypes.add(NameType.SHORT_STANDARD);
}
if (!nameTypes.isEmpty()) {
// Find matches in the TimeZoneNames
tznamesMatches = _tznames.find(text, start, nameTypes);
}
return tznamesMatches;
}
/**
* Returns a collection of time zone display name matches for the specified types in the
* given text at the given offset. This method only finds matches from the local trie,
* that contains 1) generic location names and 2) long/short generic partial location names,
* used by this object.
* @param text the text
* @param start the start offset in the text
* @param types the set of name types.
* @return A collection of match info.
*/
private synchronized Collection<GenericMatchInfo> findLocal(String text, int start, EnumSet<GenericNameType> types) {
GenericNameSearchHandler handler = new GenericNameSearchHandler(types);
_gnamesTrie.find(text, start, handler);
if (handler.getMaxMatchLen() == (text.length() - start) || _gnamesTrieFullyLoaded) {
// perfect match
return handler.getMatches();
}
// All names are not yet loaded into the local trie.
// Load all available names into the trie. This could be very heavy.
Set<String> tzIDs = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
for (String tzID : tzIDs) {
loadStrings(tzID);
}
_gnamesTrieFullyLoaded = true;
// now, try it again
handler.resetResults();
_gnamesTrie.find(text, start, handler);
return handler.getMatches();
}
/**
* <code>TimeZoneGenericNames</code> cache implementation.
*/
private static class Cache extends SoftCache<String, TimeZoneGenericNames, ULocale> {
/* (non-Javadoc)
* @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
*/
@Override
protected TimeZoneGenericNames createInstance(String key, ULocale data) {
return new TimeZoneGenericNames(data).freeze();
}
}
/*
* The custom deserialization method.
* This implementation only read locale used by the object.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
init();
}
/**
* {@inheritDoc}
*/
public boolean isFrozen() {
return _frozen;
}
/**
* {@inheritDoc}
*/
public TimeZoneGenericNames freeze() {
_frozen = true;
return this;
}
/**
* {@inheritDoc}
*/
public TimeZoneGenericNames cloneAsThawed() {
TimeZoneGenericNames copy = null;
try {
copy = (TimeZoneGenericNames)super.clone();
copy._frozen = false;
} catch (Throwable t) {
// This should never happen
}
return copy;
}
}