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