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 }