All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.ut.LcData Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property of
 * Adobe Systems Incorporated and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Adobe Systems
 * Incorporated and its suppliers and may be covered by U.S. and Foreign
 * Patents, patents in process, and are protected by trade secret or copyright
 * law. Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained from
 * Adobe Systems Incorporated.
 */
package com.adobe.xfa.ut;


import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

import com.adobe.xfa.ut.lcdata.LcBundle;


/**
 * LcData defines locale data objects in support of localization.
 * The source of the localized data was originally Java's JDK 1.4.  However
 * this has long since been converted to use Unicode's CLDR 1.3 data. CLDR
 * data has been slightly modified to conform to XFA standards.
 * 

* The C++ implementation stores locale data in a static store. Alas, * the Java compiler's 64K data limit precludes us from doing the same. * Instead, the Java implementation stores locale data in properties files * -- one for each supported locale. *

* Here's a snippet of code illustrating the use of {@link LcData} to retrieve * abbreviated month names, and retrieve a date format. * *


 * 
 *  
 *   import com.adobe.xfa.ut.LcData;
 *  		...
 *   LcData data = new lcData("es_ES");
 *   String s = data.getAbbrMonthName(LcData.FEB);
 *   System.out.println(s);
 *   data = new lcData("pt_BR");
 *   s = data.getDateFormat(LcData.SHORT);
 *   System.out.println(s);
 *   
 *  
 * 
*

* Besides the static store, this class also maintains a more dynamic * thread-local store. The application can provide its own locale data * definitions to be stored in the runtime store. In general, when locale * information is requested, the runtime store is searched first, then the * static store. This happens on every single request, so if the runtime * store changes between two calls on an LcData data instance, the two calls * may return different results. In practice, LcData object have such limited * lifespans that this doesn't become an issue. *

*

* To populate the runtime store, the application calls method LcData.update() * with an instance of the nested class LcDada.LcRunTimeData. This class contains * several arrays of strings that hold locale information. A number of * public static final int constants provided may be used as indexes into these * arrays. *

* * @author Mike P. Tardif * @exclude from published api. */ @SuppressWarnings("unchecked") public class LcData { /** * Nested class describing a locale's data. * @exclude from published api. */ public static class LcRunTimeData { public final String localeName; public final String[] monthNames = new String[12]; // month names (JAN, ..., DEC). public final String[] abbrMonthNames = new String[12]; // abbr month names (JAN, ..., DEC). public final String[] dayNames = new String[7]; // weekday names (SUN, ..., SAT). public final String[] abbrWeekdayNames = new String[7]; // abbr weekday names (SUN, ..., SAT). public final String[] meridiemNames = new String[2]; // meridiem names (AM, PM). public final String[] eraNames = new String[2]; // era names (BC, AD). public final String[] datePatterns = new String[4]; // date format patterns (FULL, ..., SHORT). public final String[] timePatterns = new String[4]; // time format patterns (FULL, ..., SHORT). public final String[] dateTimeSymbols = new String[1]; // date and time symbols. public final String[] numberPatterns = new String[3]; // number format patterns (NUMERIC, ..., PERCENT). public final String[] numericSymbols = new String[5]; // numeric symbols (NUM_DECIMAL, ..., NUM_ZERO). public final String[] currencySymbols = new String[3]; // currency symbols (CUR_SYMBOL, ..., CUR_DECIMAL). public final List typefaceList = new ArrayList(); // typeface names. public LcRunTimeData(String sLocaleName) { int index = localeIndex(sLocaleName); if (index >= 0) sLocaleName = gLocaleList[index]; else sLocaleName = LcLocale.normalize(sLocaleName); localeName = sLocaleName; } } /* Weekday Names */ public static final int SUN = 0; public static final int MON = 1; public static final int TUE = 2; public static final int WED = 3; public static final int THU = 4; public static final int FRI = 5; public static final int SAT = 6; /* Month Names */ public static final int JAN = 0; public static final int FEB = 1; public static final int MAR = 2; public static final int APR = 3; public static final int MAY = 4; public static final int JUN = 5; public static final int JUL = 6; public static final int AUG = 7; public static final int SEP = 8; public static final int OCT = 9; public static final int NOV = 10; public static final int DEC = 11; /* Meridiem Names */ public static final int AM = 0; public static final int PM = 1; /* Era Names */ public static final int BC = 0; public static final int AD = 1; /* Date & Time Patterns */ public static final int DEFLT = 2; public static final int FULL = 0; public static final int LONG = 1; public static final int MED = 2; public static final int SHORT = 3; /* Number Patterns */ public static final int NUMERIC = 0; public static final int CURRENCY = 1; public static final int PERCENT = 2; /* Numeric Symbols */ public static final int NUM_DECIMAL = 0; public static final int NUM_GROUPING = 1; public static final int NUM_PERCENT = 2; public static final int NUM_MINUS = 3; public static final int NUM_ZERO = 4; /* Currency Symbols */ public static final int CUR_SYMBOL = 0; public static final int CUR_ISONAME = 1; public static final int CUR_DECIMAL = 2; /* Date, Time and DateTime Format Styles */ public static final int DEFLT_FMT = 0; public static final int SHORT_FMT = 1; public static final int MED_FMT = 2; public static final int LONG_FMT = 3; public static final int FULL_FMT = 4; /* Numeric Format Styles */ public static final int INTEGRAL_FMT = 0; public static final int DECIMAL_FMT = 1; public static final int CURRENCY_FMT = 2; public static final int PERCENT_FMT = 3; /* Numeric Format Options */ public static final int WITHOUT_RADIX = 0x0; public static final int WITHOUT_GROUPINGS = 0x0; public static final int WITH_GROUPINGS = 0x1; public static final int WITH_ZEDS = 0x0; public static final int WITH_EIGHTS = 0x2; public static final int WITH_RADIX = 0x4; public static final int KEEP_NINES = 0x8; public static final int withWidth(int width) { return width << 16; } public static final int withPrecision(int prec) { return (prec & 0xff) << 8; } public static final int WITHOUT_CATEGORIES = 0x0; public static final int WITH_CATEGORIES = 0x1; /* Max no. of significant digits in a double */ private static final int MAX_DBL_DIG = 18; /* Max no. of significant digits in an integer */ private static final int MAX_INT_DIG = 10; /* Max width before precision loss in a double */ private static final int MAX_DBL_WIDTH = 15; /* Date Pattern Mask */ private static final String gpDateMask = "GYMD EJFwW "; /* Time Pattern Mask */ private static final String gpTimeMask = " KHMSF AhkZz"; /* Thread local storage for the runtime store */ private final static ThreadLocal> mRuntimeMap = new ThreadLocal>() { protected Map initialValue() { return new HashMap(); } }; /** * The locales for which we have data in resources. * Must keep in value-sorted order (non case sensitive)! */ private final static String[] gLocaleList = { LcLocale.Arabic, LcLocale.Arabic_UAE, LcLocale.Arabic_Bahrain, LcLocale.Arabic_Algeria, LcLocale.Arabic_Egypt, LcLocale.Arabic_Iraq, LcLocale.Arabic_Jordan, LcLocale.Arabic_Kuwait, LcLocale.Arabic_Lebanon, LcLocale.Arabic_Libya, LcLocale.Arabic_Morocco, LcLocale.Arabic_Oman, LcLocale.Arabic_Qatar, LcLocale.Arabic_SaudiArabia, LcLocale.Arabic_Sudan, LcLocale.Arabic_Syria, LcLocale.Arabic_Tunisia, LcLocale.Arabic_Yemen, LcLocale.Azerbaijani, LcLocale.Azerbaijani_Azerbaijan, LcLocale.Azerbaijani_Cyrillic, LcLocale.Azerbaijani_Cyrillic_Azerbaijan, LcLocale.Azerbaijani_Latin, LcLocale.Azerbaijani_Latin_Azerbaijan, LcLocale.Byelorussian, LcLocale.Byelorussian_Belarus, LcLocale.Bulgarian, LcLocale.Bulgarian_Bulgaria, LcLocale.Bosnian, LcLocale.Bosnian_BosniaHerzegovina, LcLocale.C, LcLocale.Catalan, LcLocale.Catalan_Spain, LcLocale.Czech, LcLocale.Czech_CzechRepublic, LcLocale.Danish, LcLocale.Danish_Denmark, LcLocale.German, LcLocale.German_Austria, LcLocale.German_Belgium, LcLocale.German_Switzerland, LcLocale.German_Germany, LcLocale.German_Liechtenstein, LcLocale.German_Luxembourg, LcLocale.Greek, LcLocale.Greek_Greece, LcLocale.English, LcLocale.English_Australia, LcLocale.English_Belgium, LcLocale.English_Canada, LcLocale.English_UK, LcLocale.English_UK_Euro, LcLocale.English_HongKong, LcLocale.English_Ireland, LcLocale.English_India, LcLocale.English_NewZealand, LcLocale.English_Philippines, LcLocale.English_Singapore, LcLocale.English_US, LcLocale.English_US_Posix, LcLocale.English_VirginIslands, LcLocale.English_SouthAfrica, LcLocale.Spanish, LcLocale.Spanish_Argentina, LcLocale.Spanish_Bolivia, LcLocale.Spanish_Chile, LcLocale.Spanish_Colombia, LcLocale.Spanish_CostaRica, LcLocale.Spanish_DominicanRepublic, LcLocale.Spanish_Ecuador, LcLocale.Spanish_Spain, LcLocale.Spanish_Guatemala, LcLocale.Spanish_Honduras, LcLocale.Spanish_Mexico, LcLocale.Spanish_Nicaragua, LcLocale.Spanish_Panama, LcLocale.Spanish_Peru, LcLocale.Spanish_PuertoRico, LcLocale.Spanish_Paraguay, LcLocale.Spanish_ElSalvador, LcLocale.Spanish_US, LcLocale.Spanish_Uruguay, LcLocale.Spanish_Venezuela, LcLocale.Estonian, LcLocale.Estonian_Estonia, LcLocale.Basque, LcLocale.Basque_Spain, LcLocale.Persian, LcLocale.Persian_Iran, LcLocale.Finnish, LcLocale.Finnish_Finland, LcLocale.French, LcLocale.French_Belgium, LcLocale.French_Canada, LcLocale.French_Switzerland, LcLocale.French_France, LcLocale.French_Luxembourg, LcLocale.Hebrew, LcLocale.Hebrew_Israel, LcLocale.Hindi, LcLocale.Hindi_India, LcLocale.Croatian, LcLocale.Croatian_Croatia, LcLocale.Hungarian, LcLocale.Hungarian_Hungary, LcLocale.Armenian, LcLocale.Armenian_Armenia, LcLocale.Indonesian, LcLocale.Indonesian_Indonesia, LcLocale.Icelandic, LcLocale.Icelandic_Iceland, LcLocale.Italian, LcLocale.Italian_Switzerland, LcLocale.Italian_Italy, LcLocale.Japanese, LcLocale.Japanese_Japan, LcLocale.Kazakh, LcLocale.Kazakh_Kazakhstan, LcLocale.Khmer, LcLocale.Khmer_Cambodia, LcLocale.Korean, LcLocale.Korean_Korea, LcLocale.Korean_Korea_Hani, LcLocale.Lao, LcLocale.Lao_Laos, LcLocale.Lithuanian, LcLocale.Lithuanian_Lithuania, LcLocale.Latvian, LcLocale.Latvian_Latvia, LcLocale.Macedonian, LcLocale.Macedonian_Macedonia, LcLocale.Malay, LcLocale.Malay_Malaysia, LcLocale.Norwegian_Bokmal, LcLocale.Norwegian_Bokmal_Norway, LcLocale.Dutch, LcLocale.Dutch_Belgium, LcLocale.Dutch_Netherlands, LcLocale.Norwegian_Nynorsk, LcLocale.Norwegian_Nynorsk_Norway, "no", "no_NO", "no_NO_NY", LcLocale.Polish, LcLocale.Polish_Poland, LcLocale.Portuguese, LcLocale.Portuguese_Brazil, LcLocale.Portuguese_Portugal, LcLocale.Romanian, LcLocale.Romanian_Romania, "root", LcLocale.Russian, LcLocale.Russian_Russia, LcLocale.Russian_Ukraine, LcLocale.Serbo_Croatian, LcLocale.Serbo_Croatian_BosniaHerzegovina, LcLocale.Serbo_Croatian_SerbiaMontenegro, LcLocale.Serbo_Croatian_Croatia, LcLocale.Slovak, LcLocale.Slovak_Slovakia, LcLocale.Slovenian, LcLocale.Slovenian_Slovenia, LcLocale.Albanian, LcLocale.Albanian_Albania, LcLocale.Serbian, LcLocale.Serbian_Yugoslavia, LcLocale.Serbian_Cyrillic, LcLocale.Serbian_Cyrillic_SerbiaMontenegro, LcLocale.Serbian_Latin, LcLocale.Serbian_Latin_SerbiaMontenegro, LcLocale.Swedish, LcLocale.Swedish_Finland, LcLocale.Swedish_Sweden, LcLocale.Thai, LcLocale.Thai_Thailand, LcLocale.Thai_Thailand_Traditional, LcLocale.Tagalog, LcLocale.Tagalog_Philippines, LcLocale.Turkish, LcLocale.Turkish_Turkey, LcLocale.Ukrainian, LcLocale.Ukrainian_Ukraine, LcLocale.Vietnamese, LcLocale.Vietnamese_Vietnam, LcLocale.Chinese, LcLocale.Chinese_China, "zh_Hans", "zh_Hans_CN", "zh_Hans_SG", "zh_Hant", "zh_Hant_HK", "zh_Hant_TW", LcLocale.Chinese_HongKong, LcLocale.Chinese_Singapore, LcLocale.Chinese_Taiwan }; /** * Holds the static locale data loaded from properties files. * Entries are loaded as they are needed. */ private static final Map[] staticData = new Map[gLocaleList.length]; /** * Contains the index of the parent locale. * The root locale has a parent index of -1. */ private static final int[] staticParentIndex = new int[gLocaleList.length]; // The arrays allow mapping aliases that omit the script (e.g., "az_AZ") // to the fully specified locale name (e.g., "az_Latn_AZ"). private static final int numAliases = 6; private static final int[] aliasFromIndex = new int[numAliases]; private static final int[] aliasToIndex = new int[numAliases]; /** * The index of the locale that is the "root" locale. */ private static final int rootIndex; static { if (Assertions.isEnabled) for (int i = 0; i < gLocaleList.length - 1; i++) assert String.CASE_INSENSITIVE_ORDER.compare(gLocaleList[i], gLocaleList[i + 1]) < 0; rootIndex = localeIndex("root"); assert rootIndex != -1; // First pass at calculating parent staticParentIndex[rootIndex] = -1; for (int i = 0; i < gLocaleList.length; i++) { if (i == rootIndex) continue; String sLocaleName = gLocaleList[i]; if (sLocaleName.indexOf('_') == -1) staticParentIndex[i] = rootIndex; for (int j = i + 1; j < gLocaleList.length; j++) { String sFollowingLocaleName = gLocaleList[j]; if (sFollowingLocaleName.length() > sLocaleName.length() && sFollowingLocaleName.startsWith(sLocaleName) && sFollowingLocaleName.charAt(sLocaleName.length()) == '_') staticParentIndex[j] = i; else break; } } // For az and sr, there are both Latn and Cyrl scripts: // The parents of az_Cyrl and sr_Latn are root. // The parents of az_Latn and sr_Cyrl are az and sr. initParentIndex("az_Cyrl", rootIndex); initParentIndex("sr_Latn", rootIndex); // Where a country code may have a script, if the locale is // provided without the script code, we treat that as an alias // for the locale with the script code. int index = 0; initScriptAlias(index++, "zh_CN", "zh_Hans_CN"); initScriptAlias(index++, "zh_SG", "zh_Hans_SG"); initScriptAlias(index++, "zh_HK", "zh_Hant_HK"); initScriptAlias(index++, "zh_TW", "zh_Hant_TW"); initScriptAlias(index++, "az_AZ", "az_Latn_AZ"); initScriptAlias(index++, "sr_CS", "sr_Cyrl_CS"); assert index == numAliases; // for (int i = 0; i < gLocaleList.length; i++) // System.out.println(gLocaleList[i] + " - " + (staticParentIndex[i] == -1 ? "" : gLocaleList[staticParentIndex[i]])); } private static int localeIndex(String sLocaleName) { return Arrays.binarySearch(gLocaleList, sLocaleName, String.CASE_INSENSITIVE_ORDER); } private static void initParentIndex(String sLocaleName, int parentIndex) { int index = localeIndex(sLocaleName); staticParentIndex[index] = parentIndex; } private static void initScriptAlias(int index, String sLocaleName, String sRealLocaleName) { aliasFromIndex[index] = localeIndex(sLocaleName); aliasToIndex[index] = localeIndex(sRealLocaleName); } private static int mapLocaleIndexToAlias(int localeIndex) { for (int i = 0; i < numAliases; i++) { if (aliasFromIndex[i] == localeIndex) return aliasToIndex[i]; } return localeIndex; } private final String msLocale; // normalized UTS #35 locale name. private final int mnIndex; // index into static data /** * Instantiates an LcData object for the given locale. * * @param locale the locale name. */ public LcData(String locale) { int localeIndex = localeIndex(locale); if (localeIndex >= 0) { locale = gLocaleList[localeIndex]; // save normalized name localeIndex = mapLocaleIndexToAlias(localeIndex); } else { locale = LcLocale.normalize(locale); String sLocaleName = locale; while (true) { localeIndex = localeIndex(sLocaleName); if (localeIndex >= 0) { // matched a static data entry? localeIndex = mapLocaleIndexToAlias(localeIndex); break; } if (localeIndex == -1) { // This is before any other entry in the list localeIndex = rootIndex; break; } int delim = sLocaleName.lastIndexOf('_'); if (delim == -1) { localeIndex = rootIndex; break; } sLocaleName = sLocaleName.substring(0, delim); } } assert localeIndex >= 0; msLocale = locale; mnIndex = localeIndex; } private static Map getStaticData(int staticDataIndex) { Map result = staticData[staticDataIndex]; if (result == null) { result = loadStaticData(gLocaleList[staticDataIndex]); assert result != null; staticData[staticDataIndex] = result; } return result; } private static Map loadStaticData(String sLocale) { if (sLocale.equals("root")) sLocale = ""; final String resourceName = LcBundle.BUNDLE_BASE + (sLocale.length() == 0 ? "" : '_') + sLocale + ".properties"; InputStream stream = LcData.class.getClassLoader().getResourceAsStream(resourceName); Properties properties = new Properties(); try { properties.load(stream); } catch (IOException ignored) { assert false; return null; }finally{ try{ stream.close(); }catch(IOException streamCantBeClosedException){} stream = null; } Map staticData = new HashMap(properties.size()); Enumeration keys = properties.keys(); while (keys.hasMoreElements()) { String key = (String)keys.nextElement(); staticData.put(key, (String)properties.get(key)); } return staticData; } /** * Gets this LcData object's locale. * * @return Locale name (e.g., "ar_SA"). */ public String getLocale() { return msLocale; } /** * Validates the given runtime locale data. * * Applications should validate() the data before calling update(). * * @param oLcData the some runtime locale data. * @return Boolean true if valid and false otherwise. */ public static boolean validate(LcRunTimeData oLcData) { String decimal = oLcData.numericSymbols[NUM_DECIMAL]; if (decimal != null && decimal.equals(oLcData.numericSymbols[CUR_DECIMAL])) { return false; } return true; } /** * Reset this class's runtime store of locale data to its * internal defaults. */ public static void reset() { getMap().clear(); } /** * Updates (replaces) this class's runtime store of data * for the given locale with the given locale data. * * @param oLcData the runtime locale data. */ public static void update(LcRunTimeData oLcData) { Map map = getMap(); map.put(oLcData.localeName, oLcData); } /** * Gets the locale data for the given locale * from this class's runtime store. * * @param sLocale the locale name to search for. * @return The runtime locale data if found and null otherwise. */ public static LcRunTimeData get(String sLocale) { LcRunTimeData oRunTimeData = getMap().get(sLocale); if (oRunTimeData == null) oRunTimeData = new LcRunTimeData(sLocale); return oRunTimeData; } private static final PropertyRetriever retrieveWeekdayNameProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.dayNames[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getLongDayProperty(key)); } }; /** * Gets the name of the given day of the week. * * @param weekday the day of the week in the range of values 0-6, * where (0 = Sunday). * @return The weekday name, or the empty string upon error. */ public String getWeekDayName(int weekday) { if (weekday < SUN || weekday > SAT) { assert false; return ""; } return searchRuntimeThenStaticStore(weekday, retrieveWeekdayNameProperty); } private static final PropertyRetriever retrieveAbbrWeekdayNameProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.abbrWeekdayNames[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getShortDayProperty(key)); } }; /** * Gets the abbreviated name of the given day of the week. * * @param weekday the day of the week in the range of values 0-6, * where (0 = Sunday). * @return The abbreviated weekday name, or the empty string upon error. */ public String getAbbrWeekdayName(int weekday) { if (weekday < SUN || weekday > SAT) { assert false; return ""; } return searchRuntimeThenStaticStore(weekday, retrieveAbbrWeekdayNameProperty); } private static final PropertyRetriever retrieveMonthNameProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.monthNames[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getLongMonthProperty(key)); } }; /** * Gets the name of the given month of the year. * * @param month the month of the year in the range of values 0-11, * where (0 = January). * @return The month name, or the empty string upon error. */ public String getMonthName(int month) { if (month < JAN || month > DEC) { assert false; return ""; } return searchRuntimeThenStaticStore(month, retrieveMonthNameProperty); } private static final PropertyRetriever retrieveMonthAbbrNamesProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.abbrMonthNames[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getShortMonthProperty(key)); } }; /** * Gets the abbreviated name of the given month of the year. * * @param month the month of the year in the range of values 0-11, * where (0 = January). * @return The abbreviated month name, or the empty string upon error. */ public String getAbbrMonthName(int month) { if (month < JAN || month > DEC) { assert false; return ""; } return searchRuntimeThenStaticStore(month, retrieveMonthAbbrNamesProperty); } private static final PropertyRetriever retrieveMeridiemNameProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.meridiemNames[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getMeridiemProperty(key)); } }; /** * Gets the name of the given aspect of the meridiem. * * @param aspect an aspect of the meridiem in the range of values 0-1, * where (0 = AM, 1 = PM). * @return The meridiem name, or the empty string upon error. */ public String getMeridiemName(int aspect) { if (aspect < AM || aspect > PM) { assert false; return ""; } return searchRuntimeThenStaticStore(aspect, retrieveMeridiemNameProperty); } private static final PropertyRetriever retrieveEraNameProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.eraNames[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getEraProperty(key)); } }; /** * Gets the name of the given era. * * @param era an era of the calendar in the range of values 0-1, * where (0 = BC, 1 = AD). * @return The era name, or the empty string upon error. */ public String getEraName(int era) { if (era < BC || era > AD) { assert false; return ""; } return searchRuntimeThenStaticStore(era, retrieveEraNameProperty); } private static final PropertyRetriever retrieveDatePatternProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.datePatterns[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getDateFormatProperty(key)); } }; /** * Gets the date format in the given style. * * @param style the style of the format in the range of values 0-4, * where (0 = default, 1 = short, 2 = medium, 3 = long, 4 = full). * @return The date format. */ public String getDateFormat(int style) { if (style < 0 || style > 4) style = 2; if (style == 0) style = 2; return searchRuntimeThenStaticStore(4 - style, retrieveDatePatternProperty); } private static final PropertyRetriever retrieveTimePatternProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.timePatterns[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getTimeFormatProperty(key)); } }; /** * Gets the time format in the given style. * * @param style the style of the format in the range of values 0-4, * where (0 = default, 1 = short, 2 = medium, 3 = long, 4 = full). * @return The time format. */ public String getTimeFormat(int style) { if (style < 0 || style > 4) style = 2; if (style == 0) style = 2; return searchRuntimeThenStaticStore(4 - style, retrieveTimePatternProperty); } private static final PropertyRetriever retrieveDateTimeSymbolsProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.dateTimeSymbols[0]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getDateTimeSymbolsProperty()); } }; private static final PropertyRetriever retrieveDateTimeFormatProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return null; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getDateTimeFormatProperty()); } }; /** * Gets the date time pattern. * * @return The date time pattern. */ public String getDateTimePattern() { return searchRuntimeThenStaticStore(0, retrieveDateTimeSymbolsProperty); } /** * Gets the datetime format in the given style. * * @param style the style of the format in the range of values 0-4, * where (0 = default, 1 = short, 2 = medium, 3 = long, 4 = full). * @param type the type of the format in the range of values 0-1, * where (0 = w/o picture categories, 1 = w picture categories). * @return The date time format. */ public String getDateTimeFormat(int style, int type) { if (style < 0 || 4 < style) style = 2; // // Search the static store (resource bundles). The equivalent // dateTime pattern is not present in the runtime store, so do // not search there. // StringBuilder sDateTimeFmt = new StringBuilder(searchStaticStore(0, retrieveDateTimeFormatProperty)); if (type == 0) { String sDateFmt = getDateFormat(style); String sTimeFmt = getTimeFormat(style); int nPat = sDateTimeFmt.indexOf("date{}"); if (nPat >= 0) sDateTimeFmt.replace(nPat, nPat + 6, sDateFmt); nPat = sDateTimeFmt.indexOf("time{}"); if (nPat >= 0) sDateTimeFmt.replace(nPat, nPat + 6, sTimeFmt); } else if (type == 1) { String sDateFmt = getDateFormat(style); String sTimeFmt = getTimeFormat(style); int nPat = sDateTimeFmt.indexOf("date{}"); if (nPat >= 0) sDateTimeFmt.insert(nPat + 5, sDateFmt); nPat = sDateTimeFmt.indexOf("time{}"); if (nPat >= 0) sDateTimeFmt.insert(nPat + 5, sTimeFmt); } return sDateTimeFmt.toString(); } /** * Gets the local date format in the given style. * * @param style the style of the format in the range of values 0-4, * where (0 = default, 1 = short, 2 = medium, 3 = long, 4 = full). * @return The date format. */ public String getLocalDateFormat(int style) { String date = getDateFormat(style); if (StringUtils.isEmpty(date)) return ""; String pattern = getDateTimePattern(); return xlate(date, pattern, gpDateMask); } /** * Gets the local time format in the given style. * * @param style the style of the format in the range of values 0-4, * where (0 = default, 1 = short, 2 = medium, 3 = long, 4 = full). * @return The time format. */ public String getLocalTimeFormat(int style) { String time = getTimeFormat(style); if (StringUtils.isEmpty(time)) return ""; String pattern = getDateTimePattern(); return xlate(time, pattern, gpTimeMask); } /** * Gets the local datetime format. *

* This method is not functional yet. * * @return The date time format. */ public String getLocalDateTimeFormat() { return ""; } private static final PropertyRetriever retrieveNumberPatternProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.numberPatterns[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getNumberFormatProperty(key)); } }; /** * Gets the numeric format in the given style. * * @param style the style of the format in the range of values 0-2, * where (0 = number, 1 = currency, 2 = percentage). * @return The numeric format. */ public String getNumericFormat(int style) { if (style < NUMERIC || style > PERCENT) { assert false; return ""; } return searchRuntimeThenStaticStore(style, retrieveNumberPatternProperty); } /** * Gets the number format in the given style. * * @param style in the range of values 0-2, * where (0 = integral, 1 = decimal, 2 = currency). * @param option in the set of format options: *

*
  • bit 1: reset => w/o commas; set => w/ commas. *
  • bit 2: reset => w/ fractional z's; set => w/ fractional 8's. *
  • bit 4: reset => w/o radix; set => w/ radix. *
  • bit 8-15: the precision. *
  • bit 16: reset => keep precision; set => trim precision. *
  • bit 17-24: the width. *
  • */ public String getNumberFormat(int style, int option) { if (style < INTEGRAL_FMT || style > PERCENT_FMT) { // // Retrieve the locale's numeric format. Localized formats // are all in ASCII and embed no literals, so we take advantage // of this in the code that follows. Admittedly, this code ough // to live in he LcNum class. // style = DECIMAL_FMT; } StringBuilder sFormat = new StringBuilder( getNumericFormat((style > 0) ? style - 1 : style)); // // Use any alternate part because they handle negative values. // int nBar = sFormat.indexOf("|"); if (nBar >= 0) { sFormat.delete(0, nBar + 1); } // // Determine position of radix (or anything like it) // and the replicating part of the pattern, i.e., from // the separator to this radix. // final String[] dotChars = { ".", "v", "V", "E", " ", "%" }; int nDot = -1; for (int i = 0; i < dotChars.length; i++) { nDot = sFormat.indexOf(dotChars[i]); if (nDot >= 0) break; } if (nDot < 0) { nDot = sFormat.length(); } else if (StringUtils.skipOver(sFormat, "89zZ", nDot - 1) != 1) { nDot = sFormat.length(); } StringBuilder sZZZ = new StringBuilder(); int nZed = sFormat.indexOf("z,"); if (nZed >= 0) { // // Watson 1230768. Handle locales, like India, that have // pictures with more than one grouping symbol. // int nSep = nDot; int nComma = sFormat.indexOf(",", nZed + 2); if (nComma >= 0) { nSep = nComma; } if (nSep > nZed + 2) { for (int i = 1, n = nSep - nZed - 1; i <= n; i++) sZZZ.append('z'); } else sZZZ.append('z'); } else { nZed = 0; } // // If non-integral styles Then determine width and precision. // int nPrec = 0; int nWidth = MAX_INT_DIG; if (style != INTEGRAL_FMT) { nPrec = (option >> 8) & 0xff; boolean trim = ((nPrec & 0x80) == 0); nPrec &= 0x7f; if (nPrec == 0x7f) { nPrec = StringUtils.skipOver(sFormat, "89zZ", nDot + 1); } if ((option & 0xff0000) != 0) { nWidth = (option >> 16) & 0Xff; } else { nWidth = MAX_DBL_DIG; } // // Fix for Watson 1229423. If the locale's format contains // any sign pictures Then widen accordingly. Also widen if // precision of locale's picture format is greater than requested. // if (sFormat.indexOf("s") >= 0) { nWidth += 1; } if (sFormat.indexOf("(") >= 0) { nWidth += 1; } if (sFormat.indexOf(")") >= 0) { nWidth += 1; } int nFmtPrec = StringUtils.skipOver(sFormat, "89zZ", nDot + 1); if (0 < nPrec && nPrec < nFmtPrec) { nWidth += nFmtPrec - nPrec; } // // Pare down the precision if the width is big enough to yield // IEEE 754 64-bit double precision errors, which appears to be // anything over 14 significant digits. // if (trim && nPrec > 0 && nWidth > nPrec) { // // Fix for Watson 1211481. If the given precision is less // than what the locale's format dictates then widen the given // width. // if (nPrec <= sFormat.length() - 1 - nDot) { nWidth += sFormat.length() - 1 - nDot - nPrec; } for (int i = nWidth - 1; i > MAX_DBL_WIDTH; i--) { // // Never pare down the precision below what the locale's // format dictates. // if (nPrec <= sFormat.length() - 1 - nDot) break; nPrec--; } } } // // Fix for Watson 1483675 - If the locale's format contains // a dollar sign or a space then widen accordingly. // if (style == CURRENCY_FMT) { if (sFormat.indexOf("$") >= 0) nWidth += 1; if (sFormat.indexOf(" ") >= 0) nWidth += 1; } // // If percent style was wanted Then truncate after the // percent character. // if (style == PERCENT_FMT) { int nTrim = StringUtils.skipOver(sFormat, "89zZ", nDot + 1); if (nDot < sFormat.length()) sFormat.replace(nDot + 1, nDot + 1 + nTrim, ""); // // Fix for Watson 1483675 - If the locale's format // contains a percent sign then widen accordingly. // if (sFormat.indexOf("%") >= 0) nWidth += 1; } // // If integral style was wanted Then truncate at the // radix character. // else if (style == INTEGRAL_FMT || nPrec == 0 && (option & 0x4) == WITHOUT_RADIX) { int nTrim = StringUtils.skipOver(sFormat, "89zZ", nDot + 1); if (nDot < sFormat.length()) sFormat.replace(nDot, nDot + nTrim + 1, ""); } // // Otherwise for decimal and currency styles Do // replace fractional '9' pictures with '8's to // requested precision, // else if ((option & 0x2) == WITH_EIGHTS) { int nEight = nDot + 1; while ((nEight = sFormat.indexOf("z", nEight)) >= 0) { sFormat.setCharAt(nEight, '8'); } while (sFormat.length() - nDot <= nPrec) { sFormat.insert(nDot + 1, '8'); } } // // Or replace fractional '9' pictures with 'z's to requested precision // Fix for Watson 1322850 - add option to keep nines. Previously this // function would force frac. digits to be either z's or 8's with no // option for 9's. // else if ((option & 0x2) == WITH_ZEDS && !((option & 0x8) == KEEP_NINES)) { int nNine = nDot + 1; while ((nNine = sFormat.indexOf("9", nNine)) >= 0) { sFormat.setCharAt(nNine, 'z'); } while (sFormat.length() - nDot <= nPrec) { sFormat.insert(nDot + 1, 'z'); } } // // Replicate section from separator to radix to requested width. // if (StringUtils.isEmpty(sZZZ)) { sZZZ.append('z'); } else if ((option & 0x1) == WITHOUT_GROUPINGS) { // // Watson 1230768. Handle locales, like India, that have // pictures with more than one grouping symbol. // int nComma = nZed + 1; sFormat.setCharAt(nComma, 'z'); while ((nComma = sFormat.indexOf(",", nComma)) >= 0 && nComma < nDot) { sFormat.setCharAt(nComma, '8'); } } else if ((option & 0x1) == WITH_GROUPINGS) { sZZZ.setCharAt(0, ','); nWidth += (nWidth + sZZZ.length()) / sZZZ.length(); } while (sFormat.length() < nWidth) { sFormat.insert(nZed + 1, sZZZ); } return sFormat.toString(); } /** * Gets the decimal precision of the given numeric string. * * @return The decimal precision or 0 for integral values. */ public int getNumberPrecision(String sVal) { int nRadix = sVal.indexOf(getRadixSymbol()); if (nRadix >= 0) return sVal.length() - nRadix - 1; return 0; } private interface PropertyRetriever { T retrieveRuntime(LcRunTimeData runtimeData, int key); T retrieveStatic(Map staticData, int key); } private static final PropertyRetriever retrieveCurrencySymbolProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.currencySymbols[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getCurrencySymbolProperty(key)); } }; private static final PropertyRetriever retrieveNumericSymbolProperty = new PropertyRetriever() { public String retrieveRuntime(LcRunTimeData runtimeData, int key) { return runtimeData.numericSymbols[key]; } public String retrieveStatic(Map staticData, int key) { return staticData.get(LcBundle.getNumericSymbolProperty(key)); } }; private static final PropertyRetriever> retrieveTypefaceListProperty = new PropertyRetriever>() { public List retrieveRuntime(LcRunTimeData runtimeData, int key) { List typefaces = runtimeData.typefaceList; return typefaces.size() == 0 ? null : typefaces; } public List retrieveStatic(Map staticData, int key) { String sList = staticData.get(LcBundle.getTypefacesProperty()); if (StringUtils.isEmpty(sList)) return null; List typefaceList = new ArrayList(); StringTokenizer tokenizer = new StringTokenizer(sList, ";"); while (tokenizer.hasMoreTokens()) typefaceList.add(tokenizer.nextToken()); return typefaceList; } }; private T searchRuntimeStore(int key, PropertyRetriever retriever) { Map runtimeMap = mRuntimeMap.get(); if (runtimeMap.size() == 0) return null; String sLocaleName = msLocale; int staticIndex = -1; while (true) { LcRunTimeData lcData = runtimeMap.get(sLocaleName); if (lcData != null) { T value = retriever.retrieveRuntime(lcData, key); if (value != null) return value; } // Move up to a more generic locale to see if we can find the data there. // At the point that we reach a locale name that is in the static data, // we'll start following the pre-built tree that has been built for the // static locale. This ensures that we follow the same exceptions in the // locale tree (aliases, script expansion), but is is also more efficient. if (staticIndex >= 0) { int parentIndex = staticParentIndex[staticIndex]; if (parentIndex < 0) break; sLocaleName = gLocaleList[parentIndex]; staticIndex = parentIndex; } else { // We haven't found a name that corresponds to a static locale, // so trim off a terminal segment and see if that matches. int delim = sLocaleName.lastIndexOf('_'); if (delim == -1) { if (sLocaleName.equals("root")) break; else { sLocaleName = "root"; staticIndex = rootIndex; } } else { sLocaleName = sLocaleName.substring(0, delim); // See if we have found a place in the static tree. staticIndex = localeIndex(sLocaleName); if (staticIndex >= 0) staticIndex = mapLocaleIndexToAlias(staticIndex); } } } return null; } private T searchStaticStore(int key, PropertyRetriever retriever) { int staticDataIndex = mnIndex; while (true) { Map lcData = getStaticData(staticDataIndex); T value = retriever.retrieveStatic(lcData, key); if (value != null) return value; staticDataIndex = staticParentIndex[staticDataIndex]; if (staticDataIndex == -1) break; } assert false; // the locale data should always have a default at the root return null; } /** * Search up through the static store and then through the candidate store for a property. *

    * This method approximates what is repeated in most property retrieval methods * in the C++ LcData class, but here it is factored out into a separate method using * PropertyRetriever functors. Note that calling searchCandidateKey is largely * wasteful since it searches through many of the same entries in the runtime store, but * it is left here for compatibility. * @param the property type * @param key the property key * @param retriever a property retriever functor * @return a property value, or null if not found */ private T searchRuntimeThenStaticStore(int key, PropertyRetriever retriever) { T value = searchRuntimeStore(key, retriever); if (value != null) return value; return searchStaticStore(key, retriever); } /** * Gets the name of the currency. * * @return The currency name or the empty string upon error. */ public String getCurrencyName() { return searchRuntimeThenStaticStore(CUR_ISONAME, retrieveCurrencySymbolProperty); } /** * Gets the symbol of the currency. * * @return The currency symbol or the empty string upon error. */ public String getCurrencySymbol() { return searchRuntimeThenStaticStore(CUR_SYMBOL, retrieveCurrencySymbolProperty); } /** * Gets the radix of the currency. * * @return The currency radix or the empty string upon error. */ public String getCurrencyRadix() { return searchRuntimeThenStaticStore(CUR_DECIMAL, retrieveCurrencySymbolProperty); } /** * Gets the radix symbol. * * @return The radix symbol or the empty string upon error. */ public String getRadixSymbol() { return searchRuntimeThenStaticStore(NUM_DECIMAL, retrieveNumericSymbolProperty); } /** * Gets the grouping symbol. * * @return The grouping symbol or the empty string upon error. */ public String getGroupingSymbol() { return searchRuntimeThenStaticStore(NUM_GROUPING, retrieveNumericSymbolProperty); } /** * Gets the percent symbol. * * @return The percent symbol or the empty string upon error. */ public String getPercentSymbol() { return searchRuntimeThenStaticStore(NUM_PERCENT, retrieveNumericSymbolProperty); } /** * Gets the list of typefaces. * * @return The list of typefaces which is possibly empty upon error. */ public List getTypefaces() { List typefaces = searchRuntimeThenStaticStore(0, retrieveTypefaceListProperty); if (typefaces == null) typefaces = Collections.emptyList(); return typefaces; } /** * Gets the negative symbol. * * @return The negative symbol or the empty string upon error. */ public String getNegativeSymbol() { return searchRuntimeThenStaticStore(NUM_MINUS, retrieveNumericSymbolProperty); } /** * Gets the native zero digit symbol. * * @return The zero symbol or the empty string upon error. */ public String getZeroSymbol() { return searchRuntimeThenStaticStore(NUM_ZERO, retrieveNumericSymbolProperty); } /** * Gets the positive symbol. * * @return The positive symbol or the empty string upon error. */ public String getPositiveSymbol() { return "+"; // as in C++ implementation } /** * Gets all the supported locale names. * * @param oLocales the List object to be populated * with the name of all the locales for which we have * data. */ public static void getLocaleNames(List oLocales) { // // Add locales from the runtime store. // oLocales.addAll(getMap().keySet()); // // Add locales from the static store. // for (int i = 0; i < gLocaleList.length; i++) { oLocales.add(gLocaleList[i]); } } /** * Finds a locale who's currency, radix and grouping symbols match all of the * given symbols. * * @param sCurrencySymbol * the currency symbol to match. * @param sRadixSymbol * the decimal radix symbol to match. * @param sGroupingSymbol * the grouping separator symbol to match. * @return The matching locale name or the empty string upon failure. */ public static String findMatchingLocale(String sCurrencySymbol, String sRadixSymbol, String sGroupingSymbol) { // // Try the current locale for a match. // String dflt = LcLocale.getLocale(); if (testMatchingLocale(dflt, sCurrencySymbol, sRadixSymbol, sGroupingSymbol)) { return dflt; } // // Try all locales in the locale data table for a match. // for (int i = 0; i < gLocaleList.length; i++) { String locale = gLocaleList[i]; if (testMatchingLocale(locale, sCurrencySymbol, sRadixSymbol, sGroupingSymbol)) { return locale; } } return ""; } /** * Tests whether a given locale matches. * * @param locale * Name of locale to test. * @param sCurrencySymbol * Currency symbol to test for. Can be empty string if there is * no need to test for currency symbol. * @param sRadixSymbol * Radix symbol to test for. Can be empty string if there is no * need to test for radix symbol. * @param sGroupingSymbol * Grouping symbol to test for. Can be empty string if there is * no need to test for grouping symbol. * @return True if the named locale's symbols match the given non-empty * symbols; false otherwise. */ private static boolean testMatchingLocale(String locale, String sCurrencySymbol, String sRadixSymbol, String sGroupingSymbol) { LcData oData = new LcData(locale); if (!StringUtils.isEmpty(sCurrencySymbol) && (!oData.getCurrencySymbol().equals(sCurrencySymbol))) { return false; } if (!StringUtils.isEmpty(sRadixSymbol) && (!oData.getRadixSymbol().equals(sRadixSymbol))) { return false; } if (!StringUtils.isEmpty(sGroupingSymbol) && (!oData.getGroupingSymbol().equals(sGroupingSymbol))) { return false; } return true; } private static String xlate(String pPat, String pLocal, String pMask) { StringBuilder sBuf = new StringBuilder(); char prevChr = '\0'; int chrCnt = 0; boolean inQuoted = false; boolean inQuoteQuoted = false; // // For each character of the pattern Do ... // int nPatLen = pPat.length(); for (int i = 0; i < nPatLen; i++) { char chr = pPat.charAt(i); if (inQuoteQuoted) { // // If seen a quote within a quoted string ... // if (chr == '\'') { chrCnt = 0; // cases like '...'' sBuf.append(chr); } else { inQuoted = false; // cases like '...'M chrCnt = 1; prevChr = chr; } inQuoteQuoted = false; } else if (inQuoted) { // // Else if within a quoted string ... // if (chr == '\'') { inQuoteQuoted = true; // cases like '...' } else { sBuf.append(chr); // cases like '...M } chrCnt++; } else if (chr == '\'') { // // Else if start of a quoted string ... // if (chrCnt > 0) { char c = LcData.subXlate(prevChr, pLocal, pMask); // cases // like // ...M' while (chrCnt-- > 0) { sBuf.append(c); } chrCnt = 0; prevChr = 0; } inQuoted = true; } else if ('a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z') { // // Else if its a metacharacter ... // if (chr != prevChr) { if (chrCnt > 0) { char c = subXlate(prevChr, pLocal, pMask); // cases // like HHM // or :M while (chrCnt-- > 0) { sBuf.append(c); } chrCnt = 0; } prevChr = chr; } chrCnt++; } else if (chrCnt > 0) { // // Else if start of a literal ... // char c = LcData.subXlate(prevChr, pLocal, pMask); // cases // like MM- while (chrCnt-- > 0) { sBuf.append(c); } if (chr == '?' || chr == '*' || chr == '+') { sBuf.append(' '); } else { sBuf.append(chr); } chrCnt = 0; prevChr = 0; } else { // // Else yet another literal ... // if (chr == '?' || chr == '*' || chr == '+') { sBuf.append(' '); } else { sBuf.append(chr); } prevChr = 0; } } if (inQuoteQuoted) { // // Ensure quoted string is terminated. // inQuoted = false; } if (inQuoted) { sBuf.setLength(0); } if (prevChr > 0 && chrCnt > 0) { // // Translate any remaining items in the pattern. // char c = LcData.subXlate(prevChr, pLocal, pMask); while (chrCnt-- > 0) { sBuf.append(c); } } return sBuf.toString(); } private static char subXlate(char c, String pPat, String pMask) { assert (pPat != null && pMask != null); assert (pPat.length() == pMask.length()); if (c == ' ') { return c; } int index = pMask.indexOf(c); if (index >= 0) { return pPat.charAt(index); } return c; } /** * Gets the thread-local store map. This is a convenience function that * simply takes care of thread-local access and casting. * * @return The SortedMap object representing the thread-local store. */ private static final Map getMap() { return mRuntimeMap.get(); } }