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 char monetarySeparator; 48 private final String minusSign; 49 private final String exponentSeparator; 50 private final String infinity; 51 private final String NaN; 52 53 private final String numberPattern; 54 private final String currencyPattern; 55 private final String percentPattern; 56 DecimalFormatData(Locale locale)57 private DecimalFormatData(Locale locale) { 58 DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale); 59 60 decimalSeparator = dfs.getDecimalSeparator(); 61 groupingSeparator = dfs.getGroupingSeparator(); 62 percent = dfs.getPercentString(); 63 perMill = dfs.getPerMillString(); 64 monetarySeparator = dfs.getMonetaryDecimalSeparator(); 65 minusSign = dfs.getMinusSignString(); 66 exponentSeparator = dfs.getExponentSeparator(); 67 infinity = dfs.getInfinity(); 68 NaN = dfs.getNaN(); 69 zeroDigit = dfs.getZeroDigit(); 70 71 // Libcore localizes pattern separator while ICU doesn't. http://b/112080617 72 patternSeparator = loadPatternSeparator(locale); 73 74 DecimalFormat df = (DecimalFormat) NumberFormat.getInstance( 75 locale, NumberFormat.NUMBERSTYLE); 76 numberPattern = df.toPattern(); 77 78 df = (DecimalFormat) NumberFormat.getInstance(locale, NumberFormat.CURRENCYSTYLE); 79 currencyPattern = df.toPattern(); 80 81 df = (DecimalFormat) NumberFormat.getInstance(locale, NumberFormat.PERCENTSTYLE); 82 percentPattern = df.toPattern(); 83 } 84 85 /** 86 * Returns an instance. 87 * 88 * @param locale can't be null 89 * @throws NullPointerException if {@code locale} is null 90 * @return a {@link DecimalFormatData} instance 91 */ getInstance(Locale locale)92 public static DecimalFormatData getInstance(Locale locale) { 93 Objects.requireNonNull(locale, "locale can't be null"); 94 95 locale = LocaleData.getCompatibleLocaleForBug159514442(locale); 96 97 final String languageTag = locale.toLanguageTag(); 98 99 DecimalFormatData data = CACHE.get(languageTag); 100 if (data != null) { 101 return data; 102 } 103 104 data = new DecimalFormatData(locale); 105 DecimalFormatData prev = CACHE.putIfAbsent(languageTag, data); 106 if (prev != null) { 107 return prev; 108 } 109 return data; 110 } 111 112 /** 113 * Ensure that we pull in the locale data for the root locale, en_US, and the user's default 114 * locale. All devices must support the root locale and en_US, and they're used for various 115 * system things. Pre-populating the cache is especially useful on Android because 116 * we'll share this via the Zygote. 117 */ initializeCacheInZygote()118 public static void initializeCacheInZygote() { 119 getInstance(Locale.ROOT); 120 getInstance(Locale.US); 121 getInstance(Locale.getDefault()); 122 } 123 getZeroDigit()124 public char getZeroDigit() { 125 return zeroDigit; 126 } 127 getDecimalSeparator()128 public char getDecimalSeparator() { 129 return decimalSeparator; 130 } 131 getGroupingSeparator()132 public char getGroupingSeparator() { 133 return groupingSeparator; 134 } 135 getPatternSeparator()136 public char getPatternSeparator() { 137 return patternSeparator; 138 } 139 getPercent()140 public String getPercent() { 141 return percent; 142 } 143 getPerMill()144 public String getPerMill() { 145 return perMill; 146 } 147 getMonetarySeparator()148 public char getMonetarySeparator() { 149 return monetarySeparator; 150 } 151 getMinusSign()152 public String getMinusSign() { 153 return minusSign; 154 } 155 getExponentSeparator()156 public String getExponentSeparator() { 157 return exponentSeparator; 158 } 159 getInfinity()160 public String getInfinity() { 161 return infinity; 162 } 163 getNaN()164 public String getNaN() { 165 return NaN; 166 } 167 getNumberPattern()168 public String getNumberPattern() { 169 return numberPattern; 170 } 171 getCurrencyPattern()172 public String getCurrencyPattern() { 173 return currencyPattern; 174 } 175 getPercentPattern()176 public String getPercentPattern() { 177 return percentPattern; 178 } 179 180 // Libcore localizes pattern separator while ICU doesn't. http://b/112080617 loadPatternSeparator(Locale locale)181 private static char loadPatternSeparator(Locale locale) { 182 ULocale uLocale = ULocale.forLocale(locale); 183 NumberingSystem ns = NumberingSystem.getInstance(uLocale); 184 // A numbering system could be numeric or algorithmic. DecimalFormat can only use 185 // a numeric and decimal-based (radix == 10) system. Fallback to a Latin, a known numeric 186 // and decimal-based if the default numbering system isn't. All locales should have data 187 // for Latin numbering system after locale data fallback. See Numbering system section 188 // in Unicode Technical Standard #35 for more details. 189 if (ns == null || ns.getRadix() != 10 || ns.isAlgorithmic()) { 190 ns = NumberingSystem.LATIN; 191 } 192 String patternSeparator = ExtendedDecimalFormatSymbols.getInstance(uLocale, ns) 193 .getLocalizedPatternSeparator(); 194 195 if (patternSeparator == null || patternSeparator.isEmpty()) { 196 patternSeparator = ";"; 197 } 198 199 // Pattern separator in libcore supports single java character only. 200 return patternSeparator.charAt(0); 201 } 202 } 203