| /* |
| ******************************************************************************* |
| * Copyright (C) 2007-2010, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.impl; |
| |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.MissingResourceException; |
| import java.util.Set; |
| |
| import com.ibm.icu.impl.ZoneMeta.OlsonToMetaMappingEntry; |
| import com.ibm.icu.text.MessageFormat; |
| import com.ibm.icu.text.NumberingSystem; |
| import com.ibm.icu.util.BasicTimeZone; |
| import com.ibm.icu.util.TimeZone; |
| import com.ibm.icu.util.TimeZoneTransition; |
| import com.ibm.icu.util.ULocale; |
| import com.ibm.icu.util.UResourceBundle; |
| |
| /** |
| * @author yoshito |
| * |
| */ |
| public class ZoneStringFormat { |
| |
| private static final int millisPerHour = 60 * 60 * 1000; |
| private static final int millisPerMinute = 60 * 1000; |
| private static final int millisPerSecond = 1000; |
| private static final String DEFAULT_DIGIT_STRING = "0123456789"; |
| private static final String DEFAULT_GMT_FORMAT = "GMT{0}"; |
| private static final String DEFAULT_HOUR_FORMAT = "+HH:mm;-HH:mm"; |
| |
| /** |
| * Constructs a ZoneStringFormat by zone strings array. |
| * The internal structure of zoneStrings is compatible with |
| * the one used by getZoneStrings/setZoneStrings in DateFormatSymbols. |
| * |
| * @param zoneStrings zone strings |
| */ |
| public ZoneStringFormat(String[][] zoneStrings) { |
| tzidToStrings = new HashMap<String, ZoneStrings>(); |
| zoneStringsTrie = new TextTrieMap<ZoneStringInfo>(true); |
| for (int i = 0; i < zoneStrings.length; i++) { |
| String tzid = zoneStrings[i][0]; |
| String[] names = new String[ZSIDX_MAX]; |
| for (int j = 1; j < zoneStrings[i].length; j++) { |
| if (zoneStrings[i][j] != null) { |
| int typeIdx = getNameTypeIndex(j); |
| if (typeIdx != -1) { |
| names[typeIdx] = zoneStrings[i][j]; |
| |
| // Put the name into the trie |
| int type = getNameType(typeIdx); |
| ZoneStringInfo zsinfo = new ZoneStringInfo(tzid, zoneStrings[i][j], type); |
| zoneStringsTrie.put(zoneStrings[i][j], zsinfo); |
| } |
| |
| } |
| } |
| ZoneStrings zstrings = new ZoneStrings(names, true, null); |
| tzidToStrings.put(tzid, zstrings); |
| } |
| isFullyLoaded = true; |
| } |
| |
| /** |
| * Gets an instance of ZoneStringFormat for the specified locale |
| * @param locale the locale |
| * @return An instance of ZoneStringFormat for the locale |
| */ |
| public static ZoneStringFormat getInstance(ULocale locale) { |
| ZoneStringFormat zzf = ZSFORMAT_CACHE.get(locale); |
| if (zzf == null) { |
| zzf = new ZoneStringFormat(locale); |
| ZSFORMAT_CACHE.put(locale, zzf); |
| } |
| return zzf; |
| } |
| |
| public String[][] getZoneStrings() { |
| return getZoneStrings(System.currentTimeMillis()); |
| } |
| |
| private boolean inDaylightTime(TimeZone tz, long date) { |
| int[] offsets = {0,0}; |
| tz.getOffset(date, false, offsets); |
| return (offsets[1] != 0); |
| } |
| // APIs used by SimpleDateFormat to get a zone string |
| public String getSpecificLongString(TimeZone tz, long date) { |
| |
| return getString(tz.getID(), inDaylightTime(tz,date) ? ZSIDX_LONG_DAYLIGHT : ZSIDX_LONG_STANDARD, date, false /* not used */); |
| } |
| |
| public String getSpecificShortString(TimeZone tz, long date, boolean commonlyUsedOnly) { |
| return getString(tz.getID(), inDaylightTime(tz,date) ? ZSIDX_SHORT_DAYLIGHT : ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly); |
| } |
| |
| public String getGenericLongString(TimeZone tz, long date) { |
| return getGenericString(tz, date, false /* long */, false /* not used */); |
| } |
| |
| public String getGenericShortString(TimeZone tz, long date, boolean commonlyUsedOnly) { |
| return getGenericString(tz, date, true /* long */, commonlyUsedOnly); |
| } |
| |
| public String getGenericLocationString(TimeZone tz, long date) { |
| return getString(tz.getID(), ZSIDX_LOCATION, date, false /* not used */); |
| } |
| public String getLongGMTString( TimeZone tz, long date, boolean daylight ) { |
| int offset; |
| if (daylight && tz.useDaylightTime()) { |
| offset = tz.getRawOffset() + tz.getDSTSavings(); |
| } else { |
| offset = tz.getRawOffset(); |
| } |
| return getLongGMTString(tz,date,offset); |
| } |
| |
| public String getLongGMTString( TimeZone tz, long date ) { |
| return getLongGMTString(tz,date,tz.getOffset(date)); |
| } |
| |
| public String getLongGMTString( TimeZone tz, long date, int offsetIn) { |
| // Note: This code is optimized for performance, but as a result, it makes assumptions |
| // about the content and structure of the underlying CLDR data. |
| // Specifically, it assumes that the H or HH in the pattern occurs before the mm, |
| // and that there are no quoted literals in the pattern that contain H or m. |
| // As of CLDR 1.8.1, all of the data conforms to these rules, so we should probably be OK. |
| |
| StringBuffer buf = new StringBuffer(); |
| int offset = offsetIn; |
| int hfPosition = 0; |
| if (offset < 0) { |
| offset = -offset; |
| hfPosition = 1; |
| } |
| |
| int offsetH = offset / millisPerHour; |
| offset = offset % millisPerHour; |
| int offsetM = offset / millisPerMinute; |
| offset = offset % millisPerMinute; |
| int offsetS = offset / millisPerSecond; |
| |
| int subPosition = gmtFormat.indexOf("{0}"); |
| for ( int i = 0 ; i < gmtFormat.length(); i++ ) { |
| if ( i == subPosition ) { |
| String hmString = hourFormats[hfPosition]; |
| for ( int j = 0 ; j < hmString.length() ; j++) { |
| switch (hmString.charAt(j)) { |
| case 'H': |
| if ( j+1 < hmString.length() && hmString.charAt(j+1) == 'H' ) { |
| j++; |
| if (offsetH < 10) { |
| buf.append(digitString.charAt(0)); |
| } |
| } |
| if ( offsetH >= 10 ) { |
| buf.append(digitString.charAt(offsetH/10)); |
| } |
| buf.append(digitString.charAt(offsetH%10)); |
| break; |
| case 'm': |
| if ( j+1 < hmString.length() && hmString.charAt(j+1) == 'm' ) { |
| j++; |
| } |
| buf.append(digitString.charAt(offsetM/10)); |
| buf.append(digitString.charAt(offsetM%10)); |
| if ( offsetS > 0 ) { |
| int lastH = hmString.lastIndexOf('H'); |
| int firstm = hmString.indexOf('m'); |
| if ( lastH + 1 < firstm ) { |
| buf.append(hmString.substring(lastH+1,firstm)); |
| } |
| buf.append(digitString.charAt(offsetS/10)); |
| buf.append(digitString.charAt(offsetS%10)); |
| } |
| break; |
| default: |
| buf.append(hmString.charAt(j)); |
| break; |
| } |
| } |
| i += 3; |
| } else { |
| buf.append(gmtFormat.charAt(i)); |
| } |
| } |
| return buf.toString(); |
| } |
| public String getShortGMTString( TimeZone tz, long date, boolean daylight ) { |
| int offset; |
| if (daylight && tz.useDaylightTime()) { |
| offset = tz.getRawOffset() + tz.getDSTSavings(); |
| } else { |
| offset = tz.getRawOffset(); |
| } |
| return getShortGMTString(tz,date,offset); |
| } |
| |
| public String getShortGMTString( TimeZone tz, long date ) { |
| return getShortGMTString(tz,date,tz.getOffset(date)); |
| } |
| |
| public String getShortGMTString( TimeZone tz, long date, int offset ) { |
| StringBuffer buf = new StringBuffer(); |
| // RFC822 format, must use ASCII digits |
| int val = offset; |
| char sign = '+'; |
| if (val < 0) { |
| val = -val; |
| sign = '-'; |
| } |
| buf.append(sign); |
| |
| int offsetH = val / millisPerHour; |
| val = val % millisPerHour; |
| int offsetM = val / millisPerMinute; |
| val = val % millisPerMinute; |
| int offsetS = val / millisPerSecond; |
| |
| int num = 0, denom = 0; |
| if (offsetS == 0) { |
| val = offsetH*100 + offsetM; // HHmm |
| num = val % 10000; |
| denom = 1000; |
| } else { |
| val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss |
| num = val % 1000000; |
| denom = 100000; |
| } |
| while (denom >= 1) { |
| char digit = (char)((num / denom) + '0'); |
| buf.append(digit); |
| num = num % denom; |
| denom /= 10; |
| } |
| return buf.toString(); |
| } |
| // APIs used by SimpleDateFormat to lookup a zone string |
| public static class ZoneStringInfo { |
| private String id; |
| private String str; |
| private int type; |
| |
| private ZoneStringInfo(String id, String str, int type) { |
| this.id = id; |
| this.str = str; |
| this.type = type; |
| } |
| |
| public String getID() { |
| return id; |
| } |
| |
| public String getString() { |
| return str; |
| } |
| |
| public boolean isStandard() { |
| if ((type & STANDARD_LONG) != 0 || (type & STANDARD_SHORT) != 0) { |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean isDaylight() { |
| if ((type & DAYLIGHT_LONG) != 0 || (type & DAYLIGHT_SHORT) != 0) { |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean isGeneric() { |
| return !isStandard() && !isDaylight(); |
| } |
| |
| private int getType() { |
| return type; |
| } |
| } |
| |
| public ZoneStringInfo findSpecificLong(String text, int start) { |
| return find(text, start, STANDARD_LONG | DAYLIGHT_LONG); |
| } |
| |
| public ZoneStringInfo findSpecificShort(String text, int start) { |
| return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT); |
| } |
| |
| public ZoneStringInfo findGenericLong(String text, int start) { |
| return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION); |
| } |
| |
| public ZoneStringInfo findGenericShort(String text, int start) { |
| return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION); |
| } |
| |
| public ZoneStringInfo findGenericLocation(String text, int start) { |
| return find(text, start, LOCATION); |
| } |
| |
| // Following APIs are not used by SimpleDateFormat, but public for testing purpose |
| public String getLongStandard(String tzid, long date) { |
| return getString(tzid, ZSIDX_LONG_STANDARD, date, false /* not used */); |
| } |
| |
| public String getLongDaylight(String tzid, long date) { |
| return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, false /* not used */); |
| } |
| |
| public String getLongGenericNonLocation(String tzid, long date) { |
| return getString(tzid, ZSIDX_LONG_GENERIC, date, false /* not used */); |
| } |
| |
| public String getLongGenericPartialLocation(String tzid, long date) { |
| return getGenericPartialLocationString(tzid, false, date, false /* not used */); |
| } |
| |
| public String getShortStandard(String tzid, long date, boolean commonlyUsedOnly) { |
| return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly); |
| } |
| |
| public String getShortDaylight(String tzid, long date, boolean commonlyUsedOnly) { |
| return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly); |
| } |
| |
| public String getShortGenericNonLocation(String tzid, long date, boolean commonlyUsedOnly) { |
| return getString(tzid, ZSIDX_SHORT_GENERIC, date, commonlyUsedOnly); |
| } |
| |
| public String getShortGenericPartialLocation(String tzid, long date, boolean commonlyUsedOnly) { |
| return getGenericPartialLocationString(tzid, true, date, commonlyUsedOnly); |
| } |
| |
| public String getGenericLocation(String tzid) { |
| return getString(tzid, ZSIDX_LOCATION, 0L /* not used */, false /* not used */); |
| } |
| |
| /** |
| * Constructs a ZoneStringFormat by locale. Because an instance of ZoneStringFormat |
| * is read-only, only one instance for a locale is sufficient. Thus, this |
| * constructor is protected and only called from getInstance(ULocale) to |
| * create one for a locale. |
| * @param locale The locale |
| */ |
| protected ZoneStringFormat(ULocale locale) { |
| this.locale = locale; |
| tzidToStrings = new HashMap<String, ZoneStrings>(); |
| mzidToStrings = new HashMap<String, ZoneStrings>(); |
| zoneStringsTrie = new TextTrieMap<ZoneStringInfo>(true); |
| NumberingSystem ns = NumberingSystem.getInstance(locale); |
| if (ns.isAlgorithmic()) { |
| digitString = DEFAULT_DIGIT_STRING; // Using complex ns for GMT formatting doesn't make sense |
| } else { |
| digitString = ns.getDescription(); |
| } |
| |
| gmtFormat = null; |
| String hourFormatString = null; |
| try { |
| ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_ZONE_BASE_NAME, locale); |
| gmtFormat = bundle.getStringWithFallback("zoneStrings/gmtFormat"); |
| hourFormatString = bundle.getStringWithFallback("zoneStrings/hourFormat"); |
| } catch (MissingResourceException e) { |
| } |
| |
| if ( gmtFormat == null) { |
| gmtFormat = DEFAULT_GMT_FORMAT; |
| } |
| if ( hourFormatString == null) { |
| hourFormatString = DEFAULT_HOUR_FORMAT; |
| } |
| |
| hourFormats = hourFormatString.split(";", 2); |
| } |
| |
| // Load only a single zone |
| private synchronized void loadZone(String id) { |
| if (isFullyLoaded) { |
| return; |
| } |
| String tzid = ZoneMeta.getCanonicalSystemID(id); |
| if (tzid == null || tzidToStrings.containsKey(tzid)) { |
| return; |
| } |
| |
| ICUResourceBundle zoneStringsBundle = null; |
| try { |
| ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_ZONE_BASE_NAME, locale); |
| zoneStringsBundle = bundle.getWithFallback("zoneStrings"); |
| } catch (MissingResourceException e) { |
| // If no locale bundles are available, zoneStringsBundle will be null. |
| // We still want to go through the rest of zone strings initialization, |
| // because generic location format is generated from tzid for the case. |
| // The rest of code should work even zoneStrings is null. |
| } |
| |
| String[] zstrarray = new String[ZSIDX_MAX]; |
| String[] mzstrarray = new String[ZSIDX_MAX]; |
| String[][] mzPartialLoc = new String[10][4]; // maximum 10 metazones per zone |
| |
| addSingleZone(tzid, zoneStringsBundle, |
| getFallbackFormat(locale), getRegionFormat(locale), |
| zstrarray, mzstrarray, mzPartialLoc); |
| } |
| |
| // Loading all zone strings |
| private synchronized void loadFull() { |
| if (isFullyLoaded) { |
| return; |
| } |
| ICUResourceBundle zoneStringsBundle = null; |
| try { |
| ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_ZONE_BASE_NAME, locale); |
| zoneStringsBundle = bundle.getWithFallback("zoneStrings"); |
| } catch (MissingResourceException e) { |
| // If no locale bundles are available, zoneStringsBundle will be null. |
| // We still want to go through the rest of zone strings initialization, |
| // because generic location format is generated from tzid for the case. |
| // The rest of code should work even zoneStrings is null. |
| } |
| |
| String[] zoneIDs = TimeZone.getAvailableIDs(); |
| |
| String[] zstrarray = new String[ZSIDX_MAX]; |
| String[] mzstrarray = new String[ZSIDX_MAX]; |
| String[][] mzPartialLoc = new String[10][4]; // maximum 10 metazones per zone |
| |
| for (int i = 0; i < zoneIDs.length; i++) { |
| // Skip aliases |
| String tzid = ZoneMeta.getCanonicalSystemID(zoneIDs[i]); |
| if (tzid == null || !zoneIDs[i].equals(tzid)) { |
| continue; |
| } |
| |
| if (tzidToStrings.containsKey(tzid)) { |
| continue; |
| } |
| |
| addSingleZone(tzid, zoneStringsBundle, |
| getFallbackFormat(locale), getRegionFormat(locale), |
| zstrarray, mzstrarray, mzPartialLoc); |
| } |
| isFullyLoaded = true; |
| } |
| |
| // This internal initialization code must be called in a synchronized block |
| private void addSingleZone(String tzid, ICUResourceBundle zoneStringsBundle, |
| MessageFormat fallbackFmt, MessageFormat regionFmt, |
| String[] zstrarray, String[] mzstrarray, String[][] mzPartialLoc) { |
| |
| if (tzidToStrings.containsKey(tzid)) { |
| return; |
| } |
| |
| String zoneKey = tzid.replace('/', ':'); |
| zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_STANDARD); |
| zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_STANDARD); |
| zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_DAYLIGHT); |
| zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_DAYLIGHT); |
| zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_GENERIC); |
| zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_GENERIC); |
| |
| // Compose location format string |
| String countryCode = ZoneMeta.getCanonicalCountry(tzid); |
| String country = null; |
| String city = null; |
| if (countryCode != null) { |
| city = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_EXEMPLAR_CITY); |
| if (city == null) { |
| city = tzid.substring(tzid.lastIndexOf('/') + 1).replace('_', ' '); |
| } |
| country = getLocalizedCountry(countryCode, locale); |
| if (ZoneMeta.getSingleCountry(tzid) != null) { |
| // If the zone is only one zone in the country, do not add city |
| zstrarray[ZSIDX_LOCATION] = regionFmt.format(new Object[] {country}); |
| } else { |
| zstrarray[ZSIDX_LOCATION] = fallbackFmt.format(new Object[] {city, country}); |
| } |
| } else { |
| if (tzid.startsWith("Etc/")) { |
| // "Etc/xxx" is not associated with a specific location, so localized |
| // GMT format is always used as generic location format. |
| zstrarray[ZSIDX_LOCATION] = null; |
| } else { |
| // When a new time zone ID, which is actually associated with a specific |
| // location, is added in tzdata, but the current CLDR data does not have |
| // the information yet, ICU creates a generic location string based on |
| // the ID. This implementation supports canonical time zone round trip |
| // with format pattern "VVVV". See #6602 for the details. |
| String location = tzid; |
| int slashIdx = location.lastIndexOf('/'); |
| if (slashIdx == -1) { |
| // A time zone ID without slash in the tz database is not |
| // associated with a specific location. For instances, |
| // MET, CET, EET and WET fall into this catetory. |
| zstrarray[ZSIDX_LOCATION] = null; |
| } else { |
| location = tzid.substring(slashIdx + 1); |
| zstrarray[ZSIDX_LOCATION] = regionFmt.format(new Object[] {location}); |
| } |
| } |
| } |
| |
| boolean commonlyUsed = isCommonlyUsed(zoneStringsBundle, zoneKey); |
| |
| // Resolve metazones used by this zone |
| int mzPartialLocIdx = 0; |
| List<OlsonToMetaMappingEntry> metazoneMappings = ZoneMeta.getOlsonToMatazones(tzid); |
| if (metazoneMappings != null) { |
| Iterator<OlsonToMetaMappingEntry> it = metazoneMappings.iterator(); |
| while (it.hasNext()) { |
| ZoneMeta.OlsonToMetaMappingEntry mzmap = it.next(); |
| ZoneStrings mzStrings = mzidToStrings.get(mzmap.mzid); |
| if (mzStrings == null) { |
| // If the metazone strings are not yet processed, do it now. |
| String mzkey = "meta:" + mzmap.mzid; |
| boolean mzCommonlyUsed = isCommonlyUsed(zoneStringsBundle, mzkey); |
| mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_STANDARD); |
| mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_STANDARD); |
| mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_DAYLIGHT); |
| mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_DAYLIGHT); |
| mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_GENERIC); |
| mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_GENERIC); |
| mzstrarray[ZSIDX_LOCATION] = null; |
| mzStrings = new ZoneStrings(mzstrarray, mzCommonlyUsed, null); |
| mzidToStrings.put(mzmap.mzid, mzStrings); |
| |
| // Add metazone strings to the zone string trie |
| String preferredIdForLocale = ZoneMeta.getZoneIdByMetazone(mzmap.mzid, getRegion()); |
| for (int j = 0; j < mzstrarray.length; j++) { |
| if (mzstrarray[j] != null) { |
| int type = getNameType(j); |
| ZoneStringInfo zsinfo = new ZoneStringInfo(preferredIdForLocale, mzstrarray[j], type); |
| zoneStringsTrie.put(mzstrarray[j], zsinfo); |
| } |
| } |
| } |
| // Compose generic partial location format |
| String lg = mzStrings.getString(ZSIDX_LONG_GENERIC); |
| String sg = mzStrings.getString(ZSIDX_SHORT_GENERIC); |
| if (lg != null || sg != null) { |
| boolean addMzPartialLocationNames = true; |
| for (int j = 0; j < mzPartialLocIdx; j++) { |
| if (mzPartialLoc[j][0].equals(mzmap.mzid)) { |
| // already added |
| addMzPartialLocationNames = false; |
| break; |
| } |
| } |
| if (addMzPartialLocationNames) { |
| String locationPart = null; |
| // Check if the zone is the preferred zone for the territory associated with the zone |
| String preferredID = ZoneMeta.getZoneIdByMetazone(mzmap.mzid, countryCode); |
| if (tzid.equals(preferredID)) { |
| // Use country for the location |
| locationPart = country; |
| } else { |
| // Use city for the location |
| locationPart = city; |
| } |
| mzPartialLoc[mzPartialLocIdx][0] = mzmap.mzid; |
| mzPartialLoc[mzPartialLocIdx][1] = null; |
| mzPartialLoc[mzPartialLocIdx][2] = null; |
| mzPartialLoc[mzPartialLocIdx][3] = null; |
| if (locationPart != null) { |
| if (lg != null) { |
| mzPartialLoc[mzPartialLocIdx][1] = fallbackFmt.format(new Object[] {locationPart, lg}); |
| } |
| if (sg != null) { |
| mzPartialLoc[mzPartialLocIdx][2] = fallbackFmt.format(new Object[] {locationPart, sg}); |
| boolean shortMzCommonlyUsed = mzStrings.isShortFormatCommonlyUsed(); |
| if (shortMzCommonlyUsed) { |
| mzPartialLoc[mzPartialLocIdx][3] = "1"; |
| } |
| } |
| } |
| mzPartialLocIdx++; |
| } |
| } |
| } |
| } |
| String[][] genericPartialLocationNames = null; |
| if (mzPartialLocIdx != 0) { |
| // metazone generic partial location names are collected |
| genericPartialLocationNames = new String[mzPartialLocIdx][]; |
| for (int mzi = 0; mzi < mzPartialLocIdx; mzi++) { |
| genericPartialLocationNames[mzi] = mzPartialLoc[mzi].clone(); |
| } |
| } |
| // Finally, create ZoneStrings instance and put it into the tzidToStinrgs map |
| ZoneStrings zstrings = new ZoneStrings(zstrarray, commonlyUsed, genericPartialLocationNames); |
| tzidToStrings.put(tzid, zstrings); |
| |
| // Also add all available names to the zone string trie |
| if (zstrarray != null) { |
| for (int j = 0; j < zstrarray.length; j++) { |
| if (zstrarray[j] != null) { |
| int type = getNameType(j); |
| ZoneStringInfo zsinfo = new ZoneStringInfo(tzid, zstrarray[j], type); |
| zoneStringsTrie.put(zstrarray[j], zsinfo); |
| } |
| } |
| } |
| if (genericPartialLocationNames != null) { |
| for (int j = 0; j < genericPartialLocationNames.length; j++) { |
| ZoneStringInfo zsinfo; |
| if (genericPartialLocationNames[j][1] != null) { |
| zsinfo = new ZoneStringInfo(tzid, genericPartialLocationNames[j][1], GENERIC_LONG); |
| zoneStringsTrie.put(genericPartialLocationNames[j][1], zsinfo); |
| } |
| if (genericPartialLocationNames[j][2] != null) { |
| zsinfo = new ZoneStringInfo(tzid, genericPartialLocationNames[j][1], GENERIC_SHORT); |
| zoneStringsTrie.put(genericPartialLocationNames[j][2], zsinfo); |
| } |
| } |
| } |
| } |
| |
| // Name types, these bit flag are used for zone string lookup |
| private static final int LOCATION = 0x0001; |
| private static final int GENERIC_LONG = 0x0002; |
| private static final int GENERIC_SHORT = 0x0004; |
| private static final int STANDARD_LONG = 0x0008; |
| private static final int STANDARD_SHORT = 0x0010; |
| private static final int DAYLIGHT_LONG = 0x0020; |
| private static final int DAYLIGHT_SHORT = 0x0040; |
| |
| // Name type index, these constants are used for index in ZoneStrings.strings |
| private static final int ZSIDX_LOCATION = 0; |
| private static final int ZSIDX_LONG_STANDARD = 1; |
| private static final int ZSIDX_SHORT_STANDARD = 2; |
| private static final int ZSIDX_LONG_DAYLIGHT = 3; |
| private static final int ZSIDX_SHORT_DAYLIGHT = 4; |
| private static final int ZSIDX_LONG_GENERIC = 5; |
| private static final int ZSIDX_SHORT_GENERIC = 6; |
| |
| private static final int ZSIDX_MAX = ZSIDX_SHORT_GENERIC + 1; |
| |
| // ZoneStringFormat cache |
| private static ICUCache<ULocale, ZoneStringFormat> ZSFORMAT_CACHE = new SimpleCache<ULocale, ZoneStringFormat>(); |
| |
| /* |
| * The translation type of the translated zone strings |
| */ |
| private static final String |
| RESKEY_SHORT_GENERIC = "sg", |
| RESKEY_SHORT_STANDARD = "ss", |
| RESKEY_SHORT_DAYLIGHT = "sd", |
| RESKEY_LONG_GENERIC = "lg", |
| RESKEY_LONG_STANDARD = "ls", |
| RESKEY_LONG_DAYLIGHT = "ld", |
| RESKEY_EXEMPLAR_CITY = "ec", |
| RESKEY_COMMONLY_USED = "cu"; |
| |
| // Window size used for DST check for a zone in a metazone |
| private static final long DST_CHECK_RANGE = 184L*(24*60*60*1000); |
| |
| // Map from zone id to ZoneStrings |
| private Map<String, ZoneStrings> tzidToStrings; |
| |
| // Map from metazone id to ZoneStrings |
| private Map<String, ZoneStrings> mzidToStrings; |
| |
| // Zone string dictionary, used for look up |
| private TextTrieMap<ZoneStringInfo> zoneStringsTrie; |
| |
| // Locale used for initializing zone strings |
| private ULocale locale; |
| |
| // Region used for resolving a zone in a metazone, initialized by locale |
| private transient String region; |
| |
| // Loading status |
| private boolean isFullyLoaded = false; |
| |
| // Digit string - used for fast GMT formatting |
| private String digitString; |
| |
| private String gmtFormat; |
| |
| private String[] hourFormats; |
| |
| /* |
| * Private method to get a zone string except generic partial location types. |
| */ |
| private String getString(String tzid, int typeIdx, long date, boolean commonlyUsedOnly) { |
| if (!isFullyLoaded) { |
| // Lazy loading |
| loadZone(tzid); |
| } |
| |
| String result = null; |
| ZoneStrings zstrings = tzidToStrings.get(tzid); |
| if (zstrings == null) { |
| // ICU's own array does not have entries for aliases |
| String canonicalID = ZoneMeta.getCanonicalSystemID(tzid); |
| if (canonicalID != null && !canonicalID.equals(tzid)) { |
| // Canonicalize tzid here. The rest of operations |
| // require tzid to be canonicalized. |
| tzid = canonicalID; |
| zstrings = tzidToStrings.get(tzid); |
| } |
| } |
| if (zstrings != null) { |
| switch (typeIdx) { |
| case ZSIDX_LONG_STANDARD: |
| case ZSIDX_LONG_DAYLIGHT: |
| case ZSIDX_LONG_GENERIC: |
| case ZSIDX_LOCATION: |
| result = zstrings.getString(typeIdx); |
| break; |
| case ZSIDX_SHORT_STANDARD: |
| case ZSIDX_SHORT_DAYLIGHT: |
| case ZSIDX_SHORT_GENERIC: |
| if (!commonlyUsedOnly || zstrings.isShortFormatCommonlyUsed()) { |
| result = zstrings.getString(typeIdx); |
| } |
| break; |
| } |
| } |
| if (result == null && mzidToStrings != null && typeIdx != ZSIDX_LOCATION) { |
| // Try metazone |
| String mzid = ZoneMeta.getMetazoneID(tzid, date); |
| if (mzid != null) { |
| ZoneStrings mzstrings = mzidToStrings.get(mzid); |
| if (mzstrings != null) { |
| switch (typeIdx) { |
| case ZSIDX_LONG_STANDARD: |
| case ZSIDX_LONG_DAYLIGHT: |
| case ZSIDX_LONG_GENERIC: |
| result = mzstrings.getString(typeIdx); |
| break; |
| case ZSIDX_SHORT_STANDARD: |
| case ZSIDX_SHORT_DAYLIGHT: |
| case ZSIDX_SHORT_GENERIC: |
| if (!commonlyUsedOnly || mzstrings.isShortFormatCommonlyUsed()) { |
| result = mzstrings.getString(typeIdx); |
| } |
| break; |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /* |
| * Private method to get a generic string, with fallback logic involved, |
| * that is, |
| * |
| * 1. If a generic non-location string is avaiable for the zone, return it. |
| * 2. If a generic non-location string is associated with a metazone and |
| * the zone never use daylight time around the given date, use the standard |
| * string (if available). |
| * |
| * Note: In CLDR1.5.1, the same localization is used for generic and standard. |
| * In this case, we do not use the standard string and do the rest. |
| * |
| * 3. If a generic non-location string is associated with a metazone 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 avaiable) |
| * 4. If a generic non-location string is not available, use generic location |
| * string. |
| */ |
| private String getGenericString(TimeZone tz, long date, boolean isShort, boolean commonlyUsedOnly) { |
| String result = null; |
| String tzid = tz.getID(); |
| |
| if (!isFullyLoaded) { |
| // Lazy loading |
| loadZone(tzid); |
| } |
| |
| ZoneStrings zstrings = tzidToStrings.get(tzid); |
| if (zstrings == null) { |
| // ICU's own array does not have entries for aliases |
| String canonicalID = ZoneMeta.getCanonicalSystemID(tzid); |
| if (canonicalID != null && !canonicalID.equals(tzid)) { |
| // Canonicalize tzid here. The rest of operations |
| // require tzid to be canonicalized. |
| tzid = canonicalID; |
| zstrings = tzidToStrings.get(tzid); |
| } |
| } |
| if (zstrings != null) { |
| if (isShort) { |
| if (!commonlyUsedOnly || zstrings.isShortFormatCommonlyUsed()) { |
| result = zstrings.getString(ZSIDX_SHORT_GENERIC); |
| } |
| } else { |
| result = zstrings.getString(ZSIDX_LONG_GENERIC); |
| } |
| } |
| if (result == null && mzidToStrings != null) { |
| // try metazone |
| String mzid = ZoneMeta.getMetazoneID(tzid, date); |
| if (mzid != null) { |
| boolean useStandard = false; |
| if (!inDaylightTime(tz,date)) { |
| 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[] offsets = new int[2]; |
| tz.getOffset(date - DST_CHECK_RANGE, false, offsets); |
| if (offsets[1] != 0) { |
| useStandard = false; |
| } else { |
| tz.getOffset(date + DST_CHECK_RANGE, false, offsets); |
| if (offsets[1] != 0){ |
| useStandard = false; |
| } |
| } |
| } |
| } |
| if (useStandard) { |
| result = getString(tzid, (isShort ? ZSIDX_SHORT_STANDARD : ZSIDX_LONG_STANDARD), |
| date, commonlyUsedOnly); |
| |
| // Note: |
| // In CLDR 1.5.1, a same localization is used for both generic and standard |
| // for some metazones in some locales. This is actually data bugs and should |
| // be resolved in later versions of CLDR. For now, we check if the standard |
| // name is different from its generic name below. |
| if (result != null) { |
| String genericNonLocation = getString(tzid, (isShort ? ZSIDX_SHORT_GENERIC : ZSIDX_LONG_GENERIC), |
| date, commonlyUsedOnly); |
| if (genericNonLocation != null && result.equalsIgnoreCase(genericNonLocation)) { |
| result = null; |
| } |
| } |
| } |
| if (result == null){ |
| ZoneStrings mzstrings = mzidToStrings.get(mzid); |
| if (mzstrings != null) { |
| if (isShort) { |
| if (!commonlyUsedOnly || mzstrings.isShortFormatCommonlyUsed()) { |
| result = mzstrings.getString(ZSIDX_SHORT_GENERIC); |
| } |
| } else { |
| result = mzstrings.getString(ZSIDX_LONG_GENERIC); |
| } |
| } |
| if (result != null) { |
| // Check if the offsets at the given time matches the preferred zone's offsets |
| String preferredId = ZoneMeta.getZoneIdByMetazone(mzid, getRegion()); |
| if (!tzid.equals(preferredId)) { |
| // Check if the offsets at the given time are identical with the preferred zone |
| int[] offsets = {0,0}; |
| tz.getOffset(date,false,offsets); |
| int raw = offsets[0]; |
| int sav = offsets[1]; |
| TimeZone preferredZone = TimeZone.getTimeZone(preferredId); |
| int[] preferredOffsets = new int[2]; |
| // Check offset in preferred time zone with wall time. |
| // With getOffset(time, false, preferredOffsets), |
| // you may get incorrect results because of time overlap at DST->STD |
| // transition. |
| preferredZone.getOffset(date + raw + sav, true, preferredOffsets); |
| if (raw != preferredOffsets[0] || sav != preferredOffsets[1]) { |
| // Use generic partial location string as fallback |
| result = zstrings.getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly); |
| } |
| } |
| } |
| } |
| } |
| } |
| if (result == null) { |
| // Use location format as the final fallback |
| result = getString(tzid, ZSIDX_LOCATION, date, false /* not used */); |
| } |
| return result; |
| } |
| |
| /* |
| * Private method to get a generic partial location string |
| */ |
| private String getGenericPartialLocationString(String tzid, boolean isShort, long date, boolean commonlyUsedOnly) { |
| if (!isFullyLoaded) { |
| // Lazy loading |
| loadZone(tzid); |
| } |
| |
| String result = null; |
| String mzid = ZoneMeta.getMetazoneID(tzid, date); |
| if (mzid != null) { |
| ZoneStrings zstrings = tzidToStrings.get(tzid); |
| if (zstrings != null) { |
| result = zstrings.getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly); |
| } |
| } |
| return result; |
| } |
| |
| /* |
| * Gets zoneStrings compatible with DateFormatSymbols for the |
| * specified date. In CLDR 1.5, zone names can be changed |
| * time to time. This method generates flat 2-dimensional |
| * String array including zone ids and its localized strings |
| * at the moment. Thus, even you construct a new ZoneStringFormat |
| * by the zone strings array returned by this method, you will |
| * loose historic name changes. Also, commonly used flag for |
| * short types is not reflected in the result. |
| */ |
| private String[][] getZoneStrings(long date) { |
| loadFull(); |
| |
| Set<String> tzids = tzidToStrings.keySet(); |
| String[][] zoneStrings = new String[tzids.size()][8]; |
| int idx = 0; |
| for (String tzid : tzids) { |
| zoneStrings[idx][0] = tzid; |
| zoneStrings[idx][1] = getLongStandard(tzid, date); |
| zoneStrings[idx][2] = getShortStandard(tzid, date, false); |
| zoneStrings[idx][3] = getLongDaylight(tzid, date); |
| zoneStrings[idx][4] = getShortDaylight(tzid, date, false); |
| zoneStrings[idx][5] = getGenericLocation(tzid); |
| zoneStrings[idx][6] = getLongGenericNonLocation(tzid, date); |
| zoneStrings[idx][7] = getShortGenericNonLocation(tzid, date, false); |
| idx++; |
| } |
| return zoneStrings; |
| } |
| |
| /* |
| * ZoneStrings is an internal implementation class for |
| * holding localized name information for a zone/metazone |
| */ |
| private static class ZoneStrings { |
| private String[] strings; |
| private String[][] genericPartialLocationStrings; |
| private boolean commonlyUsed; |
| |
| private ZoneStrings(String[] zstrarray, boolean commonlyUsed, String[][] genericPartialLocationStrings) { |
| if (zstrarray != null) { |
| int lastIdx = -1; |
| for (int i = 0; i < zstrarray.length; i++) { |
| if (zstrarray[i] != null) { |
| lastIdx = i; |
| } |
| } |
| if (lastIdx != -1) { |
| strings = new String[lastIdx + 1]; |
| System.arraycopy(zstrarray, 0, strings, 0, lastIdx + 1); |
| } |
| } |
| this.commonlyUsed = commonlyUsed; |
| this.genericPartialLocationStrings = genericPartialLocationStrings; |
| } |
| |
| private String getString(int typeIdx) { |
| if (strings != null && typeIdx >= 0 && typeIdx < strings.length) { |
| return strings[typeIdx]; |
| } |
| return null; |
| } |
| |
| private boolean isShortFormatCommonlyUsed() { |
| return commonlyUsed; |
| } |
| |
| private String getGenericPartialLocationString(String mzid, boolean isShort, boolean commonlyUsedOnly) { |
| String result = null; |
| if (genericPartialLocationStrings != null) { |
| for (int i = 0; i < genericPartialLocationStrings.length; i++) { |
| if (genericPartialLocationStrings[i][0].equals(mzid)) { |
| if (isShort) { |
| if (!commonlyUsedOnly || genericPartialLocationStrings[i][3] != null) { |
| result = genericPartialLocationStrings[i][2]; |
| } |
| } else { |
| result = genericPartialLocationStrings[i][1]; |
| } |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| } |
| |
| /* |
| * Returns a localized zone string from bundle. |
| */ |
| private static String getZoneStringFromBundle(ICUResourceBundle bundle, String key, String type) { |
| String zstring = null; |
| if (bundle != null) { |
| try { |
| zstring = bundle.getStringWithFallback(key + "/" + type); |
| } catch (MissingResourceException ex) { |
| // throw away the exception |
| } |
| } |
| return zstring; |
| } |
| |
| /* |
| * Returns if the short strings of the zone/metazone is commonly used. |
| */ |
| private static boolean isCommonlyUsed(ICUResourceBundle bundle, String key) { |
| boolean commonlyUsed = false; |
| if (bundle != null) { |
| try { |
| UResourceBundle cuRes = bundle.getWithFallback(key + "/" + RESKEY_COMMONLY_USED); |
| int cuValue = cuRes.getInt(); |
| commonlyUsed = (cuValue != 0); |
| } catch (MissingResourceException ex) { |
| // throw away the exception |
| } |
| } |
| return commonlyUsed; |
| } |
| |
| /* |
| * Returns a localized country string for the country code. If no actual |
| * localized string is found, countryCode itself is returned. |
| */ |
| private static String getLocalizedCountry(String countryCode, ULocale locale) { |
| String countryStr = null; |
| if (countryCode != null) { |
| ICUResourceBundle rb = |
| (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_REGION_BASE_NAME, locale); |
| // |
| // TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well. |
| // |
| // if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) { |
| // country = ULocale.getDisplayCountry("xx_" + country_code, locale); |
| // } |
| // START WORKAROUND |
| ULocale rbloc = rb.getULocale(); |
| if (!rbloc.equals(ULocale.ROOT) && rbloc.getLanguage().equals(locale.getLanguage())) { |
| countryStr = ULocale.getDisplayCountry("xx_" + countryCode, locale); |
| } |
| // END WORKAROUND |
| if (countryStr == null || countryStr.length() == 0) { |
| countryStr = countryCode; |
| } |
| } |
| return countryStr; |
| } |
| |
| /* |
| * Gets an instance of MessageFormat used for formatting zone fallback string |
| */ |
| private static MessageFormat getFallbackFormat(ULocale locale) { |
| String fallbackPattern = ZoneMeta.getTZLocalizationInfo(locale, ZoneMeta.FALLBACK_FORMAT); |
| if (fallbackPattern == null) { |
| fallbackPattern = "{1} ({0})"; |
| } |
| return new MessageFormat(fallbackPattern, locale); |
| } |
| |
| /* |
| * Gets an instance of MessageFormat used for formatting zone region string |
| */ |
| private static MessageFormat getRegionFormat(ULocale locale) { |
| String regionPattern = ZoneMeta.getTZLocalizationInfo(locale, ZoneMeta.REGION_FORMAT); |
| if (regionPattern == null) { |
| regionPattern = "{0}"; |
| } |
| return new MessageFormat(regionPattern, locale); |
| } |
| |
| /* |
| * Index value mapping between DateFormatSymbols's zoneStrings and |
| * the string types defined in this class. |
| */ |
| private static final int[] INDEXMAP = { |
| -1, // 0 - zone id |
| ZSIDX_LONG_STANDARD, // 1 - long standard |
| ZSIDX_SHORT_STANDARD, // 2 - short standard |
| ZSIDX_LONG_DAYLIGHT, // 3 - long daylight |
| ZSIDX_SHORT_DAYLIGHT, // 4 - short daylight |
| ZSIDX_LOCATION, // 5 - generic location |
| ZSIDX_LONG_GENERIC, // 6 - long generic non-location |
| ZSIDX_SHORT_GENERIC // 7 - short generic non-location |
| }; |
| |
| /* |
| * Convert from zone string array index for zoneStrings used by DateFormatSymbols#get/setZoneStrings |
| * to the type constants defined by this class, such as ZSIDX_LONG_STANDARD. |
| */ |
| private static int getNameTypeIndex(int i) { |
| int idx = -1; |
| if (i >= 1 && i < INDEXMAP.length) { |
| idx = INDEXMAP[i]; |
| } |
| return idx; |
| } |
| |
| /* |
| * Mapping from name type index to name type |
| */ |
| private static final int[] NAMETYPEMAP = { |
| LOCATION, // ZSIDX_LOCATION |
| STANDARD_LONG, // ZSIDX_LONG_STANDARD |
| STANDARD_SHORT, // ZSIDX_SHORT_STANDARD |
| DAYLIGHT_LONG, // ZSIDX_LONG_DAYLIGHT |
| DAYLIGHT_SHORT, // ZSIDX_SHORT_DAYLIGHT |
| GENERIC_LONG, // ZSIDX_LONG_GENERIC |
| GENERIC_SHORT, // ZSIDX_SHORT_GENERIC |
| }; |
| |
| private static int getNameType(int typeIdx) { |
| int type = -1; |
| if (typeIdx >= 0 && typeIdx < NAMETYPEMAP.length) { |
| type = NAMETYPEMAP[typeIdx]; |
| } |
| return type; |
| } |
| |
| /* |
| * Returns region used for ZoneMeta#getZoneIdByMetazone. |
| */ |
| private String getRegion() { |
| if (region == null) { |
| if (locale != null) { |
| region = locale.getCountry(); |
| if (region.length() == 0) { |
| ULocale tmp = ULocale.addLikelySubtags(locale); |
| region = tmp.getCountry(); |
| } |
| } else { |
| region = ""; |
| } |
| } |
| return region; |
| } |
| |
| // This method does lazy zone string loading |
| private ZoneStringInfo find(String text, int start, int types) { |
| ZoneStringInfo result = subFind(text, start, types); |
| if (isFullyLoaded) { |
| return result; |
| } |
| // When zone string data is partially loaded, |
| // this method return the result only when |
| // the input text is fully consumed. |
| if (result != null) { |
| int matchLen = result.getString().length(); |
| if (text.length() - start == matchLen) { |
| return result; |
| } |
| } |
| // Now load all zone strings |
| loadFull(); |
| return subFind(text, start, types); |
| } |
| |
| /* |
| * Find a prefix matching time zone for the given zone string types. |
| * @param text The text contains a time zone string |
| * @param start The start index within the text |
| * @param types The bit mask representing a set of requested types |
| * @return If any zone string matched for the requested types, returns a |
| * ZoneStringInfo for the longest match. If no matches are found for |
| * the requested types, returns a ZoneStringInfo for the longest match |
| * for any other types. If nothing matches at all, returns null. |
| */ |
| private ZoneStringInfo subFind(String text, int start, int types) { |
| ZoneStringInfo result = null; |
| ZoneStringSearchResultHandler handler = new ZoneStringSearchResultHandler(); |
| zoneStringsTrie.find(text, start, handler); |
| List<ZoneStringInfo> list = handler.getMatchedZoneStrings(); |
| ZoneStringInfo fallback = null; |
| if (list != null && list.size() > 0) { |
| Iterator<ZoneStringInfo> it = list.iterator(); |
| while (it.hasNext()) { |
| ZoneStringInfo tmp = it.next(); |
| if ((types & tmp.getType()) != 0) { |
| if (result == null || result.getString().length() < tmp.getString().length()) { |
| result = tmp; |
| } else if (result.getString().length() == tmp.getString().length()) { |
| // Tie breaker - there are some examples that a |
| // long standard name is identical with a location |
| // name - for example, "Uruguay Time". In this case, |
| // we interpret it as generic, not specific. |
| if (tmp.isGeneric() && !result.isGeneric()) { |
| result = tmp; |
| } |
| } |
| } else if (result == null) { |
| if (fallback == null || fallback.getString().length() < tmp.getString().length()) { |
| fallback = tmp; |
| } else if (fallback.getString().length() == tmp.getString().length()) { |
| if (tmp.isGeneric() && !fallback.isGeneric()) { |
| fallback = tmp; |
| } |
| } |
| } |
| } |
| } |
| if (result == null && fallback != null) { |
| result = fallback; |
| } |
| return result; |
| } |
| |
| |
| |
| private static class ZoneStringSearchResultHandler implements TextTrieMap.ResultHandler<ZoneStringInfo> { |
| |
| private ArrayList<ZoneStringInfo> resultList; |
| |
| public boolean handlePrefixMatch(int matchLength, Iterator<ZoneStringInfo> values) { |
| if (resultList == null) { |
| resultList = new ArrayList<ZoneStringInfo>(); |
| } |
| while (values.hasNext()) { |
| ZoneStringInfo zsitem = values.next(); |
| if (zsitem == null) { |
| break; |
| } |
| int i = 0; |
| for (; i < resultList.size(); i++) { |
| ZoneStringInfo tmp = resultList.get(i); |
| if (zsitem.getType() == tmp.getType()) { |
| if (matchLength > tmp.getString().length()) { |
| resultList.set(i, zsitem); |
| } |
| break; |
| } |
| } |
| if (i == resultList.size()) { |
| // not found in the current list |
| resultList.add(zsitem); |
| } |
| } |
| return true; |
| } |
| |
| List<ZoneStringInfo> getMatchedZoneStrings() { |
| if (resultList == null || resultList.size() == 0) { |
| return null; |
| } |
| return resultList; |
| } |
| } |
| } |