Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/java/text/ChoiceFormat.java
41152 views
1
/*
2
* Copyright (c) 1996, 2019, 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
26
/*
27
* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
28
* (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
29
*
30
* The original version of this source code and documentation is copyrighted
31
* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
32
* materials are provided under terms of a License Agreement between Taligent
33
* and Sun. This technology is protected by multiple US and International
34
* patents. This notice and attribution to Taligent may not be removed.
35
* Taligent is a registered trademark of Taligent, Inc.
36
*
37
*/
38
39
package java.text;
40
41
import java.io.InvalidObjectException;
42
import java.io.IOException;
43
import java.io.ObjectInputStream;
44
import java.util.Arrays;
45
46
/**
47
* A {@code ChoiceFormat} allows you to attach a format to a range of numbers.
48
* It is generally used in a {@code MessageFormat} for handling plurals.
49
* The choice is specified with an ascending list of doubles, where each item
50
* specifies a half-open interval up to the next item:
51
* <blockquote>
52
* <pre>
53
* X matches j if and only if limit[j] &le; X &lt; limit[j+1]
54
* </pre>
55
* </blockquote>
56
* If there is no match, then either the first or last index is used, depending
57
* on whether the number (X) is too low or too high. If the limit array is not
58
* in ascending order, the results of formatting will be incorrect. ChoiceFormat
59
* also accepts <code>&#92;u221E</code> as equivalent to infinity(INF).
60
*
61
* <p>
62
* <strong>Note:</strong>
63
* {@code ChoiceFormat} differs from the other {@code Format}
64
* classes in that you create a {@code ChoiceFormat} object with a
65
* constructor (not with a {@code getInstance} style factory
66
* method). The factory methods aren't necessary because {@code ChoiceFormat}
67
* doesn't require any complex setup for a given locale. In fact,
68
* {@code ChoiceFormat} doesn't implement any locale specific behavior.
69
*
70
* <p>
71
* When creating a {@code ChoiceFormat}, you must specify an array of formats
72
* and an array of limits. The length of these arrays must be the same.
73
* For example,
74
* <ul>
75
* <li>
76
* <em>limits</em> = {1,2,3,4,5,6,7}<br>
77
* <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}
78
* <li>
79
* <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br>
80
* <em>formats</em> = {"no files", "one file", "many files"}<br>
81
* ({@code nextDouble} can be used to get the next higher double, to
82
* make the half-open interval.)
83
* </ul>
84
*
85
* <p>
86
* Here is a simple example that shows formatting and parsing:
87
* <blockquote>
88
* <pre>{@code
89
* double[] limits = {1,2,3,4,5,6,7};
90
* String[] dayOfWeekNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"};
91
* ChoiceFormat form = new ChoiceFormat(limits, dayOfWeekNames);
92
* ParsePosition status = new ParsePosition(0);
93
* for (double i = 0.0; i <= 8.0; ++i) {
94
* status.setIndex(0);
95
* System.out.println(i + " -> " + form.format(i) + " -> "
96
* + form.parse(form.format(i),status));
97
* }
98
* }</pre>
99
* </blockquote>
100
* Here is a more complex example, with a pattern format:
101
* <blockquote>
102
* <pre>{@code
103
* double[] filelimits = {0,1,2};
104
* String[] filepart = {"are no files","is one file","are {2} files"};
105
* ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
106
* Format[] testFormats = {fileform, null, NumberFormat.getInstance()};
107
* MessageFormat pattform = new MessageFormat("There {0} on {1}");
108
* pattform.setFormats(testFormats);
109
* Object[] testArgs = {null, "ADisk", null};
110
* for (int i = 0; i < 4; ++i) {
111
* testArgs[0] = new Integer(i);
112
* testArgs[2] = testArgs[0];
113
* System.out.println(pattform.format(testArgs));
114
* }
115
* }</pre>
116
* </blockquote>
117
* <p>
118
* Specifying a pattern for ChoiceFormat objects is fairly straightforward.
119
* For example:
120
* <blockquote>
121
* <pre>{@code
122
* ChoiceFormat fmt = new ChoiceFormat(
123
* "-1#is negative| 0#is zero or fraction | 1#is one |1.0<is 1+ |2#is two |2<is more than 2.");
124
* System.out.println("Formatter Pattern : " + fmt.toPattern());
125
*
126
* System.out.println("Format with -INF : " + fmt.format(Double.NEGATIVE_INFINITY));
127
* System.out.println("Format with -1.0 : " + fmt.format(-1.0));
128
* System.out.println("Format with 0 : " + fmt.format(0));
129
* System.out.println("Format with 0.9 : " + fmt.format(0.9));
130
* System.out.println("Format with 1.0 : " + fmt.format(1));
131
* System.out.println("Format with 1.5 : " + fmt.format(1.5));
132
* System.out.println("Format with 2 : " + fmt.format(2));
133
* System.out.println("Format with 2.1 : " + fmt.format(2.1));
134
* System.out.println("Format with NaN : " + fmt.format(Double.NaN));
135
* System.out.println("Format with +INF : " + fmt.format(Double.POSITIVE_INFINITY));
136
* }</pre>
137
* </blockquote>
138
* And the output result would be like the following:
139
* <blockquote>
140
* <pre>{@code
141
* Format with -INF : is negative
142
* Format with -1.0 : is negative
143
* Format with 0 : is zero or fraction
144
* Format with 0.9 : is zero or fraction
145
* Format with 1.0 : is one
146
* Format with 1.5 : is 1+
147
* Format with 2 : is two
148
* Format with 2.1 : is more than 2.
149
* Format with NaN : is negative
150
* Format with +INF : is more than 2.
151
* }</pre>
152
* </blockquote>
153
*
154
* <h2><a id="synchronization">Synchronization</a></h2>
155
*
156
* <p>
157
* Choice formats are not synchronized.
158
* It is recommended to create separate format instances for each thread.
159
* If multiple threads access a format concurrently, it must be synchronized
160
* externally.
161
*
162
*
163
* @see DecimalFormat
164
* @see MessageFormat
165
* @author Mark Davis
166
* @since 1.1
167
*/
168
public class ChoiceFormat extends NumberFormat {
169
170
// Proclaim serial compatibility with 1.1 FCS
171
@java.io.Serial
172
private static final long serialVersionUID = 1795184449645032964L;
173
174
/**
175
* Sets the pattern.
176
* @param newPattern See the class description.
177
* @throws NullPointerException if {@code newPattern}
178
* is {@code null}
179
*/
180
public void applyPattern(String newPattern) {
181
StringBuffer[] segments = new StringBuffer[2];
182
for (int i = 0; i < segments.length; ++i) {
183
segments[i] = new StringBuffer();
184
}
185
double[] newChoiceLimits = new double[30];
186
String[] newChoiceFormats = new String[30];
187
int count = 0;
188
int part = 0;
189
double startValue = 0;
190
double oldStartValue = Double.NaN;
191
boolean inQuote = false;
192
for (int i = 0; i < newPattern.length(); ++i) {
193
char ch = newPattern.charAt(i);
194
if (ch=='\'') {
195
// Check for "''" indicating a literal quote
196
if ((i+1)<newPattern.length() && newPattern.charAt(i+1)==ch) {
197
segments[part].append(ch);
198
++i;
199
} else {
200
inQuote = !inQuote;
201
}
202
} else if (inQuote) {
203
segments[part].append(ch);
204
} else if (ch == '<' || ch == '#' || ch == '\u2264') {
205
if (segments[0].length() == 0) {
206
throw new IllegalArgumentException("Each interval must"
207
+ " contain a number before a format");
208
}
209
210
String tempBuffer = segments[0].toString();
211
if (tempBuffer.equals("\u221E")) {
212
startValue = Double.POSITIVE_INFINITY;
213
} else if (tempBuffer.equals("-\u221E")) {
214
startValue = Double.NEGATIVE_INFINITY;
215
} else {
216
startValue = Double.parseDouble(tempBuffer);
217
}
218
219
if (ch == '<' && startValue != Double.POSITIVE_INFINITY &&
220
startValue != Double.NEGATIVE_INFINITY) {
221
startValue = nextDouble(startValue);
222
}
223
if (startValue <= oldStartValue) {
224
throw new IllegalArgumentException("Incorrect order of"
225
+ " intervals, must be in ascending order");
226
}
227
segments[0].setLength(0);
228
part = 1;
229
} else if (ch == '|') {
230
if (count == newChoiceLimits.length) {
231
newChoiceLimits = doubleArraySize(newChoiceLimits);
232
newChoiceFormats = doubleArraySize(newChoiceFormats);
233
}
234
newChoiceLimits[count] = startValue;
235
newChoiceFormats[count] = segments[1].toString();
236
++count;
237
oldStartValue = startValue;
238
segments[1].setLength(0);
239
part = 0;
240
} else {
241
segments[part].append(ch);
242
}
243
}
244
// clean up last one
245
if (part == 1) {
246
if (count == newChoiceLimits.length) {
247
newChoiceLimits = doubleArraySize(newChoiceLimits);
248
newChoiceFormats = doubleArraySize(newChoiceFormats);
249
}
250
newChoiceLimits[count] = startValue;
251
newChoiceFormats[count] = segments[1].toString();
252
++count;
253
}
254
choiceLimits = new double[count];
255
System.arraycopy(newChoiceLimits, 0, choiceLimits, 0, count);
256
choiceFormats = new String[count];
257
System.arraycopy(newChoiceFormats, 0, choiceFormats, 0, count);
258
}
259
260
/**
261
* Gets the pattern.
262
*
263
* @return the pattern string
264
*/
265
public String toPattern() {
266
StringBuilder result = new StringBuilder();
267
for (int i = 0; i < choiceLimits.length; ++i) {
268
if (i != 0) {
269
result.append('|');
270
}
271
// choose based upon which has less precision
272
// approximate that by choosing the closest one to an integer.
273
// could do better, but it's not worth it.
274
double less = previousDouble(choiceLimits[i]);
275
double tryLessOrEqual = Math.abs(Math.IEEEremainder(choiceLimits[i], 1.0d));
276
double tryLess = Math.abs(Math.IEEEremainder(less, 1.0d));
277
278
if (tryLessOrEqual < tryLess) {
279
result.append(choiceLimits[i]);
280
result.append('#');
281
} else {
282
if (choiceLimits[i] == Double.POSITIVE_INFINITY) {
283
result.append("\u221E");
284
} else if (choiceLimits[i] == Double.NEGATIVE_INFINITY) {
285
result.append("-\u221E");
286
} else {
287
result.append(less);
288
}
289
result.append('<');
290
}
291
// Append choiceFormats[i], using quotes if there are special characters.
292
// Single quotes themselves must be escaped in either case.
293
String text = choiceFormats[i];
294
boolean needQuote = text.indexOf('<') >= 0
295
|| text.indexOf('#') >= 0
296
|| text.indexOf('\u2264') >= 0
297
|| text.indexOf('|') >= 0;
298
if (needQuote) result.append('\'');
299
if (text.indexOf('\'') < 0) result.append(text);
300
else {
301
for (int j=0; j<text.length(); ++j) {
302
char c = text.charAt(j);
303
result.append(c);
304
if (c == '\'') result.append(c);
305
}
306
}
307
if (needQuote) result.append('\'');
308
}
309
return result.toString();
310
}
311
312
/**
313
* Constructs with limits and corresponding formats based on the pattern.
314
*
315
* @param newPattern the new pattern string
316
* @throws NullPointerException if {@code newPattern} is
317
* {@code null}
318
* @see #applyPattern
319
*/
320
public ChoiceFormat(String newPattern) {
321
applyPattern(newPattern);
322
}
323
324
/**
325
* Constructs with the limits and the corresponding formats.
326
*
327
* @param limits limits in ascending order
328
* @param formats corresponding format strings
329
* @throws NullPointerException if {@code limits} or {@code formats}
330
* is {@code null}
331
* @see #setChoices
332
*/
333
public ChoiceFormat(double[] limits, String[] formats) {
334
setChoices(limits, formats);
335
}
336
337
/**
338
* Set the choices to be used in formatting.
339
* @param limits contains the top value that you want
340
* parsed with that format, and should be in ascending sorted order. When
341
* formatting X, the choice will be the i, where
342
* limit[i] &le; X {@literal <} limit[i+1].
343
* If the limit array is not in ascending order, the results of formatting
344
* will be incorrect.
345
* @param formats are the formats you want to use for each limit.
346
* They can be either Format objects or Strings.
347
* When formatting with object Y,
348
* if the object is a NumberFormat, then ((NumberFormat) Y).format(X)
349
* is called. Otherwise Y.toString() is called.
350
* @throws NullPointerException if {@code limits} or
351
* {@code formats} is {@code null}
352
*/
353
public void setChoices(double[] limits, String formats[]) {
354
if (limits.length != formats.length) {
355
throw new IllegalArgumentException(
356
"Array and limit arrays must be of the same length.");
357
}
358
choiceLimits = Arrays.copyOf(limits, limits.length);
359
choiceFormats = Arrays.copyOf(formats, formats.length);
360
}
361
362
/**
363
* Get the limits passed in the constructor.
364
* @return the limits.
365
*/
366
public double[] getLimits() {
367
double[] newLimits = Arrays.copyOf(choiceLimits, choiceLimits.length);
368
return newLimits;
369
}
370
371
/**
372
* Get the formats passed in the constructor.
373
* @return the formats.
374
*/
375
public Object[] getFormats() {
376
Object[] newFormats = Arrays.copyOf(choiceFormats, choiceFormats.length);
377
return newFormats;
378
}
379
380
// Overrides
381
382
/**
383
* Specialization of format. This method really calls
384
* {@code format(double, StringBuffer, FieldPosition)}
385
* thus the range of longs that are supported is only equal to
386
* the range that can be stored by double. This will never be
387
* a practical limitation.
388
*/
389
public StringBuffer format(long number, StringBuffer toAppendTo,
390
FieldPosition status) {
391
return format((double)number, toAppendTo, status);
392
}
393
394
/**
395
* Returns pattern with formatted double.
396
* @param number number to be formatted and substituted.
397
* @param toAppendTo where text is appended.
398
* @param status ignore no useful status is returned.
399
* @throws NullPointerException if {@code toAppendTo}
400
* is {@code null}
401
*/
402
public StringBuffer format(double number, StringBuffer toAppendTo,
403
FieldPosition status) {
404
// find the number
405
int i;
406
for (i = 0; i < choiceLimits.length; ++i) {
407
if (!(number >= choiceLimits[i])) {
408
// same as number < choiceLimits, except catchs NaN
409
break;
410
}
411
}
412
--i;
413
if (i < 0) i = 0;
414
// return either a formatted number, or a string
415
return toAppendTo.append(choiceFormats[i]);
416
}
417
418
/**
419
* Parses a Number from the input text.
420
* @param text the source text.
421
* @param status an input-output parameter. On input, the
422
* status.index field indicates the first character of the
423
* source text that should be parsed. On exit, if no error
424
* occurred, status.index is set to the first unparsed character
425
* in the source text. On exit, if an error did occur,
426
* status.index is unchanged and status.errorIndex is set to the
427
* first index of the character that caused the parse to fail.
428
* @return A Number representing the value of the number parsed.
429
* @throws NullPointerException if {@code status} is {@code null}
430
* or if {@code text} is {@code null} and the list of
431
* choice strings is not empty.
432
*/
433
public Number parse(String text, ParsePosition status) {
434
// find the best number (defined as the one with the longest parse)
435
int start = status.index;
436
int furthest = start;
437
double bestNumber = Double.NaN;
438
double tempNumber = 0.0;
439
for (int i = 0; i < choiceFormats.length; ++i) {
440
String tempString = choiceFormats[i];
441
if (text.regionMatches(start, tempString, 0, tempString.length())) {
442
status.index = start + tempString.length();
443
tempNumber = choiceLimits[i];
444
if (status.index > furthest) {
445
furthest = status.index;
446
bestNumber = tempNumber;
447
if (furthest == text.length()) break;
448
}
449
}
450
}
451
status.index = furthest;
452
if (status.index == start) {
453
status.errorIndex = furthest;
454
}
455
return Double.valueOf(bestNumber);
456
}
457
458
/**
459
* Finds the least double greater than {@code d}.
460
* If {@code NaN}, returns same value.
461
* <p>Used to make half-open intervals.
462
*
463
* @implNote This is equivalent to calling
464
* {@link Math#nextUp(double) Math.nextUp(d)}
465
*
466
* @param d the reference value
467
* @return the least double value greather than {@code d}
468
* @see #previousDouble
469
*/
470
public static final double nextDouble (double d) {
471
return Math.nextUp(d);
472
}
473
474
/**
475
* Finds the greatest double less than {@code d}.
476
* If {@code NaN}, returns same value.
477
*
478
* @implNote This is equivalent to calling
479
* {@link Math#nextDown(double) Math.nextDown(d)}
480
*
481
* @param d the reference value
482
* @return the greatest double value less than {@code d}
483
* @see #nextDouble
484
*/
485
public static final double previousDouble (double d) {
486
return Math.nextDown(d);
487
}
488
489
/**
490
* Overrides Cloneable
491
*/
492
public Object clone()
493
{
494
ChoiceFormat other = (ChoiceFormat) super.clone();
495
// for primitives or immutables, shallow clone is enough
496
other.choiceLimits = choiceLimits.clone();
497
other.choiceFormats = choiceFormats.clone();
498
return other;
499
}
500
501
/**
502
* Generates a hash code for the message format object.
503
*/
504
public int hashCode() {
505
int result = choiceLimits.length;
506
if (choiceFormats.length > 0) {
507
// enough for reasonable distribution
508
result ^= choiceFormats[choiceFormats.length-1].hashCode();
509
}
510
return result;
511
}
512
513
/**
514
* Equality comparison between two
515
*/
516
public boolean equals(Object obj) {
517
if (obj == null) return false;
518
if (this == obj) // quick check
519
return true;
520
if (getClass() != obj.getClass())
521
return false;
522
ChoiceFormat other = (ChoiceFormat) obj;
523
return (Arrays.equals(choiceLimits, other.choiceLimits)
524
&& Arrays.equals(choiceFormats, other.choiceFormats));
525
}
526
527
/**
528
* After reading an object from the input stream, do a simple verification
529
* to maintain class invariants.
530
* @throws InvalidObjectException if the objects read from the stream is invalid.
531
*/
532
@java.io.Serial
533
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
534
in.defaultReadObject();
535
if (choiceLimits.length != choiceFormats.length) {
536
throw new InvalidObjectException(
537
"limits and format arrays of different length.");
538
}
539
}
540
541
// ===============privates===========================
542
543
/**
544
* A list of lower bounds for the choices. The formatter will return
545
* {@code choiceFormats[i]} if the number being formatted is greater than or equal to
546
* {@code choiceLimits[i]} and less than {@code choiceLimits[i+1]}.
547
* @serial
548
*/
549
private double[] choiceLimits;
550
551
/**
552
* A list of choice strings. The formatter will return
553
* {@code choiceFormats[i]} if the number being formatted is greater than or equal to
554
* {@code choiceLimits[i]} and less than {@code choiceLimits[i+1]}.
555
* @serial
556
*/
557
private String[] choiceFormats;
558
559
/**
560
* Finds the least double greater than {@code d} (if {@code positive} is
561
* {@code true}), or the greatest double less than {@code d} (if
562
* {@code positive} is {@code false}).
563
* If {@code NaN}, returns same value.
564
*
565
* @implNote This is equivalent to calling
566
* {@code positive ? Math.nextUp(d) : Math.nextDown(d)}
567
*
568
* @param d the reference value
569
* @param positive {@code true} if the least double is desired;
570
* {@code false} otherwise
571
* @return the least or greater double value
572
*/
573
public static double nextDouble (double d, boolean positive) {
574
return positive ? Math.nextUp(d) : Math.nextDown(d);
575
}
576
577
private static double[] doubleArraySize(double[] array) {
578
int oldSize = array.length;
579
double[] newArray = new double[oldSize * 2];
580
System.arraycopy(array, 0, newArray, 0, oldSize);
581
return newArray;
582
}
583
584
private String[] doubleArraySize(String[] array) {
585
int oldSize = array.length;
586
String[] newArray = new String[oldSize * 2];
587
System.arraycopy(array, 0, newArray, 0, oldSize);
588
return newArray;
589
}
590
591
}
592
593