OLD | NEW |
1 // © 2016 and later: Unicode, Inc. and others. | 1 // © 2016 and later: Unicode, Inc. and others. |
2 // License & terms of use: http://www.unicode.org/copyright.html#License | 2 // License & terms of use: http://www.unicode.org/copyright.html#License |
3 /* | 3 /* |
4 ******************************************************************************* | 4 ******************************************************************************* |
5 * Copyright (C) 1996-2016, Google, International Business Machines Corporation
and | 5 * Copyright (C) 1996-2016, Google, International Business Machines Corporation
and |
6 * others. All Rights Reserved. * | 6 * others. All Rights Reserved. * |
7 ******************************************************************************* | 7 ******************************************************************************* |
8 */ | 8 */ |
9 | 9 |
10 package com.ibm.icu.text; | 10 package com.ibm.icu.text; |
11 | 11 |
12 import java.io.IOException; | 12 import java.io.IOException; |
13 import java.io.NotSerializableException; | 13 import java.io.NotSerializableException; |
14 import java.io.ObjectInputStream; | 14 import java.io.ObjectInputStream; |
15 import java.io.ObjectOutputStream; | 15 import java.io.ObjectOutputStream; |
16 import java.math.BigDecimal; | 16 import java.math.BigDecimal; |
17 import java.math.BigInteger; | 17 import java.math.BigInteger; |
18 import java.text.AttributedCharacterIterator; | 18 import java.text.AttributedCharacterIterator; |
19 import java.text.FieldPosition; | 19 import java.text.FieldPosition; |
20 import java.text.ParsePosition; | 20 import java.text.ParsePosition; |
21 import java.util.Arrays; | |
22 import java.util.Collection; | |
23 import java.util.HashMap; | |
24 import java.util.Locale; | 21 import java.util.Locale; |
25 import java.util.Map; | 22 |
26 import java.util.Map.Entry; | 23 import com.ibm.icu.impl.number.FormatQuantity4; |
27 import java.util.regex.Pattern; | 24 import com.ibm.icu.impl.number.Properties; |
28 | |
29 import com.ibm.icu.text.CompactDecimalDataCache.Data; | |
30 import com.ibm.icu.text.PluralRules.FixedDecimal; | |
31 import com.ibm.icu.util.Currency; | |
32 import com.ibm.icu.util.CurrencyAmount; | |
33 import com.ibm.icu.util.Output; | |
34 import com.ibm.icu.util.ULocale; | 25 import com.ibm.icu.util.ULocale; |
35 | 26 |
36 /** | 27 /** |
37 * The CompactDecimalFormat produces abbreviated numbers, suitable for display i
n environments will limited real estate. | 28 * The CompactDecimalFormat produces abbreviated numbers, suitable for display i
n environments will |
38 * For example, 'Hits: 1.2B' instead of 'Hits: 1,200,000,000'. The format will b
e appropriate for the given language, | 29 * limited real estate. For example, 'Hits: 1.2B' instead of 'Hits: 1,200,000,00
0'. The format will |
39 * such as "1,2 Mrd." for German. | 30 * be appropriate for the given language, such as "1,2 Mrd." for German. |
40 * <p> | 31 * |
41 * For numbers under 1000 trillion (under 10^15, such as 123,456,789,012,345), t
he result will be short for supported | 32 * <p>For numbers under 1000 trillion (under 10^15, such as 123,456,789,012,345)
, the result will be |
42 * languages. However, the result may sometimes exceed 7 characters, such as whe
n there are combining marks or thin | 33 * short for supported languages. However, the result may sometimes exceed 7 cha
racters, such as |
43 * characters. In such cases, the visual width in fonts should still be short. | 34 * when there are combining marks or thin characters. In such cases, the visual
width in fonts |
44 * <p> | 35 * should still be short. |
45 * By default, there are 2 significant digits. After creation, if more than thre
e significant digits are set (with | 36 * |
46 * setMaximumSignificantDigits), or if a fixed number of digits are set (with se
tMaximumIntegerDigits or | 37 * <p>By default, there are 2 significant digits. After creation, if more than t
hree significant |
47 * setMaximumFractionDigits), then result may be wider. | 38 * digits are set (with setMaximumSignificantDigits), or if a fixed number of di
gits are set (with |
48 * <p> | 39 * setMaximumIntegerDigits or setMaximumFractionDigits), then result may be wide
r. |
49 * The "short" style is also capable of formatting currency amounts, such as "$1
.2M" instead of "$1,200,000.00" (English) or | 40 * |
50 * "5,3 Mio. €" instead of "5.300.000,00 €" (German). Localized data concerning
longer formats is not available yet in | 41 * <p>The "short" style is also capable of formatting currency amounts, such as
"$1.2M" instead of |
51 * the Unicode CLDR. Because of this, attempting to format a currency amount usi
ng the "long" style will produce | 42 * "$1,200,000.00" (English) or "5,3 Mio. €" instead of "5.300.000,00 €" (German
). Localized data |
52 * an UnsupportedOperationException. | 43 * concerning longer formats is not available yet in the Unicode CLDR. Because o
f this, attempting |
53 * | 44 * to format a currency amount using the "long" style will produce an Unsupporte
dOperationException. |
54 * At this time, negative numbers and parsing are not supported, and will produc
e an UnsupportedOperationException. | 45 * |
55 * Resetting the pattern prefixes or suffixes is not supported; the method calls
are ignored. | 46 * <p>At this time, negative numbers and parsing are not supported, and will pro
duce an |
56 * <p> | 47 * UnsupportedOperationException. Resetting the pattern prefixes or suffixes is
not supported; the |
57 * Note that important methods, like setting the number of decimals, will be mov
ed up from DecimalFormat to | 48 * method calls are ignored. |
58 * NumberFormat. | 49 * |
| 50 * <p>Note that important methods, like setting the number of decimals, will be
moved up from |
| 51 * DecimalFormat to NumberFormat. |
59 * | 52 * |
60 * @author markdavis | 53 * @author markdavis |
61 * @stable ICU 49 | 54 * @stable ICU 49 |
62 */ | 55 */ |
63 public class CompactDecimalFormat extends DecimalFormat { | 56 public class CompactDecimalFormat extends DecimalFormat { |
64 | 57 |
65 private static final long serialVersionUID = 4716293295276629682L; | 58 /** |
66 | 59 * Style parameter for CompactDecimalFormat. |
67 // private static final int POSITIVE_PREFIX = 0, POSITIVE_SUFFIX = 1, AFFIX_S
IZE = 2; | 60 * |
68 private static final CompactDecimalDataCache cache = new CompactDecimalDataC
ache(); | 61 * @stable ICU 50 |
69 | 62 */ |
70 private final Map<String, DecimalFormat.Unit[]> units; | 63 public enum CompactStyle { |
71 private final Map<String, DecimalFormat.Unit[]> currencyUnits; | |
72 private final long[] divisor; | |
73 private final long[] currencyDivisor; | |
74 private final Map<String, Unit> pluralToCurrencyAffixes; | |
75 private CompactStyle style; | |
76 | |
77 // null if created internally using explicit prefixes and suffixes. | |
78 private final PluralRules pluralRules; | |
79 | |
80 /** | 64 /** |
81 * Style parameter for CompactDecimalFormat. | 65 * Short version, like "1.2T" |
| 66 * |
82 * @stable ICU 50 | 67 * @stable ICU 50 |
83 */ | 68 */ |
84 public enum CompactStyle { | 69 SHORT, |
85 /** | |
86 * Short version, like "1.2T" | |
87 * @stable ICU 50 | |
88 */ | |
89 SHORT, | |
90 /** | |
91 * Longer version, like "1.2 trillion", if available. May return same re
sult as SHORT if not. | |
92 * @stable ICU 50 | |
93 */ | |
94 LONG | |
95 } | |
96 | |
97 /** | 70 /** |
98 * Create a CompactDecimalFormat appropriate for a locale. The result may | 71 * Longer version, like "1.2 trillion", if available. May return same result
as SHORT if not. |
99 * be affected by the number system in the locale, such as ar-u-nu-latn. | |
100 * | 72 * |
101 * @param locale the desired locale | |
102 * @param style the compact style | |
103 * @stable ICU 50 | 73 * @stable ICU 50 |
104 */ | 74 */ |
105 public static CompactDecimalFormat getInstance(ULocale locale, CompactStyle
style) { | 75 LONG |
106 return new CompactDecimalFormat(locale, style); | 76 } |
| 77 |
| 78 /** |
| 79 * Create a CompactDecimalFormat appropriate for a locale. The result may be a
ffected by the |
| 80 * number system in the locale, such as ar-u-nu-latn. |
| 81 * |
| 82 * @param locale the desired locale |
| 83 * @param style the compact style |
| 84 * @stable ICU 50 |
| 85 */ |
| 86 public static CompactDecimalFormat getInstance(ULocale locale, CompactStyle st
yle) { |
| 87 return new CompactDecimalFormat(locale, style); |
| 88 } |
| 89 |
| 90 /** |
| 91 * Create a CompactDecimalFormat appropriate for a locale. The result may be a
ffected by the |
| 92 * number system in the locale, such as ar-u-nu-latn. |
| 93 * |
| 94 * @param locale the desired locale |
| 95 * @param style the compact style |
| 96 * @stable ICU 50 |
| 97 */ |
| 98 public static CompactDecimalFormat getInstance(Locale locale, CompactStyle sty
le) { |
| 99 return new CompactDecimalFormat(ULocale.forLocale(locale), style); |
| 100 } |
| 101 |
| 102 /** |
| 103 * The public mechanism is CompactDecimalFormat.getInstance(). |
| 104 * |
| 105 * @param locale the desired locale |
| 106 * @param style the compact style |
| 107 */ |
| 108 CompactDecimalFormat(ULocale locale, CompactStyle style) { |
| 109 // Use the locale's default pattern |
| 110 String pattern = getPattern(locale, 0); |
| 111 symbols = DecimalFormatSymbols.getInstance(locale); |
| 112 properties = new Properties(); |
| 113 properties.setCompactStyle(style); |
| 114 exportedProperties = new Properties(); |
| 115 setPropertiesFromPattern(pattern, true); |
| 116 if (style == CompactStyle.SHORT) { |
| 117 // TODO: This was setGroupingUsed(false) in ICU 58. Is it okay that I chan
ged it for ICU 59? |
| 118 properties.setMinimumGroupingDigits(2); |
107 } | 119 } |
108 | 120 refreshFormatter(); |
109 /** | 121 } |
110 * Create a CompactDecimalFormat appropriate for a locale. The result may | 122 |
111 * be affected by the number system in the locale, such as ar-u-nu-latn. | 123 /** |
112 * | 124 * {@inheritDoc} |
113 * @param locale the desired locale | 125 * |
114 * @param style the compact style | 126 * @stable ICU 49 |
115 * @stable ICU 50 | 127 */ |
116 */ | 128 @Override |
117 public static CompactDecimalFormat getInstance(Locale locale, CompactStyle s
tyle) { | 129 public boolean equals(Object obj) { |
118 return new CompactDecimalFormat(ULocale.forLocale(locale), style); | 130 return super.equals(obj); |
119 } | 131 } |
120 | 132 |
121 /** | 133 /** |
122 * The public mechanism is CompactDecimalFormat.getInstance(). | 134 * {@inheritDoc} |
123 * | 135 * |
124 * @param locale | 136 * @stable ICU 49 |
125 * the desired locale | 137 */ |
126 * @param style | 138 @Override |
127 * the compact style | 139 public StringBuffer format(double number, StringBuffer toAppendTo, FieldPositi
on pos) { |
128 */ | 140 FormatQuantity4 fq = new FormatQuantity4(number); |
129 CompactDecimalFormat(ULocale locale, CompactStyle style) { | 141 formatter.format(fq, toAppendTo, pos); |
130 this.pluralRules = PluralRules.forLocale(locale); | 142 fq.populateUFieldPosition(pos); |
131 DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale); | 143 return toAppendTo; |
132 CompactDecimalDataCache.Data data = getData(locale, style); | 144 } |
133 CompactDecimalDataCache.Data currencyData = getCurrencyData(locale); | 145 |
134 this.units = data.units; | 146 /** |
135 this.divisor = data.divisors; | 147 * {@inheritDoc} |
136 this.currencyUnits = currencyData.units; | 148 * |
137 this.currencyDivisor = currencyData.divisors; | 149 * @stable ICU 50 |
138 this.style = style; | 150 */ |
139 pluralToCurrencyAffixes = null; | 151 @Override |
140 | 152 public AttributedCharacterIterator formatToCharacterIterator(Object obj) { |
141 // DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getCurrenc
yInstance(locale); | 153 if (!(obj instanceof Number)) throw new IllegalArgumentException(); |
142 // // TODO fix to use plural-dependent affixes | 154 Number number = (Number) obj; |
143 // Unit currency = new Unit(currencyFormat.getPositivePrefix(), currencyF
ormat.getPositiveSuffix()); | 155 FormatQuantity4 fq = new FormatQuantity4(number); |
144 // pluralToCurrencyAffixes = new HashMap<String,Unit>(); | 156 AttributedCharacterIterator result = formatter.formatToCharacterIterator(fq)
; |
145 // for (String key : pluralRules.getKeywords()) { | 157 return result; |
146 // pluralToCurrencyAffixes.put(key, currency); | 158 } |
147 // } | 159 |
148 // // TODO fix to get right symbol for the count | 160 /** |
149 | 161 * {@inheritDoc} |
150 finishInit(style, format.toPattern(), format.getDecimalFormatSymbols()); | 162 * |
151 } | 163 * @stable ICU 49 |
152 | 164 */ |
153 /** | 165 @Override |
154 * Create a short number "from scratch". Intended for internal use. The pref
ix, suffix, and divisor arrays are | 166 public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition
pos) { |
155 * parallel, and provide the information for each power of 10. When formatti
ng a value, the correct power of 10 is | 167 FormatQuantity4 fq = new FormatQuantity4(number); |
156 * found, then the value is divided by the divisor, and the prefix and suffi
x are set (using | 168 formatter.format(fq, toAppendTo, pos); |
157 * setPositivePrefix/Suffix). | 169 fq.populateUFieldPosition(pos); |
158 * | 170 return toAppendTo; |
159 * @param pattern | 171 } |
160 * A number format pattern. Note that the prefix and suffix are d
iscarded, and the decimals are | 172 |
161 * overridden by default. | 173 /** |
162 * @param formatSymbols | 174 * {@inheritDoc} |
163 * Decimal format symbols, typically from a locale. | 175 * |
164 * @param style | 176 * @stable ICU 49 |
165 * compact style. | 177 */ |
166 * @param divisor | 178 @Override |
167 * An array of prefix values, one for each power of 10 from 0 to
14 | 179 public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPo
sition pos) { |
168 * @param pluralAffixes | 180 FormatQuantity4 fq = new FormatQuantity4(number); |
169 * A map from plural categories to affixes. | 181 formatter.format(fq, toAppendTo, pos); |
170 * @param currencyAffixes | 182 fq.populateUFieldPosition(pos); |
171 * A map from plural categories to currency affixes. | 183 return toAppendTo; |
172 * @param debugCreationErrors | 184 } |
173 * A collection of strings for debugging. If null on input, then
any errors found will be added to that | 185 |
174 * collection instead of throwing exceptions. | 186 /** |
175 * @internal | 187 * {@inheritDoc} |
176 * @deprecated This API is ICU internal only. | 188 * |
177 */ | 189 * @stable ICU 49 |
178 @Deprecated | 190 */ |
179 public CompactDecimalFormat(String pattern, DecimalFormatSymbols formatSymbo
ls, | 191 @Override |
180 CompactStyle style, PluralRules pluralRules, | 192 public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPo
sition pos) { |
181 long[] divisor, Map<String,String[][]> pluralAffixes, Map<String, St
ring[]> currencyAffixes, | 193 FormatQuantity4 fq = new FormatQuantity4(number); |
182 Collection<String> debugCreationErrors) { | 194 formatter.format(fq, toAppendTo, pos); |
183 | 195 fq.populateUFieldPosition(pos); |
184 this.pluralRules = pluralRules; | 196 return toAppendTo; |
185 this.units = otherPluralVariant(pluralAffixes, divisor, debugCreationErr
ors); | 197 } |
186 this.currencyUnits = otherPluralVariant(pluralAffixes, divisor, debugCre
ationErrors); | 198 |
187 if (!pluralRules.getKeywords().equals(this.units.keySet())) { | 199 /** |
188 debugCreationErrors.add("Missmatch in pluralCategories, should be: "
+ pluralRules.getKeywords() + ", was actually " + this.units.keySet()); | 200 * {@inheritDoc} |
189 } | 201 * |
190 this.divisor = divisor.clone(); | 202 * @stable ICU 49 |
191 this.currencyDivisor = divisor.clone(); | 203 */ |
192 if (currencyAffixes == null) { | 204 @Override |
193 pluralToCurrencyAffixes = null; | 205 public StringBuffer format( |
194 } else { | 206 com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition
pos) { |
195 pluralToCurrencyAffixes = new HashMap<String,Unit>(); | 207 FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal()); |
196 for (Entry<String, String[]> s : currencyAffixes.entrySet()) { | 208 formatter.format(fq, toAppendTo, pos); |
197 String[] pair = s.getValue(); | 209 fq.populateUFieldPosition(pos); |
198 pluralToCurrencyAffixes.put(s.getKey(), new Unit(pair[0], pair[1
])); | 210 return toAppendTo; |
199 } | 211 } |
200 } | 212 |
201 finishInit(style, pattern, formatSymbols); | 213 // /** |
202 } | 214 // * {@inheritDoc} |
203 | 215 // * |
204 private void finishInit(CompactStyle style, String pattern, DecimalFormatSym
bols formatSymbols) { | 216 // * @internal ICU 57 technology preview |
205 applyPattern(pattern); | 217 // * @deprecated This API might change or be removed in a future release. |
206 setDecimalFormatSymbols(formatSymbols); | 218 // */ |
207 setMaximumSignificantDigits(2); // default significant digits | 219 // @Override |
208 setSignificantDigitsUsed(true); | 220 // @Deprecated |
209 if (style == CompactStyle.SHORT) { | 221 // public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo,
FieldPosition pos) { |
210 setGroupingUsed(false); | 222 // // TODO(sffc) |
211 } | 223 // throw new UnsupportedOperationException(); |
212 setCurrency(null); | 224 // } |
213 } | 225 |
214 | 226 /** |
215 /** | 227 * Parsing is currently unsupported, and throws an UnsupportedOperationExcepti
on. |
216 * {@inheritDoc} | 228 * |
217 * @stable ICU 49 | 229 * @stable ICU 49 |
218 */ | 230 */ |
219 @Override | 231 @Override |
220 public boolean equals(Object obj) { | 232 public Number parse(String text, ParsePosition parsePosition) { |
221 if (obj == null) | 233 throw new UnsupportedOperationException(); |
222 return false; | 234 } |
223 if (!super.equals(obj)) | 235 |
224 return false; // super does class check | 236 // DISALLOW Serialization, at least while draft |
225 CompactDecimalFormat other = (CompactDecimalFormat) obj; | 237 |
226 return mapsAreEqual(units, other.units) | 238 private void writeObject(ObjectOutputStream out) throws IOException { |
227 && Arrays.equals(divisor, other.divisor) | 239 throw new NotSerializableException(); |
228 && (pluralToCurrencyAffixes == other.pluralToCurrencyAffixes | 240 } |
229 || pluralToCurrencyAffixes != null && pluralToCurrencyAffixes.eq
uals(other.pluralToCurrencyAffixes)) | 241 |
230 && pluralRules.equals(other.pluralRules); | 242 private void readObject(ObjectInputStream in) throws IOException { |
231 } | 243 throw new NotSerializableException(); |
232 | 244 } |
233 private boolean mapsAreEqual( | |
234 Map<String, DecimalFormat.Unit[]> lhs, Map<String, DecimalFormat.Uni
t[]> rhs) { | |
235 if (lhs.size() != rhs.size()) { | |
236 return false; | |
237 } | |
238 // For each MapEntry in lhs, see if there is a matching one in rhs. | |
239 for (Map.Entry<String, DecimalFormat.Unit[]> entry : lhs.entrySet()) { | |
240 DecimalFormat.Unit[] value = rhs.get(entry.getKey()); | |
241 if (value == null || !Arrays.equals(entry.getValue(), value)) { | |
242 return false; | |
243 } | |
244 } | |
245 return true; | |
246 } | |
247 | |
248 /** | |
249 * {@inheritDoc} | |
250 * @stable ICU 49 | |
251 */ | |
252 @Override | |
253 public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosi
tion pos) { | |
254 return format(number, null, toAppendTo, pos); | |
255 } | |
256 | |
257 /** | |
258 * {@inheritDoc} | |
259 * @stable ICU 50 | |
260 */ | |
261 @Override | |
262 public AttributedCharacterIterator formatToCharacterIterator(Object obj) { | |
263 if (!(obj instanceof Number)) { | |
264 throw new IllegalArgumentException(); | |
265 } | |
266 Number number = (Number) obj; | |
267 Amount amount = toAmount(number.doubleValue(), null, null); | |
268 return super.formatToCharacterIterator(amount.getQty(), amount.getUnit()
); | |
269 } | |
270 | |
271 /** | |
272 * {@inheritDoc} | |
273 * @stable ICU 49 | |
274 */ | |
275 @Override | |
276 public StringBuffer format(long number, StringBuffer toAppendTo, FieldPositi
on pos) { | |
277 return format((double) number, toAppendTo, pos); | |
278 } | |
279 | |
280 /** | |
281 * {@inheritDoc} | |
282 * @stable ICU 49 | |
283 */ | |
284 @Override | |
285 public StringBuffer format(BigInteger number, StringBuffer toAppendTo, Field
Position pos) { | |
286 return format(number.doubleValue(), toAppendTo, pos); | |
287 } | |
288 | |
289 /** | |
290 * {@inheritDoc} | |
291 * @stable ICU 49 | |
292 */ | |
293 @Override | |
294 public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, Field
Position pos) { | |
295 return format(number.doubleValue(), toAppendTo, pos); | |
296 } | |
297 | |
298 /** | |
299 * {@inheritDoc} | |
300 * @stable ICU 49 | |
301 */ | |
302 @Override | |
303 public StringBuffer format(com.ibm.icu.math.BigDecimal number, StringBuffer
toAppendTo, FieldPosition pos) { | |
304 return format(number.doubleValue(), toAppendTo, pos); | |
305 } | |
306 /** | |
307 * {@inheritDoc} | |
308 * @internal ICU 57 technology preview | |
309 * @deprecated This API might change or be removed in a future release. | |
310 */ | |
311 @Override | |
312 @Deprecated | |
313 public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo,
FieldPosition pos) { | |
314 return format(currAmt.getNumber().doubleValue(), currAmt.getCurrency(),
toAppendTo, pos); | |
315 } | |
316 | |
317 /** | |
318 * Parsing is currently unsupported, and throws an UnsupportedOperationExcep
tion. | |
319 * @stable ICU 49 | |
320 */ | |
321 @Override | |
322 public Number parse(String text, ParsePosition parsePosition) { | |
323 throw new UnsupportedOperationException(); | |
324 } | |
325 | |
326 // DISALLOW Serialization, at least while draft | |
327 | |
328 private void writeObject(ObjectOutputStream out) throws IOException { | |
329 throw new NotSerializableException(); | |
330 } | |
331 | |
332 private void readObject(ObjectInputStream in) throws IOException { | |
333 throw new NotSerializableException(); | |
334 } | |
335 | |
336 /* INTERNALS */ | |
337 private StringBuffer format(double number, Currency curr, StringBuffer toApp
endTo, FieldPosition pos) { | |
338 if (curr != null && style == CompactStyle.LONG) { | |
339 throw new UnsupportedOperationException("CompactDecimalFormat does n
ot support LONG style for currency."); | |
340 } | |
341 | |
342 // Compute the scaled amount, prefix, and suffix appropriate for the num
ber's magnitude. | |
343 Output<Unit> currencyUnit = new Output<Unit>(); | |
344 Amount amount = toAmount(number, curr, currencyUnit); | |
345 Unit unit = amount.getUnit(); | |
346 | |
347 // Note that currencyUnit is a remnant. In almost all cases, it will be
null. | |
348 StringBuffer prefix = new StringBuffer(); | |
349 StringBuffer suffix = new StringBuffer(); | |
350 if (currencyUnit.value != null) { | |
351 currencyUnit.value.writePrefix(prefix); | |
352 } | |
353 unit.writePrefix(prefix); | |
354 unit.writeSuffix(suffix); | |
355 if (currencyUnit.value != null) { | |
356 currencyUnit.value.writeSuffix(suffix); | |
357 } | |
358 | |
359 if (curr == null) { | |
360 // Prevent locking when not formatting a currency number. | |
361 toAppendTo.append(escape(prefix.toString())); | |
362 super.format(amount.getQty(), toAppendTo, pos); | |
363 toAppendTo.append(escape(suffix.toString())); | |
364 | |
365 } else { | |
366 // To perform the formatting, we set this DecimalFormat's pattern to
have the correct prefix, suffix, | |
367 // and currency, and then reset it back to what it was before. | |
368 // This has to be synchronized since this information is held in the
state of the DecimalFormat object. | |
369 synchronized(this) { | |
370 | |
371 String originalPattern = this.toPattern(); | |
372 Currency originalCurrency = this.getCurrency(); | |
373 StringBuffer newPattern = new StringBuffer(); | |
374 | |
375 // Write prefixes and suffixes to the pattern. Note that we hav
e to apply it to both halves of a | |
376 // positive/negative format (separated by ';') | |
377 int semicolonPos = originalPattern.indexOf(';'); | |
378 newPattern.append(prefix); | |
379 if (semicolonPos != -1) { | |
380 newPattern.append(originalPattern, 0, semicolonPos); | |
381 newPattern.append(suffix); | |
382 newPattern.append(';'); | |
383 newPattern.append(prefix); | |
384 } | |
385 newPattern.append(originalPattern, semicolonPos + 1, originalPat
tern.length()); | |
386 newPattern.append(suffix); | |
387 | |
388 // Overwrite the pattern and currency. | |
389 setCurrency(curr); | |
390 applyPattern(newPattern.toString()); | |
391 | |
392 // Actually perform the formatting. | |
393 super.format(amount.getQty(), toAppendTo, pos); | |
394 | |
395 // Reset the pattern and currency. | |
396 setCurrency(originalCurrency); | |
397 applyPattern(originalPattern); | |
398 } | |
399 } | |
400 return toAppendTo; | |
401 } | |
402 | |
403 private static final Pattern UNESCAPE_QUOTE = Pattern.compile("((?<!'))'"); | |
404 | |
405 private static String escape(String string) { | |
406 if (string.indexOf('\'') >= 0) { | |
407 return UNESCAPE_QUOTE.matcher(string).replaceAll("$1"); | |
408 } | |
409 return string; | |
410 } | |
411 | |
412 private Amount toAmount(double number, Currency curr, Output<Unit> currencyU
nit) { | |
413 // We do this here so that the prefix or suffix we choose is always cons
istent | |
414 // with the rounding we do. This way, 999999 -> 1M instead of 1000K. | |
415 boolean negative = isNumberNegative(number); | |
416 number = adjustNumberAsInFormatting(number); | |
417 int base = number <= 1.0d ? 0 : (int) Math.log10(number); | |
418 if (base >= CompactDecimalDataCache.MAX_DIGITS) { | |
419 base = CompactDecimalDataCache.MAX_DIGITS - 1; | |
420 } | |
421 if (curr != null) { | |
422 number /= currencyDivisor[base]; | |
423 } else { | |
424 number /= divisor[base]; | |
425 } | |
426 String pluralVariant = getPluralForm(getFixedDecimal(number, toDigitList
(number))); | |
427 if (pluralToCurrencyAffixes != null && currencyUnit != null) { | |
428 currencyUnit.value = pluralToCurrencyAffixes.get(pluralVariant); | |
429 } | |
430 if (negative) { | |
431 number = -number; | |
432 } | |
433 if ( curr != null ) { | |
434 return new Amount(number, CompactDecimalDataCache.getUnit(currencyUn
its, pluralVariant, base)); | |
435 } else { | |
436 return new Amount(number, CompactDecimalDataCache.getUnit(units, plu
ralVariant, base)); | |
437 } | |
438 } | |
439 | |
440 private void recordError(Collection<String> creationErrors, String errorMess
age) { | |
441 if (creationErrors == null) { | |
442 throw new IllegalArgumentException(errorMessage); | |
443 } | |
444 creationErrors.add(errorMessage); | |
445 } | |
446 | |
447 /** | |
448 * Manufacture the unit list from arrays | |
449 */ | |
450 private Map<String, DecimalFormat.Unit[]> otherPluralVariant(Map<String, Str
ing[][]> pluralCategoryToPower10ToAffix, | |
451 long[] divisor, Collection<String> debugCreationErrors) { | |
452 | |
453 // check for bad divisors | |
454 if (divisor.length < CompactDecimalDataCache.MAX_DIGITS) { | |
455 recordError(debugCreationErrors, "Must have at least " + CompactDeci
malDataCache.MAX_DIGITS + " prefix items."); | |
456 } | |
457 long oldDivisor = 0; | |
458 for (int i = 0; i < divisor.length; ++i) { | |
459 | |
460 // divisor must be a power of 10, and must be less than or equal to
10^i | |
461 int log = (int) Math.log10(divisor[i]); | |
462 if (log > i) { | |
463 recordError(debugCreationErrors, "Divisor[" + i + "] must be les
s than or equal to 10^" + i | |
464 + ", but is: " + divisor[i]); | |
465 } | |
466 long roundTrip = (long) Math.pow(10.0d, log); | |
467 if (roundTrip != divisor[i]) { | |
468 recordError(debugCreationErrors, "Divisor[" + i + "] must be a p
ower of 10, but is: " + divisor[i]); | |
469 } | |
470 | |
471 if (divisor[i] < oldDivisor) { | |
472 recordError(debugCreationErrors, "Bad divisor, the divisor for 1
0E" + i + "(" + divisor[i] | |
473 + ") is less than the divisor for the divisor for 10E" +
(i - 1) + "(" + oldDivisor + ")"); | |
474 } | |
475 oldDivisor = divisor[i]; | |
476 } | |
477 | |
478 Map<String, DecimalFormat.Unit[]> result = new HashMap<String, DecimalFo
rmat.Unit[]>(); | |
479 Map<String,Integer> seen = new HashMap<String,Integer>(); | |
480 | |
481 String[][] defaultPower10ToAffix = pluralCategoryToPower10ToAffix.get("o
ther"); | |
482 | |
483 for (Entry<String, String[][]> pluralCategoryAndPower10ToAffix : pluralC
ategoryToPower10ToAffix.entrySet()) { | |
484 String pluralCategory = pluralCategoryAndPower10ToAffix.getKey(); | |
485 String[][] power10ToAffix = pluralCategoryAndPower10ToAffix.getValue
(); | |
486 | |
487 // we can't have one of the arrays be of different length | |
488 if (power10ToAffix.length != divisor.length) { | |
489 recordError(debugCreationErrors, "Prefixes & suffixes must be pr
esent for all divisors " + pluralCategory); | |
490 } | |
491 DecimalFormat.Unit[] units = new DecimalFormat.Unit[power10ToAffix.l
ength]; | |
492 for (int i = 0; i < power10ToAffix.length; i++) { | |
493 String[] pair = power10ToAffix[i]; | |
494 if (pair == null) { | |
495 pair = defaultPower10ToAffix[i]; | |
496 } | |
497 | |
498 // we can't have bad pair | |
499 if (pair.length != 2 || pair[0] == null || pair[1] == null) { | |
500 recordError(debugCreationErrors, "Prefix or suffix is null f
or " + pluralCategory + ", " + i + ", " + Arrays.asList(pair)); | |
501 continue; | |
502 } | |
503 | |
504 // we can't have two different indexes with the same display | |
505 int log = (int) Math.log10(divisor[i]); | |
506 String key = pair[0] + "\uFFFF" + pair[1] + "\uFFFF" + (i - log)
; | |
507 Integer old = seen.get(key); | |
508 if (old == null) { | |
509 seen.put(key, i); | |
510 } else if (old != i) { | |
511 recordError(debugCreationErrors, "Collision between values f
or " + i + " and " + old | |
512 + " for [prefix/suffix/index-log(divisor)" + key.rep
lace('\uFFFF', ';')); | |
513 } | |
514 | |
515 units[i] = new Unit(pair[0], pair[1]); | |
516 } | |
517 result.put(pluralCategory, units); | |
518 } | |
519 return result; | |
520 } | |
521 | |
522 private String getPluralForm(FixedDecimal fixedDecimal) { | |
523 if (pluralRules == null) { | |
524 return CompactDecimalDataCache.OTHER; | |
525 } | |
526 return pluralRules.select(fixedDecimal); | |
527 } | |
528 | |
529 /** | |
530 * Gets the data for a particular locale and style. If style is unrecognized
, | |
531 * we just return data for CompactStyle.SHORT. | |
532 * @param locale The locale. | |
533 * @param style The style. | |
534 * @return The data which must not be modified. | |
535 */ | |
536 private Data getData(ULocale locale, CompactStyle style) { | |
537 CompactDecimalDataCache.DataBundle bundle = cache.get(locale); | |
538 switch (style) { | |
539 case SHORT: | |
540 return bundle.shortData; | |
541 case LONG: | |
542 return bundle.longData; | |
543 default: | |
544 return bundle.shortData; | |
545 } | |
546 } | |
547 /** | |
548 * Gets the currency data for a particular locale. | |
549 * Currently only short currency format is supported, since that is | |
550 * the only form in CLDR. | |
551 * @param locale The locale. | |
552 * @return The data which must not be modified. | |
553 */ | |
554 private Data getCurrencyData(ULocale locale) { | |
555 CompactDecimalDataCache.DataBundle bundle = cache.get(locale); | |
556 return bundle.shortCurrencyData; | |
557 } | |
558 | |
559 private static class Amount { | |
560 private final double qty; | |
561 private final Unit unit; | |
562 | |
563 public Amount(double qty, Unit unit) { | |
564 this.qty = qty; | |
565 this.unit = unit; | |
566 } | |
567 | |
568 public double getQty() { | |
569 return qty; | |
570 } | |
571 | |
572 public Unit getUnit() { | |
573 return unit; | |
574 } | |
575 } | |
576 } | 245 } |
OLD | NEW |