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