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.Enumeration;
  30 import java.util.LinkedList;
  31 import java.util.List;
  32 import java.util.Locale;
  33 import java.util.Map;
  34 import java.util.Set;
  35 import java.util.concurrent.ConcurrentHashMap;
  36 import java.util.spi.TimeZoneNameProvider;
  37 import sun.util.calendar.ZoneInfo;
  38 import sun.util.resources.OpenListResourceBundle;
  39 
  40 /**
  41  * Utility class that deals with the localized time zone names
  42  *
  43  * @author Naoto Sato
  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<OpenListResourceBundle>> 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         List<String[]> zones = new LinkedList<>();
  77         OpenListResourceBundle rb = getBundle(locale);
  78         Enumeration<String> keys = rb.getKeys();
  79         String[] names;
  80 
  81         while(keys.hasMoreElements()) {
  82             String key = keys.nextElement();
  83 
  84             names = retrieveDisplayNames(rb, key, locale);
  85             if (names != null) {
  86                 zones.add(names);
  87             }
  88         }
  89 
  90         String[][] zonesArray = new String[zones.size()][];
  91         return zones.toArray(zonesArray);
  92     }
  93 
  94     /**
  95      * Retrieve display names for a time zone ID.
  96      */
  97     public static String[] retrieveDisplayNames(String id, Locale locale) {
  98         OpenListResourceBundle rb = getBundle(locale);
  99         return retrieveDisplayNames(rb, id, locale);
 100     }
 101 
 102     private static String[] retrieveDisplayNames(OpenListResourceBundle rb,
 103                                                 String id, Locale locale) {
 104         if (id == null || locale == null) {
 105             throw new NullPointerException();
 106         }
 107 
 108         LocaleServiceProviderPool pool =
 109             LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class);
 110         return pool.getLocalizedObject(TimeZoneNameGetter.INSTANCE, locale, id);
 111     }
 112 
 113     private static OpenListResourceBundle getBundle(Locale locale) {
 114         OpenListResourceBundle rb;
 115         SoftReference<OpenListResourceBundle> data = cachedBundles.get(locale);
 116 
 117         if (data == null || ((rb = data.get()) == null)) {
 118             rb = LocaleProviderAdapter.forJRE().getLocaleData().getTimeZoneNames(locale);
 119             data = new SoftReference<>(rb);
 120             cachedBundles.put(locale, data);
 121         }
 122 
 123         return rb;
 124     }
 125 
 126     /**
 127      * Obtains a localized time zone strings from a TimeZoneNameProvider
 128      * implementation.
 129      */
 130     private static class TimeZoneNameGetter
 131         implements LocaleServiceProviderPool.LocalizedObjectGetter<TimeZoneNameProvider,
 132                                                                    String[]>{
 133         private static final TimeZoneNameGetter INSTANCE =
 134             new TimeZoneNameGetter();
 135 
 136         @Override
 137         public String[] getObject(TimeZoneNameProvider timeZoneNameProvider,
 138                                 Locale locale,
 139                                 String requestID,
 140                                 Object... params) {
 141             assert params.length == 0;
 142             String queryID = requestID;
 143 
 144             // First, try to get names with the request ID
 145             String[] names = buildZoneStrings(timeZoneNameProvider, locale, requestID);
 146 
 147             if (names == null) {
 148                 Map<String, String> aliases = ZoneInfo.getAliasTable();
 149 
 150                 if (aliases != null) {
 151                     // Check whether this id is an alias, if so,
 152                     // look for the standard id.
 153                     if (aliases.containsKey(queryID)) {
 154                         String prevID = queryID;
 155                         while ((queryID = aliases.get(queryID)) != null) {
 156                             prevID = queryID;
 157                         }
 158                         queryID = prevID;
 159                     }
 160 
 161                     names = buildZoneStrings(timeZoneNameProvider, locale, queryID);
 162 
 163                     if (names == null) {
 164                         // There may be a case that a standard id has become an
 165                         // alias.  so, check the aliases backward.
 166                         names = examineAliases(timeZoneNameProvider, locale,
 167                                                queryID, aliases, aliases.entrySet());
 168                     }
 169                 }
 170             }
 171 
 172             if (names != null) {
 173                 names[0] = requestID;
 174             }
 175 
 176             return names;
 177         }
 178 
 179         private static String[] examineAliases(TimeZoneNameProvider tznp, Locale locale,
 180                                                String id,
 181                                                Map<String, String> aliases,
 182                                                Set<Map.Entry<String, String>> aliasesSet) {
 183             if (aliases.containsValue(id)) {
 184                 for (Map.Entry<String, String> entry : aliasesSet) {
 185                     if (entry.getValue().equals(id)) {
 186                         String alias = entry.getKey();
 187                         String[] names = buildZoneStrings(tznp, locale, alias);
 188                         if (names != null) {
 189                             return names;
 190                         } else {
 191                             names = examineAliases(tznp, locale, alias, aliases, aliasesSet);
 192                             if (names != null) {
 193                                 return names;
 194                             }
 195                         }
 196                     }
 197                 }
 198             }
 199 
 200             return null;
 201         }
 202 
 203         private static String[] buildZoneStrings(TimeZoneNameProvider tznp,
 204                                     Locale locale, String id) {
 205             String[] names = new String[5];
 206 
 207             for (int i = 1; i <= 4; i ++) {
 208                 names[i] = tznp.getDisplayName(id, i>=3, i%2, locale);
 209                 if (i >= 3 && names[i] == null) {
 210                     names[i] = names[i-2];
 211                 }
 212             }
 213 
 214             if (names[1] == null) {
 215                 // this id seems not localized by this provider
 216                 names = null;
 217             }
 218 
 219             return names;
 220         }
 221     }
 222 
 223     // No instantiation
 224     private TimeZoneNameUtility() {
 225     }
 226 }