1 /* 2 * Copyright (c) 2005, 2018, 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.Objects; 34 import java.util.Optional; 35 import java.util.Set; 36 import java.util.concurrent.ConcurrentHashMap; 37 import java.util.spi.TimeZoneNameProvider; 38 import sun.util.calendar.ZoneInfo; 39 import sun.util.cldr.CLDRLocaleProviderAdapter; 40 import static sun.util.locale.provider.LocaleProviderAdapter.Type; 41 42 /** 43 * Utility class that deals with the localized time zone names 44 * 45 * @author Naoto Sato 46 * @author Masayoshi Okutsu 47 */ 48 public final class TimeZoneNameUtility { 49 50 /** 51 * cache to hold time zone localized strings. Keyed by Locale 52 */ 53 private static final ConcurrentHashMap<Locale, SoftReference<String[][]>> cachedZoneData = 54 new ConcurrentHashMap<>(); 55 56 /** 57 * Cache for managing display names per timezone per locale 58 * The structure is: 59 * Map(key=id, value=SoftReference(Map(key=locale, value=displaynames))) 60 */ 61 private static final Map<String, SoftReference<Map<Locale, String[]>>> cachedDisplayNames = 62 new ConcurrentHashMap<>(); 63 64 /** 65 * get time zone localized strings. Enumerate all keys. 66 */ 67 public static String[][] getZoneStrings(Locale locale) { 68 String[][] zones; 69 SoftReference<String[][]> data = cachedZoneData.get(locale); 70 71 if (data == null || ((zones = data.get()) == null)) { 72 zones = loadZoneStrings(locale); 73 data = new SoftReference<>(zones); 74 cachedZoneData.put(locale, data); 75 } 76 77 return zones; 78 } 79 80 private static String[][] loadZoneStrings(Locale locale) { 81 // If the provider is a TimeZoneNameProviderImpl, call its getZoneStrings 82 // in order to avoid per-ID retrieval. 83 LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(TimeZoneNameProvider.class, locale); 84 TimeZoneNameProvider provider = adapter.getTimeZoneNameProvider(); 85 if (provider instanceof TimeZoneNameProviderImpl) { 86 String[][] zoneStrings = ((TimeZoneNameProviderImpl)provider).getZoneStrings(locale); 87 88 if (zoneStrings.length == 0 && locale.equals(Locale.ROOT)) { 89 // Unlike other *Name provider, zoneStrings search won't do the fallback 90 // name search. If the ResourceBundle found for the root locale contains no 91 // zoneStrings, just use the one for English, assuming English bundle 92 // contains all the tzids and their names. 93 zoneStrings= getZoneStrings(Locale.ENGLISH); 94 } 95 96 return zoneStrings; 97 } 98 99 // Performs per-ID retrieval. 100 Set<String> zoneIDs = LocaleProviderAdapter.forJRE().getLocaleResources(locale).getZoneIDs(); 101 List<String[]> zones = new LinkedList<>(); 102 for (String key : zoneIDs) { 103 String[] names = retrieveDisplayNamesImpl(key, locale); 104 if (names != null) { 105 zones.add(names); 106 } 107 } 108 109 String[][] zonesArray = new String[zones.size()][]; 110 return zones.toArray(zonesArray); 111 } 112 113 /** 114 * Retrieve display names for a time zone ID. 115 */ 116 public static String[] retrieveDisplayNames(String id, Locale locale) { 117 Objects.requireNonNull(id); 118 Objects.requireNonNull(locale); 119 120 return retrieveDisplayNamesImpl(id, locale); 121 } 122 123 /** 124 * Retrieves a generic time zone display name for a time zone ID. 125 * 126 * @param id time zone ID 127 * @param style TimeZone.LONG or TimeZone.SHORT 128 * @param locale desired Locale 129 * @return the requested generic time zone display name, or null if not found. 130 */ 131 public static String retrieveGenericDisplayName(String id, int style, Locale locale) { 132 String[] names = retrieveDisplayNamesImpl(id, locale); 133 if (Objects.nonNull(names)) { 134 return names[6 - style]; 135 } else { 136 return null; 137 } 138 } 139 140 /** 141 * Retrieves a standard or daylight-saving time name for the given time zone ID. 142 * 143 * @param id time zone ID 144 * @param daylight true for a daylight saving time name, or false for a standard time name 145 * @param style TimeZone.LONG or TimeZone.SHORT 146 * @param locale desired Locale 147 * @return the requested time zone name, or null if not found. 148 */ 149 public static String retrieveDisplayName(String id, boolean daylight, int style, Locale locale) { 150 String[] names = retrieveDisplayNamesImpl(id, locale); 151 if (Objects.nonNull(names)) { 152 return names[(daylight ? 4 : 2) - style]; 153 } else { 154 return null; 155 } 156 } 157 158 /** 159 * Converts the time zone id from LDML's 5-letter id to tzdb's id 160 * 161 * @param shortID time zone short ID defined in LDML 162 * @return the tzdb's time zone ID 163 */ 164 public static Optional<String> convertLDMLShortID(String shortID) { 165 return canonicalTZID(shortID); 166 } 167 168 /** 169 * Returns the canonical ID for the given ID 170 */ 171 public static Optional<String> canonicalTZID(String id) { 172 return ((CLDRLocaleProviderAdapter)LocaleProviderAdapter.forType(Type.CLDR)) 173 .canonicalTZID(id); 174 } 175 176 private static String[] retrieveDisplayNamesImpl(String id, Locale locale) { 177 LocaleServiceProviderPool pool = 178 LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class); 179 String[] names; 180 Map<Locale, String[]> perLocale = null; 181 182 SoftReference<Map<Locale, String[]>> ref = cachedDisplayNames.get(id); 183 if (Objects.nonNull(ref)) { 184 perLocale = ref.get(); 185 if (Objects.nonNull(perLocale)) { 186 names = perLocale.get(locale); 187 if (Objects.nonNull(names)) { 188 return names; 189 } 190 } 191 } 192 193 // build names array 194 names = new String[7]; 195 names[0] = id; 196 for (int i = 1; i <= 6; i ++) { 197 names[i] = pool.getLocalizedObject(TimeZoneNameGetter.INSTANCE, locale, 198 i<5 ? (i<3 ? "std" : "dst") : "generic", i%2, id); 199 } 200 201 if (Objects.isNull(perLocale)) { 202 perLocale = new ConcurrentHashMap<>(); 203 } 204 perLocale.put(locale, names); 205 ref = new SoftReference<>(perLocale); 206 cachedDisplayNames.put(id, ref); 207 return names; 208 } 209 210 211 /** 212 * Obtains a localized time zone strings from a TimeZoneNameProvider 213 * implementation. 214 */ 215 private static class TimeZoneNameGetter 216 implements LocaleServiceProviderPool.LocalizedObjectGetter<TimeZoneNameProvider, 217 String> { 218 private static final TimeZoneNameGetter INSTANCE = 219 new TimeZoneNameGetter(); 220 221 @Override 222 public String getObject(TimeZoneNameProvider timeZoneNameProvider, 223 Locale locale, 224 String requestID, 225 Object... params) { 226 assert params.length == 2; 227 int style = (int) params[0]; 228 String tzid = (String) params[1]; 229 String value = getName(timeZoneNameProvider, locale, requestID, style, tzid); 230 if (value == null) { 231 Map<String, String> aliases = ZoneInfo.getAliasTable(); 232 if (aliases != null) { 233 String canonicalID = aliases.get(tzid); 234 if (canonicalID != null) { 235 value = getName(timeZoneNameProvider, locale, requestID, style, canonicalID); 236 } 237 if (value == null) { 238 value = examineAliases(timeZoneNameProvider, locale, requestID, 239 canonicalID != null ? canonicalID : tzid, style, aliases); 240 } 241 } 242 } 243 244 return value; 245 } 246 247 private static String examineAliases(TimeZoneNameProvider tznp, Locale locale, 248 String requestID, String tzid, int style, 249 Map<String, String> aliases) { 250 for (Map.Entry<String, String> entry : aliases.entrySet()) { 251 if (entry.getValue().equals(tzid)) { 252 String alias = entry.getKey(); 253 String name = getName(tznp, locale, requestID, style, alias); 254 if (name != null) { 255 return name; 256 } 257 name = examineAliases(tznp, locale, requestID, alias, style, aliases); 258 if (name != null) { 259 return name; 260 } 261 } 262 } 263 return null; 264 } 265 266 private static String getName(TimeZoneNameProvider timeZoneNameProvider, 267 Locale locale, String requestID, int style, String tzid) { 268 String value = null; 269 switch (requestID) { 270 case "std": 271 value = timeZoneNameProvider.getDisplayName(tzid, false, style, locale); 272 break; 273 case "dst": 274 value = timeZoneNameProvider.getDisplayName(tzid, true, style, locale); 275 break; 276 case "generic": 277 value = timeZoneNameProvider.getGenericDisplayName(tzid, style, locale); 278 break; 279 } 280 return value; 281 } 282 } 283 284 // No instantiation 285 private TimeZoneNameUtility() { 286 } 287 }