]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/impl/ZoneStringFormat.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / impl / ZoneStringFormat.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 2007-2009, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 package com.ibm.icu.impl;\r
8 \r
9 import java.util.ArrayList;\r
10 import java.util.HashMap;\r
11 import java.util.Iterator;\r
12 import java.util.List;\r
13 import java.util.Map;\r
14 import java.util.MissingResourceException;\r
15 import java.util.Set;\r
16 \r
17 import com.ibm.icu.text.MessageFormat;\r
18 import com.ibm.icu.util.BasicTimeZone;\r
19 import com.ibm.icu.util.Calendar;\r
20 import com.ibm.icu.util.TimeZone;\r
21 import com.ibm.icu.util.TimeZoneTransition;\r
22 import com.ibm.icu.util.ULocale;\r
23 import com.ibm.icu.util.UResourceBundle;\r
24 \r
25 /**\r
26  * @author yoshito\r
27  *\r
28  */\r
29 public class ZoneStringFormat {\r
30     /**\r
31      * Constructs a ZoneStringFormat by zone strings array.\r
32      * The internal structure of zoneStrings is compatible with\r
33      * the one used by getZoneStrings/setZoneStrings in DateFormatSymbols.\r
34      * \r
35      * @param zoneStrings zone strings\r
36      */\r
37     public ZoneStringFormat(String[][] zoneStrings) {\r
38         tzidToStrings = new HashMap();\r
39         zoneStringsTrie = new TextTrieMap(true);\r
40         for (int i = 0; i < zoneStrings.length; i++) {\r
41             String tzid = zoneStrings[i][0];\r
42             String[] names = new String[ZSIDX_MAX];\r
43             for (int j = 1; j < zoneStrings[i].length; j++) {\r
44                 if (zoneStrings[i][j] != null) {\r
45                     int typeIdx = getNameTypeIndex(j);\r
46                     if (typeIdx != -1) {\r
47                         names[typeIdx] = zoneStrings[i][j];\r
48 \r
49                         // Put the name into the trie\r
50                         int type = getNameType(typeIdx);\r
51                         ZoneStringInfo zsinfo = new ZoneStringInfo(tzid, zoneStrings[i][j], type);\r
52                         zoneStringsTrie.put(zoneStrings[i][j], zsinfo);\r
53                     }\r
54                     \r
55                 }\r
56             }\r
57             ZoneStrings zstrings = new ZoneStrings(names, true, null);\r
58             tzidToStrings.put(tzid, zstrings);\r
59         }\r
60         isFullyLoaded = true;\r
61     }\r
62 \r
63     /**\r
64      * Gets an instance of ZoneStringFormat for the specified locale\r
65      * @param locale the locale\r
66      * @return An instance of ZoneStringFormat for the locale\r
67      */\r
68     public static ZoneStringFormat getInstance(ULocale locale) {\r
69         ZoneStringFormat tzf = (ZoneStringFormat)TZFORMAT_CACHE.get(locale);\r
70         if (tzf == null) {\r
71             tzf = new ZoneStringFormat(locale);\r
72             TZFORMAT_CACHE.put(locale, tzf);\r
73         }\r
74         return tzf;\r
75     }\r
76 \r
77     public String[][] getZoneStrings() {\r
78         return getZoneStrings(System.currentTimeMillis());\r
79     }\r
80 \r
81     // APIs used by SimpleDateFormat to get a zone string\r
82     public String getSpecificLongString(Calendar cal) {\r
83         if (cal.get(Calendar.DST_OFFSET) == 0) {\r
84             return getString(cal.getTimeZone().getID(), ZSIDX_LONG_STANDARD, cal.getTimeInMillis(), false /* not used */);\r
85         }\r
86         return getString(cal.getTimeZone().getID(), ZSIDX_LONG_DAYLIGHT, cal.getTimeInMillis(), false /* not used */);\r
87     }\r
88 \r
89     public String getSpecificShortString(Calendar cal, boolean commonlyUsedOnly) {\r
90         if (cal.get(Calendar.DST_OFFSET) == 0) {\r
91             return getString(cal.getTimeZone().getID(), ZSIDX_SHORT_STANDARD, cal.getTimeInMillis(), commonlyUsedOnly);\r
92         }\r
93         return getString(cal.getTimeZone().getID(), ZSIDX_SHORT_DAYLIGHT, cal.getTimeInMillis(), commonlyUsedOnly);\r
94     }\r
95 \r
96     public String getGenericLongString(Calendar cal) {\r
97         return getGenericString(cal, false /* long */, false /* not used */);\r
98     }\r
99 \r
100     public String getGenericShortString(Calendar cal, boolean commonlyUsedOnly) {\r
101         return getGenericString(cal, true /* long */, commonlyUsedOnly);\r
102     }\r
103 \r
104     public String getGenericLocationString(Calendar cal) {\r
105         return getString(cal.getTimeZone().getID(), ZSIDX_LOCATION, cal.getTimeInMillis(), false /* not used */);\r
106     }\r
107 \r
108     // APIs used by SimpleDateFormat to lookup a zone string\r
109     public static class ZoneStringInfo {\r
110         private String id;\r
111         private String str;\r
112         private int type;\r
113 \r
114         private ZoneStringInfo(String id, String str, int type) {\r
115             this.id = id;\r
116             this.str = str;\r
117             this.type = type;\r
118         }\r
119 \r
120         public String getID() {\r
121             return id;\r
122         }\r
123 \r
124         public String getString() {\r
125             return str;\r
126         }\r
127 \r
128         public boolean isStandard() {\r
129             if ((type & STANDARD_LONG) != 0 || (type & STANDARD_SHORT) != 0) {\r
130                 return true;\r
131             }\r
132             return false;\r
133         }\r
134 \r
135         public boolean isDaylight() {\r
136             if ((type & DAYLIGHT_LONG) != 0 || (type & DAYLIGHT_SHORT) != 0) {\r
137                 return true;\r
138             }\r
139             return false;\r
140         }\r
141 \r
142         public boolean isGeneric() {\r
143             return !isStandard() && !isDaylight();\r
144         }\r
145 \r
146         private int getType() {\r
147             return type;\r
148         }\r
149     }\r
150 \r
151     public ZoneStringInfo findSpecificLong(String text, int start) {\r
152         return find(text, start, STANDARD_LONG | DAYLIGHT_LONG);\r
153     }\r
154     \r
155     public ZoneStringInfo findSpecificShort(String text, int start) {\r
156         return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT);\r
157     }\r
158 \r
159     public ZoneStringInfo findGenericLong(String text, int start) {\r
160         return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION);\r
161     }\r
162     \r
163     public ZoneStringInfo findGenericShort(String text, int start) {\r
164         return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION);\r
165     }\r
166 \r
167     public ZoneStringInfo findGenericLocation(String text, int start) {\r
168         return find(text, start, LOCATION);\r
169     }\r
170 \r
171     // Following APIs are not used by SimpleDateFormat, but public for testing purpose\r
172     public String getLongStandard(String tzid, long date) {\r
173         return getString(tzid, ZSIDX_LONG_STANDARD, date, false /* not used */);\r
174     }\r
175 \r
176     public String getLongDaylight(String tzid, long date) {\r
177         return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, false /* not used */);\r
178     }\r
179 \r
180     public String getLongGenericNonLocation(String tzid, long date) {\r
181         return getString(tzid, ZSIDX_LONG_GENERIC, date, false /* not used */);\r
182     }\r
183 \r
184     public String getLongGenericPartialLocation(String tzid, long date) {\r
185         return getGenericPartialLocationString(tzid, false, date, false /* not used */);\r
186     }\r
187 \r
188     public String getShortStandard(String tzid, long date, boolean commonlyUsedOnly) {\r
189         return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly);\r
190     }\r
191 \r
192     public String getShortDaylight(String tzid, long date, boolean commonlyUsedOnly) {\r
193         return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly);\r
194     }\r
195 \r
196     public String getShortGenericNonLocation(String tzid, long date, boolean commonlyUsedOnly) {\r
197         return getString(tzid, ZSIDX_SHORT_GENERIC, date, commonlyUsedOnly);\r
198     }\r
199 \r
200     public String getShortGenericPartialLocation(String tzid, long date, boolean commonlyUsedOnly) {\r
201         return getGenericPartialLocationString(tzid, true, date, commonlyUsedOnly);\r
202     }\r
203 \r
204     public String getGenericLocation(String tzid) {\r
205         return getString(tzid, ZSIDX_LOCATION, 0L /* not used */, false /* not used */);\r
206     }\r
207 \r
208     /**\r
209      * Constructs a ZoneStringFormat by locale.  Because an instance of ZoneStringFormat\r
210      * is read-only, only one instance for a locale is sufficient.  Thus, this\r
211      * constructor is protected and only called from getInstance(ULocale) to\r
212      * create one for a locale.\r
213      * @param locale The locale\r
214      */\r
215     protected ZoneStringFormat(ULocale locale) {\r
216         this.locale = locale;\r
217         tzidToStrings = new HashMap();\r
218         mzidToStrings = new HashMap();\r
219         zoneStringsTrie = new TextTrieMap(true);\r
220     }\r
221 \r
222     // Load only a single zone\r
223     private synchronized void loadZone(String id) {\r
224         if (isFullyLoaded) {\r
225             return;\r
226         }\r
227         String tzid = ZoneMeta.getCanonicalSystemID(id);\r
228         if (tzid == null || tzidToStrings.containsKey(tzid)) {\r
229             return;\r
230         }\r
231 \r
232         ICUResourceBundle zoneStringsBundle = null;\r
233         try {\r
234             ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);\r
235             zoneStringsBundle = bundle.getWithFallback("zoneStrings");\r
236         } catch (MissingResourceException e) {\r
237             // If no locale bundles are available, zoneStringsBundle will be null.\r
238             // We still want to go through the rest of zone strings initialization,\r
239             // because generic location format is generated from tzid for the case.\r
240             // The rest of code should work even zoneStrings is null.\r
241         }\r
242 \r
243         String[] zstrarray = new String[ZSIDX_MAX];\r
244         String[] mzstrarray = new String[ZSIDX_MAX];\r
245         String[][] mzPartialLoc = new String[10][4]; // maximum 10 metazones per zone\r
246 \r
247         addSingleZone(tzid, zoneStringsBundle,\r
248                 getFallbackFormat(locale), getRegionFormat(locale),\r
249                 zstrarray, mzstrarray, mzPartialLoc);\r
250     }\r
251 \r
252     // Loading all zone strings\r
253     private synchronized void loadFull() {\r
254         if (isFullyLoaded) {\r
255             return;\r
256         }\r
257         ICUResourceBundle zoneStringsBundle = null;\r
258         try {\r
259             ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);\r
260             zoneStringsBundle = bundle.getWithFallback("zoneStrings");\r
261         } catch (MissingResourceException e) {\r
262             // If no locale bundles are available, zoneStringsBundle will be null.\r
263             // We still want to go through the rest of zone strings initialization,\r
264             // because generic location format is generated from tzid for the case.\r
265             // The rest of code should work even zoneStrings is null.\r
266         }\r
267 \r
268         String[] zoneIDs = TimeZone.getAvailableIDs();\r
269 \r
270         String[] zstrarray = new String[ZSIDX_MAX];\r
271         String[] mzstrarray = new String[ZSIDX_MAX];\r
272         String[][] mzPartialLoc = new String[10][4]; // maximum 10 metazones per zone\r
273 \r
274         for (int i = 0; i < zoneIDs.length; i++) {\r
275             // Skip aliases\r
276             String tzid = ZoneMeta.getCanonicalSystemID(zoneIDs[i]);\r
277             if (tzid == null || !zoneIDs[i].equals(tzid)) {\r
278                 continue;\r
279             }\r
280 \r
281             if (tzidToStrings.containsKey(tzid)) {\r
282                 continue;\r
283             }\r
284 \r
285             addSingleZone(tzid, zoneStringsBundle,\r
286                     getFallbackFormat(locale), getRegionFormat(locale),\r
287                     zstrarray, mzstrarray, mzPartialLoc);\r
288         }\r
289         isFullyLoaded = true;\r
290     }\r
291 \r
292     // This internal initialization code must be called in a synchronized block\r
293     private void addSingleZone(String tzid, ICUResourceBundle zoneStringsBundle,\r
294             MessageFormat fallbackFmt, MessageFormat regionFmt,\r
295             String[] zstrarray, String[] mzstrarray, String[][] mzPartialLoc) {\r
296 \r
297         if (tzidToStrings.containsKey(tzid)) {\r
298             return;\r
299         }\r
300 \r
301         String zoneKey = tzid.replace('/', ':');\r
302         zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_STANDARD);\r
303         zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_STANDARD);\r
304         zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_DAYLIGHT);\r
305         zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_DAYLIGHT);\r
306         zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_GENERIC);\r
307         zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_GENERIC);\r
308 \r
309         // Compose location format string\r
310         String countryCode = ZoneMeta.getCanonicalCountry(tzid);\r
311         String country = null;\r
312         String city = null;\r
313         if (countryCode != null) {\r
314             city = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_EXEMPLAR_CITY);\r
315             if (city == null) {\r
316                 city = tzid.substring(tzid.lastIndexOf('/') + 1).replace('_', ' ');\r
317             }\r
318             country = getLocalizedCountry(countryCode, locale);\r
319             if (ZoneMeta.getSingleCountry(tzid) != null) {\r
320                 // If the zone is only one zone in the country, do not add city\r
321                 zstrarray[ZSIDX_LOCATION] = regionFmt.format(new Object[] {country});\r
322             } else {\r
323                 zstrarray[ZSIDX_LOCATION] = fallbackFmt.format(new Object[] {city, country});\r
324             }\r
325         } else {\r
326             if (tzid.startsWith("Etc/")) {\r
327                 // "Etc/xxx" is not associated with a specific location, so localized\r
328                 // GMT format is always used as generic location format.\r
329                 zstrarray[ZSIDX_LOCATION] = null;\r
330             } else {\r
331                 // When a new time zone ID, which is actually associated with a specific\r
332                 // location, is added in tzdata, but the current CLDR data does not have\r
333                 // the information yet, ICU creates a generic location string based on \r
334                 // the ID.  This implementation supports canonical time zone round trip\r
335                 // with format pattern "VVVV".  See #6602 for the details.\r
336                 String location = tzid;\r
337                 int slashIdx = location.lastIndexOf('/');\r
338                 if (slashIdx == -1) {\r
339                     // A time zone ID without slash in the tz database is not\r
340                     // associated with a specific location.  For instances,\r
341                     // MET, CET, EET and WET fall into this catetory.\r
342                     zstrarray[ZSIDX_LOCATION] = null;\r
343                 } else {\r
344                     location = tzid.substring(slashIdx + 1);\r
345                     zstrarray[ZSIDX_LOCATION] = regionFmt.format(new Object[] {location});\r
346                 }\r
347             }\r
348         }\r
349 \r
350         boolean commonlyUsed = isCommonlyUsed(zoneStringsBundle, zoneKey);\r
351         \r
352         // Resolve metazones used by this zone\r
353         int mzPartialLocIdx = 0;\r
354         Map olsonToMeta = ZoneMeta.getOlsonToMetaMap();\r
355         List metazoneMappings = (List)olsonToMeta.get(tzid);\r
356         if (metazoneMappings != null) {\r
357             Iterator it = metazoneMappings.iterator();\r
358             while (it.hasNext()) {\r
359                 ZoneMeta.OlsonToMetaMappingEntry mzmap = (ZoneMeta.OlsonToMetaMappingEntry)it.next();\r
360                 ZoneStrings mzStrings = (ZoneStrings)mzidToStrings.get(mzmap.mzid);\r
361                 if (mzStrings == null) {\r
362                     // If the metazone strings are not yet processed, do it now.\r
363                     String mzkey = "meta:" + mzmap.mzid;\r
364                     boolean mzCommonlyUsed = isCommonlyUsed(zoneStringsBundle, mzkey);\r
365                     mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_STANDARD);\r
366                     mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_STANDARD);\r
367                     mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_DAYLIGHT);\r
368                     mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_DAYLIGHT);\r
369                     mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_GENERIC);\r
370                     mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_GENERIC);\r
371                     mzstrarray[ZSIDX_LOCATION] = null;\r
372                     mzStrings = new ZoneStrings(mzstrarray, mzCommonlyUsed, null);\r
373                     mzidToStrings.put(mzmap.mzid, mzStrings);\r
374 \r
375                     // Add metazone strings to the zone string trie\r
376                     String preferredIdForLocale = ZoneMeta.getZoneIdByMetazone(mzmap.mzid, getRegion());\r
377                     for (int j = 0; j < mzstrarray.length; j++) {\r
378                         if (mzstrarray[j] != null) {\r
379                             int type = getNameType(j);\r
380                             ZoneStringInfo zsinfo = new ZoneStringInfo(preferredIdForLocale, mzstrarray[j], type);\r
381                             zoneStringsTrie.put(mzstrarray[j], zsinfo);\r
382                         }\r
383                     }\r
384                 }\r
385                 // Compose generic partial location format\r
386                 String lg = mzStrings.getString(ZSIDX_LONG_GENERIC);\r
387                 String sg = mzStrings.getString(ZSIDX_SHORT_GENERIC);\r
388                 if (lg != null || sg != null) {\r
389                     boolean addMzPartialLocationNames = true;\r
390                     for (int j = 0; j < mzPartialLocIdx; j++) {\r
391                         if (mzPartialLoc[j][0].equals(mzmap.mzid)) {\r
392                             // already added\r
393                             addMzPartialLocationNames = false;\r
394                             break;\r
395                         }\r
396                     }\r
397                     if (addMzPartialLocationNames) {\r
398                         String locationPart = null;\r
399                         // Check if the zone is the preferred zone for the territory associated with the zone\r
400                         String preferredID = ZoneMeta.getZoneIdByMetazone(mzmap.mzid, countryCode);\r
401                         if (tzid.equals(preferredID)) {\r
402                             // Use country for the location\r
403                             locationPart = country;\r
404                         } else {\r
405                             // Use city for the location\r
406                             locationPart = city;\r
407                         }\r
408                         mzPartialLoc[mzPartialLocIdx][0] = mzmap.mzid;\r
409                         mzPartialLoc[mzPartialLocIdx][1] = null;\r
410                         mzPartialLoc[mzPartialLocIdx][2] = null;\r
411                         mzPartialLoc[mzPartialLocIdx][3] = null;\r
412                         if (locationPart != null) {\r
413                             if (lg != null) {\r
414                                 mzPartialLoc[mzPartialLocIdx][1] = fallbackFmt.format(new Object[] {locationPart, lg});\r
415                             }\r
416                             if (sg != null) {\r
417                                 mzPartialLoc[mzPartialLocIdx][2] = fallbackFmt.format(new Object[] {locationPart, sg});\r
418                                 boolean shortMzCommonlyUsed = mzStrings.isShortFormatCommonlyUsed();\r
419                                 if (shortMzCommonlyUsed) {\r
420                                     mzPartialLoc[mzPartialLocIdx][3] = "1";\r
421                                 }\r
422                             }\r
423                         }\r
424                         mzPartialLocIdx++;\r
425                     }\r
426                 }\r
427             }\r
428         }\r
429         String[][] genericPartialLocationNames = null;\r
430         if (mzPartialLocIdx != 0) {\r
431             // metazone generic partial location names are collected\r
432             genericPartialLocationNames = new String[mzPartialLocIdx][];\r
433             for (int mzi = 0; mzi < mzPartialLocIdx; mzi++) {\r
434                 genericPartialLocationNames[mzi] = (String[])mzPartialLoc[mzi].clone();\r
435             }\r
436         }\r
437         // Finally, create ZoneStrings instance and put it into the tzidToStinrgs map\r
438         ZoneStrings zstrings = new ZoneStrings(zstrarray, commonlyUsed, genericPartialLocationNames);\r
439         tzidToStrings.put(tzid, zstrings);\r
440 \r
441         // Also add all available names to the zone string trie\r
442         if (zstrarray != null) {\r
443             for (int j = 0; j < zstrarray.length; j++) {\r
444                 if (zstrarray[j] != null) {\r
445                     int type = getNameType(j);\r
446                     ZoneStringInfo zsinfo = new ZoneStringInfo(tzid, zstrarray[j], type);\r
447                     zoneStringsTrie.put(zstrarray[j], zsinfo);\r
448                 }\r
449             }\r
450         }\r
451         if (genericPartialLocationNames != null) {\r
452             for (int j = 0; j < genericPartialLocationNames.length; j++) {\r
453                 ZoneStringInfo zsinfo;\r
454                 if (genericPartialLocationNames[j][1] != null) {\r
455                     zsinfo = new ZoneStringInfo(tzid, genericPartialLocationNames[j][1], GENERIC_LONG);\r
456                     zoneStringsTrie.put(genericPartialLocationNames[j][1], zsinfo);\r
457                 }\r
458                 if (genericPartialLocationNames[j][2] != null) {\r
459                     zsinfo = new ZoneStringInfo(tzid, genericPartialLocationNames[j][1], GENERIC_SHORT);\r
460                     zoneStringsTrie.put(genericPartialLocationNames[j][2], zsinfo);\r
461                 }\r
462             }\r
463         }\r
464     }\r
465 \r
466     // Name types, these bit flag are used for zone string lookup\r
467     private static final int LOCATION = 0x0001;\r
468     private static final int GENERIC_LONG = 0x0002;\r
469     private static final int GENERIC_SHORT = 0x0004;\r
470     private static final int STANDARD_LONG = 0x0008;\r
471     private static final int STANDARD_SHORT = 0x0010;\r
472     private static final int DAYLIGHT_LONG = 0x0020;\r
473     private static final int DAYLIGHT_SHORT = 0x0040;\r
474     \r
475     // Name type index, these constants are used for index in ZoneStrings.strings\r
476     private static final int ZSIDX_LOCATION = 0;\r
477     private static final int ZSIDX_LONG_STANDARD = 1;\r
478     private static final int ZSIDX_SHORT_STANDARD = 2;\r
479     private static final int ZSIDX_LONG_DAYLIGHT = 3;\r
480     private static final int ZSIDX_SHORT_DAYLIGHT = 4;\r
481     private static final int ZSIDX_LONG_GENERIC = 5;\r
482     private static final int ZSIDX_SHORT_GENERIC = 6;\r
483 \r
484     private static final int ZSIDX_MAX = ZSIDX_SHORT_GENERIC + 1;\r
485 \r
486     // ZoneStringFormat cache\r
487     private static ICUCache TZFORMAT_CACHE = new SimpleCache();\r
488 \r
489     /*\r
490      * The translation type of the translated zone strings\r
491      */\r
492     private static final String\r
493          RESKEY_SHORT_GENERIC  = "sg",\r
494          RESKEY_SHORT_STANDARD = "ss",\r
495          RESKEY_SHORT_DAYLIGHT = "sd",\r
496          RESKEY_LONG_GENERIC   = "lg",\r
497          RESKEY_LONG_STANDARD  = "ls",\r
498          RESKEY_LONG_DAYLIGHT  = "ld",\r
499          RESKEY_EXEMPLAR_CITY  = "ec",\r
500          RESKEY_COMMONLY_USED  = "cu";\r
501 \r
502     // Window size used for DST check for a zone in a metazone\r
503     private static final long DST_CHECK_RANGE = 184L*(24*60*60*1000);\r
504 \r
505     // Map from zone id to ZoneStrings\r
506     private Map tzidToStrings;\r
507 \r
508     // Map from metazone id to ZoneStrings\r
509     private Map mzidToStrings;\r
510 \r
511     // Zone string dictionary, used for look up\r
512     private TextTrieMap zoneStringsTrie;\r
513 \r
514     // Locale used for initializing zone strings\r
515     private ULocale locale;\r
516 \r
517     // Region used for resolving a zone in a metazone, initialized by locale\r
518     private transient String region;\r
519 \r
520     // Loading status\r
521     private boolean isFullyLoaded = false;\r
522 \r
523     /*\r
524      * Private method to get a zone string except generic partial location types.\r
525      */\r
526     private String getString(String tzid, int typeIdx, long date, boolean commonlyUsedOnly) {\r
527         if (!isFullyLoaded) {\r
528             // Lazy loading\r
529             loadZone(tzid);\r
530         }\r
531 \r
532         String result = null;\r
533         ZoneStrings zstrings = (ZoneStrings)tzidToStrings.get(tzid);\r
534         if (zstrings == null) {\r
535             // ICU's own array does not have entries for aliases\r
536             String canonicalID = ZoneMeta.getCanonicalSystemID(tzid);\r
537             if (canonicalID != null && !canonicalID.equals(tzid)) {\r
538                 // Canonicalize tzid here.  The rest of operations\r
539                 // require tzid to be canonicalized.\r
540                 tzid = canonicalID;\r
541                 zstrings = (ZoneStrings)tzidToStrings.get(tzid);\r
542             }\r
543         }\r
544         if (zstrings != null) {\r
545             switch (typeIdx) {\r
546             case ZSIDX_LONG_STANDARD:\r
547             case ZSIDX_LONG_DAYLIGHT:\r
548             case ZSIDX_LONG_GENERIC:\r
549             case ZSIDX_LOCATION:\r
550                 result = zstrings.getString(typeIdx);\r
551                 break;\r
552             case ZSIDX_SHORT_STANDARD:\r
553             case ZSIDX_SHORT_DAYLIGHT:\r
554             case ZSIDX_SHORT_GENERIC:\r
555                 if (!commonlyUsedOnly || zstrings.isShortFormatCommonlyUsed()) {\r
556                     result = zstrings.getString(typeIdx);\r
557                 }\r
558                 break;\r
559             }\r
560         }\r
561         if (result == null && mzidToStrings != null && typeIdx != ZSIDX_LOCATION) {\r
562             // Try metazone\r
563             String mzid = ZoneMeta.getMetazoneID(tzid, date);\r
564             if (mzid != null) {\r
565                 ZoneStrings mzstrings = (ZoneStrings)mzidToStrings.get(mzid);\r
566                 if (mzstrings != null) {\r
567                     switch (typeIdx) {\r
568                     case ZSIDX_LONG_STANDARD:\r
569                     case ZSIDX_LONG_DAYLIGHT:\r
570                     case ZSIDX_LONG_GENERIC:\r
571                         result = mzstrings.getString(typeIdx);\r
572                         break;\r
573                     case ZSIDX_SHORT_STANDARD:\r
574                     case ZSIDX_SHORT_DAYLIGHT:\r
575                     case ZSIDX_SHORT_GENERIC:\r
576                         if (!commonlyUsedOnly || mzstrings.isShortFormatCommonlyUsed()) {\r
577                             result = mzstrings.getString(typeIdx);\r
578                         }\r
579                         break;\r
580                     }\r
581                 }\r
582             }\r
583         }\r
584         return result;\r
585     }\r
586 \r
587     /*\r
588      * Private method to get a generic string, with fallback logic involved,\r
589      * that is,\r
590      * \r
591      * 1. If a generic non-location string is avaiable for the zone, return it.\r
592      * 2. If a generic non-location string is associated with a metazone and \r
593      *    the zone never use daylight time around the given date, use the standard\r
594      *    string (if available).\r
595      *    \r
596      *    Note: In CLDR1.5.1, the same localization is used for generic and standard.\r
597      *    In this case, we do not use the standard string and do the rest.\r
598      *    \r
599      * 3. If a generic non-location string is associated with a metazone and\r
600      *    the offset at the given time is different from the preferred zone for the\r
601      *    current locale, then return the generic partial location string (if avaiable)\r
602      * 4. If a generic non-location string is not available, use generic location\r
603      *    string.\r
604      */\r
605     private String getGenericString(Calendar cal, boolean isShort, boolean commonlyUsedOnly) {\r
606         String result = null;\r
607         TimeZone tz = cal.getTimeZone();\r
608         String tzid = tz.getID();\r
609 \r
610         if (!isFullyLoaded) {\r
611             // Lazy loading\r
612             loadZone(tzid);\r
613         }\r
614 \r
615         ZoneStrings zstrings = (ZoneStrings)tzidToStrings.get(tzid);\r
616         if (zstrings == null) {\r
617             // ICU's own array does not have entries for aliases\r
618             String canonicalID = ZoneMeta.getCanonicalSystemID(tzid);\r
619             if (canonicalID != null && !canonicalID.equals(tzid)) {\r
620                 // Canonicalize tzid here.  The rest of operations\r
621                 // require tzid to be canonicalized.\r
622                 tzid = canonicalID;\r
623                 zstrings = (ZoneStrings)tzidToStrings.get(tzid);\r
624             }\r
625         }\r
626         if (zstrings != null) {\r
627             if (isShort) {\r
628                 if (!commonlyUsedOnly || zstrings.isShortFormatCommonlyUsed()) {\r
629                     result = zstrings.getString(ZSIDX_SHORT_GENERIC);\r
630                 }\r
631             } else {\r
632                 result = zstrings.getString(ZSIDX_LONG_GENERIC);\r
633             }\r
634         }\r
635         if (result == null && mzidToStrings != null) {\r
636             // try metazone\r
637             long time = cal.getTimeInMillis();\r
638             String mzid = ZoneMeta.getMetazoneID(tzid, time);\r
639             if (mzid != null) {\r
640                 boolean useStandard = false;\r
641                 if (cal.get(Calendar.DST_OFFSET) == 0) {\r
642                     useStandard = true;\r
643                     // Check if the zone actually uses daylight saving time around the time\r
644                     if (tz instanceof BasicTimeZone) {\r
645                         BasicTimeZone btz = (BasicTimeZone)tz;\r
646                         TimeZoneTransition before = btz.getPreviousTransition(time, true);\r
647                         if (before != null\r
648                                 && (time - before.getTime() < DST_CHECK_RANGE)\r
649                                 && before.getFrom().getDSTSavings() != 0) {\r
650                             useStandard = false;\r
651                         } else {\r
652                             TimeZoneTransition after = btz.getNextTransition(time, false);\r
653                             if (after != null\r
654                                     && (after.getTime() - time < DST_CHECK_RANGE)\r
655                                     && after.getTo().getDSTSavings() != 0) {\r
656                                 useStandard = false;\r
657                             }\r
658                         }\r
659                     } else {\r
660                         // If not BasicTimeZone... only if the instance is not an ICU's implementation.\r
661                         // We may get a wrong answer in edge case, but it should practically work OK.\r
662                         int[] offsets = new int[2];\r
663                         tz.getOffset(time - DST_CHECK_RANGE, false, offsets);\r
664                         if (offsets[1] != 0) {\r
665                             useStandard = false;\r
666                         } else {\r
667                             tz.getOffset(time + DST_CHECK_RANGE, false, offsets);\r
668                             if (offsets[1] != 0){\r
669                                 useStandard = false;\r
670                             }\r
671                         }\r
672                     }\r
673                 }\r
674                 if (useStandard) {\r
675                     result = getString(tzid, (isShort ? ZSIDX_SHORT_STANDARD : ZSIDX_LONG_STANDARD),\r
676                             time, commonlyUsedOnly);\r
677 \r
678                     // Note:\r
679                     // In CLDR 1.5.1, a same localization is used for both generic and standard\r
680                     // for some metazones in some locales.  This is actually data bugs and should\r
681                     // be resolved in later versions of CLDR.  For now, we check if the standard\r
682                     // name is different from its generic name below.\r
683                     if (result != null) {\r
684                         String genericNonLocation = getString(tzid, (isShort ? ZSIDX_SHORT_GENERIC : ZSIDX_LONG_GENERIC),\r
685                                 time, commonlyUsedOnly);\r
686                         if (genericNonLocation != null && result.equalsIgnoreCase(genericNonLocation)) {\r
687                             result = null;\r
688                         }\r
689                     }\r
690                 }\r
691                 if (result == null){\r
692                     ZoneStrings mzstrings = (ZoneStrings)mzidToStrings.get(mzid);\r
693                     if (mzstrings != null) {\r
694                         if (isShort) {\r
695                             if (!commonlyUsedOnly || mzstrings.isShortFormatCommonlyUsed()) {\r
696                                 result = mzstrings.getString(ZSIDX_SHORT_GENERIC);\r
697                             }\r
698                         } else {\r
699                             result = mzstrings.getString(ZSIDX_LONG_GENERIC);\r
700                         }\r
701                     }\r
702                     if (result != null) {\r
703                         // Check if the offsets at the given time matches the preferred zone's offsets\r
704                         String preferredId = ZoneMeta.getZoneIdByMetazone(mzid, getRegion());\r
705                         if (!tzid.equals(preferredId)) {\r
706                             // Check if the offsets at the given time are identical with the preferred zone\r
707                             int raw = cal.get(Calendar.ZONE_OFFSET);\r
708                             int sav = cal.get(Calendar.DST_OFFSET);\r
709                             TimeZone preferredZone = TimeZone.getTimeZone(preferredId);\r
710                             int[] preferredOffsets = new int[2];\r
711                             // Check offset in preferred time zone with wall time.\r
712                             // With getOffset(time, false, preferredOffsets),\r
713                             // you may get incorrect results because of time overlap at DST->STD\r
714                             // transition.\r
715                             preferredZone.getOffset(time + raw + sav, true, preferredOffsets);\r
716                             if (raw != preferredOffsets[0] || sav != preferredOffsets[1]) {\r
717                                 // Use generic partial location string as fallback\r
718                                 result = zstrings.getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly);\r
719                             }\r
720                         }\r
721                     }\r
722                 }\r
723             }\r
724         }\r
725         if (result == null) {\r
726             // Use location format as the final fallback\r
727             result = getString(tzid, ZSIDX_LOCATION, cal.getTimeInMillis(), false /* not used */);\r
728         }\r
729         return result;\r
730     }\r
731     \r
732     /*\r
733      * Private method to get a generic partial location string\r
734      */\r
735     private String getGenericPartialLocationString(String tzid, boolean isShort, long date, boolean commonlyUsedOnly) {\r
736         if (!isFullyLoaded) {\r
737             // Lazy loading\r
738             loadZone(tzid);\r
739         }\r
740 \r
741         String result = null;\r
742         String mzid = ZoneMeta.getMetazoneID(tzid, date);\r
743         if (mzid != null) {\r
744             ZoneStrings zstrings = (ZoneStrings)tzidToStrings.get(tzid);\r
745             if (zstrings != null) {\r
746                 result = zstrings.getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly);\r
747             }\r
748         }\r
749         return result;\r
750     }\r
751 \r
752     /*\r
753      * Gets zoneStrings compatible with DateFormatSymbols for the\r
754      * specified date.  In CLDR 1.5, zone names can be changed\r
755      * time to time.  This method generates flat 2-dimensional\r
756      * String array including zone ids and its localized strings\r
757      * at the moment.  Thus, even you construct a new ZoneStringFormat\r
758      * by the zone strings array returned by this method, you will\r
759      * loose historic name changes.  Also, commonly used flag for\r
760      * short types is not reflected in the result.\r
761      */\r
762     private String[][] getZoneStrings(long date) {\r
763         loadFull();\r
764 \r
765         Set tzids = tzidToStrings.keySet();\r
766         String[][] zoneStrings = new String[tzids.size()][8];\r
767         int idx = 0;\r
768         Iterator it = tzids.iterator();\r
769         while (it.hasNext()) {\r
770             String tzid = (String)it.next();\r
771             zoneStrings[idx][0] = tzid;\r
772             zoneStrings[idx][1] = getLongStandard(tzid, date);\r
773             zoneStrings[idx][2] = getShortStandard(tzid, date, false);\r
774             zoneStrings[idx][3] = getLongDaylight(tzid, date);\r
775             zoneStrings[idx][4] = getShortDaylight(tzid, date, false);\r
776             zoneStrings[idx][5] = getGenericLocation(tzid);\r
777             zoneStrings[idx][6] = getLongGenericNonLocation(tzid, date);\r
778             zoneStrings[idx][7] = getShortGenericNonLocation(tzid, date, false);\r
779             idx++;\r
780         }\r
781         return zoneStrings;\r
782     }\r
783     \r
784     /*\r
785      * ZoneStrings is an internal implementation class for\r
786      * holding localized name information for a zone/metazone\r
787      */\r
788     private static class ZoneStrings {\r
789         private String[] strings;\r
790         private String[][] genericPartialLocationStrings;\r
791         private boolean commonlyUsed;\r
792  \r
793         private ZoneStrings(String[] zstrarray, boolean commonlyUsed, String[][] genericPartialLocationStrings) {\r
794             if (zstrarray != null) {\r
795                 int lastIdx = -1;\r
796                 for (int i = 0; i < zstrarray.length; i++) {\r
797                     if (zstrarray[i] != null) {\r
798                         lastIdx = i;\r
799                     }\r
800                 }\r
801                 if (lastIdx != -1) {\r
802                     strings = new String[lastIdx + 1];\r
803                     System.arraycopy(zstrarray, 0, strings, 0, lastIdx + 1);\r
804                 }\r
805             }\r
806             this.commonlyUsed = commonlyUsed;\r
807             this.genericPartialLocationStrings = genericPartialLocationStrings;\r
808         }\r
809 \r
810         private String getString(int typeIdx) {\r
811             if (strings != null && typeIdx >= 0 && typeIdx < strings.length) {\r
812                 return strings[typeIdx];\r
813             }\r
814             return null;\r
815         }\r
816 \r
817         private boolean isShortFormatCommonlyUsed() {\r
818             return commonlyUsed;\r
819         }\r
820 \r
821         private String getGenericPartialLocationString(String mzid, boolean isShort, boolean commonlyUsedOnly) {\r
822             String result = null;\r
823             if (genericPartialLocationStrings != null) {\r
824                 for (int i = 0; i < genericPartialLocationStrings.length; i++) {\r
825                     if (genericPartialLocationStrings[i][0].equals(mzid)) {\r
826                         if (isShort) {\r
827                             if (!commonlyUsedOnly || genericPartialLocationStrings[i][3] != null) {\r
828                                 result = genericPartialLocationStrings[i][2];\r
829                             }\r
830                         } else {\r
831                             result = genericPartialLocationStrings[i][1];\r
832                         }\r
833                         break;\r
834                     }\r
835                 }\r
836             }\r
837             return result;\r
838         }\r
839     }\r
840 \r
841     /*\r
842      * Returns a localized zone string from bundle.\r
843      */\r
844     private static String getZoneStringFromBundle(ICUResourceBundle bundle, String key, String type) {\r
845         String zstring = null;\r
846         if (bundle != null) {\r
847             try {\r
848                 zstring = bundle.getStringWithFallback(key + "/" + type);\r
849             } catch (MissingResourceException ex) {\r
850                 // throw away the exception\r
851             }\r
852         }\r
853         return zstring;\r
854     }\r
855 \r
856     /*\r
857      * Returns if the short strings of the zone/metazone is commonly used.\r
858      */\r
859     private static boolean isCommonlyUsed(ICUResourceBundle bundle, String key) {\r
860         boolean commonlyUsed = false;\r
861         if (bundle != null) {\r
862             try {\r
863                 UResourceBundle cuRes = bundle.getWithFallback(key + "/" + RESKEY_COMMONLY_USED);\r
864                 int cuValue = cuRes.getInt();\r
865                 commonlyUsed = (cuValue != 0);\r
866             } catch (MissingResourceException ex) {\r
867                 // throw away the exception\r
868             }\r
869         }\r
870         return commonlyUsed;\r
871     }\r
872 \r
873     /*\r
874      * Returns a localized country string for the country code.  If no actual\r
875      * localized string is found, countryCode itself is returned.\r
876      */\r
877     private static String getLocalizedCountry(String countryCode, ULocale locale) {\r
878         String countryStr = null;\r
879         if (countryCode != null) {\r
880             ICUResourceBundle rb = \r
881                 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);\r
882 //\r
883 // TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well.\r
884 //\r
885 //            if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) {\r
886 //                country = ULocale.getDisplayCountry("xx_" + country_code, locale);\r
887 //            }\r
888 // START WORKAROUND\r
889             ULocale rbloc = rb.getULocale();\r
890             if (!rbloc.equals(ULocale.ROOT) && rbloc.getLanguage().equals(locale.getLanguage())) {\r
891                 countryStr = ULocale.getDisplayCountry("xx_" + countryCode, locale);\r
892             }\r
893 // END WORKAROUND\r
894             if (countryStr == null || countryStr.length() == 0) {\r
895                 countryStr = countryCode;\r
896             }\r
897         }\r
898         return countryStr;\r
899     }\r
900 \r
901     /*\r
902      * Gets an instance of MessageFormat used for formatting zone fallback string\r
903      */\r
904     private static MessageFormat getFallbackFormat(ULocale locale) {\r
905         String fallbackPattern = ZoneMeta.getTZLocalizationInfo(locale, ZoneMeta.FALLBACK_FORMAT);\r
906         if (fallbackPattern == null) {\r
907             fallbackPattern = "{1} ({0})";\r
908         }\r
909         return new MessageFormat(fallbackPattern, locale);\r
910     }\r
911 \r
912     /*\r
913      * Gets an instance of MessageFormat used for formatting zone region string\r
914      */\r
915     private static MessageFormat getRegionFormat(ULocale locale) {\r
916         String regionPattern = ZoneMeta.getTZLocalizationInfo(locale, ZoneMeta.REGION_FORMAT);\r
917         if (regionPattern == null) {\r
918             regionPattern = "{0}";\r
919         }\r
920         return new MessageFormat(regionPattern, locale);\r
921     }\r
922 \r
923     /*\r
924      * Index value mapping between DateFormatSymbols's zoneStrings and\r
925      * the string types defined in this class.\r
926      */\r
927     private static final int[] INDEXMAP = {\r
928         -1,             // 0 - zone id\r
929         ZSIDX_LONG_STANDARD,  // 1 - long standard\r
930         ZSIDX_SHORT_STANDARD, // 2 - short standard\r
931         ZSIDX_LONG_DAYLIGHT,  // 3 - long daylight\r
932         ZSIDX_SHORT_DAYLIGHT, // 4 - short daylight\r
933         ZSIDX_LOCATION,       // 5 - generic location\r
934         ZSIDX_LONG_GENERIC,   // 6 - long generic non-location\r
935         ZSIDX_SHORT_GENERIC   // 7 - short generic non-location\r
936     };\r
937 \r
938     /*\r
939      * Convert from zone string array index for zoneStrings used by DateFormatSymbols#get/setZoneStrings\r
940      * to the type constants defined by this class, such as ZSIDX_LONG_STANDARD.\r
941      */\r
942     private static int getNameTypeIndex(int i) {\r
943         int idx = -1;\r
944         if (i >= 1 && i < INDEXMAP.length) {\r
945             idx = INDEXMAP[i];\r
946         }\r
947         return idx;\r
948     }\r
949 \r
950     /*\r
951      * Mapping from name type index to name type\r
952      */\r
953     private static final int[] NAMETYPEMAP = {\r
954         LOCATION,       // ZSIDX_LOCATION\r
955         STANDARD_LONG,  // ZSIDX_LONG_STANDARD\r
956         STANDARD_SHORT, // ZSIDX_SHORT_STANDARD\r
957         DAYLIGHT_LONG,  // ZSIDX_LONG_DAYLIGHT\r
958         DAYLIGHT_SHORT, // ZSIDX_SHORT_DAYLIGHT\r
959         GENERIC_LONG,   // ZSIDX_LONG_GENERIC\r
960         GENERIC_SHORT,  // ZSIDX_SHORT_GENERIC\r
961     };\r
962 \r
963     private static int getNameType(int typeIdx) {\r
964         int type = -1;\r
965         if (typeIdx >= 0 && typeIdx < NAMETYPEMAP.length) {\r
966             type = NAMETYPEMAP[typeIdx];\r
967         }\r
968         return type;\r
969     }\r
970 \r
971     /*\r
972      * Returns region used for ZoneMeta#getZoneIdByMetazone.\r
973      */\r
974     private String getRegion() {\r
975         if (region == null) {\r
976             if (locale != null) {\r
977                 region = locale.getCountry();\r
978                 if (region.length() == 0) {\r
979                     ULocale tmp = ULocale.addLikelySubtags(locale);\r
980                     region = tmp.getCountry();\r
981                 }\r
982             } else {\r
983                 region = "";\r
984             }\r
985         }\r
986         return region;\r
987     }\r
988 \r
989     // This method does lazy zone string loading\r
990     private ZoneStringInfo find(String text, int start, int types) {\r
991         ZoneStringInfo result = subFind(text, start, types);\r
992         if (isFullyLoaded) {\r
993             return result;\r
994         }\r
995         // When zone string data is partially loaded,\r
996         // this method return the result only when\r
997         // the input text is fully consumed.\r
998         if (result != null) {\r
999             int matchLen = result.getString().length();\r
1000             if (text.length() - start == matchLen) {\r
1001                 return result;\r
1002             }\r
1003         }\r
1004         // Now load all zone strings\r
1005         loadFull();\r
1006         return subFind(text, start, types);\r
1007     }\r
1008 \r
1009     /*\r
1010      * Find a prefix matching time zone for the given zone string types.\r
1011      * @param text The text contains a time zone string\r
1012      * @param start The start index within the text\r
1013      * @param types The bit mask representing a set of requested types\r
1014      * @return If any zone string matched for the requested types, returns a\r
1015      * ZoneStringInfo for the longest match.  If no matches are found for\r
1016      * the requested types, returns a ZoneStringInfo for the longest match\r
1017      * for any other types.  If nothing matches at all, returns null.\r
1018      */\r
1019     private ZoneStringInfo subFind(String text, int start, int types) {\r
1020         ZoneStringInfo result = null;\r
1021         ZoneStringSearchResultHandler handler = new ZoneStringSearchResultHandler();\r
1022         zoneStringsTrie.find(text, start, handler);\r
1023         List list = handler.getMatchedZoneStrings();\r
1024         ZoneStringInfo fallback = null;\r
1025         if (list != null && list.size() > 0) {\r
1026             Iterator it = list.iterator();\r
1027             while (it.hasNext()) {\r
1028                 ZoneStringInfo tmp = (ZoneStringInfo)it.next();\r
1029                 if ((types & tmp.getType()) != 0) {\r
1030                     if (result == null || result.getString().length() < tmp.getString().length()) {\r
1031                         result = tmp;\r
1032                     } else if (result.getString().length() == tmp.getString().length()) {\r
1033                         // Tie breaker - there are some examples that a\r
1034                         // long standard name is identical with a location\r
1035                         // name - for example, "Uruguay Time".  In this case,\r
1036                         // we interpret it as generic, not specific.\r
1037                         if (tmp.isGeneric() && !result.isGeneric()) {\r
1038                             result = tmp;\r
1039                         }\r
1040                     }\r
1041                 } else if (result == null) {\r
1042                     if (fallback == null || fallback.getString().length() < tmp.getString().length()) {\r
1043                         fallback = tmp;\r
1044                     } else if (fallback.getString().length() == tmp.getString().length()) {\r
1045                         if (tmp.isGeneric() && !fallback.isGeneric()) {\r
1046                             fallback = tmp;\r
1047                         }\r
1048                     }\r
1049                 }\r
1050             }\r
1051         }\r
1052         if (result == null && fallback != null) {\r
1053             result = fallback;\r
1054         }\r
1055         return result;\r
1056     }\r
1057 \r
1058     \r
1059 \r
1060     private static class ZoneStringSearchResultHandler implements TextTrieMap.ResultHandler {\r
1061 \r
1062         private ArrayList resultList;\r
1063 \r
1064         public boolean handlePrefixMatch(int matchLength, Iterator values) {\r
1065             if (resultList == null) {\r
1066                 resultList = new ArrayList();\r
1067             }\r
1068             while (values.hasNext()) {\r
1069                 ZoneStringInfo zsitem = (ZoneStringInfo)values.next();\r
1070                 if (zsitem == null) {\r
1071                     break;\r
1072                 }\r
1073                 int i = 0;\r
1074                 for (; i < resultList.size(); i++) {\r
1075                     ZoneStringInfo tmp = (ZoneStringInfo)resultList.get(i);\r
1076                     if (zsitem.getType() == tmp.getType()) {\r
1077                         if (matchLength > tmp.getString().length()) {\r
1078                             resultList.set(i, zsitem);\r
1079                         }\r
1080                         break;\r
1081                     }\r
1082                 }\r
1083                 if (i == resultList.size()) {\r
1084                     // not found in the current list\r
1085                     resultList.add(zsitem);\r
1086                 }\r
1087             }\r
1088             return true;\r
1089         }\r
1090 \r
1091         List getMatchedZoneStrings() {\r
1092             if (resultList == null || resultList.size() == 0) {\r
1093                 return null;\r
1094             }\r
1095             return resultList;\r
1096         }\r
1097     }\r
1098 }\r