Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/make/src/classes/build/tools/cldrconverter/Bundle.java
32287 views
/*1* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package build.tools.cldrconverter;2627import java.util.ArrayList;28import java.util.Arrays;29import java.util.EnumSet;30import java.util.HashMap;31import java.util.Iterator;32import java.util.List;33import java.util.Map;3435class Bundle {36static enum Type {37LOCALENAMES, CURRENCYNAMES, TIMEZONENAMES, CALENDARDATA, FORMATDATA;3839static EnumSet<Type> ALL_TYPES = EnumSet.of(LOCALENAMES,40CURRENCYNAMES,41TIMEZONENAMES,42CALENDARDATA,43FORMATDATA);44}4546private final static Map<String, Bundle> bundles = new HashMap<>();4748private final static String[] NUMBER_PATTERN_KEYS = {49"NumberPatterns/decimal",50"NumberPatterns/currency",51"NumberPatterns/percent"52};5354private final static String[] NUMBER_ELEMENT_KEYS = {55"NumberElements/decimal",56"NumberElements/group",57"NumberElements/list",58"NumberElements/percent",59"NumberElements/zero",60"NumberElements/pattern",61"NumberElements/minus",62"NumberElements/exponential",63"NumberElements/permille",64"NumberElements/infinity",65"NumberElements/nan"66};6768private final static String[] TIME_PATTERN_KEYS = {69"DateTimePatterns/full-time",70"DateTimePatterns/long-time",71"DateTimePatterns/medium-time",72"DateTimePatterns/short-time",73};7475private final static String[] DATE_PATTERN_KEYS = {76"DateTimePatterns/full-date",77"DateTimePatterns/long-date",78"DateTimePatterns/medium-date",79"DateTimePatterns/short-date",80};8182private final static String[] DATETIME_PATTERN_KEYS = {83"DateTimePatterns/date-time"84};8586private final static String[] ERA_KEYS = {87"long.Eras",88"Eras",89"narrow.Eras"90};9192// Keys for individual time zone names93private final static String TZ_GEN_LONG_KEY = "timezone.displayname.generic.long";94private final static String TZ_GEN_SHORT_KEY = "timezone.displayname.generic.short";95private final static String TZ_STD_LONG_KEY = "timezone.displayname.standard.long";96private final static String TZ_STD_SHORT_KEY = "timezone.displayname.standard.short";97private final static String TZ_DST_LONG_KEY = "timezone.displayname.daylight.long";98private final static String TZ_DST_SHORT_KEY = "timezone.displayname.daylight.short";99private final static String[] ZONE_NAME_KEYS = {100TZ_STD_LONG_KEY,101TZ_STD_SHORT_KEY,102TZ_DST_LONG_KEY,103TZ_DST_SHORT_KEY,104TZ_GEN_LONG_KEY,105TZ_GEN_SHORT_KEY106};107108private final String id;109private final String cldrPath;110private final EnumSet<Type> bundleTypes;111private final String currencies;112113static Bundle getBundle(String id) {114return bundles.get(id);115}116117@SuppressWarnings("ConvertToStringSwitch")118Bundle(String id, String cldrPath, String bundles, String currencies) {119this.id = id;120this.cldrPath = cldrPath;121if ("localenames".equals(bundles)) {122bundleTypes = EnumSet.of(Type.LOCALENAMES);123} else if ("currencynames".equals(bundles)) {124bundleTypes = EnumSet.of(Type.CURRENCYNAMES);125} else {126bundleTypes = Type.ALL_TYPES;127}128if (currencies == null) {129currencies = "local";130}131this.currencies = currencies;132addBundle();133}134135private void addBundle() {136Bundle.bundles.put(id, this);137}138139String getID() {140return id;141}142143boolean isRoot() {144return "root".equals(id);145}146147String getCLDRPath() {148return cldrPath;149}150151EnumSet<Type> getBundleTypes() {152return bundleTypes;153}154155String getCurrencies() {156return currencies;157}158159/**160* Generate a map that contains all the data that should be161* visible for the bundle's locale162*/163Map<String, Object> getTargetMap() throws Exception {164String[] cldrBundles = getCLDRPath().split(",");165166// myMap contains resources for id.167Map<String, Object> myMap = new HashMap<>();168int index;169for (index = 0; index < cldrBundles.length; index++) {170if (cldrBundles[index].equals(id)) {171myMap.putAll(CLDRConverter.getCLDRBundle(cldrBundles[index]));172break;173}174}175176// parentsMap contains resources from id's parents.177Map<String, Object> parentsMap = new HashMap<>();178for (int i = cldrBundles.length - 1; i > index; i--) {179if (!("no".equals(cldrBundles[i]) || cldrBundles[i].startsWith("no_"))) {180parentsMap.putAll(CLDRConverter.getCLDRBundle(cldrBundles[i]));181}182}183// Duplicate myMap as parentsMap for "root" so that the184// fallback works. This is a huck, though.185if ("root".equals(cldrBundles[0])) {186assert parentsMap.isEmpty();187parentsMap.putAll(myMap);188}189190// merge individual strings into arrays191192// if myMap has any of the NumberPatterns members193for (String k : NUMBER_PATTERN_KEYS) {194if (myMap.containsKey(k)) {195String[] numberPatterns = new String[NUMBER_PATTERN_KEYS.length];196for (int i = 0; i < NUMBER_PATTERN_KEYS.length; i++) {197String key = NUMBER_PATTERN_KEYS[i];198String value = (String) myMap.remove(key);199if (value == null) {200value = (String) parentsMap.remove(key);201}202if (value.length() == 0) {203CLDRConverter.warning("empty pattern for " + key);204}205numberPatterns[i] = value;206}207myMap.put("NumberPatterns", numberPatterns);208break;209}210}211212// if myMap has any of NUMBER_ELEMENT_KEYS, create a complete NumberElements.213String defaultScript = (String) myMap.get("DefaultNumberingSystem");214@SuppressWarnings("unchecked")215List<String> scripts = (List<String>) myMap.get("numberingScripts");216if (defaultScript == null && scripts != null) {217// Some locale data has no default script for numbering even with mutiple scripts.218// Take the first one as default in that case.219defaultScript = scripts.get(0);220myMap.put("DefaultNumberingSystem", defaultScript);221}222if (scripts != null) {223for (String script : scripts) {224for (String k : NUMBER_ELEMENT_KEYS) {225String[] numberElements = new String[NUMBER_ELEMENT_KEYS.length];226for (int i = 0; i < NUMBER_ELEMENT_KEYS.length; i++) {227String key = script + "." + NUMBER_ELEMENT_KEYS[i];228String value = (String) myMap.remove(key);229if (value == null) {230if (key.endsWith("/pattern")) {231value = "#";232} else {233value = (String) parentsMap.get(key);234if (value == null) {235// the last resort is "latn"236key = "latn." + NUMBER_ELEMENT_KEYS[i];237value = (String) parentsMap.get(key);238if (value == null) {239throw new InternalError("NumberElements: null for " + key);240}241}242}243}244numberElements[i] = value;245}246myMap.put(script + "." + "NumberElements", numberElements);247break;248}249}250}251252// another hack: parentsMap is not used for date-time resources.253if ("root".equals(id)) {254parentsMap = null;255}256257for (CalendarType calendarType : CalendarType.values()) {258String calendarPrefix = calendarType.keyElementName();259// handle multiple inheritance for month and day names260handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "MonthNames");261handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "MonthAbbreviations");262handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "MonthNarrows");263handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "DayNames");264handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "DayAbbreviations");265handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "DayNarrows");266handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "AmPmMarkers");267handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "narrow.AmPmMarkers");268handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterNames");269handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterAbbreviations");270handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterNarrows");271272adjustEraNames(myMap, calendarType);273274handleDateTimeFormatPatterns(TIME_PATTERN_KEYS, myMap, parentsMap, calendarType, "TimePatterns");275handleDateTimeFormatPatterns(DATE_PATTERN_KEYS, myMap, parentsMap, calendarType, "DatePatterns");276handleDateTimeFormatPatterns(DATETIME_PATTERN_KEYS, myMap, parentsMap, calendarType, "DateTimePatterns");277}278279// First, weed out any empty timezone or metazone names from myMap.280// Fill in any missing abbreviations if locale is "en".281for (Iterator<String> it = myMap.keySet().iterator(); it.hasNext();) {282String key = it.next();283if (key.startsWith(CLDRConverter.TIMEZONE_ID_PREFIX)284|| key.startsWith(CLDRConverter.METAZONE_ID_PREFIX)) {285@SuppressWarnings("unchecked")286Map<String, String> nameMap = (Map<String, String>) myMap.get(key);287if (nameMap.isEmpty()) {288// Some zones have only exemplarCity, which become empty.289// Remove those from the map.290it.remove();291continue;292}293294if (id.startsWith("en")) {295fillInAbbrs(key, nameMap);296}297}298}299for (Iterator<String> it = myMap.keySet().iterator(); it.hasNext();) {300String key = it.next();301if (key.startsWith(CLDRConverter.TIMEZONE_ID_PREFIX)302|| key.startsWith(CLDRConverter.METAZONE_ID_PREFIX)) {303@SuppressWarnings("unchecked")304Map<String, String> nameMap = (Map<String, String>) myMap.get(key);305// Convert key/value pairs to an array.306String[] names = new String[ZONE_NAME_KEYS.length];307int ix = 0;308for (String nameKey : ZONE_NAME_KEYS) {309String name = nameMap.get(nameKey);310if (name == null) {311@SuppressWarnings("unchecked")312Map<String, String> parentNames = (Map<String, String>) parentsMap.get(key);313if (parentNames != null) {314name = parentNames.get(nameKey);315}316}317names[ix++] = name;318}319if (hasNulls(names)) {320String metaKey = toMetaZoneKey(key);321if (metaKey != null) {322Object obj = myMap.get(metaKey);323if (obj instanceof String[]) {324String[] metaNames = (String[]) obj;325for (int i = 0; i < names.length; i++) {326if (names[i] == null) {327names[i] = metaNames[i];328}329}330} else if (obj instanceof Map) {331@SuppressWarnings("unchecked")332Map<String, String> m = (Map<String, String>) obj;333for (int i = 0; i < names.length; i++) {334if (names[i] == null) {335names[i] = m.get(ZONE_NAME_KEYS[i]);336}337}338}339}340// If there are still any nulls, try filling in them from en data.341if (hasNulls(names) && !id.equals("en")) {342@SuppressWarnings("unchecked")343String[] enNames = (String[]) Bundle.getBundle("en").getTargetMap().get(key);344if (enNames == null) {345if (metaKey != null) {346@SuppressWarnings("unchecked")347String[] metaNames = (String[]) Bundle.getBundle("en").getTargetMap().get(metaKey);348enNames = metaNames;349}350}351if (enNames != null) {352for (int i = 0; i < names.length; i++) {353if (names[i] == null) {354names[i] = enNames[i];355}356}357}358// If there are still nulls, give up names.359if (hasNulls(names)) {360names = null;361}362}363}364// replace the Map with the array365if (names != null) {366myMap.put(key, names);367} else {368it.remove();369}370}371}372return myMap;373}374375private void handleMultipleInheritance(Map<String, Object> map, Map<String, Object> parents, String key) {376String formatKey = key + "/format";377Object format = map.get(formatKey);378if (format != null) {379map.remove(formatKey);380map.put(key, format);381if (fillInElements(parents, formatKey, format)) {382map.remove(key);383}384}385String standaloneKey = key + "/stand-alone";386Object standalone = map.get(standaloneKey);387if (standalone != null) {388map.remove(standaloneKey);389String realKey = key;390if (format != null) {391realKey = "standalone." + key;392}393map.put(realKey, standalone);394if (fillInElements(parents, standaloneKey, standalone)) {395map.remove(realKey);396}397}398}399400/**401* Fills in any empty elements with its parent element. Returns true if the resulting array is402* identical to its parent array.403*404* @param parents405* @param key406* @param value407* @return true if the resulting array is identical to its parent array.408*/409private boolean fillInElements(Map<String, Object> parents, String key, Object value) {410if (parents == null) {411return false;412}413if (value instanceof String[]) {414Object pvalue = parents.get(key);415if (pvalue != null && pvalue instanceof String[]) {416String[] strings = (String[]) value;417String[] pstrings = (String[]) pvalue;418for (int i = 0; i < strings.length; i++) {419if (strings[i] == null || strings[i].length() == 0) {420strings[i] = pstrings[i];421}422}423return Arrays.equals(strings, pstrings);424}425}426return false;427}428429/*430* Adjusts String[] for era names because JRE's Calendars use different431* ERA value indexes in the Buddhist, Japanese Imperial, and Islamic calendars.432*/433private void adjustEraNames(Map<String, Object> map, CalendarType type) {434String[][] eraNames = new String[ERA_KEYS.length][];435String[] realKeys = new String[ERA_KEYS.length];436int index = 0;437for (String key : ERA_KEYS) {438String realKey = type.keyElementName() + key;439String[] value = (String[]) map.get(realKey);440if (value != null) {441switch (type) {442case GREGORIAN:443break;444445case JAPANESE:446{447String[] newValue = new String[value.length + 1];448String[] julianEras = (String[]) map.get(key);449if (julianEras != null && julianEras.length >= 2) {450newValue[0] = julianEras[1];451} else {452newValue[0] = "";453}454System.arraycopy(value, 0, newValue, 1, value.length);455value = newValue;456}457break;458459case BUDDHIST:460// Replace the value461value = new String[] {"BC", value[0]};462break;463464case ISLAMIC:465// Replace the value466value = new String[] {"", value[0]};467break;468}469if (!key.equals(realKey)) {470map.put(realKey, value);471}472}473realKeys[index] = realKey;474eraNames[index++] = value;475}476for (int i = 0; i < eraNames.length; i++) {477if (eraNames[i] == null) {478map.put(realKeys[i], null);479}480}481}482483private void handleDateTimeFormatPatterns(String[] patternKeys, Map<String, Object> myMap, Map<String, Object> parentsMap,484CalendarType calendarType, String name) {485String calendarPrefix = calendarType.keyElementName();486for (String k : patternKeys) {487if (myMap.containsKey(calendarPrefix + k)) {488int len = patternKeys.length;489List<String> rawPatterns = new ArrayList<>(len);490List<String> patterns = new ArrayList<>(len);491for (int i = 0; i < len; i++) {492String key = calendarPrefix + patternKeys[i];493String pattern = (String) myMap.remove(key);494if (pattern == null) {495pattern = (String) parentsMap.remove(key);496}497rawPatterns.add(i, pattern);498if (pattern != null) {499patterns.add(i, translateDateFormatLetters(calendarType, pattern));500} else {501patterns.add(i, null);502}503}504// If patterns is empty or has any nulls, discard patterns.505if (patterns.isEmpty()) {506return;507}508for (String p : patterns) {509if (p == null) {510return;511}512}513String key = calendarPrefix + name;514if (!rawPatterns.equals(patterns)) {515myMap.put("java.time." + key, rawPatterns.toArray(new String[len]));516}517myMap.put(key, patterns.toArray(new String[len]));518break;519}520}521}522523private String translateDateFormatLetters(CalendarType calendarType, String cldrFormat) {524String pattern = cldrFormat;525int length = pattern.length();526boolean inQuote = false;527StringBuilder jrePattern = new StringBuilder(length);528int count = 0;529char lastLetter = 0;530531for (int i = 0; i < length; i++) {532char c = pattern.charAt(i);533534if (c == '\'') {535// '' is treated as a single quote regardless of being536// in a quoted section.537if ((i + 1) < length) {538char nextc = pattern.charAt(i + 1);539if (nextc == '\'') {540i++;541if (count != 0) {542convert(calendarType, lastLetter, count, jrePattern);543lastLetter = 0;544count = 0;545}546jrePattern.append("''");547continue;548}549}550if (!inQuote) {551if (count != 0) {552convert(calendarType, lastLetter, count, jrePattern);553lastLetter = 0;554count = 0;555}556inQuote = true;557} else {558inQuote = false;559}560jrePattern.append(c);561continue;562}563if (inQuote) {564jrePattern.append(c);565continue;566}567if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {568if (count != 0) {569convert(calendarType, lastLetter, count, jrePattern);570lastLetter = 0;571count = 0;572}573jrePattern.append(c);574continue;575}576577if (lastLetter == 0 || lastLetter == c) {578lastLetter = c;579count++;580continue;581}582convert(calendarType, lastLetter, count, jrePattern);583lastLetter = c;584count = 1;585}586587if (inQuote) {588throw new InternalError("Unterminated quote in date-time pattern: " + cldrFormat);589}590591if (count != 0) {592convert(calendarType, lastLetter, count, jrePattern);593}594if (cldrFormat.contentEquals(jrePattern)) {595return cldrFormat;596}597return jrePattern.toString();598}599600private String toMetaZoneKey(String tzKey) {601if (tzKey.startsWith(CLDRConverter.TIMEZONE_ID_PREFIX)) {602String tz = tzKey.substring(CLDRConverter.TIMEZONE_ID_PREFIX.length());603String meta = CLDRConverter.handlerMetaZones.get(tz);604if (meta != null) {605return CLDRConverter.METAZONE_ID_PREFIX + meta;606}607}608return null;609}610611private void fillInAbbrs(String key, Map<String, String> map) {612fillInAbbrs(TZ_STD_LONG_KEY, TZ_STD_SHORT_KEY, map);613fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);614fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);615616// If the standard std is "Standard Time" and daylight std is "Summer Time",617// replace the standard std with the generic std to avoid using618// the same abbrivation except for Australia time zone names.619String std = map.get(TZ_STD_SHORT_KEY);620String dst = map.get(TZ_DST_SHORT_KEY);621String gen = map.get(TZ_GEN_SHORT_KEY);622if (std != null) {623if (dst == null) {624// if dst is null, create long and short names from the standard625// std. ("Something Standard Time" to "Something Daylight Time",626// or "Something Time" to "Something Summer Time")627String name = map.get(TZ_STD_LONG_KEY);628if (name != null) {629if (name.contains("Standard Time")) {630name = name.replace("Standard Time", "Daylight Time");631} else if (name.endsWith("Mean Time")) {632name = name.replace("Mean Time", "Summer Time");633} else if (name.endsWith(" Time")) {634name = name.replace(" Time", " Summer Time");635}636map.put(TZ_DST_LONG_KEY, name);637fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);638}639}640if (gen == null) {641String name = map.get(TZ_STD_LONG_KEY);642if (name != null) {643if (name.endsWith("Standard Time")) {644name = name.replace("Standard Time", "Time");645} else if (name.endsWith("Mean Time")) {646name = name.replace("Mean Time", "Time");647}648map.put(TZ_GEN_LONG_KEY, name);649fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);650}651}652}653}654655private void fillInAbbrs(String longKey, String shortKey, Map<String, String> map) {656String abbr = map.get(shortKey);657if (abbr == null) {658String name = map.get(longKey);659if (name != null) {660abbr = toAbbr(name);661if (abbr != null) {662map.put(shortKey, abbr);663}664}665}666}667668private String toAbbr(String name) {669String[] substrs = name.split("\\s+");670StringBuilder sb = new StringBuilder();671for (String s : substrs) {672char c = s.charAt(0);673if (c >= 'A' && c <= 'Z') {674sb.append(c);675}676}677return sb.length() > 0 ? sb.toString() : null;678}679680private void convert(CalendarType calendarType, char cldrLetter, int count, StringBuilder sb) {681switch (cldrLetter) {682case 'G':683if (calendarType != CalendarType.GREGORIAN) {684// Adjust the number of 'G's for JRE SimpleDateFormat685if (count == 5) {686// CLDR narrow -> JRE short687count = 1;688} else if (count == 1) {689// CLDR abbr -> JRE long690count = 4;691}692}693appendN(cldrLetter, count, sb);694break;695696// TODO: support 'c' and 'e' in JRE SimpleDateFormat697// Use 'u' and 'E' for now.698case 'c':699case 'e':700switch (count) {701case 1:702sb.append('u');703break;704case 3:705case 4:706appendN('E', count, sb);707break;708case 5:709appendN('E', 3, sb);710break;711}712break;713714case 'v':715case 'V':716appendN('z', count, sb);717break;718719case 'Z':720if (count == 4 || count == 5) {721sb.append("XXX");722}723break;724725case 'u':726case 'U':727case 'q':728case 'Q':729case 'l':730case 'g':731case 'j':732case 'A':733throw new InternalError(String.format("Unsupported letter: '%c', count=%d%n",734cldrLetter, count));735default:736appendN(cldrLetter, count, sb);737break;738}739}740741private void appendN(char c, int n, StringBuilder sb) {742for (int i = 0; i < n; i++) {743sb.append(c);744}745}746747private static boolean hasNulls(Object[] array) {748for (int i = 0; i < array.length; i++) {749if (array[i] == null) {750return true;751}752}753return false;754}755}756757758