2 *******************************************************************************
3 * Copyright (C) 2011-2013, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.impl;
9 import java.io.IOException;
10 import java.io.ObjectInputStream;
11 import java.io.Serializable;
12 import java.lang.ref.WeakReference;
13 import java.text.MessageFormat;
14 import java.util.Collection;
15 import java.util.EnumSet;
16 import java.util.Iterator;
17 import java.util.LinkedList;
18 import java.util.MissingResourceException;
20 import java.util.concurrent.ConcurrentHashMap;
22 import com.ibm.icu.impl.TextTrieMap.ResultHandler;
23 import com.ibm.icu.text.LocaleDisplayNames;
24 import com.ibm.icu.text.TimeZoneFormat.TimeType;
25 import com.ibm.icu.text.TimeZoneNames;
26 import com.ibm.icu.text.TimeZoneNames.MatchInfo;
27 import com.ibm.icu.text.TimeZoneNames.NameType;
28 import com.ibm.icu.util.BasicTimeZone;
29 import com.ibm.icu.util.Freezable;
30 import com.ibm.icu.util.Output;
31 import com.ibm.icu.util.TimeZone;
32 import com.ibm.icu.util.TimeZone.SystemTimeZoneType;
33 import com.ibm.icu.util.TimeZoneTransition;
34 import com.ibm.icu.util.ULocale;
37 * This class interact with TimeZoneNames and LocaleDisplayNames
38 * to format and parse time zone's generic display names.
39 * It is not recommended to use this class directly, instead
40 * use com.ibm.icu.text.TimeZoneFormat.
42 public class TimeZoneGenericNames implements Serializable, Freezable<TimeZoneGenericNames> {
44 // Note: This class implements Serializable, but we no longer serialize instance of
45 // TimeZoneGenericNames in ICU 49. ICU 4.8 com.ibm.icu.text.TimeZoneFormat used to
46 // serialize TimeZoneGenericNames field. TimeZoneFormat no longer read TimeZoneGenericNames
47 // field, we have to keep TimeZoneGenericNames Serializable. Otherwise it fails to read
48 // (unused) TimeZoneGenericNames serialized data.
50 private static final long serialVersionUID = 2729910342063468417L;
53 * Generic name type enum
55 public enum GenericNameType {
56 LOCATION ("LONG", "SHORT"),
60 String[] _fallbackTypeOf;
61 GenericNameType(String... fallbackTypeOf) {
62 _fallbackTypeOf = fallbackTypeOf;
65 public boolean isFallbackTypeOf(GenericNameType type) {
66 String typeStr = type.toString();
67 for (String t : _fallbackTypeOf) {
68 if (t.equals(typeStr)) {
77 * Format pattern enum used for composing location and partial location names
80 // The format pattern such as "{0} Time", where {0} is the country or city.
81 REGION_FORMAT("regionFormat", "({0})"),
83 // Note: FALLBACK_REGION_FORMAT is no longer used since ICU 50/CLDR 22.1
84 // The format pattern such as "{1} Time ({0})", where {1} is the country and {0} is a city.
85 //FALLBACK_REGION_FORMAT("fallbackRegionFormat", "{1} ({0})"),
87 // The format pattern such as "{1} ({0})", where {1} is the metazone, and {0} is the country or city.
88 FALLBACK_FORMAT("fallbackFormat", "{1} ({0})");
93 Pattern(String key, String defaultVal) {
95 _defaultVal = defaultVal;
102 String defaultValue() {
107 private ULocale _locale;
108 private TimeZoneNames _tznames;
110 private transient boolean _frozen;
111 private transient String _region;
112 private transient WeakReference<LocaleDisplayNames> _localeDisplayNamesRef;
113 private transient MessageFormat[] _patternFormatters;
115 private transient ConcurrentHashMap<String, String> _genericLocationNamesMap;
116 private transient ConcurrentHashMap<String, String> _genericPartialLocationNamesMap;
117 private transient TextTrieMap<NameInfo> _gnamesTrie;
118 private transient boolean _gnamesTrieFullyLoaded;
120 private static Cache GENERIC_NAMES_CACHE = new Cache();
122 // Window size used for DST check for a zone in a metazone (about a half year)
123 private static final long DST_CHECK_RANGE = 184L*(24*60*60*1000);
125 private static final NameType[] GENERIC_NON_LOCATION_TYPES =
126 {NameType.LONG_GENERIC, NameType.SHORT_GENERIC};
130 * Constructs a <code>TimeZoneGenericNames</code> with the given locale
131 * and the <code>TimeZoneNames</code>.
132 * @param locale the locale
133 * @param tznames the TimeZoneNames
135 public TimeZoneGenericNames(ULocale locale, TimeZoneNames tznames) {
142 * Private method initializing the instance of <code>TimeZoneGenericName</code>.
143 * This method should be called from a constructor and readObject.
145 private void init() {
146 if (_tznames == null) {
147 _tznames = TimeZoneNames.getInstance(_locale);
149 _genericLocationNamesMap = new ConcurrentHashMap<String, String>();
150 _genericPartialLocationNamesMap = new ConcurrentHashMap<String, String>();
152 _gnamesTrie = new TextTrieMap<NameInfo>(true);
153 _gnamesTrieFullyLoaded = false;
155 // Preload zone strings for the default time zone
156 TimeZone tz = TimeZone.getDefault();
157 String tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
158 if (tzCanonicalID != null) {
159 loadStrings(tzCanonicalID);
164 * Constructs a <code>TimeZoneGenericNames</code> with the given locale.
165 * This constructor is private and called from {@link #getInstance(ULocale)}.
166 * @param locale the locale
168 private TimeZoneGenericNames(ULocale locale) {
173 * The factory method of <code>TimeZoneGenericNames</code>. This static method
174 * returns a frozen instance of cached <code>TimeZoneGenericNames</code>.
175 * @param locale the locale
176 * @return A frozen <code>TimeZoneGenericNames</code>.
178 public static TimeZoneGenericNames getInstance(ULocale locale) {
179 String key = locale.getBaseName();
180 return GENERIC_NAMES_CACHE.getInstance(key, locale);
184 * Returns the display name of the time zone for the given name type
185 * at the given date, or null if the display name is not available.
187 * @param tz the time zone
188 * @param type the generic name type - see {@link GenericNameType}
189 * @param date the date
190 * @return the display name of the time zone for the given name type
191 * at the given date, or null.
193 public String getDisplayName(TimeZone tz, GenericNameType type, long date) {
195 String tzCanonicalID = null;
198 tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
199 if (tzCanonicalID != null) {
200 name = getGenericLocationName(tzCanonicalID);
205 name = formatGenericNonLocationName(tz, type, date);
207 tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
208 if (tzCanonicalID != null) {
209 name = getGenericLocationName(tzCanonicalID);
218 * Returns the generic location name for the given canonical time zone ID.
220 * @param canonicalTzID the canonical time zone ID
221 * @return the generic location name for the given canonical time zone ID.
223 public String getGenericLocationName(String canonicalTzID) {
224 if (canonicalTzID == null || canonicalTzID.length() == 0) {
227 String name = _genericLocationNamesMap.get(canonicalTzID);
229 if (name.length() == 0) {
230 // empty string to indicate the name is not available
236 Output<Boolean> isPrimary = new Output<Boolean>();
237 String countryCode = ZoneMeta.getCanonicalCountry(canonicalTzID, isPrimary);
238 if (countryCode != null) {
239 if (isPrimary.value) {
240 // If this is only the single zone in the country, use the country name
241 String country = getLocaleDisplayNames().regionDisplayName(countryCode);
242 name = formatPattern(Pattern.REGION_FORMAT, country);
244 // If there are multiple zones including this in the country,
245 // use the exemplar city name
247 // getExemplarLocationName should return non-empty String
248 // if the time zone is associated with a location
249 String city = _tznames.getExemplarLocationName(canonicalTzID);
250 name = formatPattern(Pattern.REGION_FORMAT, city);
255 _genericLocationNamesMap.putIfAbsent(canonicalTzID.intern(), "");
257 synchronized (this) { // we have to sync the name map and the trie
258 canonicalTzID = canonicalTzID.intern();
259 String tmp = _genericLocationNamesMap.putIfAbsent(canonicalTzID, name.intern());
261 // Also put the name info the to trie
262 NameInfo info = new NameInfo();
263 info.tzID = canonicalTzID;
264 info.type = GenericNameType.LOCATION;
265 _gnamesTrie.put(name, info);
275 * Sets the pattern string for the pattern type.
276 * Note: This method is designed for CLDR ST - not for common use.
277 * @param patType the pattern type
278 * @param patStr the pattern string
279 * @return this object.
281 public TimeZoneGenericNames setFormatPattern(Pattern patType, String patStr) {
283 throw new UnsupportedOperationException("Attempt to modify frozen object");
286 // Changing pattern will invalidates cached names
287 if (!_genericLocationNamesMap.isEmpty()) {
288 _genericLocationNamesMap = new ConcurrentHashMap<String, String>();
290 if (!_genericPartialLocationNamesMap.isEmpty()) {
291 _genericPartialLocationNamesMap = new ConcurrentHashMap<String, String>();
294 _gnamesTrieFullyLoaded = false;
296 if (_patternFormatters == null) {
297 _patternFormatters = new MessageFormat[Pattern.values().length];
299 _patternFormatters[patType.ordinal()] = new MessageFormat(patStr);
304 * Private method to get a generic string, with fallback logics involved,
307 * 1. If a generic non-location string is available for the zone, return it.
308 * 2. If a generic non-location string is associated with a meta zone and
309 * the zone never use daylight time around the given date, use the standard
310 * string (if available).
311 * 3. If a generic non-location string is associated with a meta zone and
312 * the offset at the given time is different from the preferred zone for the
313 * current locale, then return the generic partial location string (if available)
314 * 4. If a generic non-location string is not available, use generic location
317 * @param tz the requested time zone
318 * @param date the date
319 * @param type the generic name type, either LONG or SHORT
320 * @return the name used for a generic name type, which could be the
321 * generic name, or the standard name (if the zone does not observes DST
322 * around the date), or the partial location name.
324 private String formatGenericNonLocationName(TimeZone tz, GenericNameType type, long date) {
325 assert(type == GenericNameType.LONG || type == GenericNameType.SHORT);
326 String tzID = ZoneMeta.getCanonicalCLDRID(tz);
332 // Try to get a name from time zone first
333 NameType nameType = (type == GenericNameType.LONG) ? NameType.LONG_GENERIC : NameType.SHORT_GENERIC;
334 String name = _tznames.getTimeZoneDisplayName(tzID, nameType);
341 String mzID = _tznames.getMetaZoneID(tzID, date);
343 boolean useStandard = false;
344 int[] offsets = {0, 0};
345 tz.getOffset(date, false, offsets);
347 if (offsets[1] == 0) {
349 // Check if the zone actually uses daylight saving time around the time
350 if (tz instanceof BasicTimeZone) {
351 BasicTimeZone btz = (BasicTimeZone)tz;
352 TimeZoneTransition before = btz.getPreviousTransition(date, true);
354 && (date - before.getTime() < DST_CHECK_RANGE)
355 && before.getFrom().getDSTSavings() != 0) {
358 TimeZoneTransition after = btz.getNextTransition(date, false);
360 && (after.getTime() - date < DST_CHECK_RANGE)
361 && after.getTo().getDSTSavings() != 0) {
366 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
367 // We may get a wrong answer in edge case, but it should practically work OK.
368 int[] tmpOffsets = new int[2];
369 tz.getOffset(date - DST_CHECK_RANGE, false, tmpOffsets);
370 if (tmpOffsets[1] != 0) {
373 tz.getOffset(date + DST_CHECK_RANGE, false, tmpOffsets);
374 if (tmpOffsets[1] != 0){
381 NameType stdNameType = (nameType == NameType.LONG_GENERIC) ?
382 NameType.LONG_STANDARD : NameType.SHORT_STANDARD;
383 String stdName = _tznames.getDisplayName(tzID, stdNameType, date);
384 if (stdName != null) {
387 // TODO: revisit this issue later
388 // In CLDR, a same display name is used for both generic and standard
389 // for some meta zones in some locales. This looks like a data bugs.
390 // For now, we check if the standard name is different from its generic
392 String mzGenericName = _tznames.getMetaZoneDisplayName(mzID, nameType);
393 if (stdName.equalsIgnoreCase(mzGenericName)) {
400 // Get a name from meta zone
401 String mzName = _tznames.getMetaZoneDisplayName(mzID, nameType);
402 if (mzName != null) {
403 // Check if we need to use a partial location format.
404 // This check is done by comparing offset with the meta zone's
405 // golden zone at the given date.
406 String goldenID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
407 if (goldenID != null && !goldenID.equals(tzID)) {
408 TimeZone goldenZone = TimeZone.getFrozenTimeZone(goldenID);
409 int[] offsets1 = {0, 0};
411 // Check offset in the golden zone with wall time.
412 // With getOffset(date, false, offsets1),
413 // you may get incorrect results because of time overlap at DST->STD
415 goldenZone.getOffset(date + offsets[0] + offsets[1], true, offsets1);
417 if (offsets[0] != offsets1[0] || offsets[1] != offsets1[1]) {
418 // Now we need to use a partial location format.
419 name = getPartialLocationName(tzID, mzID, (nameType == NameType.LONG_GENERIC), mzName);
433 * Private simple pattern formatter used for formatting generic location names
434 * and partial location names. We intentionally use JDK MessageFormat
435 * for performance reason.
437 * @param pat the message pattern enum
438 * @param args the format argument(s)
439 * @return the formatted string
441 private synchronized String formatPattern(Pattern pat, String... args) {
442 if (_patternFormatters == null) {
443 _patternFormatters = new MessageFormat[Pattern.values().length];
446 int idx = pat.ordinal();
447 if (_patternFormatters[idx] == null) {
450 ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(
451 ICUResourceBundle.ICU_ZONE_BASE_NAME, _locale);
452 patText = bundle.getStringWithFallback("zoneStrings/" + pat.key());
453 } catch (MissingResourceException e) {
454 patText = pat.defaultValue();
457 _patternFormatters[idx] = new MessageFormat(patText);
459 return _patternFormatters[idx].format(args);
463 * Private method returning LocaleDisplayNames instance for the locale of this
464 * instance. Because LocaleDisplayNames is only used for generic
465 * location formant and partial location format, the LocaleDisplayNames
466 * is instantiated lazily.
468 * @return the instance of LocaleDisplayNames for the locale of this object.
470 private synchronized LocaleDisplayNames getLocaleDisplayNames() {
471 LocaleDisplayNames locNames = null;
472 if (_localeDisplayNamesRef != null) {
473 locNames = _localeDisplayNamesRef.get();
475 if (locNames == null) {
476 locNames = LocaleDisplayNames.getInstance(_locale);
477 _localeDisplayNamesRef = new WeakReference<LocaleDisplayNames>(locNames);
482 private synchronized void loadStrings(String tzCanonicalID) {
483 if (tzCanonicalID == null || tzCanonicalID.length() == 0) {
486 // getGenericLocationName() formats a name and put it into the trie
487 getGenericLocationName(tzCanonicalID);
489 // Generic partial location format
490 Set<String> mzIDs = _tznames.getAvailableMetaZoneIDs(tzCanonicalID);
491 for (String mzID : mzIDs) {
492 // if this time zone is not the golden zone of the meta zone,
493 // partial location name (such as "PT (Los Angeles)") might be
495 String goldenID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
496 if (!tzCanonicalID.equals(goldenID)) {
497 for (NameType genNonLocType : GENERIC_NON_LOCATION_TYPES) {
498 String mzGenName = _tznames.getMetaZoneDisplayName(mzID, genNonLocType);
499 if (mzGenName != null) {
500 // getPartialLocationName() formats a name and put it into the trie
501 getPartialLocationName(tzCanonicalID, mzID, (genNonLocType == NameType.LONG_GENERIC), mzGenName);
509 * Private method returning the target region. The target regions is determined by
510 * the locale of this instance. When a generic name is coming from
511 * a meta zone, this region is used for checking if the time zone
512 * is a reference zone of the meta zone.
514 * @return the target region
516 private synchronized String getTargetRegion() {
517 if (_region == null) {
518 _region = _locale.getCountry();
519 if (_region.length() == 0) {
520 ULocale tmp = ULocale.addLikelySubtags(_locale);
521 _region = tmp.getCountry();
522 if (_region.length() == 0) {
531 * Private method for formatting partial location names. This format
532 * is used when a generic name of a meta zone is available, but the given
533 * time zone is not a reference zone (golden zone) of the meta zone.
535 * @param tzID the canonical time zone ID
536 * @param mzID the meta zone ID
537 * @param isLong true when long generic name
538 * @param mzDisplayName the meta zone generic display name
539 * @return the partial location format string
541 private String getPartialLocationName(String tzID, String mzID, boolean isLong, String mzDisplayName) {
542 String letter = isLong ? "L" : "S";
543 String key = tzID + "&" + mzID + "#" + letter;
544 String name = _genericPartialLocationNamesMap.get(key);
548 String location = null;
549 String countryCode = ZoneMeta.getCanonicalCountry(tzID);
550 if (countryCode != null) {
551 // Is this the golden zone for the region?
552 String regionalGolden = _tznames.getReferenceZoneID(mzID, countryCode);
553 if (tzID.equals(regionalGolden)) {
555 location = getLocaleDisplayNames().regionDisplayName(countryCode);
557 // Otherwise, use exemplar city name
558 location = _tznames.getExemplarLocationName(tzID);
561 location = _tznames.getExemplarLocationName(tzID);
562 if (location == null) {
563 // This could happen when the time zone is not associated with a country,
564 // and its ID is not hierarchical, for example, CST6CDT.
565 // We use the canonical ID itself as the location for this case.
569 name = formatPattern(Pattern.FALLBACK_FORMAT, location, mzDisplayName);
570 synchronized (this) { // we have to sync the name map and the trie
571 String tmp = _genericPartialLocationNamesMap.putIfAbsent(key.intern(), name.intern());
573 NameInfo info = new NameInfo();
574 info.tzID = tzID.intern();
575 info.type = isLong ? GenericNameType.LONG : GenericNameType.SHORT;
576 _gnamesTrie.put(name, info);
585 * A private class used for storing the name information in the local trie.
587 private static class NameInfo {
589 GenericNameType type;
593 * A class used for returning the name search result used by
594 * {@link TimeZoneGenericNames#find(String, int, EnumSet)}.
596 public static class GenericMatchInfo {
597 GenericNameType nameType;
600 TimeType timeType = TimeType.UNKNOWN;
602 public GenericNameType nameType() {
606 public String tzID() {
610 public TimeType timeType() {
614 public int matchLength() {
620 * A private class implementing the search callback interface in
621 * <code>TextTrieMap</code> for collecting match results.
623 private static class GenericNameSearchHandler implements ResultHandler<NameInfo> {
624 private EnumSet<GenericNameType> _types;
625 private Collection<GenericMatchInfo> _matches;
626 private int _maxMatchLen;
628 GenericNameSearchHandler(EnumSet<GenericNameType> types) {
633 * @see com.ibm.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, java.util.Iterator)
635 public boolean handlePrefixMatch(int matchLength, Iterator<NameInfo> values) {
636 while (values.hasNext()) {
637 NameInfo info = values.next();
638 if (_types != null && !_types.contains(info.type)) {
641 GenericMatchInfo matchInfo = new GenericMatchInfo();
642 matchInfo.tzID = info.tzID;
643 matchInfo.nameType = info.type;
644 matchInfo.matchLength = matchLength;
645 //matchInfo.timeType = TimeType.UNKNOWN;
646 if (_matches == null) {
647 _matches = new LinkedList<GenericMatchInfo>();
649 _matches.add(matchInfo);
650 if (matchLength > _maxMatchLen) {
651 _maxMatchLen = matchLength;
658 * Returns the match results
659 * @return the match results
661 public Collection<GenericMatchInfo> getMatches() {
666 * Returns the maximum match length, or 0 if no match was found
667 * @return the maximum match length
669 public int getMaxMatchLen() {
674 * Resets the match results
676 public void resetResults() {
683 * Returns the best match of time zone display name for the specified types in the
684 * given text at the given offset.
685 * @param text the text
686 * @param start the start offset in the text
687 * @param genericTypes the set of name types.
688 * @return the best matching name info.
690 public GenericMatchInfo findBestMatch(String text, int start, EnumSet<GenericNameType> genericTypes) {
691 if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
692 throw new IllegalArgumentException("bad input text or range");
694 GenericMatchInfo bestMatch = null;
696 // Find matches in the TimeZoneNames first
697 Collection<MatchInfo> tznamesMatches = findTimeZoneNames(text, start, genericTypes);
698 if (tznamesMatches != null) {
699 MatchInfo longestMatch = null;
700 for (MatchInfo match : tznamesMatches) {
701 if (longestMatch == null || match.matchLength() > longestMatch.matchLength()) {
702 longestMatch = match;
705 if (longestMatch != null) {
706 bestMatch = createGenericMatchInfo(longestMatch);
707 if (bestMatch.matchLength() == (text.length() - start)) {
711 // TODO Some time zone uses a same name for the long standard name
712 // and the location name. When the match is a long standard name,
713 // then we need to check if the name is same with the location name.
714 // This is probably a data error or a design bug.
715 // if (bestMatch.nameType != GenericNameType.LONG || bestMatch.timeType != TimeType.STANDARD) {
719 // TODO The deprecation of commonlyUsed flag introduced the name
720 // conflict not only for long standard names, but short standard names too.
721 // These short names (found in zh_Hant) should be gone once we clean
722 // up CLDR time zone display name data. Once the short name conflict
723 // problem (with location name) is resolved, we should change the condition
724 // below back to the original one above. -Yoshito (2011-09-14)
725 if (bestMatch.timeType != TimeType.STANDARD) {
732 // Find matches in the local trie
733 Collection<GenericMatchInfo> localMatches = findLocal(text, start, genericTypes);
734 if (localMatches != null) {
735 for (GenericMatchInfo match : localMatches) {
736 // TODO See the above TODO. We use match.matchLength() >= bestMatch.matcheLength()
737 // for the reason described above.
738 //if (bestMatch == null || match.matchLength() > bestMatch.matchLength()) {
739 if (bestMatch == null || match.matchLength() >= bestMatch.matchLength()) {
749 * Returns a collection of time zone display name matches for the specified types in the
750 * given text at the given offset.
751 * @param text the text
752 * @param start the start offset in the text
753 * @param genericTypes the set of name types.
754 * @return A collection of match info.
756 public Collection<GenericMatchInfo> find(String text, int start, EnumSet<GenericNameType> genericTypes) {
757 if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
758 throw new IllegalArgumentException("bad input text or range");
760 // Find matches in the local trie
761 Collection<GenericMatchInfo> results = findLocal(text, start, genericTypes);
763 // Also find matches in the TimeZoneNames
764 Collection<MatchInfo> tznamesMatches = findTimeZoneNames(text, start, genericTypes);
765 if (tznamesMatches != null) {
766 // transform matches and append them to local matches
767 for (MatchInfo match : tznamesMatches) {
768 if (results == null) {
769 results = new LinkedList<GenericMatchInfo>();
771 results.add(createGenericMatchInfo(match));
778 * Returns a <code>GenericMatchInfo</code> for the given <code>MatchInfo</code>.
779 * @param matchInfo the MatchInfo
780 * @return A GenericMatchInfo
782 private GenericMatchInfo createGenericMatchInfo(MatchInfo matchInfo) {
783 GenericNameType nameType = null;
784 TimeType timeType = TimeType.UNKNOWN;
785 switch (matchInfo.nameType()) {
787 nameType = GenericNameType.LONG;
788 timeType = TimeType.STANDARD;
791 nameType = GenericNameType.LONG;
794 nameType = GenericNameType.SHORT;
795 timeType = TimeType.STANDARD;
798 nameType = GenericNameType.SHORT;
801 throw new IllegalArgumentException("Unexpected MatchInfo name type - " + matchInfo.nameType());
804 String tzID = matchInfo.tzID();
806 String mzID = matchInfo.mzID();
807 assert(mzID != null);
808 tzID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
810 assert(tzID != null);
812 GenericMatchInfo gmatch = new GenericMatchInfo();
813 gmatch.nameType = nameType;
815 gmatch.matchLength = matchInfo.matchLength();
816 gmatch.timeType = timeType;
822 * Returns a collection of time zone display name matches for the specified types in the
823 * given text at the given offset. This method only finds matches from the TimeZoneNames
824 * used by this object.
825 * @param text the text
826 * @param start the start offset in the text
827 * @param types the set of name types.
828 * @return A collection of match info.
830 private Collection<MatchInfo> findTimeZoneNames(String text, int start, EnumSet<GenericNameType> types) {
831 Collection<MatchInfo> tznamesMatches = null;
833 // Check if the target name type is really in the TimeZoneNames
834 EnumSet<NameType> nameTypes = EnumSet.noneOf(NameType.class);
835 if (types.contains(GenericNameType.LONG)) {
836 nameTypes.add(NameType.LONG_GENERIC);
837 nameTypes.add(NameType.LONG_STANDARD);
839 if (types.contains(GenericNameType.SHORT)) {
840 nameTypes.add(NameType.SHORT_GENERIC);
841 nameTypes.add(NameType.SHORT_STANDARD);
844 if (!nameTypes.isEmpty()) {
845 // Find matches in the TimeZoneNames
846 tznamesMatches = _tznames.find(text, start, nameTypes);
848 return tznamesMatches;
852 * Returns a collection of time zone display name matches for the specified types in the
853 * given text at the given offset. This method only finds matches from the local trie,
854 * that contains 1) generic location names and 2) long/short generic partial location names,
855 * used by this object.
856 * @param text the text
857 * @param start the start offset in the text
858 * @param types the set of name types.
859 * @return A collection of match info.
861 private synchronized Collection<GenericMatchInfo> findLocal(String text, int start, EnumSet<GenericNameType> types) {
862 GenericNameSearchHandler handler = new GenericNameSearchHandler(types);
863 _gnamesTrie.find(text, start, handler);
864 if (handler.getMaxMatchLen() == (text.length() - start) || _gnamesTrieFullyLoaded) {
866 return handler.getMatches();
869 // All names are not yet loaded into the local trie.
870 // Load all available names into the trie. This could be very heavy.
872 Set<String> tzIDs = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
873 for (String tzID : tzIDs) {
876 _gnamesTrieFullyLoaded = true;
879 handler.resetResults();
880 _gnamesTrie.find(text, start, handler);
881 return handler.getMatches();
885 * <code>TimeZoneGenericNames</code> cache implementation.
887 private static class Cache extends SoftCache<String, TimeZoneGenericNames, ULocale> {
890 * @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
893 protected TimeZoneGenericNames createInstance(String key, ULocale data) {
894 return new TimeZoneGenericNames(data).freeze();
900 * The custom deserialization method.
901 * This implementation only read locale used by the object.
903 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
904 in.defaultReadObject();
911 public boolean isFrozen() {
918 public TimeZoneGenericNames freeze() {
926 public TimeZoneGenericNames cloneAsThawed() {
927 TimeZoneGenericNames copy = null;
929 copy = (TimeZoneGenericNames)super.clone();
930 copy._frozen = false;
931 } catch (Throwable t) {
932 // This should never happen