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 }