1 /*
   2  * Copyright (c) 2012, 2017, 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 package sun.util.locale.provider;
  26 
  27 import static java.util.Calendar.*;
  28 import java.util.Comparator;
  29 import java.util.Locale;
  30 import java.util.Map;
  31 import java.util.Set;
  32 import java.util.TreeMap;
  33 import java.util.spi.CalendarNameProvider;
  34 import sun.util.calendar.CalendarSystem;
  35 import sun.util.calendar.Era;
  36 
  37 /**
  38  * Concrete implementation of the  {@link java.util.spi.CalendarDataProvider
  39  * CalendarDataProvider} class for the JRE LocaleProviderAdapter.
  40  *
  41  * @author Masayoshi Okutsu
  42  * @author Naoto Sato
  43  */
  44 public class CalendarNameProviderImpl extends CalendarNameProvider implements AvailableLanguageTags {
  45     private final LocaleProviderAdapter.Type type;
  46     private final Set<String> langtags;
  47 
  48     public CalendarNameProviderImpl(LocaleProviderAdapter.Type type, Set<String> langtags) {
  49         this.type = type;
  50         this.langtags = langtags;
  51     }
  52 
  53     @Override
  54     public String getDisplayName(String calendarType, int field, int value, int style, Locale locale) {
  55         return getDisplayNameImpl(calendarType, field, value, style, locale, false);
  56     }
  57 
  58     public String getJavaTimeDisplayName(String calendarType, int field, int value, int style, Locale locale) {
  59         return getDisplayNameImpl(calendarType, field, value, style, locale, true);
  60     }
  61 
  62     public String getDisplayNameImpl(String calendarType, int field, int value, int style, Locale locale, boolean javatime) {
  63         String name = null;
  64         String key = getResourceKey(calendarType, field, style, javatime);
  65         if (key != null) {
  66             LocaleResources lr = LocaleProviderAdapter.forType(type).getLocaleResources(locale);
  67             String[] strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
  68 
  69             // If standalone names are requested and no "standalone." resources are found,
  70             // try the default ones instead.
  71             if (strings == null && key.indexOf("standalone.") != -1) {
  72                 key = key.replaceFirst("standalone.", "");
  73                 strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
  74             }
  75 
  76             if (strings != null && strings.length > 0) {
  77                 if (field == DAY_OF_WEEK || field == YEAR) {
  78                     --value;
  79                 }
  80                 if (value < 0 || value > strings.length) {
  81                     return null;
  82                 } else if (value == strings.length) {
  83                     if (field == ERA && "japanese".equals(calendarType)) {
  84                         // get the supplemental era, if any, specified through 
  85                         // the property "jdk.calendar.japanese.supplemental.era"
  86                         // which is always the last element.
  87                         Era[] jeras = CalendarSystem.forName("japanese").getEras();
  88                         if (jeras.length == value) {
  89                             Era supEra = jeras[value - 1]; // 0-based index
  90                             return style == LONG ?
  91                                 supEra.getName() :
  92                                 supEra.getAbbreviation();
  93                         }
  94                     }
  95                     return null;
  96                 }
  97                 name = strings[value];
  98                 // If name is empty in standalone, try its `format' style.
  99                 if (name.length() == 0
 100                         && (style == SHORT_STANDALONE || style == LONG_STANDALONE
 101                             || style == NARROW_STANDALONE)) {
 102                     name = getDisplayName(calendarType, field, value,
 103                                           getBaseStyle(style),
 104                                           locale);
 105                 }
 106             }
 107         }
 108         return name;
 109     }
 110 
 111     private static int[] REST_OF_STYLES = {
 112         SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE,
 113         NARROW_FORMAT, NARROW_STANDALONE
 114     };
 115 
 116     @Override
 117     public Map<String, Integer> getDisplayNames(String calendarType, int field, int style, Locale locale) {
 118         Map<String, Integer> names;
 119         if (style == ALL_STYLES) {
 120             names = getDisplayNamesImpl(calendarType, field, SHORT_FORMAT, locale, false);
 121             for (int st : REST_OF_STYLES) {
 122                 names.putAll(getDisplayNamesImpl(calendarType, field, st, locale, false));
 123             }
 124         } else {
 125             // specific style
 126             names = getDisplayNamesImpl(calendarType, field, style, locale, false);
 127         }
 128         return names.isEmpty() ? null : names;
 129     }
 130 
 131     // NOTE: This method should be used ONLY BY JSR 310 classes.
 132     public Map<String, Integer> getJavaTimeDisplayNames(String calendarType, int field, int style, Locale locale) {
 133         Map<String, Integer> names;
 134         names = getDisplayNamesImpl(calendarType, field, style, locale, true);
 135         return names.isEmpty() ? null : names;
 136     }
 137 
 138     private Map<String, Integer> getDisplayNamesImpl(String calendarType, int field,
 139                                                      int style, Locale locale, boolean javatime) {
 140         String key = getResourceKey(calendarType, field, style, javatime);
 141         Map<String, Integer> map = new TreeMap<>(LengthBasedComparator.INSTANCE);
 142         if (key != null) {
 143             LocaleResources lr = LocaleProviderAdapter.forType(type).getLocaleResources(locale);
 144             String[] strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
 145 
 146             // If standalone names are requested and no "standalone." resources are found,
 147             // try the default ones instead.
 148             if (strings == null && key.indexOf("standalone.") != -1) {
 149                 key = key.replaceFirst("standalone.", "");
 150                 strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
 151             }
 152 
 153             if (strings != null) {
 154                 if (!hasDuplicates(strings)) {
 155                     if (field == YEAR) {
 156                         if (strings.length > 0) {
 157                             map.put(strings[0], 1);
 158                         }
 159                     } else {
 160                         int base = (field == DAY_OF_WEEK) ? 1 : 0;
 161                         for (int i = 0; i < strings.length; i++) {
 162                             String name = strings[i];
 163                             // Ignore any empty string (some standalone month names
 164                             // are not defined)
 165                             if (name.length() == 0) {
 166                                 continue;
 167                             }
 168                             map.put(name, base + i);
 169                         }
 170                     }
 171                 }
 172             }
 173         }
 174         return map;
 175     }
 176 
 177     private int getBaseStyle(int style) {
 178         return style & ~(SHORT_STANDALONE - SHORT_FORMAT);
 179     }
 180 
 181     /**
 182      * Comparator implementation for TreeMap which iterates keys from longest
 183      * to shortest.
 184      */
 185     private static class LengthBasedComparator implements Comparator<String> {
 186         private static final LengthBasedComparator INSTANCE = new LengthBasedComparator();
 187 
 188         private LengthBasedComparator() {
 189         }
 190 
 191         @Override
 192         public int compare(String o1, String o2) {
 193             int n = o2.length() - o1.length();
 194             return (n == 0) ? o1.compareTo(o2) : n;
 195         }
 196     }
 197 
 198     @Override
 199     public Locale[] getAvailableLocales() {
 200         return LocaleProviderAdapter.toLocaleArray(langtags);
 201     }
 202 
 203     @Override
 204     public boolean isSupportedLocale(Locale locale) {
 205         if (Locale.ROOT.equals(locale)) {
 206             return true;
 207         }
 208         String calendarType = null;
 209         if (locale.hasExtensions()) {
 210             calendarType = locale.getUnicodeLocaleType("ca");
 211             locale = locale.stripExtensions();
 212         }
 213 
 214         if (calendarType != null) {
 215             switch (calendarType) {
 216             case "buddhist":
 217             case "japanese":
 218             case "gregory":
 219             case "islamic":
 220             case "roc":
 221                 break;
 222             default:
 223                 // Unknown calendar type
 224                 return false;
 225             }
 226         }
 227         if (langtags.contains(locale.toLanguageTag())) {
 228             return true;
 229         }
 230         if (type == LocaleProviderAdapter.Type.JRE) {
 231             String oldname = locale.toString().replace('_', '-');
 232             return langtags.contains(oldname);
 233         }
 234         return false;
 235     }
 236 
 237     @Override
 238     public Set<String> getAvailableLanguageTags() {
 239         return langtags;
 240     }
 241 
 242     private boolean hasDuplicates(String[] strings) {
 243         int len = strings.length;
 244         for (int i = 0; i < len - 1; i++) {
 245             String a = strings[i];
 246             if (a != null) {
 247                 for (int j = i + 1; j < len; j++) {
 248                     if (a.equals(strings[j]))  {
 249                         return true;
 250                     }
 251                 }
 252             }
 253         }
 254         return false;
 255     }
 256 
 257     private String getResourceKey(String type, int field, int style, boolean javatime) {
 258         int baseStyle = getBaseStyle(style);
 259         boolean isStandalone = (style != baseStyle);
 260 
 261         if ("gregory".equals(type)) {
 262             type = null;
 263         }
 264         boolean isNarrow = (baseStyle == NARROW_FORMAT);
 265         StringBuilder key = new StringBuilder();
 266         // If javatime is true, use prefix "java.time.".
 267         if (javatime) {
 268             key.append("java.time.");
 269         }
 270         switch (field) {
 271         case ERA:
 272             if (type != null) {
 273                 key.append(type).append('.');
 274             }
 275             if (isNarrow) {
 276                 key.append("narrow.");
 277             } else {
 278                 // JRE and CLDR use different resource key conventions
 279                 // due to historical reasons. (JRE DateFormatSymbols.getEras returns
 280                 // abbreviations while other getShort*() return abbreviations.)
 281                 if (this.type == LocaleProviderAdapter.Type.JRE) {
 282                     if (javatime) {
 283                         if (baseStyle == LONG) {
 284                             key.append("long.");
 285                         }
 286                     }
 287                     if (baseStyle == SHORT) {
 288                         key.append("short.");
 289                     }
 290                 } else { // this.type == LocaleProviderAdapter.Type.CLDR
 291                     if (baseStyle == LONG) {
 292                         key.append("long.");
 293                     }
 294                 }
 295             }
 296             key.append("Eras");
 297             break;
 298 
 299         case YEAR:
 300             if (!isNarrow) {
 301                 key.append(type).append(".FirstYear");
 302             }
 303             break;
 304 
 305         case MONTH:
 306             if ("islamic".equals(type)) {
 307                 key.append(type).append('.');
 308             }
 309             if (isStandalone) {
 310                 key.append("standalone.");
 311             }
 312             key.append("Month").append(toStyleName(baseStyle));
 313             break;
 314 
 315         case DAY_OF_WEEK:
 316             // support standalone day names
 317             if (isStandalone) {
 318                 key.append("standalone.");
 319             }
 320             key.append("Day").append(toStyleName(baseStyle));
 321             break;
 322 
 323         case AM_PM:
 324             if (isNarrow) {
 325                 key.append("narrow.");
 326             }
 327             key.append("AmPmMarkers");
 328             break;
 329         }
 330         return key.length() > 0 ? key.toString() : null;
 331     }
 332 
 333     private String toStyleName(int baseStyle) {
 334         switch (baseStyle) {
 335         case SHORT:
 336             return "Abbreviations";
 337         case NARROW_FORMAT:
 338             return "Narrows";
 339         }
 340         return "Names";
 341     }
 342 }