Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/java/text/CompactNumberFormat.java
41152 views
1
/*
2
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
package java.text;
26
27
import java.io.IOException;
28
import java.io.InvalidObjectException;
29
import java.io.ObjectInputStream;
30
import java.math.BigDecimal;
31
import java.math.BigInteger;
32
import java.math.RoundingMode;
33
import java.util.ArrayList;
34
import java.util.Arrays;
35
import java.util.HashMap;
36
import java.util.List;
37
import java.util.Locale;
38
import java.util.Map;
39
import java.util.Objects;
40
import java.util.concurrent.atomic.AtomicInteger;
41
import java.util.concurrent.atomic.AtomicLong;
42
import java.util.regex.Matcher;
43
import java.util.regex.Pattern;
44
import java.util.stream.Collectors;
45
46
47
/**
48
* <p>
49
* {@code CompactNumberFormat} is a concrete subclass of {@code NumberFormat}
50
* that formats a decimal number in its compact form.
51
*
52
* The compact number formatting is designed for the environment where the space
53
* is limited, and the formatted string can be displayed in that limited space.
54
* It is defined by LDML's specification for
55
* <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats">
56
* Compact Number Formats</a>. A compact number formatting refers
57
* to the representation of a number in a shorter form, based on the patterns
58
* provided for a given locale.
59
*
60
* <p>
61
* For example:
62
* <br>In the {@link java.util.Locale#US US locale}, {@code 1000} can be formatted
63
* as {@code "1K"}, and {@code 1000000} as {@code "1M"}, depending upon the
64
* <a href = "#compact_number_style" >style</a> used.
65
* <br>In the {@code "hi_IN"} locale, {@code 1000} can be formatted as
66
* "1 \u0939\u091C\u093C\u093E\u0930", and {@code 50000000} as "5 \u0915.",
67
* depending upon the <a href = "#compact_number_style" >style</a> used.
68
*
69
* <p>
70
* To obtain a {@code CompactNumberFormat} for a locale, use one
71
* of the factory methods given by {@code NumberFormat} for compact number
72
* formatting. For example,
73
* {@link NumberFormat#getCompactNumberInstance(Locale, Style)}.
74
*
75
* <blockquote><pre>
76
* NumberFormat fmt = NumberFormat.getCompactNumberInstance(
77
* new Locale("hi", "IN"), NumberFormat.Style.SHORT);
78
* String result = fmt.format(1000);
79
* </pre></blockquote>
80
*
81
* <h2><a id="compact_number_style">Style</a></h2>
82
* <p>
83
* A number can be formatted in the compact forms with two different
84
* styles, {@link NumberFormat.Style#SHORT SHORT}
85
* and {@link NumberFormat.Style#LONG LONG}. Use
86
* {@link NumberFormat#getCompactNumberInstance(Locale, Style)} for formatting and
87
* parsing a number in {@link NumberFormat.Style#SHORT SHORT} or
88
* {@link NumberFormat.Style#LONG LONG} compact form,
89
* where the given {@code Style} parameter requests the desired
90
* format. A {@link NumberFormat.Style#SHORT SHORT} style
91
* compact number instance in the {@link java.util.Locale#US US locale} formats
92
* {@code 10000} as {@code "10K"}. However, a
93
* {@link NumberFormat.Style#LONG LONG} style instance in same locale
94
* formats {@code 10000} as {@code "10 thousand"}.
95
*
96
* <h2><a id="compact_number_patterns">Compact Number Patterns</a></h2>
97
* <p>
98
* The compact number patterns are represented in a series of patterns where each
99
* pattern is used to format a range of numbers. An example of
100
* {@link NumberFormat.Style#SHORT SHORT} styled compact number patterns
101
* for the {@link java.util.Locale#US US locale} is {@code {"", "", "", "0K",
102
* "00K", "000K", "0M", "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"}},
103
* ranging from {@code 10}<sup>{@code 0}</sup> to {@code 10}<sup>{@code 14}</sup>.
104
* There can be any number of patterns and they are
105
* strictly index based starting from the range {@code 10}<sup>{@code 0}</sup>.
106
* For example, in the above patterns, pattern at index 3
107
* ({@code "0K"}) is used for formatting {@code number >= 1000 and number < 10000},
108
* pattern at index 4 ({@code "00K"}) is used for formatting
109
* {@code number >= 10000 and number < 100000} and so on. In most of the locales,
110
* patterns with the range
111
* {@code 10}<sup>{@code 0}</sup>-{@code 10}<sup>{@code 2}</sup> are empty
112
* strings, which implicitly means a special pattern {@code "0"}.
113
* A special pattern {@code "0"} is used for any range which does not contain
114
* a compact pattern. This special pattern can appear explicitly for any specific
115
* range, or considered as a default pattern for an empty string.
116
*
117
* <p>
118
* A compact pattern contains a positive and negative subpattern
119
* separated by a subpattern boundary character {@code ';' (U+003B)},
120
* for example, {@code "0K;-0K"}. Each subpattern has a prefix,
121
* minimum integer digits, and suffix. The negative subpattern
122
* is optional, if absent, then the positive subpattern prefixed with the
123
* minus sign ({@code '-' U+002D HYPHEN-MINUS}) is used as the negative
124
* subpattern. That is, {@code "0K"} alone is equivalent to {@code "0K;-0K"}.
125
* If there is an explicit negative subpattern, it serves only to specify
126
* the negative prefix and suffix. The number of minimum integer digits,
127
* and other characteristics are all the same as the positive pattern.
128
* That means that {@code "0K;-00K"} produces precisely the same behavior
129
* as {@code "0K;-0K"}.
130
*
131
* <p>
132
* Many characters in a compact pattern are taken literally, they are matched
133
* during parsing and output unchanged during formatting.
134
* <a href = "DecimalFormat.html#special_pattern_character">Special characters</a>,
135
* on the other hand, stand for other characters, strings, or classes of
136
* characters. They must be quoted, using single quote {@code ' (U+0027)}
137
* unless noted otherwise, if they are to appear in the prefix or suffix
138
* as literals. For example, 0\u0915'.'.
139
*
140
* <h3>Plurals</h3>
141
* <p>
142
* In case some localization requires compact number patterns to be different for
143
* plurals, each singular and plural pattern can be enumerated within a pair of
144
* curly brackets <code>'{' (U+007B)</code> and <code>'}' (U+007D)</code>, separated
145
* by a space {@code ' ' (U+0020)}. If this format is used, each pattern needs to be
146
* prepended by its {@code count}, followed by a single colon {@code ':' (U+003A)}.
147
* If the pattern includes spaces literally, they must be quoted.
148
* <p>
149
* For example, the compact number pattern representing millions in German locale can be
150
* specified as {@code "{one:0' 'Million other:0' 'Millionen}"}. The {@code count}
151
* follows LDML's
152
* <a href="https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules">
153
* Language Plural Rules</a>.
154
* <p>
155
* A compact pattern has the following syntax:
156
* <blockquote><pre>
157
* <i>Pattern:</i>
158
* <i>SimplePattern</i>
159
* '{' <i>PluralPattern</i> <i>[' ' PluralPattern]<sub>optional</sub></i> '}'
160
* <i>SimplePattern:</i>
161
* <i>PositivePattern</i>
162
* <i>PositivePattern</i> <i>[; NegativePattern]<sub>optional</sub></i>
163
* <i>PluralPattern:</i>
164
* <i>Count</i>:<i>SimplePattern</i>
165
* <i>Count:</i>
166
* "zero" / "one" / "two" / "few" / "many" / "other"
167
* <i>PositivePattern:</i>
168
* <i>Prefix<sub>optional</sub></i> <i>MinimumInteger</i> <i>Suffix<sub>optional</sub></i>
169
* <i>NegativePattern:</i>
170
* <i>Prefix<sub>optional</sub></i> <i>MinimumInteger</i> <i>Suffix<sub>optional</sub></i>
171
* <i>Prefix:</i>
172
* Any Unicode characters except &#92;uFFFE, &#92;uFFFF, and
173
* <a href = "DecimalFormat.html#special_pattern_character">special characters</a>.
174
* <i>Suffix:</i>
175
* Any Unicode characters except &#92;uFFFE, &#92;uFFFF, and
176
* <a href = "DecimalFormat.html#special_pattern_character">special characters</a>.
177
* <i>MinimumInteger:</i>
178
* 0
179
* 0 <i>MinimumInteger</i>
180
* </pre></blockquote>
181
*
182
* <h2>Formatting</h2>
183
* The default formatting behavior returns a formatted string with no fractional
184
* digits, however users can use the {@link #setMinimumFractionDigits(int)}
185
* method to include the fractional part.
186
* The number {@code 1000.0} or {@code 1000} is formatted as {@code "1K"}
187
* not {@code "1.00K"} (in the {@link java.util.Locale#US US locale}). For this
188
* reason, the patterns provided for formatting contain only the minimum
189
* integer digits, prefix and/or suffix, but no fractional part.
190
* For example, patterns used are {@code {"", "", "", 0K, 00K, ...}}. If the pattern
191
* selected for formatting a number is {@code "0"} (special pattern),
192
* either explicit or defaulted, then the general number formatting provided by
193
* {@link java.text.DecimalFormat DecimalFormat}
194
* for the specified locale is used.
195
*
196
* <h2>Parsing</h2>
197
* The default parsing behavior does not allow a grouping separator until
198
* grouping used is set to {@code true} by using
199
* {@link #setGroupingUsed(boolean)}. The parsing of the fractional part
200
* depends on the {@link #isParseIntegerOnly()}. For example, if the
201
* parse integer only is set to true, then the fractional part is skipped.
202
*
203
* <h2>Rounding</h2>
204
* {@code CompactNumberFormat} provides rounding modes defined in
205
* {@link java.math.RoundingMode} for formatting. By default, it uses
206
* {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}.
207
*
208
* @see NumberFormat.Style
209
* @see NumberFormat
210
* @see DecimalFormat
211
* @since 12
212
*/
213
public final class CompactNumberFormat extends NumberFormat {
214
215
@java.io.Serial
216
private static final long serialVersionUID = 7128367218649234678L;
217
218
/**
219
* The patterns for compact form of numbers for this
220
* {@code CompactNumberFormat}. A possible example is
221
* {@code {"", "", "", "0K", "00K", "000K", "0M", "00M", "000M", "0B",
222
* "00B", "000B", "0T", "00T", "000T"}} ranging from
223
* {@code 10}<sup>{@code 0}</sup>-{@code 10}<sup>{@code 14}</sup>,
224
* where each pattern is used to format a range of numbers.
225
* For example, {@code "0K"} is used for formatting
226
* {@code number >= 1000 and number < 10000}, {@code "00K"} is used for
227
* formatting {@code number >= 10000 and number < 100000} and so on.
228
* This field must not be {@code null}.
229
*
230
* @serial
231
*/
232
private String[] compactPatterns;
233
234
/**
235
* List of positive prefix patterns of this formatter's
236
* compact number patterns.
237
*/
238
private transient List<Patterns> positivePrefixPatterns;
239
240
/**
241
* List of negative prefix patterns of this formatter's
242
* compact number patterns.
243
*/
244
private transient List<Patterns> negativePrefixPatterns;
245
246
/**
247
* List of positive suffix patterns of this formatter's
248
* compact number patterns.
249
*/
250
private transient List<Patterns> positiveSuffixPatterns;
251
252
/**
253
* List of negative suffix patterns of this formatter's
254
* compact number patterns.
255
*/
256
private transient List<Patterns> negativeSuffixPatterns;
257
258
/**
259
* List of divisors of this formatter's compact number patterns.
260
* Divisor can be either Long or BigInteger (if the divisor value goes
261
* beyond long boundary)
262
*/
263
private transient List<Number> divisors;
264
265
/**
266
* List of place holders that represent minimum integer digits at each index
267
* for each count.
268
*/
269
private transient List<Patterns> placeHolderPatterns;
270
271
/**
272
* The {@code DecimalFormatSymbols} object used by this format.
273
* It contains the symbols used to format numbers. For example,
274
* the grouping separator, decimal separator, and so on.
275
* This field must not be {@code null}.
276
*
277
* @serial
278
* @see DecimalFormatSymbols
279
*/
280
private DecimalFormatSymbols symbols;
281
282
/**
283
* The decimal pattern which is used for formatting the numbers
284
* matching special pattern "0". This field must not be {@code null}.
285
*
286
* @serial
287
* @see DecimalFormat
288
*/
289
private final String decimalPattern;
290
291
/**
292
* A {@code DecimalFormat} used by this format for getting corresponding
293
* general number formatting behavior for compact numbers.
294
*
295
*/
296
private transient DecimalFormat decimalFormat;
297
298
/**
299
* A {@code DecimalFormat} used by this format for getting general number
300
* formatting behavior for the numbers which can't be represented as compact
301
* numbers. For example, number matching the special pattern "0" are
302
* formatted through general number format pattern provided by
303
* {@link java.text.DecimalFormat DecimalFormat}
304
* for the specified locale.
305
*
306
*/
307
private transient DecimalFormat defaultDecimalFormat;
308
309
/**
310
* The number of digits between grouping separators in the integer portion
311
* of a compact number. For the grouping to work while formatting, this
312
* field needs to be greater than 0 with grouping used set as true.
313
* This field must not be negative.
314
*
315
* @serial
316
*/
317
private byte groupingSize = 0;
318
319
/**
320
* Returns whether the {@link #parse(String, ParsePosition)}
321
* method returns {@code BigDecimal}.
322
*
323
* @serial
324
*/
325
private boolean parseBigDecimal = false;
326
327
/**
328
* The {@code RoundingMode} used in this compact number format.
329
* This field must not be {@code null}.
330
*
331
* @serial
332
*/
333
private RoundingMode roundingMode = RoundingMode.HALF_EVEN;
334
335
/**
336
* The {@code pluralRules} used in this compact number format.
337
* {@code pluralRules} is a String designating plural rules which associate
338
* the {@code Count} keyword, such as "{@code one}", and the
339
* actual integer number. Its syntax is defined in Unicode Consortium's
340
* <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax">
341
* Plural rules syntax</a>.
342
* The default value is an empty string, meaning there is no plural rules.
343
*
344
* @serial
345
* @since 14
346
*/
347
private String pluralRules = "";
348
349
/**
350
* The map for plural rules that maps LDML defined tags (e.g. "one") to
351
* its rule.
352
*/
353
private transient Map<String, String> rulesMap;
354
355
/**
356
* Special pattern used for compact numbers
357
*/
358
private static final String SPECIAL_PATTERN = "0";
359
360
/**
361
* Multiplier for compact pattern range. In
362
* the list compact patterns each compact pattern
363
* specify the range with the multiplication factor of 10
364
* of its previous compact pattern range.
365
* For example, 10^0, 10^1, 10^2, 10^3, 10^4...
366
*
367
*/
368
private static final int RANGE_MULTIPLIER = 10;
369
370
/**
371
* Creates a {@code CompactNumberFormat} using the given decimal pattern,
372
* decimal format symbols and compact patterns.
373
* To obtain the instance of {@code CompactNumberFormat} with the standard
374
* compact patterns for a {@code Locale} and {@code Style},
375
* it is recommended to use the factory methods given by
376
* {@code NumberFormat} for compact number formatting. For example,
377
* {@link NumberFormat#getCompactNumberInstance(Locale, Style)}.
378
*
379
* @param decimalPattern a decimal pattern for general number formatting
380
* @param symbols the set of symbols to be used
381
* @param compactPatterns an array of
382
* <a href = "CompactNumberFormat.html#compact_number_patterns">
383
* compact number patterns</a>
384
* @throws NullPointerException if any of the given arguments is
385
* {@code null}
386
* @throws IllegalArgumentException if the given {@code decimalPattern} or the
387
* {@code compactPatterns} array contains an invalid pattern
388
* or if a {@code null} appears in the array of compact
389
* patterns
390
* @see DecimalFormat#DecimalFormat(java.lang.String, DecimalFormatSymbols)
391
* @see DecimalFormatSymbols
392
*/
393
public CompactNumberFormat(String decimalPattern,
394
DecimalFormatSymbols symbols, String[] compactPatterns) {
395
this(decimalPattern, symbols, compactPatterns, "");
396
}
397
398
/**
399
* Creates a {@code CompactNumberFormat} using the given decimal pattern,
400
* decimal format symbols, compact patterns, and plural rules.
401
* To obtain the instance of {@code CompactNumberFormat} with the standard
402
* compact patterns for a {@code Locale}, {@code Style}, and {@code pluralRules},
403
* it is recommended to use the factory methods given by
404
* {@code NumberFormat} for compact number formatting. For example,
405
* {@link NumberFormat#getCompactNumberInstance(Locale, Style)}.
406
*
407
* @param decimalPattern a decimal pattern for general number formatting
408
* @param symbols the set of symbols to be used
409
* @param compactPatterns an array of
410
* <a href = "CompactNumberFormat.html#compact_number_patterns">
411
* compact number patterns</a>
412
* @param pluralRules a String designating plural rules which associate
413
* the {@code Count} keyword, such as "{@code one}", and the
414
* actual integer number. Its syntax is defined in Unicode Consortium's
415
* <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax">
416
* Plural rules syntax</a>
417
* @throws NullPointerException if any of the given arguments is
418
* {@code null}
419
* @throws IllegalArgumentException if the given {@code decimalPattern},
420
* the {@code compactPatterns} array contains an invalid pattern,
421
* a {@code null} appears in the array of compact patterns,
422
* or if the given {@code pluralRules} contains an invalid syntax
423
* @see DecimalFormat#DecimalFormat(java.lang.String, DecimalFormatSymbols)
424
* @see DecimalFormatSymbols
425
* @since 14
426
*/
427
public CompactNumberFormat(String decimalPattern,
428
DecimalFormatSymbols symbols, String[] compactPatterns,
429
String pluralRules) {
430
431
Objects.requireNonNull(decimalPattern, "decimalPattern");
432
Objects.requireNonNull(symbols, "symbols");
433
Objects.requireNonNull(compactPatterns, "compactPatterns");
434
Objects.requireNonNull(pluralRules, "pluralRules");
435
436
this.symbols = symbols;
437
// Instantiating the DecimalFormat with "0" pattern; this acts just as a
438
// basic pattern; the properties (For example, prefix/suffix)
439
// are later computed based on the compact number formatting process.
440
decimalFormat = new DecimalFormat(SPECIAL_PATTERN, this.symbols);
441
442
// Initializing the super class state with the decimalFormat values
443
// to represent this CompactNumberFormat.
444
// For setting the digits counts, use overridden setXXX methods of this
445
// CompactNumberFormat, as it performs check with the max range allowed
446
// for compact number formatting
447
setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits());
448
setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits());
449
setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits());
450
setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits());
451
452
super.setGroupingUsed(decimalFormat.isGroupingUsed());
453
super.setParseIntegerOnly(decimalFormat.isParseIntegerOnly());
454
455
this.compactPatterns = compactPatterns;
456
457
// DecimalFormat used for formatting numbers with special pattern "0".
458
// Formatting is delegated to the DecimalFormat's number formatting
459
// with no fraction digits
460
this.decimalPattern = decimalPattern;
461
defaultDecimalFormat = new DecimalFormat(this.decimalPattern,
462
this.symbols);
463
defaultDecimalFormat.setMaximumFractionDigits(0);
464
465
this.pluralRules = pluralRules;
466
467
// Process compact patterns to extract the prefixes, suffixes, place holders, and
468
// divisors
469
processCompactPatterns();
470
}
471
472
/**
473
* Formats a number to produce a string representing its compact form.
474
* The number can be of any subclass of {@link java.lang.Number}.
475
* @param number the number to format
476
* @param toAppendTo the {@code StringBuffer} to which the formatted
477
* text is to be appended
478
* @param fieldPosition keeps track on the position of the field within
479
* the returned string. For example, for formatting
480
* a number {@code 123456789} in the
481
* {@link java.util.Locale#US US locale},
482
* if the given {@code fieldPosition} is
483
* {@link NumberFormat#INTEGER_FIELD}, the begin
484
* index and end index of {@code fieldPosition}
485
* will be set to 0 and 3, respectively for the
486
* output string {@code 123M}. Similarly, positions
487
* of the prefix and the suffix fields can be
488
* obtained using {@link NumberFormat.Field#PREFIX}
489
* and {@link NumberFormat.Field#SUFFIX} respectively.
490
* @return the {@code StringBuffer} passed in as {@code toAppendTo}
491
* @throws IllegalArgumentException if {@code number} is
492
* {@code null} or not an instance of {@code Number}
493
* @throws NullPointerException if {@code toAppendTo} or
494
* {@code fieldPosition} is {@code null}
495
* @throws ArithmeticException if rounding is needed with rounding
496
* mode being set to {@code RoundingMode.UNNECESSARY}
497
* @see FieldPosition
498
*/
499
@Override
500
public final StringBuffer format(Object number,
501
StringBuffer toAppendTo,
502
FieldPosition fieldPosition) {
503
504
if (number == null) {
505
throw new IllegalArgumentException("Cannot format null as a number");
506
}
507
508
if (number instanceof Long || number instanceof Integer
509
|| number instanceof Short || number instanceof Byte
510
|| number instanceof AtomicInteger
511
|| number instanceof AtomicLong
512
|| (number instanceof BigInteger
513
&& ((BigInteger) number).bitLength() < 64)) {
514
return format(((Number) number).longValue(), toAppendTo,
515
fieldPosition);
516
} else if (number instanceof BigDecimal) {
517
return format((BigDecimal) number, toAppendTo, fieldPosition);
518
} else if (number instanceof BigInteger) {
519
return format((BigInteger) number, toAppendTo, fieldPosition);
520
} else if (number instanceof Number) {
521
return format(((Number) number).doubleValue(), toAppendTo, fieldPosition);
522
} else {
523
throw new IllegalArgumentException("Cannot format "
524
+ number.getClass().getName() + " as a number");
525
}
526
}
527
528
/**
529
* Formats a double to produce a string representing its compact form.
530
* @param number the double number to format
531
* @param result where the text is to be appended
532
* @param fieldPosition keeps track on the position of the field within
533
* the returned string. For example, to format
534
* a number {@code 1234567.89} in the
535
* {@link java.util.Locale#US US locale}
536
* if the given {@code fieldPosition} is
537
* {@link NumberFormat#INTEGER_FIELD}, the begin
538
* index and end index of {@code fieldPosition}
539
* will be set to 0 and 1, respectively for the
540
* output string {@code 1M}. Similarly, positions
541
* of the prefix and the suffix fields can be
542
* obtained using {@link NumberFormat.Field#PREFIX}
543
* and {@link NumberFormat.Field#SUFFIX} respectively.
544
* @return the {@code StringBuffer} passed in as {@code result}
545
* @throws NullPointerException if {@code result} or
546
* {@code fieldPosition} is {@code null}
547
* @throws ArithmeticException if rounding is needed with rounding
548
* mode being set to {@code RoundingMode.UNNECESSARY}
549
* @see FieldPosition
550
*/
551
@Override
552
public StringBuffer format(double number, StringBuffer result,
553
FieldPosition fieldPosition) {
554
555
fieldPosition.setBeginIndex(0);
556
fieldPosition.setEndIndex(0);
557
return format(number, result, fieldPosition.getFieldDelegate());
558
}
559
560
private StringBuffer format(double number, StringBuffer result,
561
FieldDelegate delegate) {
562
563
boolean nanOrInfinity = decimalFormat.handleNaN(number, result, delegate);
564
if (nanOrInfinity) {
565
return result;
566
}
567
568
boolean isNegative = ((number < 0.0)
569
|| (number == 0.0 && 1 / number < 0.0));
570
571
nanOrInfinity = decimalFormat.handleInfinity(number, result, delegate, isNegative);
572
if (nanOrInfinity) {
573
return result;
574
}
575
576
// Round the double value with min fraction digits, the integer
577
// part of the rounded value is used for matching the compact
578
// number pattern
579
// For example, if roundingMode is HALF_UP with min fraction
580
// digits = 0, the number 999.6 should round up
581
// to 1000 and outputs 1K/thousand in "en_US" locale
582
DigitList dList = new DigitList();
583
dList.setRoundingMode(getRoundingMode());
584
number = isNegative ? -number : number;
585
dList.set(isNegative, number, getMinimumFractionDigits());
586
587
double roundedNumber = dList.getDouble();
588
int compactDataIndex = selectCompactPattern((long) roundedNumber);
589
if (compactDataIndex != -1) {
590
long divisor = (Long) divisors.get(compactDataIndex);
591
int iPart = getIntegerPart(number, divisor);
592
String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
593
String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
594
595
if (!prefix.isEmpty() || !suffix.isEmpty()) {
596
appendPrefix(result, prefix, delegate);
597
if (!placeHolderPatterns.get(compactDataIndex).get(iPart).isEmpty()) {
598
roundedNumber = roundedNumber / divisor;
599
decimalFormat.setDigitList(roundedNumber, isNegative, getMaximumFractionDigits());
600
decimalFormat.subformatNumber(result, delegate, isNegative,
601
false, getMaximumIntegerDigits(), getMinimumIntegerDigits(),
602
getMaximumFractionDigits(), getMinimumFractionDigits());
603
appendSuffix(result, suffix, delegate);
604
}
605
} else {
606
defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative);
607
}
608
} else {
609
defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative);
610
}
611
return result;
612
}
613
614
/**
615
* Formats a long to produce a string representing its compact form.
616
* @param number the long number to format
617
* @param result where the text is to be appended
618
* @param fieldPosition keeps track on the position of the field within
619
* the returned string. For example, to format
620
* a number {@code 123456789} in the
621
* {@link java.util.Locale#US US locale},
622
* if the given {@code fieldPosition} is
623
* {@link NumberFormat#INTEGER_FIELD}, the begin
624
* index and end index of {@code fieldPosition}
625
* will be set to 0 and 3, respectively for the
626
* output string {@code 123M}. Similarly, positions
627
* of the prefix and the suffix fields can be
628
* obtained using {@link NumberFormat.Field#PREFIX}
629
* and {@link NumberFormat.Field#SUFFIX} respectively.
630
* @return the {@code StringBuffer} passed in as {@code result}
631
* @throws NullPointerException if {@code result} or
632
* {@code fieldPosition} is {@code null}
633
* @throws ArithmeticException if rounding is needed with rounding
634
* mode being set to {@code RoundingMode.UNNECESSARY}
635
* @see FieldPosition
636
*/
637
@Override
638
public StringBuffer format(long number, StringBuffer result,
639
FieldPosition fieldPosition) {
640
641
fieldPosition.setBeginIndex(0);
642
fieldPosition.setEndIndex(0);
643
return format(number, result, fieldPosition.getFieldDelegate());
644
}
645
646
private StringBuffer format(long number, StringBuffer result, FieldDelegate delegate) {
647
boolean isNegative = (number < 0);
648
if (isNegative) {
649
number = -number;
650
}
651
652
if (number < 0) { // LONG_MIN
653
BigInteger bigIntegerValue = BigInteger.valueOf(number);
654
return format(bigIntegerValue, result, delegate, true);
655
}
656
657
int compactDataIndex = selectCompactPattern(number);
658
if (compactDataIndex != -1) {
659
long divisor = (Long) divisors.get(compactDataIndex);
660
int iPart = getIntegerPart(number, divisor);
661
String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
662
String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
663
if (!prefix.isEmpty() || !suffix.isEmpty()) {
664
appendPrefix(result, prefix, delegate);
665
if (!placeHolderPatterns.get(compactDataIndex).get(iPart).isEmpty()) {
666
if ((number % divisor == 0)) {
667
number = number / divisor;
668
decimalFormat.setDigitList(number, isNegative, 0);
669
decimalFormat.subformatNumber(result, delegate,
670
isNegative, true, getMaximumIntegerDigits(),
671
getMinimumIntegerDigits(), getMaximumFractionDigits(),
672
getMinimumFractionDigits());
673
} else {
674
// To avoid truncation of fractional part store
675
// the value in double and follow double path instead of
676
// long path
677
double dNumber = (double) number / divisor;
678
decimalFormat.setDigitList(dNumber, isNegative, getMaximumFractionDigits());
679
decimalFormat.subformatNumber(result, delegate,
680
isNegative, false, getMaximumIntegerDigits(),
681
getMinimumIntegerDigits(), getMaximumFractionDigits(),
682
getMinimumFractionDigits());
683
}
684
appendSuffix(result, suffix, delegate);
685
}
686
} else {
687
number = isNegative ? -number : number;
688
defaultDecimalFormat.format(number, result, delegate);
689
}
690
} else {
691
number = isNegative ? -number : number;
692
defaultDecimalFormat.format(number, result, delegate);
693
}
694
return result;
695
}
696
697
/**
698
* Formats a BigDecimal to produce a string representing its compact form.
699
* @param number the BigDecimal number to format
700
* @param result where the text is to be appended
701
* @param fieldPosition keeps track on the position of the field within
702
* the returned string. For example, to format
703
* a number {@code 1234567.89} in the
704
* {@link java.util.Locale#US US locale},
705
* if the given {@code fieldPosition} is
706
* {@link NumberFormat#INTEGER_FIELD}, the begin
707
* index and end index of {@code fieldPosition}
708
* will be set to 0 and 1, respectively for the
709
* output string {@code 1M}. Similarly, positions
710
* of the prefix and the suffix fields can be
711
* obtained using {@link NumberFormat.Field#PREFIX}
712
* and {@link NumberFormat.Field#SUFFIX} respectively.
713
* @return the {@code StringBuffer} passed in as {@code result}
714
* @throws ArithmeticException if rounding is needed with rounding
715
* mode being set to {@code RoundingMode.UNNECESSARY}
716
* @throws NullPointerException if any of the given parameter
717
* is {@code null}
718
* @see FieldPosition
719
*/
720
private StringBuffer format(BigDecimal number, StringBuffer result,
721
FieldPosition fieldPosition) {
722
723
Objects.requireNonNull(number);
724
fieldPosition.setBeginIndex(0);
725
fieldPosition.setEndIndex(0);
726
return format(number, result, fieldPosition.getFieldDelegate());
727
}
728
729
private StringBuffer format(BigDecimal number, StringBuffer result,
730
FieldDelegate delegate) {
731
732
boolean isNegative = number.signum() == -1;
733
if (isNegative) {
734
number = number.negate();
735
}
736
737
// Round the value with min fraction digits, the integer
738
// part of the rounded value is used for matching the compact
739
// number pattern
740
// For example, If roundingMode is HALF_UP with min fraction digits = 0,
741
// the number 999.6 should round up
742
// to 1000 and outputs 1K/thousand in "en_US" locale
743
number = number.setScale(getMinimumFractionDigits(), getRoundingMode());
744
745
int compactDataIndex;
746
if (number.toBigInteger().bitLength() < 64) {
747
long longNumber = number.toBigInteger().longValue();
748
compactDataIndex = selectCompactPattern(longNumber);
749
} else {
750
compactDataIndex = selectCompactPattern(number.toBigInteger());
751
}
752
753
if (compactDataIndex != -1) {
754
Number divisor = divisors.get(compactDataIndex);
755
int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue());
756
String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
757
String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
758
if (!prefix.isEmpty() || !suffix.isEmpty()) {
759
appendPrefix(result, prefix, delegate);
760
if (!placeHolderPatterns.get(compactDataIndex).get(iPart).isEmpty()) {
761
number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode());
762
decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits());
763
decimalFormat.subformatNumber(result, delegate, isNegative,
764
false, getMaximumIntegerDigits(), getMinimumIntegerDigits(),
765
getMaximumFractionDigits(), getMinimumFractionDigits());
766
appendSuffix(result, suffix, delegate);
767
}
768
} else {
769
number = isNegative ? number.negate() : number;
770
defaultDecimalFormat.format(number, result, delegate);
771
}
772
} else {
773
number = isNegative ? number.negate() : number;
774
defaultDecimalFormat.format(number, result, delegate);
775
}
776
return result;
777
}
778
779
/**
780
* Formats a BigInteger to produce a string representing its compact form.
781
* @param number the BigInteger number to format
782
* @param result where the text is to be appended
783
* @param fieldPosition keeps track on the position of the field within
784
* the returned string. For example, to format
785
* a number {@code 123456789} in the
786
* {@link java.util.Locale#US US locale},
787
* if the given {@code fieldPosition} is
788
* {@link NumberFormat#INTEGER_FIELD}, the begin index
789
* and end index of {@code fieldPosition} will be set
790
* to 0 and 3, respectively for the output string
791
* {@code 123M}. Similarly, positions of the
792
* prefix and the suffix fields can be obtained
793
* using {@link NumberFormat.Field#PREFIX} and
794
* {@link NumberFormat.Field#SUFFIX} respectively.
795
* @return the {@code StringBuffer} passed in as {@code result}
796
* @throws ArithmeticException if rounding is needed with rounding
797
* mode being set to {@code RoundingMode.UNNECESSARY}
798
* @throws NullPointerException if any of the given parameter
799
* is {@code null}
800
* @see FieldPosition
801
*/
802
private StringBuffer format(BigInteger number, StringBuffer result,
803
FieldPosition fieldPosition) {
804
805
Objects.requireNonNull(number);
806
fieldPosition.setBeginIndex(0);
807
fieldPosition.setEndIndex(0);
808
return format(number, result, fieldPosition.getFieldDelegate(), false);
809
}
810
811
private StringBuffer format(BigInteger number, StringBuffer result,
812
FieldDelegate delegate, boolean formatLong) {
813
814
boolean isNegative = number.signum() == -1;
815
if (isNegative) {
816
number = number.negate();
817
}
818
819
int compactDataIndex = selectCompactPattern(number);
820
if (compactDataIndex != -1) {
821
Number divisor = divisors.get(compactDataIndex);
822
int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue());
823
String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
824
String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
825
if (!prefix.isEmpty() || !suffix.isEmpty()) {
826
appendPrefix(result, prefix, delegate);
827
if (!placeHolderPatterns.get(compactDataIndex).get(iPart).isEmpty()) {
828
if (number.mod(new BigInteger(divisor.toString()))
829
.compareTo(BigInteger.ZERO) == 0) {
830
number = number.divide(new BigInteger(divisor.toString()));
831
832
decimalFormat.setDigitList(number, isNegative, 0);
833
decimalFormat.subformatNumber(result, delegate,
834
isNegative, true, getMaximumIntegerDigits(),
835
getMinimumIntegerDigits(), getMaximumFractionDigits(),
836
getMinimumFractionDigits());
837
} else {
838
// To avoid truncation of fractional part store the value in
839
// BigDecimal and follow BigDecimal path instead of
840
// BigInteger path
841
BigDecimal nDecimal = new BigDecimal(number)
842
.divide(new BigDecimal(divisor.toString()), getRoundingMode());
843
decimalFormat.setDigitList(nDecimal, isNegative, getMaximumFractionDigits());
844
decimalFormat.subformatNumber(result, delegate,
845
isNegative, false, getMaximumIntegerDigits(),
846
getMinimumIntegerDigits(), getMaximumFractionDigits(),
847
getMinimumFractionDigits());
848
}
849
appendSuffix(result, suffix, delegate);
850
}
851
} else {
852
number = isNegative ? number.negate() : number;
853
defaultDecimalFormat.format(number, result, delegate, formatLong);
854
}
855
} else {
856
number = isNegative ? number.negate() : number;
857
defaultDecimalFormat.format(number, result, delegate, formatLong);
858
}
859
return result;
860
}
861
862
/**
863
* Obtain the designated affix from the appropriate list of affixes,
864
* based on the given arguments.
865
*/
866
private String getAffix(boolean isExpanded, boolean isPrefix, boolean isNegative, int compactDataIndex, int iPart) {
867
return (isExpanded ? (isPrefix ? (isNegative ? negativePrefixes : positivePrefixes) :
868
(isNegative ? negativeSuffixes : positiveSuffixes)) :
869
(isPrefix ? (isNegative ? negativePrefixPatterns : positivePrefixPatterns) :
870
(isNegative ? negativeSuffixPatterns : positiveSuffixPatterns)))
871
.get(compactDataIndex).get(iPart);
872
}
873
874
/**
875
* Appends the {@code prefix} to the {@code result} and also set the
876
* {@code NumberFormat.Field.SIGN} and {@code NumberFormat.Field.PREFIX}
877
* field positions.
878
* @param result the resulting string, where the pefix is to be appended
879
* @param prefix prefix to append
880
* @param delegate notified of the locations of
881
* {@code NumberFormat.Field.SIGN} and
882
* {@code NumberFormat.Field.PREFIX} fields
883
*/
884
private void appendPrefix(StringBuffer result, String prefix,
885
FieldDelegate delegate) {
886
append(result, expandAffix(prefix), delegate,
887
getFieldPositions(prefix, NumberFormat.Field.PREFIX));
888
}
889
890
/**
891
* Appends {@code suffix} to the {@code result} and also set the
892
* {@code NumberFormat.Field.SIGN} and {@code NumberFormat.Field.SUFFIX}
893
* field positions.
894
* @param result the resulting string, where the suffix is to be appended
895
* @param suffix suffix to append
896
* @param delegate notified of the locations of
897
* {@code NumberFormat.Field.SIGN} and
898
* {@code NumberFormat.Field.SUFFIX} fields
899
*/
900
private void appendSuffix(StringBuffer result, String suffix,
901
FieldDelegate delegate) {
902
append(result, expandAffix(suffix), delegate,
903
getFieldPositions(suffix, NumberFormat.Field.SUFFIX));
904
}
905
906
/**
907
* Appends the {@code string} to the {@code result}.
908
* {@code delegate} is notified of SIGN, PREFIX and/or SUFFIX
909
* field positions.
910
* @param result the resulting string, where the text is to be appended
911
* @param string the text to append
912
* @param delegate notified of the locations of sub fields
913
* @param positions a list of {@code FieldPostion} in the given
914
* string
915
*/
916
private void append(StringBuffer result, String string,
917
FieldDelegate delegate, List<FieldPosition> positions) {
918
if (!string.isEmpty()) {
919
int start = result.length();
920
result.append(string);
921
for (FieldPosition fp : positions) {
922
Format.Field attribute = fp.getFieldAttribute();
923
delegate.formatted(attribute, attribute,
924
start + fp.getBeginIndex(),
925
start + fp.getEndIndex(), result);
926
}
927
}
928
}
929
930
/**
931
* Expands an affix {@code pattern} into a string of literals.
932
* All characters in the pattern are literals unless prefixed by QUOTE.
933
* The character prefixed by QUOTE is replaced with its respective
934
* localized literal.
935
* @param pattern a compact number pattern affix
936
* @return an expanded affix
937
*/
938
private String expandAffix(String pattern) {
939
// Return if no quoted character exists
940
if (pattern.indexOf(QUOTE) < 0) {
941
return pattern;
942
}
943
StringBuilder sb = new StringBuilder();
944
for (int index = 0; index < pattern.length();) {
945
char ch = pattern.charAt(index++);
946
if (ch == QUOTE) {
947
ch = pattern.charAt(index++);
948
if (ch == MINUS_SIGN) {
949
sb.append(symbols.getMinusSignText());
950
continue;
951
}
952
}
953
sb.append(ch);
954
}
955
return sb.toString();
956
}
957
958
/**
959
* Returns a list of {@code FieldPostion} in the given {@code pattern}.
960
* @param pattern the pattern to be parsed for {@code FieldPosition}
961
* @param field whether a PREFIX or SUFFIX field
962
* @return a list of {@code FieldPostion}
963
*/
964
private List<FieldPosition> getFieldPositions(String pattern, Field field) {
965
List<FieldPosition> positions = new ArrayList<>();
966
StringBuilder affix = new StringBuilder();
967
int stringIndex = 0;
968
for (int index = 0; index < pattern.length();) {
969
char ch = pattern.charAt(index++);
970
if (ch == QUOTE) {
971
ch = pattern.charAt(index++);
972
if (ch == MINUS_SIGN) {
973
String minusText = symbols.getMinusSignText();
974
FieldPosition fp = new FieldPosition(NumberFormat.Field.SIGN);
975
fp.setBeginIndex(stringIndex);
976
fp.setEndIndex(stringIndex + minusText.length());
977
positions.add(fp);
978
stringIndex += minusText.length();
979
affix.append(minusText);
980
continue;
981
}
982
}
983
stringIndex++;
984
affix.append(ch);
985
}
986
if (affix.length() != 0) {
987
FieldPosition fp = new FieldPosition(field);
988
fp.setBeginIndex(0);
989
fp.setEndIndex(affix.length());
990
positions.add(fp);
991
}
992
return positions;
993
}
994
995
/**
996
* Select the index of the matched compact number pattern for
997
* the given {@code long} {@code number}.
998
*
999
* @param number number to be formatted
1000
* @return index of matched compact pattern;
1001
* -1 if no compact patterns specified
1002
*/
1003
private int selectCompactPattern(long number) {
1004
1005
if (compactPatterns.length == 0) {
1006
return -1;
1007
}
1008
1009
// Minimum index can be "0", max index can be "size - 1"
1010
int dataIndex = number <= 1 ? 0 : (int) Math.log10(number);
1011
dataIndex = Math.min(dataIndex, compactPatterns.length - 1);
1012
return dataIndex;
1013
}
1014
1015
/**
1016
* Select the index of the matched compact number
1017
* pattern for the given {@code BigInteger} {@code number}.
1018
*
1019
* @param number number to be formatted
1020
* @return index of matched compact pattern;
1021
* -1 if no compact patterns specified
1022
*/
1023
private int selectCompactPattern(BigInteger number) {
1024
1025
int matchedIndex = -1;
1026
if (compactPatterns.length == 0) {
1027
return matchedIndex;
1028
}
1029
1030
BigInteger currentValue = BigInteger.ONE;
1031
1032
// For formatting a number, the greatest type less than
1033
// or equal to number is used
1034
for (int index = 0; index < compactPatterns.length; index++) {
1035
if (number.compareTo(currentValue) > 0) {
1036
// Input number is greater than current type; try matching with
1037
// the next
1038
matchedIndex = index;
1039
currentValue = currentValue.multiply(BigInteger.valueOf(RANGE_MULTIPLIER));
1040
continue;
1041
}
1042
if (number.compareTo(currentValue) < 0) {
1043
// Current type is greater than the input number;
1044
// take the previous pattern
1045
break;
1046
} else {
1047
// Equal
1048
matchedIndex = index;
1049
break;
1050
}
1051
}
1052
return matchedIndex;
1053
}
1054
1055
/**
1056
* Formats an Object producing an {@code AttributedCharacterIterator}.
1057
* The returned {@code AttributedCharacterIterator} can be used
1058
* to build the resulting string, as well as to determine information
1059
* about the resulting string.
1060
* <p>
1061
* Each attribute key of the {@code AttributedCharacterIterator} will
1062
* be of type {@code NumberFormat.Field}, with the attribute value
1063
* being the same as the attribute key. The prefix and the suffix
1064
* parts of the returned iterator (if present) are represented by
1065
* the attributes {@link NumberFormat.Field#PREFIX} and
1066
* {@link NumberFormat.Field#SUFFIX} respectively.
1067
*
1068
*
1069
* @throws NullPointerException if obj is null
1070
* @throws IllegalArgumentException when the Format cannot format the
1071
* given object
1072
* @throws ArithmeticException if rounding is needed with rounding
1073
* mode being set to {@code RoundingMode.UNNECESSARY}
1074
* @param obj The object to format
1075
* @return an {@code AttributedCharacterIterator} describing the
1076
* formatted value
1077
*/
1078
@Override
1079
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
1080
CharacterIteratorFieldDelegate delegate
1081
= new CharacterIteratorFieldDelegate();
1082
StringBuffer sb = new StringBuffer();
1083
1084
if (obj instanceof Double || obj instanceof Float) {
1085
format(((Number) obj).doubleValue(), sb, delegate);
1086
} else if (obj instanceof Long || obj instanceof Integer
1087
|| obj instanceof Short || obj instanceof Byte
1088
|| obj instanceof AtomicInteger || obj instanceof AtomicLong) {
1089
format(((Number) obj).longValue(), sb, delegate);
1090
} else if (obj instanceof BigDecimal) {
1091
format((BigDecimal) obj, sb, delegate);
1092
} else if (obj instanceof BigInteger) {
1093
format((BigInteger) obj, sb, delegate, false);
1094
} else if (obj == null) {
1095
throw new NullPointerException(
1096
"formatToCharacterIterator must be passed non-null object");
1097
} else {
1098
throw new IllegalArgumentException(
1099
"Cannot format given Object as a Number");
1100
}
1101
return delegate.getIterator(sb.toString());
1102
}
1103
1104
/**
1105
* Computes the divisor using minimum integer digits and
1106
* matched pattern index.
1107
* @param minIntDigits string of 0s in compact pattern
1108
* @param patternIndex index of matched compact pattern
1109
* @return divisor value for the number matching the compact
1110
* pattern at given {@code patternIndex}
1111
*/
1112
private Number computeDivisor(String minIntDigits, int patternIndex) {
1113
int count = minIntDigits.length();
1114
Number matchedValue;
1115
// The divisor value can go above long range, if the compact patterns
1116
// goes above index 18, divisor may need to be stored as BigInteger,
1117
// since long can't store numbers >= 10^19,
1118
if (patternIndex < 19) {
1119
matchedValue = (long) Math.pow(RANGE_MULTIPLIER, patternIndex);
1120
} else {
1121
matchedValue = BigInteger.valueOf(RANGE_MULTIPLIER).pow(patternIndex);
1122
}
1123
Number divisor = matchedValue;
1124
if (count > 0) {
1125
if (matchedValue instanceof BigInteger bigValue) {
1126
if (bigValue.compareTo(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count - 1))) < 0) {
1127
throw new IllegalArgumentException("Invalid Pattern"
1128
+ " [" + compactPatterns[patternIndex]
1129
+ "]: min integer digits specified exceeds the limit"
1130
+ " for the index " + patternIndex);
1131
}
1132
divisor = bigValue.divide(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count - 1)));
1133
} else {
1134
long longValue = (long) matchedValue;
1135
if (longValue < (long) Math.pow(RANGE_MULTIPLIER, count - 1)) {
1136
throw new IllegalArgumentException("Invalid Pattern"
1137
+ " [" + compactPatterns[patternIndex]
1138
+ "]: min integer digits specified exceeds the limit"
1139
+ " for the index " + patternIndex);
1140
}
1141
divisor = longValue / (long) Math.pow(RANGE_MULTIPLIER, count - 1);
1142
}
1143
}
1144
return divisor;
1145
}
1146
1147
/**
1148
* Process the series of compact patterns to compute the
1149
* series of prefixes, suffixes and their respective divisor
1150
* value.
1151
*
1152
*/
1153
private static final Pattern PLURALS =
1154
Pattern.compile("^\\{(?<plurals>.*)}$");
1155
private static final Pattern COUNT_PATTERN =
1156
Pattern.compile("(zero|one|two|few|many|other):((' '|[^ ])+)[ ]*");
1157
private void processCompactPatterns() {
1158
int size = compactPatterns.length;
1159
positivePrefixPatterns = new ArrayList<>(size);
1160
negativePrefixPatterns = new ArrayList<>(size);
1161
positiveSuffixPatterns = new ArrayList<>(size);
1162
negativeSuffixPatterns = new ArrayList<>(size);
1163
divisors = new ArrayList<>(size);
1164
placeHolderPatterns = new ArrayList<>(size);
1165
1166
for (int index = 0; index < size; index++) {
1167
String text = compactPatterns[index];
1168
positivePrefixPatterns.add(new Patterns());
1169
negativePrefixPatterns.add(new Patterns());
1170
positiveSuffixPatterns.add(new Patterns());
1171
negativeSuffixPatterns.add(new Patterns());
1172
placeHolderPatterns.add(new Patterns());
1173
1174
// check if it is the old style
1175
Matcher m = text != null ? PLURALS.matcher(text) : null;
1176
if (m != null && m.matches()) {
1177
final int idx = index;
1178
String plurals = m.group("plurals");
1179
COUNT_PATTERN.matcher(plurals).results()
1180
.forEach(mr -> applyPattern(mr.group(1), mr.group(2), idx));
1181
} else {
1182
applyPattern("other", text, index);
1183
}
1184
}
1185
1186
rulesMap = buildPluralRulesMap();
1187
}
1188
1189
/**
1190
* Build the plural rules map.
1191
*
1192
* @throws IllegalArgumentException if the {@code pluralRules} has invalid syntax,
1193
* or its length exceeds 2,048 chars
1194
*/
1195
private Map<String, String> buildPluralRulesMap() {
1196
// length limitation check. 2K for now.
1197
if (pluralRules.length() > 2_048) {
1198
throw new IllegalArgumentException("plural rules is too long (> 2,048)");
1199
}
1200
1201
try {
1202
return Arrays.stream(pluralRules.split(";"))
1203
.map(this::validateRule)
1204
.collect(Collectors.toMap(
1205
r -> r.replaceFirst(":.*", ""),
1206
r -> r.replaceFirst("[^:]+:", "")
1207
));
1208
} catch (IllegalStateException ise) {
1209
throw new IllegalArgumentException(ise);
1210
}
1211
}
1212
1213
// Patterns for plurals syntax validation
1214
private static final String EXPR = "([niftvwe])\\s*(([/%])\\s*(\\d+))*";
1215
private static final String RELATION = "(!?=)";
1216
private static final String VALUE_RANGE = "((\\d+)\\.\\.(\\d+)|\\d+)";
1217
private static final String CONDITION = EXPR + "\\s*" +
1218
RELATION + "\\s*" +
1219
VALUE_RANGE + "\\s*" +
1220
"(,\\s*" + VALUE_RANGE + ")*";
1221
private static final Pattern PLURALRULES_PATTERN =
1222
Pattern.compile("(zero|one|two|few|many):\\s*" +
1223
CONDITION +
1224
"(\\s*(and|or)\\s*" + CONDITION + ")*");
1225
1226
/**
1227
* Validates a plural rule.
1228
* @param rule rule to validate
1229
* @throws IllegalArgumentException if the {@code rule} has invalid syntax
1230
* @return the input rule (trimmed)
1231
*/
1232
private String validateRule(String rule) {
1233
rule = rule.trim();
1234
if (!rule.isEmpty() && !rule.equals("other:")) {
1235
Matcher validator = PLURALRULES_PATTERN.matcher(rule);
1236
if (!validator.matches()) {
1237
throw new IllegalArgumentException("Invalid plural rules syntax: " + rule);
1238
}
1239
}
1240
1241
return rule;
1242
}
1243
1244
/**
1245
* Process a compact pattern at a specific {@code index}
1246
* @param pattern the compact pattern to be processed
1247
* @param index index in the array of compact patterns
1248
*
1249
*/
1250
private void applyPattern(String count, String pattern, int index) {
1251
1252
if (pattern == null) {
1253
throw new IllegalArgumentException("A null compact pattern" +
1254
" encountered at index: " + index);
1255
}
1256
1257
int start = 0;
1258
boolean gotNegative = false;
1259
1260
String positivePrefix = "";
1261
String positiveSuffix = "";
1262
String negativePrefix = "";
1263
String negativeSuffix = "";
1264
String zeros = "";
1265
for (int j = 1; j >= 0 && start < pattern.length(); --j) {
1266
1267
StringBuffer prefix = new StringBuffer();
1268
StringBuffer suffix = new StringBuffer();
1269
boolean inQuote = false;
1270
// The phase ranges from 0 to 2. Phase 0 is the prefix. Phase 1 is
1271
// the section of the pattern with digits. Phase 2 is the suffix.
1272
// The separation of the characters into phases is
1273
// strictly enforced; if phase 1 characters are to appear in the
1274
// suffix, for example, they must be quoted.
1275
int phase = 0;
1276
1277
// The affix is either the prefix or the suffix.
1278
StringBuffer affix = prefix;
1279
1280
for (int pos = start; pos < pattern.length(); ++pos) {
1281
char ch = pattern.charAt(pos);
1282
switch (phase) {
1283
case 0:
1284
case 2:
1285
// Process the prefix / suffix characters
1286
if (inQuote) {
1287
// A quote within quotes indicates either the closing
1288
// quote or two quotes, which is a quote literal. That
1289
// is, we have the second quote in 'do' or 'don''t'.
1290
if (ch == QUOTE) {
1291
if ((pos + 1) < pattern.length()
1292
&& pattern.charAt(pos + 1) == QUOTE) {
1293
++pos;
1294
affix.append("''"); // 'don''t'
1295
} else {
1296
inQuote = false; // 'do'
1297
}
1298
continue;
1299
}
1300
} else {
1301
// Process unquoted characters seen in prefix or suffix
1302
// phase.
1303
switch (ch) {
1304
case ZERO_DIGIT:
1305
phase = 1;
1306
--pos; // Reprocess this character
1307
continue;
1308
case QUOTE:
1309
// A quote outside quotes indicates either the
1310
// opening quote or two quotes, which is a quote
1311
// literal. That is, we have the first quote in 'do'
1312
// or o''clock.
1313
if ((pos + 1) < pattern.length()
1314
&& pattern.charAt(pos + 1) == QUOTE) {
1315
++pos;
1316
affix.append("''"); // o''clock
1317
} else {
1318
inQuote = true; // 'do'
1319
}
1320
continue;
1321
case SEPARATOR:
1322
// Don't allow separators before we see digit
1323
// characters of phase 1, and don't allow separators
1324
// in the second pattern (j == 0).
1325
if (phase == 0 || j == 0) {
1326
throw new IllegalArgumentException(
1327
"Unquoted special character '"
1328
+ ch + "' in pattern \"" + pattern + "\"");
1329
}
1330
start = pos + 1;
1331
pos = pattern.length();
1332
continue;
1333
case MINUS_SIGN:
1334
affix.append("'-");
1335
continue;
1336
case DECIMAL_SEPARATOR:
1337
case GROUPING_SEPARATOR:
1338
case DIGIT:
1339
case PERCENT:
1340
case PER_MILLE:
1341
case CURRENCY_SIGN:
1342
throw new IllegalArgumentException(
1343
"Unquoted special character '" + ch
1344
+ "' in pattern \"" + pattern + "\"");
1345
default:
1346
break;
1347
}
1348
}
1349
// Note that if we are within quotes, or if this is an
1350
// unquoted, non-special character, then we usually fall
1351
// through to here.
1352
affix.append(ch);
1353
break;
1354
1355
case 1:
1356
// The negative subpattern (j = 0) serves only to specify the
1357
// negative prefix and suffix, so all the phase 1 characters,
1358
// for example, digits, zeroDigit, groupingSeparator,
1359
// decimalSeparator, exponent are ignored
1360
if (j == 0) {
1361
while (pos < pattern.length()) {
1362
char negPatternChar = pattern.charAt(pos);
1363
if (negPatternChar == ZERO_DIGIT) {
1364
++pos;
1365
} else {
1366
// Not a phase 1 character, consider it as
1367
// suffix and parse it in phase 2
1368
--pos; //process it again in outer loop
1369
phase = 2;
1370
affix = suffix;
1371
break;
1372
}
1373
}
1374
continue;
1375
}
1376
// Consider only '0' as valid pattern char which can appear
1377
// in number part, rest can be either suffix or prefix
1378
if (ch == ZERO_DIGIT) {
1379
zeros = zeros + "0";
1380
} else {
1381
phase = 2;
1382
affix = suffix;
1383
--pos;
1384
}
1385
break;
1386
}
1387
}
1388
1389
if (inQuote) {
1390
throw new IllegalArgumentException("Invalid single quote"
1391
+ " in pattern \"" + pattern + "\"");
1392
}
1393
1394
if (j == 1) {
1395
positivePrefix = prefix.toString();
1396
positiveSuffix = suffix.toString();
1397
negativePrefix = positivePrefix;
1398
negativeSuffix = positiveSuffix;
1399
} else {
1400
negativePrefix = prefix.toString();
1401
negativeSuffix = suffix.toString();
1402
gotNegative = true;
1403
}
1404
1405
// If there is no negative pattern, or if the negative pattern is
1406
// identical to the positive pattern, then prepend the minus sign to
1407
// the positive pattern to form the negative pattern.
1408
if (!gotNegative
1409
|| (negativePrefix.equals(positivePrefix)
1410
&& negativeSuffix.equals(positiveSuffix))) {
1411
negativeSuffix = positiveSuffix;
1412
negativePrefix = "'-" + positivePrefix;
1413
}
1414
}
1415
1416
// Only if positive affix exists; else put empty strings
1417
if (!positivePrefix.isEmpty() || !positiveSuffix.isEmpty()) {
1418
positivePrefixPatterns.get(index).put(count, positivePrefix);
1419
negativePrefixPatterns.get(index).put(count, negativePrefix);
1420
positiveSuffixPatterns.get(index).put(count, positiveSuffix);
1421
negativeSuffixPatterns.get(index).put(count, negativeSuffix);
1422
placeHolderPatterns.get(index).put(count, zeros);
1423
if (divisors.size() <= index) {
1424
divisors.add(computeDivisor(zeros, index));
1425
}
1426
} else {
1427
positivePrefixPatterns.get(index).put(count, "");
1428
negativePrefixPatterns.get(index).put(count, "");
1429
positiveSuffixPatterns.get(index).put(count, "");
1430
negativeSuffixPatterns.get(index).put(count, "");
1431
placeHolderPatterns.get(index).put(count, "");
1432
if (divisors.size() <= index) {
1433
divisors.add(1L);
1434
}
1435
}
1436
}
1437
1438
private final transient DigitList digitList = new DigitList();
1439
private static final int STATUS_INFINITE = 0;
1440
private static final int STATUS_POSITIVE = 1;
1441
private static final int STATUS_LENGTH = 2;
1442
1443
private static final char ZERO_DIGIT = '0';
1444
private static final char DIGIT = '#';
1445
private static final char DECIMAL_SEPARATOR = '.';
1446
private static final char GROUPING_SEPARATOR = ',';
1447
private static final char MINUS_SIGN = '-';
1448
private static final char PERCENT = '%';
1449
private static final char PER_MILLE = '\u2030';
1450
private static final char SEPARATOR = ';';
1451
private static final char CURRENCY_SIGN = '\u00A4';
1452
private static final char QUOTE = '\'';
1453
1454
// Expanded form of positive/negative prefix/suffix,
1455
// the expanded form contains special characters in
1456
// its localized form, which are used for matching
1457
// while parsing a string to number
1458
private transient List<Patterns> positivePrefixes;
1459
private transient List<Patterns> negativePrefixes;
1460
private transient List<Patterns> positiveSuffixes;
1461
private transient List<Patterns> negativeSuffixes;
1462
1463
private void expandAffixPatterns() {
1464
positivePrefixes = new ArrayList<>(compactPatterns.length);
1465
negativePrefixes = new ArrayList<>(compactPatterns.length);
1466
positiveSuffixes = new ArrayList<>(compactPatterns.length);
1467
negativeSuffixes = new ArrayList<>(compactPatterns.length);
1468
for (int index = 0; index < compactPatterns.length; index++) {
1469
positivePrefixes.add(positivePrefixPatterns.get(index).expandAffix());
1470
negativePrefixes.add(negativePrefixPatterns.get(index).expandAffix());
1471
positiveSuffixes.add(positiveSuffixPatterns.get(index).expandAffix());
1472
negativeSuffixes.add(negativeSuffixPatterns.get(index).expandAffix());
1473
}
1474
}
1475
1476
/**
1477
* Parses a compact number from a string to produce a {@code Number}.
1478
* <p>
1479
* The method attempts to parse text starting at the index given by
1480
* {@code pos}.
1481
* If parsing succeeds, then the index of {@code pos} is updated
1482
* to the index after the last character used (parsing does not necessarily
1483
* use all characters up to the end of the string), and the parsed
1484
* number is returned. The updated {@code pos} can be used to
1485
* indicate the starting point for the next call to this method.
1486
* If an error occurs, then the index of {@code pos} is not
1487
* changed, the error index of {@code pos} is set to the index of
1488
* the character where the error occurred, and {@code null} is returned.
1489
* <p>
1490
* The value is the numeric part in the given text multiplied
1491
* by the numeric equivalent of the affix attached
1492
* (For example, "K" = 1000 in {@link java.util.Locale#US US locale}).
1493
* The subclass returned depends on the value of
1494
* {@link #isParseBigDecimal}.
1495
* <ul>
1496
* <li>If {@link #isParseBigDecimal()} is false (the default),
1497
* most integer values are returned as {@code Long}
1498
* objects, no matter how they are written: {@code "17K"} and
1499
* {@code "17.000K"} both parse to {@code Long.valueOf(17000)}.
1500
* If the value cannot fit into {@code Long}, then the result is
1501
* returned as {@code Double}. This includes values with a
1502
* fractional part, infinite values, {@code NaN},
1503
* and the value -0.0.
1504
* <p>
1505
* Callers may use the {@code Number} methods {@code doubleValue},
1506
* {@code longValue}, etc., to obtain the type they want.
1507
*
1508
* <li>If {@link #isParseBigDecimal()} is true, values are returned
1509
* as {@code BigDecimal} objects. The special cases negative
1510
* and positive infinity and NaN are returned as {@code Double}
1511
* instances holding the values of the corresponding
1512
* {@code Double} constants.
1513
* </ul>
1514
* <p>
1515
* {@code CompactNumberFormat} parses all Unicode characters that represent
1516
* decimal digits, as defined by {@code Character.digit()}. In
1517
* addition, {@code CompactNumberFormat} also recognizes as digits the ten
1518
* consecutive characters starting with the localized zero digit defined in
1519
* the {@code DecimalFormatSymbols} object.
1520
* <p>
1521
* {@code CompactNumberFormat} parse does not allow parsing scientific
1522
* notations. For example, parsing a string {@code "1.05E4K"} in
1523
* {@link java.util.Locale#US US locale} breaks at character 'E'
1524
* and returns 1.05.
1525
*
1526
* @param text the string to be parsed
1527
* @param pos a {@code ParsePosition} object with index and error
1528
* index information as described above
1529
* @return the parsed value, or {@code null} if the parse fails
1530
* @throws NullPointerException if {@code text} or
1531
* {@code pos} is null
1532
*
1533
*/
1534
@Override
1535
public Number parse(String text, ParsePosition pos) {
1536
1537
Objects.requireNonNull(text);
1538
Objects.requireNonNull(pos);
1539
1540
// Lazily expanding the affix patterns, on the first parse
1541
// call on this instance
1542
// If not initialized, expand and load all affixes
1543
if (positivePrefixes == null) {
1544
expandAffixPatterns();
1545
}
1546
1547
// The compact number multiplier for parsed string.
1548
// Its value is set on parsing prefix and suffix. For example,
1549
// in the {@link java.util.Locale#US US locale} parsing {@code "1K"}
1550
// sets its value to 1000, as K (thousand) is abbreviated form of 1000.
1551
Number cnfMultiplier = 1L;
1552
1553
// Special case NaN
1554
if (text.regionMatches(pos.index, symbols.getNaN(),
1555
0, symbols.getNaN().length())) {
1556
pos.index = pos.index + symbols.getNaN().length();
1557
return Double.NaN;
1558
}
1559
1560
int position = pos.index;
1561
int oldStart = pos.index;
1562
boolean gotPositive = false;
1563
boolean gotNegative = false;
1564
int matchedPosIndex = -1;
1565
int matchedNegIndex = -1;
1566
String matchedPosPrefix = "";
1567
String matchedNegPrefix = "";
1568
String defaultPosPrefix = defaultDecimalFormat.getPositivePrefix();
1569
String defaultNegPrefix = defaultDecimalFormat.getNegativePrefix();
1570
double num = parseNumberPart(text, position);
1571
1572
// Prefix matching
1573
for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) {
1574
String positivePrefix = getAffix(true, true, false, compactIndex, (int)num);
1575
String negativePrefix = getAffix(true, true, true, compactIndex, (int)num);
1576
1577
// Do not break if a match occur; there is a possibility that the
1578
// subsequent affixes may match the longer subsequence in the given
1579
// string.
1580
// For example, matching "Mdx 3" with "M", "Md" as prefix should
1581
// match with "Md"
1582
boolean match = matchAffix(text, position, positivePrefix,
1583
defaultPosPrefix, matchedPosPrefix);
1584
if (match) {
1585
matchedPosIndex = compactIndex;
1586
matchedPosPrefix = positivePrefix;
1587
gotPositive = true;
1588
}
1589
1590
match = matchAffix(text, position, negativePrefix,
1591
defaultNegPrefix, matchedNegPrefix);
1592
if (match) {
1593
matchedNegIndex = compactIndex;
1594
matchedNegPrefix = negativePrefix;
1595
gotNegative = true;
1596
}
1597
}
1598
1599
// Given text does not match the non empty valid compact prefixes
1600
// check with the default prefixes
1601
if (!gotPositive && !gotNegative) {
1602
if (text.regionMatches(pos.index, defaultPosPrefix, 0,
1603
defaultPosPrefix.length())) {
1604
// Matches the default positive prefix
1605
matchedPosPrefix = defaultPosPrefix;
1606
gotPositive = true;
1607
}
1608
if (text.regionMatches(pos.index, defaultNegPrefix, 0,
1609
defaultNegPrefix.length())) {
1610
// Matches the default negative prefix
1611
matchedNegPrefix = defaultNegPrefix;
1612
gotNegative = true;
1613
}
1614
}
1615
1616
// If both match, take the longest one
1617
if (gotPositive && gotNegative) {
1618
if (matchedPosPrefix.length() > matchedNegPrefix.length()) {
1619
gotNegative = false;
1620
} else if (matchedPosPrefix.length() < matchedNegPrefix.length()) {
1621
gotPositive = false;
1622
}
1623
}
1624
1625
// Update the position and take compact multiplier
1626
// only if it matches the compact prefix, not the default
1627
// prefix; else multiplier should be 1
1628
// If there's no number part, no need to go further, just
1629
// return the multiplier.
1630
if (gotPositive || gotNegative) {
1631
position += gotPositive ? matchedPosPrefix.length() : matchedNegPrefix.length();
1632
int matchedIndex = gotPositive ? matchedPosIndex : matchedNegIndex;
1633
if (matchedIndex != -1) {
1634
cnfMultiplier = divisors.get(matchedIndex);
1635
if (placeHolderPatterns.get(matchedIndex).get(num).isEmpty()) {
1636
pos.index = position;
1637
return cnfMultiplier;
1638
}
1639
}
1640
}
1641
1642
digitList.setRoundingMode(getRoundingMode());
1643
boolean[] status = new boolean[STATUS_LENGTH];
1644
1645
// Call DecimalFormat.subparseNumber() method to parse the
1646
// number part of the input text
1647
position = decimalFormat.subparseNumber(text, position,
1648
digitList, false, false, status);
1649
1650
if (position == -1) {
1651
// Unable to parse the number successfully
1652
pos.index = oldStart;
1653
pos.errorIndex = oldStart;
1654
return null;
1655
}
1656
1657
// If parse integer only is true and the parsing is broken at
1658
// decimal point, then pass/ignore all digits and move pointer
1659
// at the start of suffix, to process the suffix part
1660
if (isParseIntegerOnly()
1661
&& text.charAt(position) == symbols.getDecimalSeparator()) {
1662
position++; // Pass decimal character
1663
for (; position < text.length(); ++position) {
1664
char ch = text.charAt(position);
1665
int digit = ch - symbols.getZeroDigit();
1666
if (digit < 0 || digit > 9) {
1667
digit = Character.digit(ch, 10);
1668
// Parse all digit characters
1669
if (!(digit >= 0 && digit <= 9)) {
1670
break;
1671
}
1672
}
1673
}
1674
}
1675
1676
// Number parsed successfully; match prefix and
1677
// suffix to obtain multiplier
1678
pos.index = position;
1679
Number multiplier = computeParseMultiplier(text, pos,
1680
gotPositive ? matchedPosPrefix : matchedNegPrefix,
1681
status, gotPositive, gotNegative, num);
1682
1683
if (multiplier.longValue() == -1L) {
1684
return null;
1685
} else if (multiplier.longValue() != 1L) {
1686
cnfMultiplier = multiplier;
1687
}
1688
1689
// Special case INFINITY
1690
if (status[STATUS_INFINITE]) {
1691
if (status[STATUS_POSITIVE]) {
1692
return Double.POSITIVE_INFINITY;
1693
} else {
1694
return Double.NEGATIVE_INFINITY;
1695
}
1696
}
1697
1698
if (isParseBigDecimal()) {
1699
BigDecimal bigDecimalResult = digitList.getBigDecimal();
1700
1701
if (cnfMultiplier.longValue() != 1) {
1702
bigDecimalResult = bigDecimalResult
1703
.multiply(new BigDecimal(cnfMultiplier.toString()));
1704
}
1705
if (!status[STATUS_POSITIVE]) {
1706
bigDecimalResult = bigDecimalResult.negate();
1707
}
1708
return bigDecimalResult;
1709
} else {
1710
Number cnfResult;
1711
if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) {
1712
long longResult = digitList.getLong();
1713
cnfResult = generateParseResult(longResult, false,
1714
longResult < 0, status, cnfMultiplier);
1715
} else {
1716
cnfResult = generateParseResult(digitList.getDouble(),
1717
true, false, status, cnfMultiplier);
1718
}
1719
return cnfResult;
1720
}
1721
}
1722
1723
private static final Pattern DIGITS = Pattern.compile("\\p{Nd}+");
1724
/**
1725
* Parse the number part in the input text into a number
1726
*
1727
* @param text input text to be parsed
1728
* @param position starting position
1729
* @return the number
1730
*/
1731
private double parseNumberPart(String text, int position) {
1732
if (text.startsWith(symbols.getInfinity(), position)) {
1733
return Double.POSITIVE_INFINITY;
1734
} else if (!text.startsWith(symbols.getNaN(), position)) {
1735
Matcher m = DIGITS.matcher(text);
1736
if (m.find(position)) {
1737
String digits = m.group();
1738
int cp = digits.codePointAt(0);
1739
if (Character.isDigit(cp)) {
1740
return Double.parseDouble(digits.codePoints()
1741
.map(Character::getNumericValue)
1742
.mapToObj(Integer::toString)
1743
.collect(Collectors.joining()));
1744
}
1745
} else {
1746
// no numbers. return 1.0 for possible no-placeholder pattern
1747
return 1.0;
1748
}
1749
}
1750
return Double.NaN;
1751
}
1752
1753
/**
1754
* Returns the parsed result by multiplying the parsed number
1755
* with the multiplier representing the prefix and suffix.
1756
*
1757
* @param number parsed number component
1758
* @param gotDouble whether the parsed number contains decimal
1759
* @param gotLongMin whether the parsed number is Long.MIN
1760
* @param status boolean status flags indicating whether the
1761
* value is infinite and whether it is positive
1762
* @param cnfMultiplier compact number multiplier
1763
* @return parsed result
1764
*/
1765
private Number generateParseResult(Number number, boolean gotDouble,
1766
boolean gotLongMin, boolean[] status, Number cnfMultiplier) {
1767
1768
if (gotDouble) {
1769
if (cnfMultiplier.longValue() != 1L) {
1770
double doubleResult = number.doubleValue() * cnfMultiplier.doubleValue();
1771
doubleResult = (double) convertIfNegative(doubleResult, status, gotLongMin);
1772
// Check if a double can be represeneted as a long
1773
long longResult = (long) doubleResult;
1774
gotDouble = ((doubleResult != (double) longResult)
1775
|| (doubleResult == 0.0 && 1 / doubleResult < 0.0));
1776
return gotDouble ? (Number) doubleResult : (Number) longResult;
1777
}
1778
} else {
1779
if (cnfMultiplier.longValue() != 1L) {
1780
Number result;
1781
if ((cnfMultiplier instanceof Long) && !gotLongMin) {
1782
long longMultiplier = (long) cnfMultiplier;
1783
try {
1784
result = Math.multiplyExact(number.longValue(),
1785
longMultiplier);
1786
} catch (ArithmeticException ex) {
1787
// If number * longMultiplier can not be represented
1788
// as long return as double
1789
result = number.doubleValue() * cnfMultiplier.doubleValue();
1790
}
1791
} else {
1792
// cnfMultiplier can not be stored into long or the number
1793
// part is Long.MIN, return as double
1794
result = number.doubleValue() * cnfMultiplier.doubleValue();
1795
}
1796
return convertIfNegative(result, status, gotLongMin);
1797
}
1798
}
1799
1800
// Default number
1801
return convertIfNegative(number, status, gotLongMin);
1802
}
1803
1804
/**
1805
* Negate the parsed value if the positive status flag is false
1806
* and the value is not a Long.MIN
1807
* @param number parsed value
1808
* @param status boolean status flags indicating whether the
1809
* value is infinite and whether it is positive
1810
* @param gotLongMin whether the parsed number is Long.MIN
1811
* @return the resulting value
1812
*/
1813
private Number convertIfNegative(Number number, boolean[] status,
1814
boolean gotLongMin) {
1815
1816
if (!status[STATUS_POSITIVE] && !gotLongMin) {
1817
if (number instanceof Long) {
1818
return -(long) number;
1819
} else {
1820
return -(double) number;
1821
}
1822
} else {
1823
return number;
1824
}
1825
}
1826
1827
/**
1828
* Attempts to match the given {@code affix} in the
1829
* specified {@code text}.
1830
*/
1831
private boolean matchAffix(String text, int position, String affix,
1832
String defaultAffix, String matchedAffix) {
1833
1834
// Check with the compact affixes which are non empty and
1835
// do not match with default affix
1836
if (!affix.isEmpty() && !affix.equals(defaultAffix)) {
1837
// Look ahead only for the longer match than the previous match
1838
if (matchedAffix.length() < affix.length()) {
1839
return text.regionMatches(position, affix, 0, affix.length());
1840
}
1841
}
1842
return false;
1843
}
1844
1845
/**
1846
* Attempts to match given {@code prefix} and {@code suffix} in
1847
* the specified {@code text}.
1848
*/
1849
private boolean matchPrefixAndSuffix(String text, int position, String prefix,
1850
String matchedPrefix, String defaultPrefix, String suffix,
1851
String matchedSuffix, String defaultSuffix) {
1852
1853
// Check the compact pattern suffix only if there is a
1854
// compact prefix match or a default prefix match
1855
// because the compact prefix and suffix should match at the same
1856
// index to obtain the multiplier.
1857
// The prefix match is required because of the possibility of
1858
// same prefix at multiple index, in which case matching the suffix
1859
// is used to obtain the single match
1860
1861
if (prefix.equals(matchedPrefix)
1862
|| matchedPrefix.equals(defaultPrefix)) {
1863
return matchAffix(text, position, suffix, defaultSuffix, matchedSuffix);
1864
}
1865
return false;
1866
}
1867
1868
/**
1869
* Computes multiplier by matching the given {@code matchedPrefix}
1870
* and suffix in the specified {@code text} from the lists of
1871
* prefixes and suffixes extracted from compact patterns.
1872
*
1873
* @param text the string to parse
1874
* @param parsePosition the {@code ParsePosition} object representing the
1875
* index and error index of the parse string
1876
* @param matchedPrefix prefix extracted which needs to be matched to
1877
* obtain the multiplier
1878
* @param status upon return contains boolean status flags indicating
1879
* whether the value is positive
1880
* @param gotPositive based on the prefix parsed; whether the number is positive
1881
* @param gotNegative based on the prefix parsed; whether the number is negative
1882
* @return the multiplier matching the prefix and suffix; -1 otherwise
1883
*/
1884
private Number computeParseMultiplier(String text, ParsePosition parsePosition,
1885
String matchedPrefix, boolean[] status, boolean gotPositive,
1886
boolean gotNegative, double num) {
1887
1888
int position = parsePosition.index;
1889
boolean gotPos = false;
1890
boolean gotNeg = false;
1891
int matchedPosIndex = -1;
1892
int matchedNegIndex = -1;
1893
String matchedPosSuffix = "";
1894
String matchedNegSuffix = "";
1895
for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) {
1896
String positivePrefix = getAffix(true, true, false, compactIndex, (int)num);
1897
String negativePrefix = getAffix(true, true, true, compactIndex, (int)num);
1898
String positiveSuffix = getAffix(true, false, false, compactIndex, (int)num);
1899
String negativeSuffix = getAffix(true, false, true, compactIndex, (int)num);
1900
1901
// Do not break if a match occur; there is a possibility that the
1902
// subsequent affixes may match the longer subsequence in the given
1903
// string.
1904
// For example, matching "3Mdx" with "M", "Md" should match with "Md"
1905
boolean match = matchPrefixAndSuffix(text, position, positivePrefix, matchedPrefix,
1906
defaultDecimalFormat.getPositivePrefix(), positiveSuffix,
1907
matchedPosSuffix, defaultDecimalFormat.getPositiveSuffix());
1908
if (match) {
1909
matchedPosIndex = compactIndex;
1910
matchedPosSuffix = positiveSuffix;
1911
gotPos = true;
1912
}
1913
1914
match = matchPrefixAndSuffix(text, position, negativePrefix, matchedPrefix,
1915
defaultDecimalFormat.getNegativePrefix(), negativeSuffix,
1916
matchedNegSuffix, defaultDecimalFormat.getNegativeSuffix());
1917
if (match) {
1918
matchedNegIndex = compactIndex;
1919
matchedNegSuffix = negativeSuffix;
1920
gotNeg = true;
1921
}
1922
}
1923
1924
// Suffix in the given text does not match with the compact
1925
// patterns suffixes; match with the default suffix
1926
if (!gotPos && !gotNeg) {
1927
String positiveSuffix = defaultDecimalFormat.getPositiveSuffix();
1928
String negativeSuffix = defaultDecimalFormat.getNegativeSuffix();
1929
if (text.regionMatches(position, positiveSuffix, 0,
1930
positiveSuffix.length())) {
1931
// Matches the default positive prefix
1932
matchedPosSuffix = positiveSuffix;
1933
gotPos = true;
1934
}
1935
if (text.regionMatches(position, negativeSuffix, 0,
1936
negativeSuffix.length())) {
1937
// Matches the default negative suffix
1938
matchedNegSuffix = negativeSuffix;
1939
gotNeg = true;
1940
}
1941
}
1942
1943
// If both matches, take the longest one
1944
if (gotPos && gotNeg) {
1945
if (matchedPosSuffix.length() > matchedNegSuffix.length()) {
1946
gotNeg = false;
1947
} else if (matchedPosSuffix.length() < matchedNegSuffix.length()) {
1948
gotPos = false;
1949
} else {
1950
// If longest comparison fails; take the positive and negative
1951
// sign of matching prefix
1952
gotPos = gotPositive;
1953
gotNeg = gotNegative;
1954
}
1955
}
1956
1957
// Fail if neither or both
1958
if (gotPos == gotNeg) {
1959
parsePosition.errorIndex = position;
1960
return -1L;
1961
}
1962
1963
Number cnfMultiplier;
1964
// Update the parse position index and take compact multiplier
1965
// only if it matches the compact suffix, not the default
1966
// suffix; else multiplier should be 1
1967
if (gotPos) {
1968
parsePosition.index = position + matchedPosSuffix.length();
1969
cnfMultiplier = matchedPosIndex != -1
1970
? divisors.get(matchedPosIndex) : 1L;
1971
} else {
1972
parsePosition.index = position + matchedNegSuffix.length();
1973
cnfMultiplier = matchedNegIndex != -1
1974
? divisors.get(matchedNegIndex) : 1L;
1975
}
1976
status[STATUS_POSITIVE] = gotPos;
1977
return cnfMultiplier;
1978
}
1979
1980
/**
1981
* Reconstitutes this {@code CompactNumberFormat} from a stream
1982
* (that is, deserializes it) after performing some validations.
1983
* This method throws InvalidObjectException, if the stream data is invalid
1984
* because of the following reasons,
1985
* <ul>
1986
* <li> If any of the {@code decimalPattern}, {@code compactPatterns},
1987
* {@code symbols} or {@code roundingMode} is {@code null}.
1988
* <li> If the {@code decimalPattern} or the {@code compactPatterns} array
1989
* contains an invalid pattern or if a {@code null} appears in the array of
1990
* compact patterns.
1991
* <li> If the {@code minimumIntegerDigits} is greater than the
1992
* {@code maximumIntegerDigits} or the {@code minimumFractionDigits} is
1993
* greater than the {@code maximumFractionDigits}. This check is performed
1994
* by superclass's Object.
1995
* <li> If any of the minimum/maximum integer/fraction digit count is
1996
* negative. This check is performed by superclass's readObject.
1997
* <li> If the minimum or maximum integer digit count is larger than 309 or
1998
* if the minimum or maximum fraction digit count is larger than 340.
1999
* <li> If the grouping size is negative or larger than 127.
2000
* </ul>
2001
* If the {@code pluralRules} field is not deserialized from the stream, it
2002
* will be set to an empty string.
2003
*
2004
* @param inStream the stream
2005
* @throws IOException if an I/O error occurs
2006
* @throws ClassNotFoundException if the class of a serialized object
2007
* could not be found
2008
*/
2009
@java.io.Serial
2010
private void readObject(ObjectInputStream inStream) throws IOException,
2011
ClassNotFoundException {
2012
2013
inStream.defaultReadObject();
2014
if (decimalPattern == null || compactPatterns == null
2015
|| symbols == null || roundingMode == null) {
2016
throw new InvalidObjectException("One of the 'decimalPattern',"
2017
+ " 'compactPatterns', 'symbols' or 'roundingMode'"
2018
+ " is null");
2019
}
2020
2021
// Check only the maximum counts because NumberFormat.readObject has
2022
// already ensured that the maximum is greater than the minimum count.
2023
if (getMaximumIntegerDigits() > DecimalFormat.DOUBLE_INTEGER_DIGITS
2024
|| getMaximumFractionDigits() > DecimalFormat.DOUBLE_FRACTION_DIGITS) {
2025
throw new InvalidObjectException("Digit count out of range");
2026
}
2027
2028
// Check if the grouping size is negative, on an attempt to
2029
// put value > 127, it wraps around, so check just negative value
2030
if (groupingSize < 0) {
2031
throw new InvalidObjectException("Grouping size is negative");
2032
}
2033
2034
// pluralRules is since 14. Fill in empty string if it is null
2035
if (pluralRules == null) {
2036
pluralRules = "";
2037
}
2038
2039
try {
2040
processCompactPatterns();
2041
} catch (IllegalArgumentException ex) {
2042
throw new InvalidObjectException(ex.getMessage());
2043
}
2044
2045
decimalFormat = new DecimalFormat(SPECIAL_PATTERN, symbols);
2046
decimalFormat.setMaximumFractionDigits(getMaximumFractionDigits());
2047
decimalFormat.setMinimumFractionDigits(getMinimumFractionDigits());
2048
decimalFormat.setMaximumIntegerDigits(getMaximumIntegerDigits());
2049
decimalFormat.setMinimumIntegerDigits(getMinimumIntegerDigits());
2050
decimalFormat.setRoundingMode(getRoundingMode());
2051
decimalFormat.setGroupingSize(getGroupingSize());
2052
decimalFormat.setGroupingUsed(isGroupingUsed());
2053
decimalFormat.setParseIntegerOnly(isParseIntegerOnly());
2054
2055
try {
2056
defaultDecimalFormat = new DecimalFormat(decimalPattern, symbols);
2057
defaultDecimalFormat.setMaximumFractionDigits(0);
2058
} catch (IllegalArgumentException ex) {
2059
throw new InvalidObjectException(ex.getMessage());
2060
}
2061
2062
}
2063
2064
/**
2065
* Sets the maximum number of digits allowed in the integer portion of a
2066
* number.
2067
* The maximum allowed integer range is 309, if the {@code newValue} &gt; 309,
2068
* then the maximum integer digits count is set to 309. Negative input
2069
* values are replaced with 0.
2070
*
2071
* @param newValue the maximum number of integer digits to be shown
2072
* @see #getMaximumIntegerDigits()
2073
*/
2074
@Override
2075
public void setMaximumIntegerDigits(int newValue) {
2076
// The maximum integer digits is checked with the allowed range before calling
2077
// the DecimalFormat.setMaximumIntegerDigits, which performs the negative check
2078
// on the given newValue while setting it as max integer digits.
2079
// For example, if a negative value is specified, it is replaced with 0
2080
decimalFormat.setMaximumIntegerDigits(Math.min(newValue,
2081
DecimalFormat.DOUBLE_INTEGER_DIGITS));
2082
super.setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits());
2083
if (decimalFormat.getMinimumIntegerDigits() > decimalFormat.getMaximumIntegerDigits()) {
2084
decimalFormat.setMinimumIntegerDigits(decimalFormat.getMaximumIntegerDigits());
2085
super.setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits());
2086
}
2087
}
2088
2089
/**
2090
* Sets the minimum number of digits allowed in the integer portion of a
2091
* number.
2092
* The maximum allowed integer range is 309, if the {@code newValue} &gt; 309,
2093
* then the minimum integer digits count is set to 309. Negative input
2094
* values are replaced with 0.
2095
*
2096
* @param newValue the minimum number of integer digits to be shown
2097
* @see #getMinimumIntegerDigits()
2098
*/
2099
@Override
2100
public void setMinimumIntegerDigits(int newValue) {
2101
// The minimum integer digits is checked with the allowed range before calling
2102
// the DecimalFormat.setMinimumIntegerDigits, which performs check on the given
2103
// newValue while setting it as min integer digits. For example, if a negative
2104
// value is specified, it is replaced with 0
2105
decimalFormat.setMinimumIntegerDigits(Math.min(newValue,
2106
DecimalFormat.DOUBLE_INTEGER_DIGITS));
2107
super.setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits());
2108
if (decimalFormat.getMinimumIntegerDigits() > decimalFormat.getMaximumIntegerDigits()) {
2109
decimalFormat.setMaximumIntegerDigits(decimalFormat.getMinimumIntegerDigits());
2110
super.setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits());
2111
}
2112
}
2113
2114
/**
2115
* Sets the minimum number of digits allowed in the fraction portion of a
2116
* number.
2117
* The maximum allowed fraction range is 340, if the {@code newValue} &gt; 340,
2118
* then the minimum fraction digits count is set to 340. Negative input
2119
* values are replaced with 0.
2120
*
2121
* @param newValue the minimum number of fraction digits to be shown
2122
* @see #getMinimumFractionDigits()
2123
*/
2124
@Override
2125
public void setMinimumFractionDigits(int newValue) {
2126
// The minimum fraction digits is checked with the allowed range before
2127
// calling the DecimalFormat.setMinimumFractionDigits, which performs
2128
// check on the given newValue while setting it as min fraction
2129
// digits. For example, if a negative value is specified, it is
2130
// replaced with 0
2131
decimalFormat.setMinimumFractionDigits(Math.min(newValue,
2132
DecimalFormat.DOUBLE_FRACTION_DIGITS));
2133
super.setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits());
2134
if (decimalFormat.getMinimumFractionDigits() > decimalFormat.getMaximumFractionDigits()) {
2135
decimalFormat.setMaximumFractionDigits(decimalFormat.getMinimumFractionDigits());
2136
super.setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits());
2137
}
2138
}
2139
2140
/**
2141
* Sets the maximum number of digits allowed in the fraction portion of a
2142
* number.
2143
* The maximum allowed fraction range is 340, if the {@code newValue} &gt; 340,
2144
* then the maximum fraction digits count is set to 340. Negative input
2145
* values are replaced with 0.
2146
*
2147
* @param newValue the maximum number of fraction digits to be shown
2148
* @see #getMaximumFractionDigits()
2149
*/
2150
@Override
2151
public void setMaximumFractionDigits(int newValue) {
2152
// The maximum fraction digits is checked with the allowed range before
2153
// calling the DecimalFormat.setMaximumFractionDigits, which performs
2154
// check on the given newValue while setting it as max fraction digits.
2155
// For example, if a negative value is specified, it is replaced with 0
2156
decimalFormat.setMaximumFractionDigits(Math.min(newValue,
2157
DecimalFormat.DOUBLE_FRACTION_DIGITS));
2158
super.setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits());
2159
if (decimalFormat.getMinimumFractionDigits() > decimalFormat.getMaximumFractionDigits()) {
2160
decimalFormat.setMinimumFractionDigits(decimalFormat.getMaximumFractionDigits());
2161
super.setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits());
2162
}
2163
}
2164
2165
/**
2166
* Gets the {@link java.math.RoundingMode} used in this
2167
* {@code CompactNumberFormat}.
2168
*
2169
* @return the {@code RoundingMode} used for this
2170
* {@code CompactNumberFormat}
2171
* @see #setRoundingMode(RoundingMode)
2172
*/
2173
@Override
2174
public RoundingMode getRoundingMode() {
2175
return roundingMode;
2176
}
2177
2178
/**
2179
* Sets the {@link java.math.RoundingMode} used in this
2180
* {@code CompactNumberFormat}.
2181
*
2182
* @param roundingMode the {@code RoundingMode} to be used
2183
* @see #getRoundingMode()
2184
* @throws NullPointerException if {@code roundingMode} is {@code null}
2185
*/
2186
@Override
2187
public void setRoundingMode(RoundingMode roundingMode) {
2188
decimalFormat.setRoundingMode(roundingMode);
2189
this.roundingMode = roundingMode;
2190
}
2191
2192
/**
2193
* Returns the grouping size. Grouping size is the number of digits between
2194
* grouping separators in the integer portion of a number. For example,
2195
* in the compact number {@code "12,347 trillion"} for the
2196
* {@link java.util.Locale#US US locale}, the grouping size is 3.
2197
*
2198
* @return the grouping size
2199
* @see #setGroupingSize
2200
* @see java.text.NumberFormat#isGroupingUsed
2201
* @see java.text.DecimalFormatSymbols#getGroupingSeparator
2202
*/
2203
public int getGroupingSize() {
2204
return groupingSize;
2205
}
2206
2207
/**
2208
* Sets the grouping size. Grouping size is the number of digits between
2209
* grouping separators in the integer portion of a number. For example,
2210
* in the compact number {@code "12,347 trillion"} for the
2211
* {@link java.util.Locale#US US locale}, the grouping size is 3. The grouping
2212
* size must be greater than or equal to zero and less than or equal to 127.
2213
*
2214
* @param newValue the new grouping size
2215
* @see #getGroupingSize
2216
* @see java.text.NumberFormat#setGroupingUsed
2217
* @see java.text.DecimalFormatSymbols#setGroupingSeparator
2218
* @throws IllegalArgumentException if {@code newValue} is negative or
2219
* larger than 127
2220
*/
2221
public void setGroupingSize(int newValue) {
2222
if (newValue < 0 || newValue > 127) {
2223
throw new IllegalArgumentException(
2224
"The value passed is negative or larger than 127");
2225
}
2226
groupingSize = (byte) newValue;
2227
decimalFormat.setGroupingSize(groupingSize);
2228
}
2229
2230
/**
2231
* Returns true if grouping is used in this format. For example, with
2232
* grouping on and grouping size set to 3, the number {@code 12346567890987654}
2233
* can be formatted as {@code "12,347 trillion"} in the
2234
* {@link java.util.Locale#US US locale}.
2235
* The grouping separator is locale dependent.
2236
*
2237
* @return {@code true} if grouping is used;
2238
* {@code false} otherwise
2239
* @see #setGroupingUsed
2240
*/
2241
@Override
2242
public boolean isGroupingUsed() {
2243
return super.isGroupingUsed();
2244
}
2245
2246
/**
2247
* Sets whether or not grouping will be used in this format.
2248
*
2249
* @param newValue {@code true} if grouping is used;
2250
* {@code false} otherwise
2251
* @see #isGroupingUsed
2252
*/
2253
@Override
2254
public void setGroupingUsed(boolean newValue) {
2255
decimalFormat.setGroupingUsed(newValue);
2256
super.setGroupingUsed(newValue);
2257
}
2258
2259
/**
2260
* Returns true if this format parses only an integer from the number
2261
* component of a compact number.
2262
* Parsing an integer means that only an integer is considered from the
2263
* number component, prefix/suffix is still considered to compute the
2264
* resulting output.
2265
* For example, in the {@link java.util.Locale#US US locale}, if this method
2266
* returns {@code true}, the string {@code "1234.78 thousand"} would be
2267
* parsed as the value {@code 1234000} (1234 (integer part) * 1000
2268
* (thousand)) and the fractional part would be skipped.
2269
* The exact format accepted by the parse operation is locale dependent.
2270
*
2271
* @return {@code true} if compact numbers should be parsed as integers
2272
* only; {@code false} otherwise
2273
*/
2274
@Override
2275
public boolean isParseIntegerOnly() {
2276
return super.isParseIntegerOnly();
2277
}
2278
2279
/**
2280
* Sets whether or not this format parses only an integer from the number
2281
* component of a compact number.
2282
*
2283
* @param value {@code true} if compact numbers should be parsed as
2284
* integers only; {@code false} otherwise
2285
* @see #isParseIntegerOnly
2286
*/
2287
@Override
2288
public void setParseIntegerOnly(boolean value) {
2289
decimalFormat.setParseIntegerOnly(value);
2290
super.setParseIntegerOnly(value);
2291
}
2292
2293
/**
2294
* Returns whether the {@link #parse(String, ParsePosition)}
2295
* method returns {@code BigDecimal}. The default value is false.
2296
*
2297
* @return {@code true} if the parse method returns BigDecimal;
2298
* {@code false} otherwise
2299
* @see #setParseBigDecimal
2300
*
2301
*/
2302
public boolean isParseBigDecimal() {
2303
return parseBigDecimal;
2304
}
2305
2306
/**
2307
* Sets whether the {@link #parse(String, ParsePosition)}
2308
* method returns {@code BigDecimal}.
2309
*
2310
* @param newValue {@code true} if the parse method returns BigDecimal;
2311
* {@code false} otherwise
2312
* @see #isParseBigDecimal
2313
*
2314
*/
2315
public void setParseBigDecimal(boolean newValue) {
2316
parseBigDecimal = newValue;
2317
}
2318
2319
/**
2320
* Checks if this {@code CompactNumberFormat} is equal to the
2321
* specified {@code obj}. The objects of type {@code CompactNumberFormat}
2322
* are compared, other types return false; obeys the general contract of
2323
* {@link java.lang.Object#equals(java.lang.Object) Object.equals}.
2324
*
2325
* @param obj the object to compare with
2326
* @return true if this is equal to the other {@code CompactNumberFormat}
2327
*/
2328
@Override
2329
public boolean equals(Object obj) {
2330
2331
if (!super.equals(obj)) {
2332
return false;
2333
}
2334
2335
CompactNumberFormat other = (CompactNumberFormat) obj;
2336
return decimalPattern.equals(other.decimalPattern)
2337
&& symbols.equals(other.symbols)
2338
&& Arrays.equals(compactPatterns, other.compactPatterns)
2339
&& roundingMode.equals(other.roundingMode)
2340
&& pluralRules.equals(other.pluralRules)
2341
&& groupingSize == other.groupingSize
2342
&& parseBigDecimal == other.parseBigDecimal;
2343
}
2344
2345
/**
2346
* Returns the hash code for this {@code CompactNumberFormat} instance.
2347
*
2348
* @return hash code for this {@code CompactNumberFormat}
2349
*/
2350
@Override
2351
public int hashCode() {
2352
return 31 * super.hashCode() +
2353
Objects.hash(decimalPattern, symbols, roundingMode, pluralRules)
2354
+ Arrays.hashCode(compactPatterns) + groupingSize
2355
+ Boolean.hashCode(parseBigDecimal);
2356
}
2357
2358
/**
2359
* Creates and returns a copy of this {@code CompactNumberFormat}
2360
* instance.
2361
*
2362
* @return a clone of this instance
2363
*/
2364
@Override
2365
public CompactNumberFormat clone() {
2366
CompactNumberFormat other = (CompactNumberFormat) super.clone();
2367
other.compactPatterns = compactPatterns.clone();
2368
other.symbols = (DecimalFormatSymbols) symbols.clone();
2369
return other;
2370
}
2371
2372
/**
2373
* Abstraction of affix or number (represented by zeros) patterns for each "count" tag.
2374
*/
2375
private final class Patterns {
2376
private final Map<String, String> patternsMap = new HashMap<>();
2377
2378
void put(String count, String pattern) {
2379
patternsMap.put(count, pattern);
2380
}
2381
2382
String get(double num) {
2383
return patternsMap.getOrDefault(getPluralCategory(num),
2384
patternsMap.getOrDefault("other", ""));
2385
}
2386
2387
Patterns expandAffix() {
2388
Patterns ret = new Patterns();
2389
patternsMap.forEach((key, value) -> ret.put(key, CompactNumberFormat.this.expandAffix(value)));
2390
return ret;
2391
}
2392
}
2393
2394
private int getIntegerPart(double number, double divisor) {
2395
return BigDecimal.valueOf(number)
2396
.divide(BigDecimal.valueOf(divisor), roundingMode).intValue();
2397
}
2398
2399
/**
2400
* Returns LDML's tag from the plurals rules
2401
*
2402
* @param input input number in double type
2403
* @return LDML "count" tag
2404
*/
2405
private String getPluralCategory(double input) {
2406
if (rulesMap != null) {
2407
return rulesMap.entrySet().stream()
2408
.filter(e -> matchPluralRule(e.getValue(), input))
2409
.map(Map.Entry::getKey)
2410
.findFirst()
2411
.orElse("other");
2412
}
2413
2414
// defaults to "other"
2415
return "other";
2416
}
2417
2418
private static boolean matchPluralRule(String condition, double input) {
2419
return Arrays.stream(condition.split("or"))
2420
.anyMatch(and_condition -> Arrays.stream(and_condition.split("and"))
2421
.allMatch(r -> relationCheck(r, input)));
2422
}
2423
2424
private static final String NAMED_EXPR = "(?<op>[niftvwe])\\s*((?<div>[/%])\\s*(?<val>\\d+))*";
2425
private static final String NAMED_RELATION = "(?<rel>!?=)";
2426
private static final String NAMED_VALUE_RANGE = "(?<start>\\d+)\\.\\.(?<end>\\d+)|(?<value>\\d+)";
2427
private static final Pattern EXPR_PATTERN = Pattern.compile(NAMED_EXPR);
2428
private static final Pattern RELATION_PATTERN = Pattern.compile(NAMED_RELATION);
2429
private static final Pattern VALUE_RANGE_PATTERN = Pattern.compile(NAMED_VALUE_RANGE);
2430
2431
/**
2432
* Checks if the 'input' equals the value, or within the range.
2433
*
2434
* @param valueOrRange A string representing either a single value or a range
2435
* @param input to examine in double
2436
* @return match indicator
2437
*/
2438
private static boolean valOrRangeMatches(String valueOrRange, double input) {
2439
Matcher m = VALUE_RANGE_PATTERN.matcher(valueOrRange);
2440
2441
if (m.find()) {
2442
String value = m.group("value");
2443
if (value != null) {
2444
return input == Double.parseDouble(value);
2445
} else {
2446
return input >= Double.parseDouble(m.group("start")) &&
2447
input <= Double.parseDouble(m.group("end"));
2448
}
2449
}
2450
2451
return false;
2452
}
2453
2454
/**
2455
* Checks if the input value satisfies the relation. Each possible value or range is
2456
* separated by a comma ','
2457
*
2458
* @param relation relation string, e.g, "n = 1, 3..5", or "n != 1, 3..5"
2459
* @param input value to examine in double
2460
* @return boolean to indicate whether the relation satisfies or not. If the relation
2461
* is '=', true if any of the possible value/range satisfies. If the relation is '!=',
2462
* none of the possible value/range should satisfy to return true.
2463
*/
2464
private static boolean relationCheck(String relation, double input) {
2465
Matcher expr = EXPR_PATTERN.matcher(relation);
2466
2467
if (expr.find()) {
2468
double lop = evalLOperand(expr, input);
2469
Matcher rel = RELATION_PATTERN.matcher(relation);
2470
2471
if (rel.find(expr.end())) {
2472
var conditions =
2473
Arrays.stream(relation.substring(rel.end()).split(","));
2474
2475
if (Objects.equals(rel.group("rel"), "!=")) {
2476
return conditions.noneMatch(c -> valOrRangeMatches(c, lop));
2477
} else {
2478
return conditions.anyMatch(c -> valOrRangeMatches(c, lop));
2479
}
2480
}
2481
}
2482
2483
return false;
2484
}
2485
2486
/**
2487
* Evaluates the left operand value.
2488
*
2489
* @param expr Match result
2490
* @param input value to examine in double
2491
* @return resulting double value
2492
*/
2493
private static double evalLOperand(Matcher expr, double input) {
2494
double ret = 0;
2495
2496
if (input == Double.POSITIVE_INFINITY) {
2497
ret = input;
2498
} else {
2499
String op = expr.group("op");
2500
if (Objects.equals(op, "n") || Objects.equals(op, "i")) {
2501
ret = input;
2502
}
2503
2504
String divop = expr.group("div");
2505
if (divop != null) {
2506
String divisor = expr.group("val");
2507
switch (divop) {
2508
case "%" -> ret %= Double.parseDouble(divisor);
2509
case "/" -> ret /= Double.parseDouble(divisor);
2510
}
2511
}
2512
}
2513
2514
return ret;
2515
}
2516
}
2517
2518