1 /* 2 * Copyright (c) 2005, 2017, 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 ((CLDRLocaleProviderAdapter)LocaleProviderAdapter.forType(Type.CLDR)) 166 .getTimeZoneID(shortID) 167 .map(id -> id.replaceAll("\\s.*", "")); 168 } 169 170 private static String[] retrieveDisplayNamesImpl(String id, Locale locale) { 171 LocaleServiceProviderPool pool = 172 LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class); 173 String[] names; 174 Map<Locale, String[]> perLocale = null; 175 176 SoftReference<Map<Locale, String[]>> ref = cachedDisplayNames.get(id); 177 if (Objects.nonNull(ref)) { 178 perLocale = ref.get(); 179 if (Objects.nonNull(perLocale)) { 180 names = perLocale.get(locale); 181 if (Objects.nonNull(names)) { 182 return names; 183 } 184 } 185 } 186 187 // build names array 188 names = new String[7]; 189 names[0] = id; 190 for (int i = 1; i <= 6; i ++) { 191 names[i] = pool.getLocalizedObject(TimeZoneNameGetter.INSTANCE, locale, 192 i<5 ? (i<3 ? "std" : "dst") : "generic", i%2, id); 193 } 194 195 if (Objects.isNull(perLocale)) { 196 perLocale = new ConcurrentHashMap<>(); 197 } 198 perLocale.put(locale, names); 199 ref = new SoftReference<>(perLocale); 200 cachedDisplayNames.put(id, ref); 201 return names; 202 } 203 204 205 /** 206 * Obtains a localized time zone strings from a TimeZoneNameProvider 207 * implementation. 208 */ 209 private static class TimeZoneNameGetter 210 implements LocaleServiceProviderPool.LocalizedObjectGetter<TimeZoneNameProvider, 211 String> { 212 private static final TimeZoneNameGetter INSTANCE = 213 new TimeZoneNameGetter(); 214 215 @Override 216 public String getObject(TimeZoneNameProvider timeZoneNameProvider, 217 Locale locale, 218 String requestID, 219 Object... params) { 220 assert params.length == 2; 221 int style = (int) params[0]; 222 String tzid = (String) params[1]; 223 String value = getName(timeZoneNameProvider, locale, requestID, style, tzid); 224 if (value == null) { 225 Map<String, String> aliases = ZoneInfo.getAliasTable(); 226 if (aliases != null) { 227 String canonicalID = aliases.get(tzid); 228 if (canonicalID != null) { 229 value = getName(timeZoneNameProvider, locale, requestID, style, canonicalID); 230 } 231 if (value == null) { 232 value = examineAliases(timeZoneNameProvider, locale, requestID, 233 canonicalID != null ? canonicalID : tzid, style, aliases); 234 } 235 } 236 } 237 238 return value; 239 } 240 241 private static String examineAliases(TimeZoneNameProvider tznp, Locale locale, 242 String requestID, String tzid, int style, 243 Map<String, String> aliases) { 244 for (Map.Entry<String, String> entry : aliases.entrySet()) { 245 if (entry.getValue().equals(tzid)) { 246 String alias = entry.getKey(); 247 String name = getName(tznp, locale, requestID, style, alias); 248 if (name != null) { 249 return name; 250 } 251 name = examineAliases(tznp, locale, requestID, alias, style, aliases); 252 if (name != null) { 253 return name; 254 } 255 } 256 } 257 return null; 258 } 259 260 private static String getName(TimeZoneNameProvider timeZoneNameProvider, 261 Locale locale, String requestID, int style, String tzid) { 262 String value = null; 263 switch (requestID) { 264 case "std": 265 value = timeZoneNameProvider.getDisplayName(tzid, false, style, locale); 266 break; 267 case "dst": 268 value = timeZoneNameProvider.getDisplayName(tzid, true, style, locale); 269 break; 270 case "generic": 271 value = timeZoneNameProvider.getGenericDisplayName(tzid, style, locale); 272 break; 273 } 274 return value; 275 } 276 } 277 278 // No instantiation 279 private TimeZoneNameUtility() { 280 } 281 }