]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/main/classes/core/src/com/ibm/icu/impl/ZoneStringFormat.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / main / classes / core / src / com / ibm / icu / impl / ZoneStringFormat.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 2007-2010, 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.impl.ZoneMeta.OlsonToMetaMappingEntry;\r
18 import com.ibm.icu.text.MessageFormat;\r
19 import com.ibm.icu.util.BasicTimeZone;\r
20 import com.ibm.icu.util.Calendar;\r
21 import com.ibm.icu.util.TimeZone;\r
22 import com.ibm.icu.util.TimeZoneTransition;\r
23 import com.ibm.icu.util.ULocale;\r
24 import com.ibm.icu.util.UResourceBundle;\r
25 \r
26 /**\r
27  * @author yoshito\r
28  *\r
29  */\r
30 public class ZoneStringFormat {\r
31     /**\r
32      * Constructs a ZoneStringFormat by zone strings array.\r
33      * The internal structure of zoneStrings is compatible with\r
34      * the one used by getZoneStrings/setZoneStrings in DateFormatSymbols.\r
35      * \r
36      * @param zoneStrings zone strings\r
37      */\r
38     public ZoneStringFormat(String[][] zoneStrings) {\r
39         tzidToStrings = new HashMap<String, ZoneStrings>();\r
40         zoneStringsTrie = new TextTrieMap<ZoneStringInfo>(true);\r
41         for (int i = 0; i < zoneStrings.length; i++) {\r
42             String tzid = zoneStrings[i][0];\r
43             String[] names = new String[ZSIDX_MAX];\r
44             for (int j = 1; j < zoneStrings[i].length; j++) {\r
45                 if (zoneStrings[i][j] != null) {\r
46                     int typeIdx = getNameTypeIndex(j);\r
47                     if (typeIdx != -1) {\r
48                         names[typeIdx] = zoneStrings[i][j];\r
49 \r
50                         // Put the name into the trie\r
51                         int type = getNameType(typeIdx);\r
52                         ZoneStringInfo zsinfo = new ZoneStringInfo(tzid, zoneStrings[i][j], type);\r
53                         zoneStringsTrie.put(zoneStrings[i][j], zsinfo);\r
54                     }\r
55                     \r
56                 }\r
57             }\r
58             ZoneStrings zstrings = new ZoneStrings(names, true, null);\r
59             tzidToStrings.put(tzid, zstrings);\r
60         }\r
61         isFullyLoaded = true;\r
62     }\r
63 \r
64     /**\r
65      * Gets an instance of ZoneStringFormat for the specified locale\r
66      * @param locale the locale\r
67      * @return An instance of ZoneStringFormat for the locale\r
68      */\r
69     public static ZoneStringFormat getInstance(ULocale locale) {\r
70         ZoneStringFormat tzf = TZFORMAT_CACHE.get(locale);\r
71         if (tzf == null) {\r
72             tzf = new ZoneStringFormat(locale);\r
73             TZFORMAT_CACHE.put(locale, tzf);\r
74         }\r
75         return tzf;\r
76     }\r
77 \r
78     public String[][] getZoneStrings() {\r
79         return getZoneStrings(System.currentTimeMillis());\r
80     }\r
81 \r
82     // APIs used by SimpleDateFormat to get a zone string\r
83     public String getSpecificLongString(Calendar cal) {\r
84         if (cal.get(Calendar.DST_OFFSET) == 0) {\r
85             return getString(cal.getTimeZone().getID(), ZSIDX_LONG_STANDARD, cal.getTimeInMillis(), false /* not used */);\r
86         }\r
87         return getString(cal.getTimeZone().getID(), ZSIDX_LONG_DAYLIGHT, cal.getTimeInMillis(), false /* not used */);\r
88     }\r
89 \r
90     public String getSpecificShortString(Calendar cal, boolean commonlyUsedOnly) {\r
91         if (cal.get(Calendar.DST_OFFSET) == 0) {\r
92             return getString(cal.getTimeZone().getID(), ZSIDX_SHORT_STANDARD, cal.getTimeInMillis(), commonlyUsedOnly);\r
93         }\r
94         return getString(cal.getTimeZone().getID(), ZSIDX_SHORT_DAYLIGHT, cal.getTimeInMillis(), commonlyUsedOnly);\r
95     }\r
96 \r
97     public String getGenericLongString(Calendar cal) {\r
98         return getGenericString(cal, false /* long */, false /* not used */);\r
99     }\r
100 \r
101     public String getGenericShortString(Calendar cal, boolean commonlyUsedOnly) {\r
102         return getGenericString(cal, true /* long */, commonlyUsedOnly);\r
103     }\r
104 \r
105     public String getGenericLocationString(Calendar cal) {\r
106         return getString(cal.getTimeZone().getID(), ZSIDX_LOCATION, cal.getTimeInMillis(), false /* not used */);\r
107     }\r
108 \r
109     // APIs used by SimpleDateFormat to lookup a zone string\r
110     public static class ZoneStringInfo {\r
111         private String id;\r
112         private String str;\r
113         private int type;\r
114 \r
115         private ZoneStringInfo(String id, String str, int type) {\r
116             this.id = id;\r
117             this.str = str;\r
118             this.type = type;\r
119         }\r
120 \r
121         public String getID() {\r
122             return id;\r
123         }\r
124 \r
125         public String getString() {\r
126             return str;\r
127         }\r
128 \r
129         public boolean isStandard() {\r
130             if ((type & STANDARD_LONG) != 0 || (type & STANDARD_SHORT) != 0) {\r
131                 return true;\r
132             }\r
133             return false;\r
134         }\r
135 \r
136         public boolean isDaylight() {\r
137             if ((type & DAYLIGHT_LONG) != 0 || (type & DAYLIGHT_SHORT) != 0) {\r
138                 return true;\r
139             }\r
140             return false;\r
141         }\r
142 \r
143         public boolean isGeneric() {\r
144             return !isStandard() && !isDaylight();\r
145         }\r
146 \r
147         private int getType() {\r
148             return type;\r
149         }\r
150     }\r
151 \r
152     public ZoneStringInfo findSpecificLong(String text, int start) {\r
153         return find(text, start, STANDARD_LONG | DAYLIGHT_LONG);\r
154     }\r
155     \r
156     public ZoneStringInfo findSpecificShort(String text, int start) {\r
157         return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT);\r
158     }\r
159 \r
160     public ZoneStringInfo findGenericLong(String text, int start) {\r
161         return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION);\r
162     }\r
163     \r
164     public ZoneStringInfo findGenericShort(String text, int start) {\r
165         return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION);\r
166     }\r
167 \r
168     public ZoneStringInfo findGenericLocation(String text, int start) {\r
169         return find(text, start, LOCATION);\r
170     }\r
171 \r
172     // Following APIs are not used by SimpleDateFormat, but public for testing purpose\r
173     public String getLongStandard(String tzid, long date) {\r
174         return getString(tzid, ZSIDX_LONG_STANDARD, date, false /* not used */);\r
175     }\r
176 \r
177     public String getLongDaylight(String tzid, long date) {\r
178         return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, false /* not used */);\r
179     }\r
180 \r
181     public String getLongGenericNonLocation(String tzid, long date) {\r
182         return getString(tzid, ZSIDX_LONG_GENERIC, date, false /* not used */);\r
183     }\r
184 \r
185     public String getLongGenericPartialLocation(String tzid, long date) {\r
186         return getGenericPartialLocationString(tzid, false, date, false /* not used */);\r
187     }\r
188 \r
189     public String getShortStandard(String tzid, long date, boolean commonlyUsedOnly) {\r
190         return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly);\r
191     }\r
192 \r
193     public String getShortDaylight(String tzid, long date, boolean commonlyUsedOnly) {\r
194         return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly);\r
195     }\r
196 \r
197     public String getShortGenericNonLocation(String tzid, long date, boolean commonlyUsedOnly) {\r
198         return getString(tzid, ZSIDX_SHORT_GENERIC, date, commonlyUsedOnly);\r
199     }\r
200 \r
201     public String getShortGenericPartialLocation(String tzid, long date, boolean commonlyUsedOnly) {\r
202         return getGenericPartialLocationString(tzid, true, date, commonlyUsedOnly);\r
203     }\r
204 \r
205     public String getGenericLocation(String tzid) {\r
206         return getString(tzid, ZSIDX_LOCATION, 0L /* not used */, false /* not used */);\r
207     }\r
208 \r
209     /**\r
210      * Constructs a ZoneStringFormat by locale.  Because an instance of ZoneStringFormat\r
211      * is read-only, only one instance for a locale is sufficient.  Thus, this\r
212      * constructor is protected and only called from getInstance(ULocale) to\r
213      * create one for a locale.\r
214      * @param locale The locale\r
215      */\r
216     protected ZoneStringFormat(ULocale locale) {\r
217         this.locale = locale;\r
218         tzidToStrings = new HashMap<String, ZoneStrings>();\r
219         mzidToStrings = new HashMap<String, ZoneStrings>();\r
220         zoneStringsTrie = new TextTrieMap<ZoneStringInfo>(true);\r
221     }\r
222 \r
223     // Load only a single zone\r
224     private synchronized void loadZone(String id) {\r
225         if (isFullyLoaded) {\r
226             return;\r
227         }\r
228         String tzid = ZoneMeta.getCanonicalSystemID(id);\r
229         if (tzid == null || tzidToStrings.containsKey(tzid)) {\r
230             return;\r
231         }\r
232 \r
233         ICUResourceBundle zoneStringsBundle = null;\r
234         try {\r
235             ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);\r
236             zoneStringsBundle = bundle.getWithFallback("zoneStrings");\r
237         } catch (MissingResourceException e) {\r
238             // If no locale bundles are available, zoneStringsBundle will be null.\r
239             // We still want to go through the rest of zone strings initialization,\r
240             // because generic location format is generated from tzid for the case.\r
241             // The rest of code should work even zoneStrings is null.\r
242         }\r
243 \r
244         String[] zstrarray = new String[ZSIDX_MAX];\r
245         String[] mzstrarray = new String[ZSIDX_MAX];\r
246         String[][] mzPartialLoc = new String[10][4]; // maximum 10 metazones per zone\r
247 \r
248         addSingleZone(tzid, zoneStringsBundle,\r
249                 getFallbackFormat(locale), getRegionFormat(locale),\r
250                 zstrarray, mzstrarray, mzPartialLoc);\r
251     }\r
252 \r
253     // Loading all zone strings\r
254     private synchronized void loadFull() {\r
255         if (isFullyLoaded) {\r
256             return;\r
257         }\r
258         ICUResourceBundle zoneStringsBundle = null;\r
259         try {\r
260             ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);\r
261             zoneStringsBundle = bundle.getWithFallback("zoneStrings");\r
262         } catch (MissingResourceException e) {\r
263             // If no locale bundles are available, zoneStringsBundle will be null.\r
264             // We still want to go through the rest of zone strings initialization,\r
265             // because generic location format is generated from tzid for the case.\r
266             // The rest of code should work even zoneStrings is null.\r
267         }\r
268 \r
269         String[] zoneIDs = TimeZone.getAvailableIDs();\r
270 \r
271         String[] zstrarray = new String[ZSIDX_MAX];\r
272         String[] mzstrarray = new String[ZSIDX_MAX];\r
273         String[][] mzPartialLoc = new String[10][4]; // maximum 10 metazones per zone\r
274 \r
275         for (int i = 0; i < zoneIDs.length; i++) {\r
276             // Skip aliases\r
277              String tzid = ZoneMeta.getCanonicalSystemID(zoneIDs[i]);\r
278             if (tzid == null || !zoneIDs[i].equals(tzid)) {\r
279                 continue;\r
280             }\r
281 \r
282             if (tzidToStrings.containsKey(tzid)) {\r
283                 continue;\r
284             }\r
285 \r
286             addSingleZone(tzid, zoneStringsBundle,\r
287                     getFallbackFormat(locale), getRegionFormat(locale),\r
288                     zstrarray, mzstrarray, mzPartialLoc);\r
289         }\r
290         isFullyLoaded = true;\r
291     }\r
292 \r
293     // This internal initialization code must be called in a synchronized block\r
294     private void addSingleZone(String tzid, ICUResourceBundle zoneStringsBundle,\r
295             MessageFormat fallbackFmt, MessageFormat regionFmt,\r
296             String[] zstrarray, String[] mzstrarray, String[][] mzPartialLoc) {\r
297 \r
298         if (tzidToStrings.containsKey(tzid)) {\r
299             return;\r
300         }\r
301 \r
302         String zoneKey = tzid.replace('/', ':');\r
303         zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_STANDARD);\r
304         zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_STANDARD);\r
305         zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_DAYLIGHT);\r
306         zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_DAYLIGHT);\r
307         zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_GENERIC);\r
308         zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_GENERIC);\r
309 \r
310         // Compose location format string\r
311         String countryCode = ZoneMeta.getCanonicalCountry(tzid);\r
312         String country = null;\r
313         String city = null;\r
314         if (countryCode != null) {\r
315             city = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_EXEMPLAR_CITY);\r
316             if (city == null) {\r
317                 city = tzid.substring(tzid.lastIndexOf('/') + 1).replace('_', ' ');\r
318             }\r
319             country = getLocalizedCountry(countryCode, locale);\r
320             if (ZoneMeta.getSingleCountry(tzid) != null) {\r
321                 // If the zone is only one zone in the country, do not add city\r
322                 zstrarray[ZSIDX_LOCATION] = regionFmt.format(new Object[] {country});\r
323             } else {\r
324                 zstrarray[ZSIDX_LOCATION] = fallbackFmt.format(new Object[] {city, country});\r
325             }\r
326         } else {\r
327             if (tzid.startsWith("Etc/")) {\r
328                 // "Etc/xxx" is not associated with a specific location, so localized\r
329                 // GMT format is always used as generic location format.\r
330                 zstrarray[ZSIDX_LOCATION] = null;\r
331             } else {\r
332                 // When a new time zone ID, which is actually associated with a specific\r
333                 // location, is added in tzdata, but the current CLDR data does not have\r
334                 // the information yet, ICU creates a generic location string based on \r
335                 // the ID.  This implementation supports canonical time zone round trip\r
336                 // with format pattern "VVVV".  See #6602 for the details.\r
337                 String location = tzid;\r
338                 int slashIdx = location.lastIndexOf('/');\r
339                 if (slashIdx == -1) {\r
340                     // A time zone ID without slash in the tz database is not\r
341                     // associated with a specific location.  For instances,\r
342                     // MET, CET, EET and WET fall into this catetory.\r
343                     zstrarray[ZSIDX_LOCATION] = null;\r
344                 } else {\r
345                     location = tzid.substring(slashIdx + 1);\r
346                     zstrarray[ZSIDX_LOCATION] = regionFmt.format(new Object[] {location});\r
347                 }\r
348             }\r
349         }\r
350 \r
351         boolean commonlyUsed = isCommonlyUsed(zoneStringsBundle, zoneKey);\r
352         \r
353         // Resolve metazones used by this zone\r
354         int mzPartialLocIdx = 0;\r
355         List<OlsonToMetaMappingEntry> metazoneMappings = ZoneMeta.getOlsonToMatazones(tzid);\r
356         if (metazoneMappings != null) {\r
357             Iterator<OlsonToMetaMappingEntry> it = metazoneMappings.iterator();\r
358             while (it.hasNext()) {\r
359                 ZoneMeta.OlsonToMetaMappingEntry mzmap = it.next();\r
360                 ZoneStrings mzStrings = 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] = 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<ULocale, ZoneStringFormat> TZFORMAT_CACHE = new SimpleCache<ULocale, ZoneStringFormat>();\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<String, ZoneStrings> tzidToStrings;\r
507 \r
508     // Map from metazone id to ZoneStrings\r
509     private Map<String, ZoneStrings> mzidToStrings;\r
510 \r
511     // Zone string dictionary, used for look up\r
512     private TextTrieMap<ZoneStringInfo> 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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<String> tzids = tzidToStrings.keySet();\r
766         String[][] zoneStrings = new String[tzids.size()][8];\r
767         int idx = 0;\r
768         for (String tzid : tzids) {\r
769             zoneStrings[idx][0] = tzid;\r
770             zoneStrings[idx][1] = getLongStandard(tzid, date);\r
771             zoneStrings[idx][2] = getShortStandard(tzid, date, false);\r
772             zoneStrings[idx][3] = getLongDaylight(tzid, date);\r
773             zoneStrings[idx][4] = getShortDaylight(tzid, date, false);\r
774             zoneStrings[idx][5] = getGenericLocation(tzid);\r
775             zoneStrings[idx][6] = getLongGenericNonLocation(tzid, date);\r
776             zoneStrings[idx][7] = getShortGenericNonLocation(tzid, date, false);\r
777             idx++;\r
778         }\r
779         return zoneStrings;\r
780     }\r
781     \r
782     /*\r
783      * ZoneStrings is an internal implementation class for\r
784      * holding localized name information for a zone/metazone\r
785      */\r
786     private static class ZoneStrings {\r
787         private String[] strings;\r
788         private String[][] genericPartialLocationStrings;\r
789         private boolean commonlyUsed;\r
790  \r
791         private ZoneStrings(String[] zstrarray, boolean commonlyUsed, String[][] genericPartialLocationStrings) {\r
792             if (zstrarray != null) {\r
793                 int lastIdx = -1;\r
794                 for (int i = 0; i < zstrarray.length; i++) {\r
795                     if (zstrarray[i] != null) {\r
796                         lastIdx = i;\r
797                     }\r
798                 }\r
799                 if (lastIdx != -1) {\r
800                     strings = new String[lastIdx + 1];\r
801                     System.arraycopy(zstrarray, 0, strings, 0, lastIdx + 1);\r
802                 }\r
803             }\r
804             this.commonlyUsed = commonlyUsed;\r
805             this.genericPartialLocationStrings = genericPartialLocationStrings;\r
806         }\r
807 \r
808         private String getString(int typeIdx) {\r
809             if (strings != null && typeIdx >= 0 && typeIdx < strings.length) {\r
810                 return strings[typeIdx];\r
811             }\r
812             return null;\r
813         }\r
814 \r
815         private boolean isShortFormatCommonlyUsed() {\r
816             return commonlyUsed;\r
817         }\r
818 \r
819         private String getGenericPartialLocationString(String mzid, boolean isShort, boolean commonlyUsedOnly) {\r
820             String result = null;\r
821             if (genericPartialLocationStrings != null) {\r
822                 for (int i = 0; i < genericPartialLocationStrings.length; i++) {\r
823                     if (genericPartialLocationStrings[i][0].equals(mzid)) {\r
824                         if (isShort) {\r
825                             if (!commonlyUsedOnly || genericPartialLocationStrings[i][3] != null) {\r
826                                 result = genericPartialLocationStrings[i][2];\r
827                             }\r
828                         } else {\r
829                             result = genericPartialLocationStrings[i][1];\r
830                         }\r
831                         break;\r
832                     }\r
833                 }\r
834             }\r
835             return result;\r
836         }\r
837     }\r
838 \r
839     /*\r
840      * Returns a localized zone string from bundle.\r
841      */\r
842     private static String getZoneStringFromBundle(ICUResourceBundle bundle, String key, String type) {\r
843         String zstring = null;\r
844         if (bundle != null) {\r
845             try {\r
846                 zstring = bundle.getStringWithFallback(key + "/" + type);\r
847             } catch (MissingResourceException ex) {\r
848                 // throw away the exception\r
849             }\r
850         }\r
851         return zstring;\r
852     }\r
853 \r
854     /*\r
855      * Returns if the short strings of the zone/metazone is commonly used.\r
856      */\r
857     private static boolean isCommonlyUsed(ICUResourceBundle bundle, String key) {\r
858         boolean commonlyUsed = false;\r
859         if (bundle != null) {\r
860             try {\r
861                 UResourceBundle cuRes = bundle.getWithFallback(key + "/" + RESKEY_COMMONLY_USED);\r
862                 int cuValue = cuRes.getInt();\r
863                 commonlyUsed = (cuValue != 0);\r
864             } catch (MissingResourceException ex) {\r
865                 // throw away the exception\r
866             }\r
867         }\r
868         return commonlyUsed;\r
869     }\r
870 \r
871     /*\r
872      * Returns a localized country string for the country code.  If no actual\r
873      * localized string is found, countryCode itself is returned.\r
874      */\r
875     private static String getLocalizedCountry(String countryCode, ULocale locale) {\r
876         String countryStr = null;\r
877         if (countryCode != null) {\r
878             ICUResourceBundle rb = \r
879                 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_REGION_BASE_NAME, locale);\r
880 //\r
881 // TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well.\r
882 //\r
883 //            if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) {\r
884 //                country = ULocale.getDisplayCountry("xx_" + country_code, locale);\r
885 //            }\r
886 // START WORKAROUND\r
887             ULocale rbloc = rb.getULocale();\r
888             if (!rbloc.equals(ULocale.ROOT) && rbloc.getLanguage().equals(locale.getLanguage())) {\r
889                 countryStr = ULocale.getDisplayCountry("xx_" + countryCode, locale);\r
890             }\r
891 // END WORKAROUND\r
892             if (countryStr == null || countryStr.length() == 0) {\r
893                 countryStr = countryCode;\r
894             }\r
895         }\r
896         return countryStr;\r
897     }\r
898 \r
899     /*\r
900      * Gets an instance of MessageFormat used for formatting zone fallback string\r
901      */\r
902     private static MessageFormat getFallbackFormat(ULocale locale) {\r
903         String fallbackPattern = ZoneMeta.getTZLocalizationInfo(locale, ZoneMeta.FALLBACK_FORMAT);\r
904         if (fallbackPattern == null) {\r
905             fallbackPattern = "{1} ({0})";\r
906         }\r
907         return new MessageFormat(fallbackPattern, locale);\r
908     }\r
909 \r
910     /*\r
911      * Gets an instance of MessageFormat used for formatting zone region string\r
912      */\r
913     private static MessageFormat getRegionFormat(ULocale locale) {\r
914         String regionPattern = ZoneMeta.getTZLocalizationInfo(locale, ZoneMeta.REGION_FORMAT);\r
915         if (regionPattern == null) {\r
916             regionPattern = "{0}";\r
917         }\r
918         return new MessageFormat(regionPattern, locale);\r
919     }\r
920 \r
921     /*\r
922      * Index value mapping between DateFormatSymbols's zoneStrings and\r
923      * the string types defined in this class.\r
924      */\r
925     private static final int[] INDEXMAP = {\r
926         -1,             // 0 - zone id\r
927         ZSIDX_LONG_STANDARD,  // 1 - long standard\r
928         ZSIDX_SHORT_STANDARD, // 2 - short standard\r
929         ZSIDX_LONG_DAYLIGHT,  // 3 - long daylight\r
930         ZSIDX_SHORT_DAYLIGHT, // 4 - short daylight\r
931         ZSIDX_LOCATION,       // 5 - generic location\r
932         ZSIDX_LONG_GENERIC,   // 6 - long generic non-location\r
933         ZSIDX_SHORT_GENERIC   // 7 - short generic non-location\r
934     };\r
935 \r
936     /*\r
937      * Convert from zone string array index for zoneStrings used by DateFormatSymbols#get/setZoneStrings\r
938      * to the type constants defined by this class, such as ZSIDX_LONG_STANDARD.\r
939      */\r
940     private static int getNameTypeIndex(int i) {\r
941         int idx = -1;\r
942         if (i >= 1 && i < INDEXMAP.length) {\r
943             idx = INDEXMAP[i];\r
944         }\r
945         return idx;\r
946     }\r
947 \r
948     /*\r
949      * Mapping from name type index to name type\r
950      */\r
951     private static final int[] NAMETYPEMAP = {\r
952         LOCATION,       // ZSIDX_LOCATION\r
953         STANDARD_LONG,  // ZSIDX_LONG_STANDARD\r
954         STANDARD_SHORT, // ZSIDX_SHORT_STANDARD\r
955         DAYLIGHT_LONG,  // ZSIDX_LONG_DAYLIGHT\r
956         DAYLIGHT_SHORT, // ZSIDX_SHORT_DAYLIGHT\r
957         GENERIC_LONG,   // ZSIDX_LONG_GENERIC\r
958         GENERIC_SHORT,  // ZSIDX_SHORT_GENERIC\r
959     };\r
960 \r
961     private static int getNameType(int typeIdx) {\r
962         int type = -1;\r
963         if (typeIdx >= 0 && typeIdx < NAMETYPEMAP.length) {\r
964             type = NAMETYPEMAP[typeIdx];\r
965         }\r
966         return type;\r
967     }\r
968 \r
969     /*\r
970      * Returns region used for ZoneMeta#getZoneIdByMetazone.\r
971      */\r
972     private String getRegion() {\r
973         if (region == null) {\r
974             if (locale != null) {\r
975                 region = locale.getCountry();\r
976                 if (region.length() == 0) {\r
977                     ULocale tmp = ULocale.addLikelySubtags(locale);\r
978                     region = tmp.getCountry();\r
979                 }\r
980             } else {\r
981                 region = "";\r
982             }\r
983         }\r
984         return region;\r
985     }\r
986 \r
987     // This method does lazy zone string loading\r
988     private ZoneStringInfo find(String text, int start, int types) {\r
989         ZoneStringInfo result = subFind(text, start, types);\r
990         if (isFullyLoaded) {\r
991             return result;\r
992         }\r
993         // When zone string data is partially loaded,\r
994         // this method return the result only when\r
995         // the input text is fully consumed.\r
996         if (result != null) {\r
997             int matchLen = result.getString().length();\r
998             if (text.length() - start == matchLen) {\r
999                 return result;\r
1000             }\r
1001         }\r
1002         // Now load all zone strings\r
1003         loadFull();\r
1004         return subFind(text, start, types);\r
1005     }\r
1006 \r
1007     /*\r
1008      * Find a prefix matching time zone for the given zone string types.\r
1009      * @param text The text contains a time zone string\r
1010      * @param start The start index within the text\r
1011      * @param types The bit mask representing a set of requested types\r
1012      * @return If any zone string matched for the requested types, returns a\r
1013      * ZoneStringInfo for the longest match.  If no matches are found for\r
1014      * the requested types, returns a ZoneStringInfo for the longest match\r
1015      * for any other types.  If nothing matches at all, returns null.\r
1016      */\r
1017     private ZoneStringInfo subFind(String text, int start, int types) {\r
1018         ZoneStringInfo result = null;\r
1019         ZoneStringSearchResultHandler handler = new ZoneStringSearchResultHandler();\r
1020         zoneStringsTrie.find(text, start, handler);\r
1021         List<ZoneStringInfo> list = handler.getMatchedZoneStrings();\r
1022         ZoneStringInfo fallback = null;\r
1023         if (list != null && list.size() > 0) {\r
1024             Iterator<ZoneStringInfo> it = list.iterator();\r
1025             while (it.hasNext()) {\r
1026                 ZoneStringInfo tmp = it.next();\r
1027                 if ((types & tmp.getType()) != 0) {\r
1028                     if (result == null || result.getString().length() < tmp.getString().length()) {\r
1029                         result = tmp;\r
1030                     } else if (result.getString().length() == tmp.getString().length()) {\r
1031                         // Tie breaker - there are some examples that a\r
1032                         // long standard name is identical with a location\r
1033                         // name - for example, "Uruguay Time".  In this case,\r
1034                         // we interpret it as generic, not specific.\r
1035                         if (tmp.isGeneric() && !result.isGeneric()) {\r
1036                             result = tmp;\r
1037                         }\r
1038                     }\r
1039                 } else if (result == null) {\r
1040                     if (fallback == null || fallback.getString().length() < tmp.getString().length()) {\r
1041                         fallback = tmp;\r
1042                     } else if (fallback.getString().length() == tmp.getString().length()) {\r
1043                         if (tmp.isGeneric() && !fallback.isGeneric()) {\r
1044                             fallback = tmp;\r
1045                         }\r
1046                     }\r
1047                 }\r
1048             }\r
1049         }\r
1050         if (result == null && fallback != null) {\r
1051             result = fallback;\r
1052         }\r
1053         return result;\r
1054     }\r
1055 \r
1056     \r
1057 \r
1058     private static class ZoneStringSearchResultHandler implements TextTrieMap.ResultHandler<ZoneStringInfo> {\r
1059 \r
1060         private ArrayList<ZoneStringInfo> resultList;\r
1061 \r
1062         public boolean handlePrefixMatch(int matchLength, Iterator<ZoneStringInfo> values) {\r
1063             if (resultList == null) {\r
1064                 resultList = new ArrayList<ZoneStringInfo>();\r
1065             }\r
1066             while (values.hasNext()) {\r
1067                 ZoneStringInfo zsitem = values.next();\r
1068                 if (zsitem == null) {\r
1069                     break;\r
1070                 }\r
1071                 int i = 0;\r
1072                 for (; i < resultList.size(); i++) {\r
1073                     ZoneStringInfo tmp = resultList.get(i);\r
1074                     if (zsitem.getType() == tmp.getType()) {\r
1075                         if (matchLength > tmp.getString().length()) {\r
1076                             resultList.set(i, zsitem);\r
1077                         }\r
1078                         break;\r
1079                     }\r
1080                 }\r
1081                 if (i == resultList.size()) {\r
1082                     // not found in the current list\r
1083                     resultList.add(zsitem);\r
1084                 }\r
1085             }\r
1086             return true;\r
1087         }\r
1088 \r
1089         List<ZoneStringInfo> getMatchedZoneStrings() {\r
1090             if (resultList == null || resultList.size() == 0) {\r
1091                 return null;\r
1092             }\r
1093             return resultList;\r
1094         }\r
1095     }\r
1096 }\r