1 /* 2 * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.util.locale.provider; 27 28 import java.lang.ref.SoftReference; 29 import java.util.LinkedList; 30 import java.util.List; 31 import java.util.Locale; 32 import java.util.Map; 33 import java.util.Set; 34 import java.util.concurrent.ConcurrentHashMap; 35 import java.util.spi.TimeZoneNameProvider; 36 import sun.util.calendar.ZoneInfo; 37 38 /** 39 * Utility class that deals with the localized time zone names 40 * 41 * @author Naoto Sato 42 * @author Masayoshi Okutsu 43 */ 44 public final class TimeZoneNameUtility { 45 46 /** 47 * cache to hold time zone localized strings. Keyed by Locale 48 */ 49 private static ConcurrentHashMap<Locale, SoftReference<String[][]>> cachedZoneData = 50 new ConcurrentHashMap<>(); 51 52 /** 53 * Cache for managing display names per timezone per locale 54 * The structure is: 55 * Map(key=id, value=SoftReference(Map(key=locale, value=displaynames))) 56 */ 57 private static final Map<String, SoftReference<Map<Locale, String[]>>> cachedDisplayNames = 58 new ConcurrentHashMap<>(); 59 60 /** 61 * get time zone localized strings. Enumerate all keys. 62 */ 63 public static String[][] getZoneStrings(Locale locale) { 64 String[][] zones; 65 SoftReference<String[][]> data = cachedZoneData.get(locale); 66 67 if (data == null || ((zones = data.get()) == null)) { 68 zones = loadZoneStrings(locale); 69 data = new SoftReference<>(zones); 70 cachedZoneData.put(locale, data); 71 } 72 73 return zones; 74 } 75 76 private static String[][] loadZoneStrings(Locale locale) { 77 // If the provider is a TimeZoneNameProviderImpl, call its getZoneStrings 78 // in order to avoid per-ID retrieval. 79 LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(TimeZoneNameProvider.class, locale); 80 TimeZoneNameProvider provider = adapter.getTimeZoneNameProvider(); 81 if (provider instanceof TimeZoneNameProviderImpl) { 82 return ((TimeZoneNameProviderImpl)provider).getZoneStrings(locale); 83 } 84 85 // Performs per-ID retrieval. 86 Set<String> zoneIDs = LocaleProviderAdapter.forJRE().getLocaleResources(locale).getZoneIDs(); 87 List<String[]> zones = new LinkedList<>(); 88 for (String key : zoneIDs) { 89 String[] names = retrieveDisplayNamesImpl(key, locale); 90 if (names != null) { 91 zones.add(names); 92 } 93 } 94 95 String[][] zonesArray = new String[zones.size()][]; 96 return zones.toArray(zonesArray); 97 } 98 99 /** 100 * Retrieve display names for a time zone ID. 101 */ 102 public static String[] retrieveDisplayNames(String id, Locale locale) { 103 if (id == null || locale == null) { 104 throw new NullPointerException(); 105 } 106 return retrieveDisplayNamesImpl(id, locale); 107 } 108 109 /** 110 * Retrieves a generic time zone display name for a time zone ID. 111 * 112 * @param id time zone ID 113 * @param style TimeZone.LONG or TimeZone.SHORT 114 * @param locale desired Locale 115 * @return the requested generic time zone display name, or null if not found. 116 */ 117 public static String retrieveGenericDisplayName(String id, int style, Locale locale) { 118 LocaleServiceProviderPool pool = 119 LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class); 120 return pool.getLocalizedObject(TimeZoneNameGetter.INSTANCE, locale, "generic", style, id); 121 } 122 123 /** 124 * Retrieves a standard or daylight-saving time name for the given time zone ID. 125 * 126 * @param id time zone ID 127 * @param daylight true for a daylight saving time name, or false for a standard time name 128 * @param style TimeZone.LONG or TimeZone.SHORT 129 * @param locale desired Locale 130 * @return the requested time zone name, or null if not found. 131 */ 132 public static String retrieveDisplayName(String id, boolean daylight, int style, Locale locale) { 133 LocaleServiceProviderPool pool = 134 LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class); 135 return pool.getLocalizedObject(TimeZoneNameGetter.INSTANCE, locale, daylight ? "dst" : "std", style, id); 136 } 137 138 private static String[] retrieveDisplayNamesImpl(String id, Locale locale) { 139 LocaleServiceProviderPool pool = 140 LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class); 141 142 SoftReference<Map<Locale, String[]>> ref = cachedDisplayNames.get(id); 143 if (ref != null) { 144 Map<Locale, String[]> perLocale = ref.get(); 145 if (perLocale != null) { 146 String[] names = perLocale.get(locale); 147 if (names != null) { 148 return names; 149 } 150 names = pool.getLocalizedObject(TimeZoneNameArrayGetter.INSTANCE, locale, id); 151 if (names != null) { 152 perLocale.put(locale, names); 153 } 154 return names; 155 } 156 } 157 158 String[] names = pool.getLocalizedObject(TimeZoneNameArrayGetter.INSTANCE, locale, id); 159 if (names != null) { 160 Map<Locale, String[]> perLocale = new ConcurrentHashMap<>(); 161 perLocale.put(locale, names); 162 ref = new SoftReference<>(perLocale); 163 cachedDisplayNames.put(id, ref); 164 } 165 return names; 166 } 167 168 /** 169 * Obtains a localized time zone strings from a TimeZoneNameProvider 170 * implementation. 171 */ 172 private static class TimeZoneNameArrayGetter 173 implements LocaleServiceProviderPool.LocalizedObjectGetter<TimeZoneNameProvider, 174 String[]>{ 175 private static final TimeZoneNameArrayGetter INSTANCE = 176 new TimeZoneNameArrayGetter(); 177 178 @Override 179 public String[] getObject(TimeZoneNameProvider timeZoneNameProvider, 180 Locale locale, 181 String requestID, 182 Object... params) { 183 assert params.length == 0; 184 185 // First, try to get names with the request ID 186 String[] names = buildZoneStrings(timeZoneNameProvider, locale, requestID); 187 188 if (names == null) { 189 Map<String, String> aliases = ZoneInfo.getAliasTable(); 190 191 if (aliases != null) { 192 // Check whether this id is an alias, if so, 193 // look for the standard id. 194 String canonicalID = aliases.get(requestID); 195 if (canonicalID != null) { 196 names = buildZoneStrings(timeZoneNameProvider, locale, canonicalID); 197 } 198 if (names == null) { 199 // There may be a case that a standard id has become an 200 // alias. so, check the aliases backward. 201 names = examineAliases(timeZoneNameProvider, locale, 202 canonicalID == null ? requestID : canonicalID, aliases); 203 } 204 } 205 } 206 207 if (names != null) { 208 names[0] = requestID; 209 } 210 211 return names; 212 } 213 214 private static String[] examineAliases(TimeZoneNameProvider tznp, Locale locale, 215 String id, 216 Map<String, String> aliases) { 217 if (aliases.containsValue(id)) { 218 for (Map.Entry<String, String> entry : aliases.entrySet()) { 219 if (entry.getValue().equals(id)) { 220 String alias = entry.getKey(); 221 String[] names = buildZoneStrings(tznp, locale, alias); 222 if (names != null) { 223 return names; 224 } 225 names = examineAliases(tznp, locale, alias, aliases); 226 if (names != null) { 227 return names; 228 } 229 } 230 } 231 } 232 233 return null; 234 } 235 236 private static String[] buildZoneStrings(TimeZoneNameProvider tznp, 237 Locale locale, String id) { 238 String[] names = new String[5]; 239 240 for (int i = 1; i <= 4; i ++) { 241 names[i] = tznp.getDisplayName(id, i>=3, i%2, locale); 242 if (i >= 3 && names[i] == null) { 243 names[i] = names[i-2]; 244 } 245 } 246 247 if (names[1] == null) { 248 // this id seems not localized by this provider 249 names = null; 250 } 251 252 return names; 253 } 254 } 255 256 private static class TimeZoneNameGetter 257 implements LocaleServiceProviderPool.LocalizedObjectGetter<TimeZoneNameProvider, 258 String> { 259 private static final TimeZoneNameGetter INSTANCE = 260 new TimeZoneNameGetter(); 261 262 @Override 263 public String getObject(TimeZoneNameProvider timeZoneNameProvider, 264 Locale locale, 265 String requestID, 266 Object... params) { 267 assert params.length == 2; 268 int style = (int) params[0]; 269 String tzid = (String) params[1]; 270 String value = getName(timeZoneNameProvider, locale, requestID, style, tzid); 271 if (value == null) { 272 Map<String, String> aliases = ZoneInfo.getAliasTable(); 273 if (aliases != null) { 274 String canonicalID = aliases.get(tzid); 275 if (canonicalID != null) { 276 value = getName(timeZoneNameProvider, locale, requestID, style, canonicalID); 277 } 278 if (value == null) { 279 value = examineAliases(timeZoneNameProvider, locale, requestID, 280 canonicalID != null ? canonicalID : tzid, style, aliases); 281 } 282 } 283 } 284 285 return value; 286 } 287 288 private static String examineAliases(TimeZoneNameProvider tznp, Locale locale, 289 String requestID, String tzid, int style, 290 Map<String, String> aliases) { 291 if (aliases.containsValue(tzid)) { 292 for (Map.Entry<String, String> entry : aliases.entrySet()) { 293 if (entry.getValue().equals(tzid)) { 294 String alias = entry.getKey(); 295 String name = getName(tznp, locale, requestID, style, alias); 296 if (name != null) { 297 return name; 298 } 299 name = examineAliases(tznp, locale, requestID, alias, style, aliases); 300 if (name != null) { 301 return name; 302 } 303 } 304 } 305 } 306 return null; 307 } 308 309 private static String getName(TimeZoneNameProvider timeZoneNameProvider, 310 Locale locale, String requestID, int style, String tzid) { 311 String value = null; 312 switch (requestID) { 313 case "std": 314 value = timeZoneNameProvider.getDisplayName(tzid, false, style, locale); 315 break; 316 case "dst": 317 value = timeZoneNameProvider.getDisplayName(tzid, true, style, locale); 318 break; 319 case "generic": 320 value = timeZoneNameProvider.getGenericDisplayName(tzid, style, locale); 321 break; 322 } 323 return value; 324 } 325 } 326 327 // No instantiation 328 private TimeZoneNameUtility() { 329 } 330 }