com.ibm.icu.impl.TimeZoneNamesImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
/*
*******************************************************************************
* Copyright (C) 2011-2013, 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.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import com.ibm.icu.impl.TextTrieMap.ResultHandler;
import com.ibm.icu.text.TimeZoneNames;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.TimeZone.SystemTimeZoneType;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
/**
* The standard ICU implementation of TimeZoneNames
*/
public class TimeZoneNamesImpl extends TimeZoneNames {
private static final long serialVersionUID = -2179814848495897472L;
private static final String ZONE_STRINGS_BUNDLE = "zoneStrings";
private static final String MZ_PREFIX = "meta:";
private static Set METAZONE_IDS;
private static final TZ2MZsCache TZ_TO_MZS_CACHE = new TZ2MZsCache();
private static final MZ2TZsCache MZ_TO_TZS_CACHE = new MZ2TZsCache();
private transient ICUResourceBundle _zoneStrings;
// These are hard cache. We create only one TimeZoneNamesImpl per locale
// and it's stored in SoftCache, so we do not need to worry about the
// footprint much.
private transient ConcurrentHashMap _mzNamesMap;
private transient ConcurrentHashMap _tzNamesMap;
private transient TextTrieMap _namesTrie;
private transient boolean _namesTrieFullyLoaded;
public TimeZoneNamesImpl(ULocale locale) {
initialize(locale);
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs()
*/
@Override
public synchronized Set getAvailableMetaZoneIDs() {
if (METAZONE_IDS == null) {
UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
UResourceBundle mapTimezones = bundle.get("mapTimezones");
Set keys = mapTimezones.keySet();
METAZONE_IDS = Collections.unmodifiableSet(keys);
}
return METAZONE_IDS;
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String)
*/
@Override
public Set getAvailableMetaZoneIDs(String tzID) {
if (tzID == null || tzID.length() == 0) {
return Collections.emptySet();
}
List maps = TZ_TO_MZS_CACHE.getInstance(tzID, tzID);
if (maps.isEmpty()) {
return Collections.emptySet();
}
Set mzIDs = new HashSet(maps.size());
for (MZMapEntry map : maps) {
mzIDs.add(map.mzID());
}
// make it unmodifiable because of the API contract. We may cache the results in futre.
return Collections.unmodifiableSet(mzIDs);
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getMetaZoneID(java.lang.String, long)
*/
@Override
public String getMetaZoneID(String tzID, long date) {
if (tzID == null || tzID.length() == 0) {
return null;
}
String mzID = null;
List maps = TZ_TO_MZS_CACHE.getInstance(tzID, tzID);
for (MZMapEntry map : maps) {
if (date >= map.from() && date < map.to()) {
mzID = map.mzID();
break;
}
}
return mzID;
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getReferenceZoneID(java.lang.String, java.lang.String)
*/
@Override
public String getReferenceZoneID(String mzID, String region) {
if (mzID == null || mzID.length() == 0) {
return null;
}
String refID = null;
Map regionTzMap = MZ_TO_TZS_CACHE.getInstance(mzID, mzID);
if (!regionTzMap.isEmpty()) {
refID = regionTzMap.get(region);
if (refID == null) {
refID = regionTzMap.get("001");
}
}
return refID;
}
/*
* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getMetaZoneDisplayName(java.lang.String, com.ibm.icu.text.TimeZoneNames.NameType)
*/
@Override
public String getMetaZoneDisplayName(String mzID, NameType type) {
if (mzID == null || mzID.length() == 0) {
return null;
}
return loadMetaZoneNames(mzID).getName(type);
}
/*
* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getTimeZoneDisplayName(java.lang.String, com.ibm.icu.text.TimeZoneNames.NameType)
*/
@Override
public String getTimeZoneDisplayName(String tzID, NameType type) {
if (tzID == null || tzID.length() == 0) {
return null;
}
return loadTimeZoneNames(tzID).getName(type);
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getExemplarLocationName(java.lang.String)
*/
@Override
public String getExemplarLocationName(String tzID) {
if (tzID == null || tzID.length() == 0) {
return null;
}
String locName = loadTimeZoneNames(tzID).getName(NameType.EXEMPLAR_LOCATION);
return locName;
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#find(java.lang.CharSequence, int, java.util.Set)
*/
@Override
public synchronized Collection find(CharSequence text, int start, EnumSet nameTypes) {
if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
throw new IllegalArgumentException("bad input text or range");
}
NameSearchHandler handler = new NameSearchHandler(nameTypes);
_namesTrie.find(text, start, handler);
if (handler.getMaxMatchLen() == (text.length() - start) || _namesTrieFullyLoaded) {
// perfect match
return handler.getMatches();
}
// All names are not yet loaded into the trie
// time zone names
Set tzIDs = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
for (String tzID : tzIDs) {
loadTimeZoneNames(tzID);
}
// meta zone names
Set mzIDs = getAvailableMetaZoneIDs();
for (String mzID : mzIDs) {
loadMetaZoneNames(mzID);
}
_namesTrieFullyLoaded = true;
// now, try it again
handler.resetResults();
_namesTrie.find(text, start, handler);
return handler.getMatches();
}
/**
* Initialize the transient fields, called from the constructor and
* readObject.
*
* @param locale The locale
*/
private void initialize(ULocale locale) {
ICUResourceBundle bundle = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(
ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);
_zoneStrings = (ICUResourceBundle)bundle.get(ZONE_STRINGS_BUNDLE);
_tzNamesMap = new ConcurrentHashMap();
_mzNamesMap = new ConcurrentHashMap();
_namesTrie = new TextTrieMap(true);
_namesTrieFullyLoaded = false;
// Preload zone strings for the default time zone
TimeZone tz = TimeZone.getDefault();
String tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzCanonicalID != null) {
loadStrings(tzCanonicalID);
}
}
/**
* Load all strings used by the specified time zone.
* This is called from the initializer to load default zone's
* strings.
* @param tzCanonicalID the canonical time zone ID
*/
private synchronized void loadStrings(String tzCanonicalID) {
if (tzCanonicalID == null || tzCanonicalID.length() == 0) {
return;
}
loadTimeZoneNames(tzCanonicalID);
Set mzIDs = getAvailableMetaZoneIDs(tzCanonicalID);
for (String mzID : mzIDs) {
loadMetaZoneNames(mzID);
}
}
/*
* The custom serialization method.
* This implementation only preserve locale object used for the names.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
ULocale locale = _zoneStrings.getULocale();
out.writeObject(locale);
}
/*
* The custom deserialization method.
* This implementation only read locale object used by the object.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
ULocale locale = (ULocale)in.readObject();
initialize(locale);
}
/**
* Returns a set of names for the given meta zone ID. This method loads
* the set of names into the internal map and trie for future references.
* @param mzID the meta zone ID
* @return An instance of ZNames that includes a set of meta zone display names.
*/
private synchronized ZNames loadMetaZoneNames(String mzID) {
ZNames znames = _mzNamesMap.get(mzID);
if (znames == null) {
znames = ZNames.getInstance(_zoneStrings, MZ_PREFIX + mzID);
// put names into the trie
mzID = mzID.intern();
for (NameType t : NameType.values()) {
String name = znames.getName(t);
if (name != null) {
NameInfo info = new NameInfo();
info.mzID = mzID;
info.type = t;
_namesTrie.put(name, info);
}
}
ZNames tmpZnames = _mzNamesMap.putIfAbsent(mzID, znames);
znames = (tmpZnames == null) ? znames : tmpZnames;
}
return znames;
}
/**
* Returns a set of names for the given time zone ID. This method loads
* the set of names into the internal map and trie for future references.
* @param tzID the canonical time zone ID
* @return An instance of TZNames that includes a set of time zone display names.
*/
private synchronized TZNames loadTimeZoneNames(String tzID) {
TZNames tznames = _tzNamesMap.get(tzID);
if (tznames == null) {
tznames = TZNames.getInstance(_zoneStrings, tzID.replace('/', ':'), tzID);
// put names into the trie
tzID = tzID.intern();
for (NameType t : NameType.values()) {
String name = tznames.getName(t);
if (name != null) {
NameInfo info = new NameInfo();
info.tzID = tzID;
info.type = t;
_namesTrie.put(name, info);
}
}
TZNames tmpTznames = _tzNamesMap.putIfAbsent(tzID, tznames);
tznames = (tmpTznames == null) ? tznames : tmpTznames;
}
return tznames;
}
/**
* An instance of NameInfo is stored in the zone names trie.
*/
private static class NameInfo {
String tzID;
String mzID;
NameType type;
}
/**
* NameSearchHandler is used for collecting name matches.
*/
private static class NameSearchHandler implements ResultHandler {
private EnumSet _nameTypes;
private Collection _matches;
private int _maxMatchLen;
NameSearchHandler(EnumSet nameTypes) {
_nameTypes = nameTypes;
}
/* (non-Javadoc)
* @see com.ibm.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, java.util.Iterator)
*/
public boolean handlePrefixMatch(int matchLength, Iterator values) {
while (values.hasNext()) {
NameInfo ninfo = values.next();
if (_nameTypes != null && !_nameTypes.contains(ninfo.type)) {
continue;
}
MatchInfo minfo;
if (ninfo.tzID != null) {
minfo = new MatchInfo(ninfo.type, ninfo.tzID, null, matchLength);
} else {
assert(ninfo.mzID != null);
minfo = new MatchInfo(ninfo.type, null, ninfo.mzID, matchLength);
}
if (_matches == null) {
_matches = new LinkedList();
}
_matches.add(minfo);
if (matchLength > _maxMatchLen) {
_maxMatchLen = matchLength;
}
}
return true;
}
/**
* Returns the match results
* @return the match results
*/
public Collection getMatches() {
if (_matches == null) {
return Collections.emptyList();
}
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;
}
}
/**
* This class stores name data for a meta zone
*/
private static class ZNames {
private static final ZNames EMPTY_ZNAMES = new ZNames(null);
private String[] _names;
private static final String[] KEYS = {"lg", "ls", "ld", "sg", "ss", "sd"};
protected ZNames(String[] names) {
_names = names;
}
public static ZNames getInstance(ICUResourceBundle zoneStrings, String key) {
String[] names = loadData(zoneStrings, key);
if (names == null) {
return EMPTY_ZNAMES;
}
return new ZNames(names);
}
public String getName(NameType type) {
if (_names == null) {
return null;
}
String name = null;
switch (type) {
case LONG_GENERIC:
name = _names[0];
break;
case LONG_STANDARD:
name = _names[1];
break;
case LONG_DAYLIGHT:
name = _names[2];
break;
case SHORT_GENERIC:
name = _names[3];
break;
case SHORT_STANDARD:
name = _names[4];
break;
case SHORT_DAYLIGHT:
name = _names[5];
break;
case EXEMPLAR_LOCATION:
name = null; // implemented by subclass
break;
}
return name;
}
protected static String[] loadData(ICUResourceBundle zoneStrings, String key) {
if (zoneStrings == null || key == null || key.length() == 0) {
return null;
}
ICUResourceBundle table = null;
try {
table = zoneStrings.getWithFallback(key);
} catch (MissingResourceException e) {
return null;
}
boolean isEmpty = true;
String[] names = new String[KEYS.length];
for (int i = 0; i < names.length; i++) {
try {
names[i] = table.getStringWithFallback(KEYS[i]);
isEmpty = false;
} catch (MissingResourceException e) {
names[i] = null;
}
}
if (isEmpty) {
return null;
}
return names;
}
}
/**
* This class stores name data for a single time zone
*/
private static class TZNames extends ZNames {
private String _locationName;
private static final TZNames EMPTY_TZNAMES = new TZNames(null, null);
public static TZNames getInstance(ICUResourceBundle zoneStrings, String key, String tzID) {
if (zoneStrings == null || key == null || key.length() == 0) {
return EMPTY_TZNAMES;
}
String[] names = loadData(zoneStrings, key);
String locationName = null;
ICUResourceBundle table = null;
try {
table = zoneStrings.getWithFallback(key);
locationName = table.getStringWithFallback("ec");
} catch (MissingResourceException e) {
// fall through
}
if (locationName == null) {
locationName = getDefaultExemplarLocationName(tzID);
}
if (locationName == null && names == null) {
return EMPTY_TZNAMES;
}
return new TZNames(names, locationName);
}
public String getName(NameType type) {
if (type == NameType.EXEMPLAR_LOCATION) {
return _locationName;
}
return super.getName(type);
}
private TZNames(String[] names, String locationName) {
super(names);
_locationName = locationName;
}
}
//
// Canonical time zone ID -> meta zone ID
//
private static class MZMapEntry {
private String _mzID;
private long _from;
private long _to;
MZMapEntry(String mzID, long from, long to) {
_mzID = mzID;
_from = from;
_to = to;
}
String mzID() {
return _mzID;
}
long from() {
return _from;
}
long to() {
return _to;
}
}
private static class TZ2MZsCache extends SoftCache, String> {
/* (non-Javadoc)
* @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
*/
@Override
protected List createInstance(String key, String data) {
List mzMaps = null;
UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
UResourceBundle metazoneInfoBundle = bundle.get("metazoneInfo");
String tzkey = data.replace('/', ':');
try {
UResourceBundle zoneBundle = metazoneInfoBundle.get(tzkey);
mzMaps = new ArrayList(zoneBundle.getSize());
for (int idx = 0; idx < zoneBundle.getSize(); idx++) {
UResourceBundle mz = zoneBundle.get(idx);
String mzid = mz.getString(0);
String fromStr = "1970-01-01 00:00";
String toStr = "9999-12-31 23:59";
if (mz.getSize() == 3) {
fromStr = mz.getString(1);
toStr = mz.getString(2);
}
long from, to;
from = parseDate(fromStr);
to = parseDate(toStr);
mzMaps.add(new MZMapEntry(mzid, from, to));
}
} catch (MissingResourceException mre) {
mzMaps = Collections.emptyList();
}
return mzMaps;
}
/**
* Private static method parsing the date text used by meta zone to
* time zone mapping data in locale resource.
*
* @param text the UTC date text in the format of "yyyy-MM-dd HH:mm",
* for example - "1970-01-01 00:00"
* @return the date
*/
private static long parseDate (String text) {
int year = 0, month = 0, day = 0, hour = 0, min = 0;
int idx;
int n;
// "yyyy" (0 - 3)
for (idx = 0; idx <= 3; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
year = 10*year + n;
} else {
throw new IllegalArgumentException("Bad year");
}
}
// "MM" (5 - 6)
for (idx = 5; idx <= 6; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
month = 10*month + n;
} else {
throw new IllegalArgumentException("Bad month");
}
}
// "dd" (8 - 9)
for (idx = 8; idx <= 9; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
day = 10*day + n;
} else {
throw new IllegalArgumentException("Bad day");
}
}
// "HH" (11 - 12)
for (idx = 11; idx <= 12; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
hour = 10*hour + n;
} else {
throw new IllegalArgumentException("Bad hour");
}
}
// "mm" (14 - 15)
for (idx = 14; idx <= 15; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
min = 10*min + n;
} else {
throw new IllegalArgumentException("Bad minute");
}
}
long date = Grego.fieldsToDay(year, month - 1, day) * Grego.MILLIS_PER_DAY
+ (long)hour * Grego.MILLIS_PER_HOUR + (long)min * Grego.MILLIS_PER_MINUTE;
return date;
}
}
//
// Meta zone ID -> time zone ID
//
private static class MZ2TZsCache extends SoftCache, String> {
/* (non-Javadoc)
* @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
*/
@Override
protected Map createInstance(String key, String data) {
Map map = null;
UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
UResourceBundle mapTimezones = bundle.get("mapTimezones");
try {
UResourceBundle regionMap = mapTimezones.get(key);
Set regions = regionMap.keySet();
map = new HashMap(regions.size());
for (String region : regions) {
String tzID = regionMap.getString(region).intern();
map.put(region.intern(), tzID);
}
} catch (MissingResourceException e) {
map = Collections.emptyMap();
}
return map;
}
}
private static final Pattern LOC_EXCLUSION_PATTERN = Pattern.compile("Etc/.*|SystemV/.*|.*/Riyadh8[7-9]");
/**
* Default exemplar location name based on time zone ID
* @param tzID the time zone ID
* @return the exemplar location name or null if location is not available.
*/
public static String getDefaultExemplarLocationName(String tzID) {
if (tzID == null || tzID.length() == 0 || LOC_EXCLUSION_PATTERN.matcher(tzID).matches()) {
return null;
}
String location = null;
int sep = tzID.lastIndexOf('/');
if (sep > 0 && sep + 1 < tzID.length()) {
location = tzID.substring(sep + 1).replace('_', ' ');
}
return location;
}
}