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