1 /*
   2  * Copyright (c) 2005, 2013, 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.Set;
  34 import java.util.concurrent.ConcurrentHashMap;
  35 import java.util.spi.TimeZoneNameProvider;
  36 import sun.util.calendar.ZoneInfo;
  37 
  38 /**
  39  * Utility class that deals with the localized time zone names
  40  *
  41  * @author Naoto Sato
  42  * @author Masayoshi Okutsu
  43  */
  44 public final class TimeZoneNameUtility {
  45 
  46     /**
  47      * cache to hold time zone localized strings. Keyed by Locale
  48      */
  49     private static ConcurrentHashMap<Locale, SoftReference<String[][]>> cachedZoneData =
  50         new ConcurrentHashMap<>();
  51 
  52     /**
  53      * Cache for managing display names per timezone per locale
  54      * The structure is:
  55      *     Map(key=id, value=SoftReference(Map(key=locale, value=displaynames)))
  56      */
  57     private static final Map<String, SoftReference<Map<Locale, String[]>>> cachedDisplayNames =
  58         new ConcurrentHashMap<>();
  59 
  60     /**
  61      * get time zone localized strings. Enumerate all keys.
  62      */
  63     public static String[][] getZoneStrings(Locale locale) {
  64         String[][] zones;
  65         SoftReference<String[][]> data = cachedZoneData.get(locale);
  66 
  67         if (data == null || ((zones = data.get()) == null)) {
  68             zones = loadZoneStrings(locale);
  69             data = new SoftReference<>(zones);
  70             cachedZoneData.put(locale, data);
  71         }
  72 
  73         return zones;
  74     }
  75 
  76     private static String[][] loadZoneStrings(Locale locale) {
  77         // If the provider is a TimeZoneNameProviderImpl, call its getZoneStrings
  78         // in order to avoid per-ID retrieval.
  79         LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(TimeZoneNameProvider.class, locale);
  80         TimeZoneNameProvider provider = adapter.getTimeZoneNameProvider();
  81         if (provider instanceof TimeZoneNameProviderImpl) {
  82             return ((TimeZoneNameProviderImpl)provider).getZoneStrings(locale);
  83         }
  84 
  85         // Performs per-ID retrieval.
  86         Set<String> zoneIDs = LocaleProviderAdapter.forJRE().getLocaleResources(locale).getZoneIDs();
  87         List<String[]> zones = new LinkedList<>();
  88         for (String key : zoneIDs) {
  89             String[] names = retrieveDisplayNamesImpl(key, locale);
  90             if (names != null) {
  91                 zones.add(names);
  92             }
  93         }
  94 
  95         String[][] zonesArray = new String[zones.size()][];
  96         return zones.toArray(zonesArray);
  97     }
  98 
  99     /**
 100      * Retrieve display names for a time zone ID.
 101      */
 102     public static String[] retrieveDisplayNames(String id, Locale locale) {
 103         if (id == null || locale == null) {
 104             throw new NullPointerException();
 105         }
 106         return retrieveDisplayNamesImpl(id, locale);
 107     }
 108 
 109     /**
 110      * Retrieves a generic time zone display name for a time zone ID.
 111      *
 112      * @param id     time zone ID
 113      * @param style  TimeZone.LONG or TimeZone.SHORT
 114      * @param locale desired Locale
 115      * @return the requested generic time zone display name, or null if not found.
 116      */
 117     public static String retrieveGenericDisplayName(String id, int style, Locale locale) {
 118         LocaleServiceProviderPool pool =
 119             LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class);
 120         return pool.getLocalizedObject(TimeZoneNameGetter.INSTANCE, locale, "generic", style, id);
 121     }
 122 
 123     /**
 124      * Retrieves a standard or daylight-saving time name for the given time zone ID.
 125      *
 126      * @param id       time zone ID
 127      * @param daylight true for a daylight saving time name, or false for a standard time name
 128      * @param style    TimeZone.LONG or TimeZone.SHORT
 129      * @param locale   desired Locale
 130      * @return the requested time zone name, or null if not found.
 131      */
 132     public static String retrieveDisplayName(String id, boolean daylight, int style, Locale locale) {
 133         LocaleServiceProviderPool pool =
 134             LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class);
 135         return pool.getLocalizedObject(TimeZoneNameGetter.INSTANCE, locale, daylight ? "dst" : "std", style, id);
 136     }
 137 
 138     private static String[] retrieveDisplayNamesImpl(String id, Locale locale) {
 139         LocaleServiceProviderPool pool =
 140             LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class);
 141 
 142         SoftReference<Map<Locale, String[]>> ref = cachedDisplayNames.get(id);
 143         if (ref != null) {
 144             Map<Locale, String[]> perLocale = ref.get();
 145             if (perLocale != null) {
 146                 String[] names = perLocale.get(locale);
 147                 if (names != null) {
 148                     return names;
 149                 }
 150                 names = pool.getLocalizedObject(TimeZoneNameArrayGetter.INSTANCE, locale, id);
 151                 if (names != null) {
 152                     perLocale.put(locale, names);
 153                 }
 154                 return names;
 155             }
 156         }
 157 
 158         String[] names = pool.getLocalizedObject(TimeZoneNameArrayGetter.INSTANCE, locale, id);
 159         if (names != null) {
 160             Map<Locale, String[]> perLocale = new ConcurrentHashMap<>();
 161             perLocale.put(locale, names);
 162             ref = new SoftReference<>(perLocale);
 163             cachedDisplayNames.put(id, ref);
 164         }
 165         return names;
 166     }
 167 
 168     /**
 169      * Obtains a localized time zone strings from a TimeZoneNameProvider
 170      * implementation.
 171      */
 172     private static class TimeZoneNameArrayGetter
 173         implements LocaleServiceProviderPool.LocalizedObjectGetter<TimeZoneNameProvider,
 174                                                                    String[]>{
 175         private static final TimeZoneNameArrayGetter INSTANCE =
 176             new TimeZoneNameArrayGetter();
 177 
 178         @Override
 179         public String[] getObject(TimeZoneNameProvider timeZoneNameProvider,
 180                                   Locale locale,
 181                                   String requestID,
 182                                   Object... params) {
 183             assert params.length == 0;
 184 
 185             // First, try to get names with the request ID
 186             String[] names = buildZoneStrings(timeZoneNameProvider, locale, requestID);
 187 
 188             if (names == null) {
 189                 Map<String, String> aliases = ZoneInfo.getAliasTable();
 190 
 191                 if (aliases != null) {
 192                     // Check whether this id is an alias, if so,
 193                     // look for the standard id.
 194                     String canonicalID = aliases.get(requestID);
 195                     if (canonicalID != null) {
 196                         names = buildZoneStrings(timeZoneNameProvider, locale, canonicalID);
 197                     }
 198                     if (names == null) {
 199                         // There may be a case that a standard id has become an
 200                         // alias.  so, check the aliases backward.
 201                         names = examineAliases(timeZoneNameProvider, locale,
 202                                    canonicalID == null ? requestID : canonicalID, aliases);
 203                     }
 204                 }
 205             }
 206 
 207             if (names != null) {
 208                 names[0] = requestID;
 209             }
 210 
 211             return names;
 212         }
 213 
 214         private static String[] examineAliases(TimeZoneNameProvider tznp, Locale locale,
 215                                                String id,
 216                                                Map<String, String> aliases) {
 217             if (aliases.containsValue(id)) {
 218                 for (Map.Entry<String, String> entry : aliases.entrySet()) {
 219                     if (entry.getValue().equals(id)) {
 220                         String alias = entry.getKey();
 221                         String[] names = buildZoneStrings(tznp, locale, alias);
 222                         if (names != null) {
 223                             return names;
 224                         }
 225                         names = examineAliases(tznp, locale, alias, aliases);
 226                         if (names != null) {
 227                             return names;
 228                         }
 229                     }
 230                 }
 231             }
 232 
 233             return null;
 234         }
 235 
 236         private static String[] buildZoneStrings(TimeZoneNameProvider tznp,
 237                                                  Locale locale, String id) {
 238             String[] names = new String[5];
 239 
 240             for (int i = 1; i <= 4; i ++) {
 241                 names[i] = tznp.getDisplayName(id, i>=3, i%2, locale);
 242 
 243                 // If the display name for DST is not supplied, copy the "standard"
 244                 // name.
 245                 if (i >= 3 && names[i] == null) {
 246                     names[i] = names[i-2];
 247                 }
 248 
 249                 // If the display name for SHORT is not supplied, copy the LONG
 250                 // name.
 251                 if (i % 2 == 0 && names[i] == null) {
 252                     names[i] = names[i-1];
 253                 }
 254             }
 255 
 256             if (names[1] == null) {
 257                 // this id seems not localized by this provider
 258                 names = null;
 259             }
 260 
 261             return names;
 262         }
 263     }
 264 
 265     private static class TimeZoneNameGetter
 266         implements LocaleServiceProviderPool.LocalizedObjectGetter<TimeZoneNameProvider,
 267                                                                    String> {
 268         private static final TimeZoneNameGetter INSTANCE =
 269             new TimeZoneNameGetter();
 270 
 271         @Override
 272         public String getObject(TimeZoneNameProvider timeZoneNameProvider,
 273                                 Locale locale,
 274                                 String requestID,
 275                                 Object... params) {
 276             assert params.length == 2;
 277             int style = (int) params[0];
 278             String tzid = (String) params[1];
 279             String value = getName(timeZoneNameProvider, locale, requestID, style, tzid);
 280             if (value == null) {
 281                 Map<String, String> aliases = ZoneInfo.getAliasTable();
 282                 if (aliases != null) {
 283                     String canonicalID = aliases.get(tzid);
 284                     if (canonicalID != null) {
 285                         value = getName(timeZoneNameProvider, locale, requestID, style, canonicalID);
 286                     }
 287                     if (value == null) {
 288                         value = examineAliases(timeZoneNameProvider, locale, requestID,
 289                                      canonicalID != null ? canonicalID : tzid, style, aliases);
 290                     }
 291                 }
 292             }
 293 
 294             return value;
 295         }
 296 
 297         private static String examineAliases(TimeZoneNameProvider tznp, Locale locale,
 298                                              String requestID, String tzid, int style,
 299                                              Map<String, String> aliases) {
 300             if (aliases.containsValue(tzid)) {
 301                 for (Map.Entry<String, String> entry : aliases.entrySet()) {
 302                     if (entry.getValue().equals(tzid)) {
 303                         String alias = entry.getKey();
 304                         String name = getName(tznp, locale, requestID, style, alias);
 305                         if (name != null) {
 306                             return name;
 307                         }
 308                         name = examineAliases(tznp, locale, requestID, alias, style, aliases);
 309                         if (name != null) {
 310                             return name;
 311                         }
 312                     }
 313                 }
 314             }
 315             return null;
 316         }
 317 
 318         private static String getName(TimeZoneNameProvider timeZoneNameProvider,
 319                                       Locale locale, String requestID, int style, String tzid) {
 320             String value = null;
 321             switch (requestID) {
 322             case "std":
 323                 value = timeZoneNameProvider.getDisplayName(tzid, false, style, locale);
 324                 break;
 325             case "dst":
 326                 value = timeZoneNameProvider.getDisplayName(tzid, true, style, locale);
 327                 break;
 328             case "generic":
 329                 value = timeZoneNameProvider.getGenericDisplayName(tzid, style, locale);
 330                 break;
 331             }
 332             return value;
 333         }
 334     }
 335 
 336     // No instantiation
 337     private TimeZoneNameUtility() {
 338     }
 339 }