1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package libcore.icu; 18 19 import android.icu.text.DecimalFormat; 20 import android.icu.text.DecimalFormatSymbols; 21 import android.icu.text.NumberFormat; 22 import android.icu.text.NumberingSystem; 23 import android.icu.util.ULocale; 24 import com.android.icu.text.ExtendedDecimalFormatSymbols; 25 import java.util.Locale; 26 import java.util.Objects; 27 import java.util.concurrent.ConcurrentHashMap; 28 29 /** 30 * Data cache for classes, e.g. {@link java.text.DecimalFormat} and 31 * {@link java.text.DecimalFormatSymbols}. 32 * 33 * @hide 34 */ 35 public class DecimalFormatData { 36 37 // TODO(http://b/217881004): Replace this with a LRU cache. 38 private static final ConcurrentHashMap<String, DecimalFormatData> CACHE = 39 new ConcurrentHashMap<>(/* initialCapacity */ 3); 40 41 private final char zeroDigit; 42 private final char decimalSeparator; 43 private final char groupingSeparator; 44 private final char patternSeparator; 45 private final String percent; 46 private final String perMill; 47 private final String monetaryDecimalSeparator; 48 private final String monetaryGroupSeparator; 49 private final String minusSign; 50 private final String exponentSeparator; 51 private final String infinity; 52 private final String NaN; 53 54 private final String numberPattern; 55 private final String currencyPattern; 56 private final String percentPattern; 57 DecimalFormatData(Locale locale)58 private DecimalFormatData(Locale locale) { 59 DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale); 60 61 decimalSeparator = dfs.getDecimalSeparator(); 62 groupingSeparator = dfs.getGroupingSeparator(); 63 percent = dfs.getPercentString(); 64 perMill = dfs.getPerMillString(); 65 monetaryDecimalSeparator = dfs.getMonetaryDecimalSeparatorString(); 66 monetaryGroupSeparator = dfs.getMonetaryGroupingSeparatorString(); 67 minusSign = dfs.getMinusSignString(); 68 exponentSeparator = dfs.getExponentSeparator(); 69 infinity = dfs.getInfinity(); 70 NaN = dfs.getNaN(); 71 zeroDigit = dfs.getZeroDigit(); 72 73 // Libcore localizes pattern separator while ICU doesn't. http://b/112080617 74 patternSeparator = loadPatternSeparator(locale); 75 76 DecimalFormat df = (DecimalFormat) NumberFormat.getInstance( 77 locale, NumberFormat.NUMBERSTYLE); 78 numberPattern = df.toPattern(); 79 80 df = (DecimalFormat) NumberFormat.getInstance(locale, NumberFormat.CURRENCYSTYLE); 81 currencyPattern = df.toPattern(); 82 83 df = (DecimalFormat) NumberFormat.getInstance(locale, NumberFormat.PERCENTSTYLE); 84 percentPattern = df.toPattern(); 85 } 86 87 /** 88 * Returns an instance. 89 * 90 * @param locale can't be null 91 * @throws NullPointerException if {@code locale} is null 92 * @return a {@link DecimalFormatData} instance 93 */ getInstance(Locale locale)94 public static DecimalFormatData getInstance(Locale locale) { 95 Objects.requireNonNull(locale, "locale can't be null"); 96 97 locale = LocaleData.getCompatibleLocaleForBug159514442(locale); 98 99 final String languageTag = locale.toLanguageTag(); 100 101 DecimalFormatData data = CACHE.get(languageTag); 102 if (data != null) { 103 return data; 104 } 105 106 data = new DecimalFormatData(locale); 107 DecimalFormatData prev = CACHE.putIfAbsent(languageTag, data); 108 if (prev != null) { 109 return prev; 110 } 111 return data; 112 } 113 114 /** 115 * Ensure that we pull in the locale data for the root locale, en_US, and the user's default 116 * locale. All devices must support the root locale and en_US, and they're used for various 117 * system things. Pre-populating the cache is especially useful on Android because 118 * we'll share this via the Zygote. 119 */ initializeCacheInZygote()120 public static void initializeCacheInZygote() { 121 getInstance(Locale.ROOT); 122 getInstance(Locale.US); 123 getInstance(Locale.getDefault()); 124 } 125 getZeroDigit()126 public char getZeroDigit() { 127 return zeroDigit; 128 } 129 getDecimalSeparator()130 public char getDecimalSeparator() { 131 return decimalSeparator; 132 } 133 getGroupingSeparator()134 public char getGroupingSeparator() { 135 return groupingSeparator; 136 } 137 getPatternSeparator()138 public char getPatternSeparator() { 139 return patternSeparator; 140 } 141 getPercent()142 public String getPercent() { 143 return percent; 144 } 145 getPerMill()146 public String getPerMill() { 147 return perMill; 148 } 149 getMonetaryDecimalSeparator()150 public String getMonetaryDecimalSeparator() { 151 return monetaryDecimalSeparator; 152 } 153 getMonetaryGroupSeparator()154 public String getMonetaryGroupSeparator() { 155 return monetaryGroupSeparator; 156 } 157 getMinusSign()158 public String getMinusSign() { 159 return minusSign; 160 } 161 getExponentSeparator()162 public String getExponentSeparator() { 163 return exponentSeparator; 164 } 165 getInfinity()166 public String getInfinity() { 167 return infinity; 168 } 169 getNaN()170 public String getNaN() { 171 return NaN; 172 } 173 getNumberPattern()174 public String getNumberPattern() { 175 return numberPattern; 176 } 177 getCurrencyPattern()178 public String getCurrencyPattern() { 179 return currencyPattern; 180 } 181 getPercentPattern()182 public String getPercentPattern() { 183 return percentPattern; 184 } 185 186 // Libcore localizes pattern separator while ICU doesn't. http://b/112080617 loadPatternSeparator(Locale locale)187 private static char loadPatternSeparator(Locale locale) { 188 ULocale uLocale = ULocale.forLocale(locale); 189 NumberingSystem ns = NumberingSystem.getInstance(uLocale); 190 // A numbering system could be numeric or algorithmic. DecimalFormat can only use 191 // a numeric and decimal-based (radix == 10) system. Fallback to a Latin, a known numeric 192 // and decimal-based if the default numbering system isn't. All locales should have data 193 // for Latin numbering system after locale data fallback. See Numbering system section 194 // in Unicode Technical Standard #35 for more details. 195 if (ns == null || ns.getRadix() != 10 || ns.isAlgorithmic()) { 196 ns = NumberingSystem.LATIN; 197 } 198 String patternSeparator = ExtendedDecimalFormatSymbols.getInstance(uLocale, ns) 199 .getLocalizedPatternSeparator(); 200 201 if (patternSeparator == null || patternSeparator.isEmpty()) { 202 patternSeparator = ";"; 203 } 204 205 // Pattern separator in libcore supports single java character only. 206 return patternSeparator.charAt(0); 207 } 208 } 209