• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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