Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/java/text/MessageFormat.java
41152 views
1
/*
2
* Copyright (c) 1996, 2020, 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.text.DecimalFormat;
45
import java.util.ArrayList;
46
import java.util.Arrays;
47
import java.util.Date;
48
import java.util.List;
49
import java.util.Locale;
50
51
52
/**
53
* {@code MessageFormat} provides a means to produce concatenated
54
* messages in a language-neutral way. Use this to construct messages
55
* displayed for end users.
56
*
57
* <p>
58
* {@code MessageFormat} takes a set of objects, formats them, then
59
* inserts the formatted strings into the pattern at the appropriate places.
60
*
61
* <p>
62
* <strong>Note:</strong>
63
* {@code MessageFormat} differs from the other {@code Format}
64
* classes in that you create a {@code MessageFormat} object with one
65
* of its constructors (not with a {@code getInstance} style factory
66
* method). The factory methods aren't necessary because {@code MessageFormat}
67
* itself doesn't implement locale specific behavior. Any locale specific
68
* behavior is defined by the pattern that you provide as well as the
69
* subformats used for inserted arguments.
70
*
71
* <h2><a id="patterns">Patterns and Their Interpretation</a></h2>
72
*
73
* {@code MessageFormat} uses patterns of the following form:
74
* <blockquote><pre>
75
* <i>MessageFormatPattern:</i>
76
* <i>String</i>
77
* <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>
78
*
79
* <i>FormatElement:</i>
80
* { <i>ArgumentIndex</i> }
81
* { <i>ArgumentIndex</i> , <i>FormatType</i> }
82
* { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }
83
*
84
* <i>FormatType: one of </i>
85
* number date time choice
86
*
87
* <i>FormatStyle:</i>
88
* short
89
* medium
90
* long
91
* full
92
* integer
93
* currency
94
* percent
95
* <i>SubformatPattern</i>
96
* </pre></blockquote>
97
*
98
* <p>Within a <i>String</i>, a pair of single quotes can be used to
99
* quote any arbitrary characters except single quotes. For example,
100
* pattern string <code>"'{0}'"</code> represents string
101
* <code>"{0}"</code>, not a <i>FormatElement</i>. A single quote itself
102
* must be represented by doubled single quotes {@code ''} throughout a
103
* <i>String</i>. For example, pattern string <code>"'{''}'"</code> is
104
* interpreted as a sequence of <code>'{</code> (start of quoting and a
105
* left curly brace), {@code ''} (a single quote), and
106
* <code>}'</code> (a right curly brace and end of quoting),
107
* <em>not</em> <code>'{'</code> and <code>'}'</code> (quoted left and
108
* right curly braces): representing string <code>"{'}"</code>,
109
* <em>not</em> <code>"{}"</code>.
110
*
111
* <p>A <i>SubformatPattern</i> is interpreted by its corresponding
112
* subformat, and subformat-dependent pattern rules apply. For example,
113
* pattern string <code>"{1,number,<u>$'#',##</u>}"</code>
114
* (<i>SubformatPattern</i> with underline) will produce a number format
115
* with the pound-sign quoted, with a result such as: {@code
116
* "$#31,45"}. Refer to each {@code Format} subclass documentation for
117
* details.
118
*
119
* <p>Any unmatched quote is treated as closed at the end of the given
120
* pattern. For example, pattern string {@code "'{0}"} is treated as
121
* pattern {@code "'{0}'"}.
122
*
123
* <p>Any curly braces within an unquoted pattern must be balanced. For
124
* example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code> are
125
* valid patterns, but <code>"ab {0'}' de"</code>, <code>"ab } de"</code>
126
* and <code>"''{''"</code> are not.
127
*
128
* <dl><dt><b>Warning:</b><dd>The rules for using quotes within message
129
* format patterns unfortunately have shown to be somewhat confusing.
130
* In particular, it isn't always obvious to localizers whether single
131
* quotes need to be doubled or not. Make sure to inform localizers about
132
* the rules, and tell them (for example, by using comments in resource
133
* bundle source files) which strings will be processed by {@code MessageFormat}.
134
* Note that localizers may need to use single quotes in translated
135
* strings where the original version doesn't have them.
136
* </dl>
137
* <p>
138
* The <i>ArgumentIndex</i> value is a non-negative integer written
139
* using the digits {@code '0'} through {@code '9'}, and represents an index into the
140
* {@code arguments} array passed to the {@code format} methods
141
* or the result array returned by the {@code parse} methods.
142
* <p>
143
* The <i>FormatType</i> and <i>FormatStyle</i> values are used to create
144
* a {@code Format} instance for the format element. The following
145
* table shows how the values map to {@code Format} instances. Combinations not
146
* shown in the table are illegal. A <i>SubformatPattern</i> must
147
* be a valid pattern string for the {@code Format} subclass used.
148
*
149
* <table class="plain">
150
* <caption style="display:none">Shows how FormatType and FormatStyle values map to Format instances</caption>
151
* <thead>
152
* <tr>
153
* <th scope="col" class="TableHeadingColor">FormatType
154
* <th scope="col" class="TableHeadingColor">FormatStyle
155
* <th scope="col" class="TableHeadingColor">Subformat Created
156
* </thead>
157
* <tbody>
158
* <tr>
159
* <th scope="row" style="text-weight: normal"><i>(none)</i>
160
* <th scope="row" style="text-weight: normal"><i>(none)</i>
161
* <td>{@code null}
162
* <tr>
163
* <th scope="row" style="text-weight: normal" rowspan=5>{@code number}
164
* <th scope="row" style="text-weight: normal"><i>(none)</i>
165
* <td>{@link NumberFormat#getInstance(Locale) NumberFormat.getInstance}{@code (getLocale())}
166
* <tr>
167
* <th scope="row" style="text-weight: normal">{@code integer}
168
* <td>{@link NumberFormat#getIntegerInstance(Locale) NumberFormat.getIntegerInstance}{@code (getLocale())}
169
* <tr>
170
* <th scope="row" style="text-weight: normal">{@code currency}
171
* <td>{@link NumberFormat#getCurrencyInstance(Locale) NumberFormat.getCurrencyInstance}{@code (getLocale())}
172
* <tr>
173
* <th scope="row" style="text-weight: normal">{@code percent}
174
* <td>{@link NumberFormat#getPercentInstance(Locale) NumberFormat.getPercentInstance}{@code (getLocale())}
175
* <tr>
176
* <th scope="row" style="text-weight: normal"><i>SubformatPattern</i>
177
* <td>{@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) DecimalFormatSymbols.getInstance}{@code (getLocale()))}
178
* <tr>
179
* <th scope="row" style="text-weight: normal" rowspan=6>{@code date}
180
* <th scope="row" style="text-weight: normal"><i>(none)</i>
181
* <td>{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}
182
* <tr>
183
* <th scope="row" style="text-weight: normal">{@code short}
184
* <td>{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())}
185
* <tr>
186
* <th scope="row" style="text-weight: normal">{@code medium}
187
* <td>{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}
188
* <tr>
189
* <th scope="row" style="text-weight: normal">{@code long}
190
* <td>{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())}
191
* <tr>
192
* <th scope="row" style="text-weight: normal">{@code full}
193
* <td>{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())}
194
* <tr>
195
* <th scope="row" style="text-weight: normal"><i>SubformatPattern</i>
196
* <td>{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())}
197
* <tr>
198
* <th scope="row" style="text-weight: normal" rowspan=6>{@code time}
199
* <th scope="row" style="text-weight: normal"><i>(none)</i>
200
* <td>{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}
201
* <tr>
202
* <th scope="row" style="text-weight: normal">{@code short}
203
* <td>{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())}
204
* <tr>
205
* <th scope="row" style="text-weight: normal">{@code medium}
206
* <td>{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}
207
* <tr>
208
* <th scope="row" style="text-weight: normal">{@code long}
209
* <td>{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())}
210
* <tr>
211
* <th scope="row" style="text-weight: normal">{@code full}
212
* <td>{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())}
213
* <tr>
214
* <th scope="row" style="text-weight: normal"><i>SubformatPattern</i>
215
* <td>{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())}
216
* <tr>
217
* <th scope="row" style="text-weight: normal">{@code choice}
218
* <th scope="row" style="text-weight: normal"><i>SubformatPattern</i>
219
* <td>{@code new} {@link ChoiceFormat#ChoiceFormat(String) ChoiceFormat}{@code (subformatPattern)}
220
* </tbody>
221
* </table>
222
*
223
* <h3>Usage Information</h3>
224
*
225
* <p>
226
* Here are some examples of usage.
227
* In real internationalized programs, the message format pattern and other
228
* static strings will, of course, be obtained from resource bundles.
229
* Other parameters will be dynamically determined at runtime.
230
* <p>
231
* The first example uses the static method {@code MessageFormat.format},
232
* which internally creates a {@code MessageFormat} for one-time use:
233
* <blockquote><pre>
234
* int planet = 7;
235
* String event = "a disturbance in the Force";
236
*
237
* String result = MessageFormat.format(
238
* "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
239
* planet, new Date(), event);
240
* </pre></blockquote>
241
* The output is:
242
* <blockquote><pre>
243
* At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
244
* </pre></blockquote>
245
*
246
* <p>
247
* The following example creates a {@code MessageFormat} instance that
248
* can be used repeatedly:
249
* <blockquote><pre>
250
* int fileCount = 1273;
251
* String diskName = "MyDisk";
252
* Object[] testArgs = {new Long(fileCount), diskName};
253
*
254
* MessageFormat form = new MessageFormat(
255
* "The disk \"{1}\" contains {0} file(s).");
256
*
257
* System.out.println(form.format(testArgs));
258
* </pre></blockquote>
259
* The output with different values for {@code fileCount}:
260
* <blockquote><pre>
261
* The disk "MyDisk" contains 0 file(s).
262
* The disk "MyDisk" contains 1 file(s).
263
* The disk "MyDisk" contains 1,273 file(s).
264
* </pre></blockquote>
265
*
266
* <p>
267
* For more sophisticated patterns, you can use a {@code ChoiceFormat}
268
* to produce correct forms for singular and plural:
269
* <blockquote><pre>
270
* MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
271
* double[] filelimits = {0,1,2};
272
* String[] filepart = {"no files","one file","{0,number} files"};
273
* ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
274
* form.setFormatByArgumentIndex(0, fileform);
275
*
276
* int fileCount = 1273;
277
* String diskName = "MyDisk";
278
* Object[] testArgs = {new Long(fileCount), diskName};
279
*
280
* System.out.println(form.format(testArgs));
281
* </pre></blockquote>
282
* The output with different values for {@code fileCount}:
283
* <blockquote><pre>
284
* The disk "MyDisk" contains no files.
285
* The disk "MyDisk" contains one file.
286
* The disk "MyDisk" contains 1,273 files.
287
* </pre></blockquote>
288
*
289
* <p>
290
* You can create the {@code ChoiceFormat} programmatically, as in the
291
* above example, or by using a pattern. See {@link ChoiceFormat}
292
* for more information.
293
* <blockquote><pre>{@code
294
* form.applyPattern(
295
* "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");
296
* }</pre></blockquote>
297
*
298
* <p>
299
* <strong>Note:</strong> As we see above, the string produced
300
* by a {@code ChoiceFormat} in {@code MessageFormat} is treated as special;
301
* occurrences of '{' are used to indicate subformats, and cause recursion.
302
* If you create both a {@code MessageFormat} and {@code ChoiceFormat}
303
* programmatically (instead of using the string patterns), then be careful not to
304
* produce a format that recurses on itself, which will cause an infinite loop.
305
* <p>
306
* When a single argument is parsed more than once in the string, the last match
307
* will be the final result of the parsing. For example,
308
* <blockquote><pre>
309
* MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
310
* Object[] objs = {new Double(3.1415)};
311
* String result = mf.format( objs );
312
* // result now equals "3.14, 3.1"
313
* objs = null;
314
* objs = mf.parse(result, new ParsePosition(0));
315
* // objs now equals {new Double(3.1)}
316
* </pre></blockquote>
317
*
318
* <p>
319
* Likewise, parsing with a {@code MessageFormat} object using patterns containing
320
* multiple occurrences of the same argument would return the last match. For
321
* example,
322
* <blockquote><pre>
323
* MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
324
* String forParsing = "x, y, z";
325
* Object[] objs = mf.parse(forParsing, new ParsePosition(0));
326
* // result now equals {new String("z")}
327
* </pre></blockquote>
328
*
329
* <h3><a id="synchronization">Synchronization</a></h3>
330
*
331
* <p>
332
* Message formats are not synchronized.
333
* It is recommended to create separate format instances for each thread.
334
* If multiple threads access a format concurrently, it must be synchronized
335
* externally.
336
*
337
* @see java.util.Locale
338
* @see Format
339
* @see NumberFormat
340
* @see DecimalFormat
341
* @see DecimalFormatSymbols
342
* @see ChoiceFormat
343
* @see DateFormat
344
* @see SimpleDateFormat
345
*
346
* @author Mark Davis
347
* @since 1.1
348
*/
349
350
public class MessageFormat extends Format {
351
352
@java.io.Serial
353
private static final long serialVersionUID = 6479157306784022952L;
354
355
/**
356
* Constructs a MessageFormat for the default
357
* {@link java.util.Locale.Category#FORMAT FORMAT} locale and the
358
* specified pattern.
359
* The constructor first sets the locale, then parses the pattern and
360
* creates a list of subformats for the format elements contained in it.
361
* Patterns and their interpretation are specified in the
362
* <a href="#patterns">class description</a>.
363
*
364
* @param pattern the pattern for this message format
365
* @throws IllegalArgumentException if the pattern is invalid
366
* @throws NullPointerException if {@code pattern} is
367
* {@code null}
368
*/
369
public MessageFormat(String pattern) {
370
this.locale = Locale.getDefault(Locale.Category.FORMAT);
371
applyPattern(pattern);
372
}
373
374
/**
375
* Constructs a MessageFormat for the specified locale and
376
* pattern.
377
* The constructor first sets the locale, then parses the pattern and
378
* creates a list of subformats for the format elements contained in it.
379
* Patterns and their interpretation are specified in the
380
* <a href="#patterns">class description</a>.
381
*
382
* @param pattern the pattern for this message format
383
* @param locale the locale for this message format
384
* @throws IllegalArgumentException if the pattern is invalid
385
* @throws NullPointerException if {@code pattern} is
386
* {@code null}
387
* @since 1.4
388
*/
389
public MessageFormat(String pattern, Locale locale) {
390
this.locale = locale;
391
applyPattern(pattern);
392
}
393
394
/**
395
* Sets the locale to be used when creating or comparing subformats.
396
* This affects subsequent calls
397
* <ul>
398
* <li>to the {@link #applyPattern applyPattern}
399
* and {@link #toPattern toPattern} methods if format elements specify
400
* a format type and therefore have the subformats created in the
401
* {@code applyPattern} method, as well as
402
* <li>to the {@code format} and
403
* {@link #formatToCharacterIterator formatToCharacterIterator} methods
404
* if format elements do not specify a format type and therefore have
405
* the subformats created in the formatting methods.
406
* </ul>
407
* Subformats that have already been created are not affected.
408
*
409
* @param locale the locale to be used when creating or comparing subformats
410
*/
411
public void setLocale(Locale locale) {
412
this.locale = locale;
413
}
414
415
/**
416
* Gets the locale that's used when creating or comparing subformats.
417
*
418
* @return the locale used when creating or comparing subformats
419
*/
420
public Locale getLocale() {
421
return locale;
422
}
423
424
425
/**
426
* Sets the pattern used by this message format.
427
* The method parses the pattern and creates a list of subformats
428
* for the format elements contained in it.
429
* Patterns and their interpretation are specified in the
430
* <a href="#patterns">class description</a>.
431
*
432
* @param pattern the pattern for this message format
433
* @throws IllegalArgumentException if the pattern is invalid
434
* @throws NullPointerException if {@code pattern} is
435
* {@code null}
436
*/
437
@SuppressWarnings("fallthrough") // fallthrough in switch is expected, suppress it
438
public void applyPattern(String pattern) {
439
StringBuilder[] segments = new StringBuilder[4];
440
// Allocate only segments[SEG_RAW] here. The rest are
441
// allocated on demand.
442
segments[SEG_RAW] = new StringBuilder();
443
444
int part = SEG_RAW;
445
int formatNumber = 0;
446
boolean inQuote = false;
447
int braceStack = 0;
448
maxOffset = -1;
449
for (int i = 0; i < pattern.length(); ++i) {
450
char ch = pattern.charAt(i);
451
if (part == SEG_RAW) {
452
if (ch == '\'') {
453
if (i + 1 < pattern.length()
454
&& pattern.charAt(i+1) == '\'') {
455
segments[part].append(ch); // handle doubles
456
++i;
457
} else {
458
inQuote = !inQuote;
459
}
460
} else if (ch == '{' && !inQuote) {
461
part = SEG_INDEX;
462
if (segments[SEG_INDEX] == null) {
463
segments[SEG_INDEX] = new StringBuilder();
464
}
465
} else {
466
segments[part].append(ch);
467
}
468
} else {
469
if (inQuote) { // just copy quotes in parts
470
segments[part].append(ch);
471
if (ch == '\'') {
472
inQuote = false;
473
}
474
} else {
475
switch (ch) {
476
case ',':
477
if (part < SEG_MODIFIER) {
478
if (segments[++part] == null) {
479
segments[part] = new StringBuilder();
480
}
481
} else {
482
segments[part].append(ch);
483
}
484
break;
485
case '{':
486
++braceStack;
487
segments[part].append(ch);
488
break;
489
case '}':
490
if (braceStack == 0) {
491
part = SEG_RAW;
492
makeFormat(i, formatNumber, segments);
493
formatNumber++;
494
// throw away other segments
495
segments[SEG_INDEX] = null;
496
segments[SEG_TYPE] = null;
497
segments[SEG_MODIFIER] = null;
498
} else {
499
--braceStack;
500
segments[part].append(ch);
501
}
502
break;
503
case ' ':
504
// Skip any leading space chars for SEG_TYPE.
505
if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) {
506
segments[part].append(ch);
507
}
508
break;
509
case '\'':
510
inQuote = true;
511
// fall through, so we keep quotes in other parts
512
default:
513
segments[part].append(ch);
514
break;
515
}
516
}
517
}
518
}
519
if (braceStack == 0 && part != 0) {
520
maxOffset = -1;
521
throw new IllegalArgumentException("Unmatched braces in the pattern.");
522
}
523
this.pattern = segments[0].toString();
524
}
525
526
527
/**
528
* Returns a pattern representing the current state of the message format.
529
* The string is constructed from internal information and therefore
530
* does not necessarily equal the previously applied pattern.
531
*
532
* @return a pattern representing the current state of the message format
533
*/
534
public String toPattern() {
535
// later, make this more extensible
536
int lastOffset = 0;
537
StringBuilder result = new StringBuilder();
538
for (int i = 0; i <= maxOffset; ++i) {
539
copyAndFixQuotes(pattern, lastOffset, offsets[i], result);
540
lastOffset = offsets[i];
541
result.append('{').append(argumentNumbers[i]);
542
Format fmt = formats[i];
543
if (fmt == null) {
544
// do nothing, string format
545
} else if (fmt instanceof NumberFormat) {
546
if (fmt.equals(NumberFormat.getInstance(locale))) {
547
result.append(",number");
548
} else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) {
549
result.append(",number,currency");
550
} else if (fmt.equals(NumberFormat.getPercentInstance(locale))) {
551
result.append(",number,percent");
552
} else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) {
553
result.append(",number,integer");
554
} else {
555
if (fmt instanceof DecimalFormat) {
556
result.append(",number,").append(((DecimalFormat)fmt).toPattern());
557
} else if (fmt instanceof ChoiceFormat) {
558
result.append(",choice,").append(((ChoiceFormat)fmt).toPattern());
559
} else {
560
// UNKNOWN
561
}
562
}
563
} else if (fmt instanceof DateFormat) {
564
int index;
565
for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) {
566
DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index],
567
locale);
568
if (fmt.equals(df)) {
569
result.append(",date");
570
break;
571
}
572
df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index],
573
locale);
574
if (fmt.equals(df)) {
575
result.append(",time");
576
break;
577
}
578
}
579
if (index >= DATE_TIME_MODIFIERS.length) {
580
if (fmt instanceof SimpleDateFormat) {
581
result.append(",date,").append(((SimpleDateFormat)fmt).toPattern());
582
} else {
583
// UNKNOWN
584
}
585
} else if (index != MODIFIER_DEFAULT) {
586
result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]);
587
}
588
} else {
589
//result.append(", unknown");
590
}
591
result.append('}');
592
}
593
copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
594
return result.toString();
595
}
596
597
/**
598
* Sets the formats to use for the values passed into
599
* {@code format} methods or returned from {@code parse}
600
* methods. The indices of elements in {@code newFormats}
601
* correspond to the argument indices used in the previously set
602
* pattern string.
603
* The order of formats in {@code newFormats} thus corresponds to
604
* the order of elements in the {@code arguments} array passed
605
* to the {@code format} methods or the result array returned
606
* by the {@code parse} methods.
607
* <p>
608
* If an argument index is used for more than one format element
609
* in the pattern string, then the corresponding new format is used
610
* for all such format elements. If an argument index is not used
611
* for any format element in the pattern string, then the
612
* corresponding new format is ignored. If fewer formats are provided
613
* than needed, then only the formats for argument indices less
614
* than {@code newFormats.length} are replaced.
615
*
616
* @param newFormats the new formats to use
617
* @throws NullPointerException if {@code newFormats} is null
618
* @since 1.4
619
*/
620
public void setFormatsByArgumentIndex(Format[] newFormats) {
621
for (int i = 0; i <= maxOffset; i++) {
622
int j = argumentNumbers[i];
623
if (j < newFormats.length) {
624
formats[i] = newFormats[j];
625
}
626
}
627
}
628
629
/**
630
* Sets the formats to use for the format elements in the
631
* previously set pattern string.
632
* The order of formats in {@code newFormats} corresponds to
633
* the order of format elements in the pattern string.
634
* <p>
635
* If more formats are provided than needed by the pattern string,
636
* the remaining ones are ignored. If fewer formats are provided
637
* than needed, then only the first {@code newFormats.length}
638
* formats are replaced.
639
* <p>
640
* Since the order of format elements in a pattern string often
641
* changes during localization, it is generally better to use the
642
* {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
643
* method, which assumes an order of formats corresponding to the
644
* order of elements in the {@code arguments} array passed to
645
* the {@code format} methods or the result array returned by
646
* the {@code parse} methods.
647
*
648
* @param newFormats the new formats to use
649
* @throws NullPointerException if {@code newFormats} is null
650
*/
651
public void setFormats(Format[] newFormats) {
652
int runsToCopy = newFormats.length;
653
if (runsToCopy > maxOffset + 1) {
654
runsToCopy = maxOffset + 1;
655
}
656
for (int i = 0; i < runsToCopy; i++) {
657
formats[i] = newFormats[i];
658
}
659
}
660
661
/**
662
* Sets the format to use for the format elements within the
663
* previously set pattern string that use the given argument
664
* index.
665
* The argument index is part of the format element definition and
666
* represents an index into the {@code arguments} array passed
667
* to the {@code format} methods or the result array returned
668
* by the {@code parse} methods.
669
* <p>
670
* If the argument index is used for more than one format element
671
* in the pattern string, then the new format is used for all such
672
* format elements. If the argument index is not used for any format
673
* element in the pattern string, then the new format is ignored.
674
*
675
* @param argumentIndex the argument index for which to use the new format
676
* @param newFormat the new format to use
677
* @since 1.4
678
*/
679
public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
680
for (int j = 0; j <= maxOffset; j++) {
681
if (argumentNumbers[j] == argumentIndex) {
682
formats[j] = newFormat;
683
}
684
}
685
}
686
687
/**
688
* Sets the format to use for the format element with the given
689
* format element index within the previously set pattern string.
690
* The format element index is the zero-based number of the format
691
* element counting from the start of the pattern string.
692
* <p>
693
* Since the order of format elements in a pattern string often
694
* changes during localization, it is generally better to use the
695
* {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
696
* method, which accesses format elements based on the argument
697
* index they specify.
698
*
699
* @param formatElementIndex the index of a format element within the pattern
700
* @param newFormat the format to use for the specified format element
701
* @throws ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or
702
* larger than the number of format elements in the pattern string
703
*/
704
public void setFormat(int formatElementIndex, Format newFormat) {
705
706
if (formatElementIndex > maxOffset) {
707
throw new ArrayIndexOutOfBoundsException(formatElementIndex);
708
}
709
formats[formatElementIndex] = newFormat;
710
}
711
712
/**
713
* Gets the formats used for the values passed into
714
* {@code format} methods or returned from {@code parse}
715
* methods. The indices of elements in the returned array
716
* correspond to the argument indices used in the previously set
717
* pattern string.
718
* The order of formats in the returned array thus corresponds to
719
* the order of elements in the {@code arguments} array passed
720
* to the {@code format} methods or the result array returned
721
* by the {@code parse} methods.
722
* <p>
723
* If an argument index is used for more than one format element
724
* in the pattern string, then the format used for the last such
725
* format element is returned in the array. If an argument index
726
* is not used for any format element in the pattern string, then
727
* null is returned in the array.
728
*
729
* @return the formats used for the arguments within the pattern
730
* @since 1.4
731
*/
732
public Format[] getFormatsByArgumentIndex() {
733
int maximumArgumentNumber = -1;
734
for (int i = 0; i <= maxOffset; i++) {
735
if (argumentNumbers[i] > maximumArgumentNumber) {
736
maximumArgumentNumber = argumentNumbers[i];
737
}
738
}
739
Format[] resultArray = new Format[maximumArgumentNumber + 1];
740
for (int i = 0; i <= maxOffset; i++) {
741
resultArray[argumentNumbers[i]] = formats[i];
742
}
743
return resultArray;
744
}
745
746
/**
747
* Gets the formats used for the format elements in the
748
* previously set pattern string.
749
* The order of formats in the returned array corresponds to
750
* the order of format elements in the pattern string.
751
* <p>
752
* Since the order of format elements in a pattern string often
753
* changes during localization, it's generally better to use the
754
* {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}
755
* method, which assumes an order of formats corresponding to the
756
* order of elements in the {@code arguments} array passed to
757
* the {@code format} methods or the result array returned by
758
* the {@code parse} methods.
759
*
760
* @return the formats used for the format elements in the pattern
761
*/
762
public Format[] getFormats() {
763
Format[] resultArray = new Format[maxOffset + 1];
764
System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);
765
return resultArray;
766
}
767
768
/**
769
* Formats an array of objects and appends the {@code MessageFormat}'s
770
* pattern, with format elements replaced by the formatted objects, to the
771
* provided {@code StringBuffer}.
772
* <p>
773
* The text substituted for the individual format elements is derived from
774
* the current subformat of the format element and the
775
* {@code arguments} element at the format element's argument index
776
* as indicated by the first matching line of the following table. An
777
* argument is <i>unavailable</i> if {@code arguments} is
778
* {@code null} or has fewer than argumentIndex+1 elements.
779
*
780
* <table class="plain">
781
* <caption style="display:none">Examples of subformat,argument,and formatted text</caption>
782
* <thead>
783
* <tr>
784
* <th scope="col">Subformat
785
* <th scope="col">Argument
786
* <th scope="col">Formatted Text
787
* </thead>
788
* <tbody>
789
* <tr>
790
* <th scope="row" style="text-weight-normal" rowspan=2><i>any</i>
791
* <th scope="row" style="text-weight-normal"><i>unavailable</i>
792
* <td><code>"{" + argumentIndex + "}"</code>
793
* <tr>
794
* <th scope="row" style="text-weight-normal">{@code null}
795
* <td>{@code "null"}
796
* <tr>
797
* <th scope="row" style="text-weight-normal">{@code instanceof ChoiceFormat}
798
* <th scope="row" style="text-weight-normal"><i>any</i>
799
* <td><code>subformat.format(argument).indexOf('{') &gt;= 0 ?<br>
800
* (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :
801
* subformat.format(argument)</code>
802
* <tr>
803
* <th scope="row" style="text-weight-normal">{@code != null}
804
* <th scope="row" style="text-weight-normal"><i>any</i>
805
* <td>{@code subformat.format(argument)}
806
* <tr>
807
* <th scope="row" style="text-weight-normal" rowspan=4>{@code null}
808
* <th scope="row" style="text-weight-normal">{@code instanceof Number}
809
* <td>{@code NumberFormat.getInstance(getLocale()).format(argument)}
810
* <tr>
811
* <th scope="row" style="text-weight-normal">{@code instanceof Date}
812
* <td>{@code DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)}
813
* <tr>
814
* <th scope="row" style="text-weight-normal">{@code instanceof String}
815
* <td>{@code argument}
816
* <tr>
817
* <th scope="row" style="text-weight-normal"><i>any</i>
818
* <td>{@code argument.toString()}
819
* </tbody>
820
* </table>
821
* <p>
822
* If {@code pos} is non-null, and refers to
823
* {@code Field.ARGUMENT}, the location of the first formatted
824
* string will be returned.
825
*
826
* @param arguments an array of objects to be formatted and substituted.
827
* @param result where text is appended.
828
* @param pos keeps track on the position of the first replaced argument
829
* in the output string.
830
* @return the string buffer passed in as {@code result}, with formatted
831
* text appended
832
* @throws IllegalArgumentException if an argument in the
833
* {@code arguments} array is not of the type
834
* expected by the format element(s) that use it.
835
* @throws NullPointerException if {@code result} is {@code null}
836
*/
837
public final StringBuffer format(Object[] arguments, StringBuffer result,
838
FieldPosition pos)
839
{
840
return subformat(arguments, result, pos, null);
841
}
842
843
/**
844
* Creates a MessageFormat with the given pattern and uses it
845
* to format the given arguments. This is equivalent to
846
* <blockquote>
847
* <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
848
* </blockquote>
849
*
850
* @param pattern the pattern string
851
* @param arguments object(s) to format
852
* @return the formatted string
853
* @throws IllegalArgumentException if the pattern is invalid,
854
* or if an argument in the {@code arguments} array
855
* is not of the type expected by the format element(s)
856
* that use it.
857
* @throws NullPointerException if {@code pattern} is {@code null}
858
*/
859
public static String format(String pattern, Object ... arguments) {
860
MessageFormat temp = new MessageFormat(pattern);
861
return temp.format(arguments);
862
}
863
864
// Overrides
865
/**
866
* Formats an array of objects and appends the {@code MessageFormat}'s
867
* pattern, with format elements replaced by the formatted objects, to the
868
* provided {@code StringBuffer}.
869
* This is equivalent to
870
* <blockquote>
871
* <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
872
* </blockquote>
873
*
874
* @param arguments an array of objects to be formatted and substituted.
875
* @param result where text is appended.
876
* @param pos keeps track on the position of the first replaced argument
877
* in the output string.
878
* @throws IllegalArgumentException if an argument in the
879
* {@code arguments} array is not of the type
880
* expected by the format element(s) that use it.
881
* @throws NullPointerException if {@code result} is {@code null}
882
*/
883
public final StringBuffer format(Object arguments, StringBuffer result,
884
FieldPosition pos)
885
{
886
return subformat((Object[]) arguments, result, pos, null);
887
}
888
889
/**
890
* Formats an array of objects and inserts them into the
891
* {@code MessageFormat}'s pattern, producing an
892
* {@code AttributedCharacterIterator}.
893
* You can use the returned {@code AttributedCharacterIterator}
894
* to build the resulting String, as well as to determine information
895
* about the resulting String.
896
* <p>
897
* The text of the returned {@code AttributedCharacterIterator} is
898
* the same that would be returned by
899
* <blockquote>
900
* <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
901
* </blockquote>
902
* <p>
903
* In addition, the {@code AttributedCharacterIterator} contains at
904
* least attributes indicating where text was generated from an
905
* argument in the {@code arguments} array. The keys of these attributes are of
906
* type {@code MessageFormat.Field}, their values are
907
* {@code Integer} objects indicating the index in the {@code arguments}
908
* array of the argument from which the text was generated.
909
* <p>
910
* The attributes/value from the underlying {@code Format}
911
* instances that {@code MessageFormat} uses will also be
912
* placed in the resulting {@code AttributedCharacterIterator}.
913
* This allows you to not only find where an argument is placed in the
914
* resulting String, but also which fields it contains in turn.
915
*
916
* @param arguments an array of objects to be formatted and substituted.
917
* @return AttributedCharacterIterator describing the formatted value.
918
* @throws NullPointerException if {@code arguments} is null.
919
* @throws IllegalArgumentException if an argument in the
920
* {@code arguments} array is not of the type
921
* expected by the format element(s) that use it.
922
* @since 1.4
923
*/
924
public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
925
StringBuffer result = new StringBuffer();
926
ArrayList<AttributedCharacterIterator> iterators = new ArrayList<>();
927
928
if (arguments == null) {
929
throw new NullPointerException(
930
"formatToCharacterIterator must be passed non-null object");
931
}
932
subformat((Object[]) arguments, result, null, iterators);
933
if (iterators.size() == 0) {
934
return createAttributedCharacterIterator("");
935
}
936
return createAttributedCharacterIterator(
937
iterators.toArray(
938
new AttributedCharacterIterator[iterators.size()]));
939
}
940
941
/**
942
* Parses the string.
943
*
944
* <p>Caveats: The parse may fail in a number of circumstances.
945
* For example:
946
* <ul>
947
* <li>If one of the arguments does not occur in the pattern.
948
* <li>If the format of an argument loses information, such as
949
* with a choice format where a large number formats to "many".
950
* <li>Does not yet handle recursion (where
951
* the substituted strings contain {n} references.)
952
* <li>Will not always find a match (or the correct match)
953
* if some part of the parse is ambiguous.
954
* For example, if the pattern "{1},{2}" is used with the
955
* string arguments {"a,b", "c"}, it will format as "a,b,c".
956
* When the result is parsed, it will return {"a", "b,c"}.
957
* <li>If a single argument is parsed more than once in the string,
958
* then the later parse wins.
959
* </ul>
960
* When the parse fails, use ParsePosition.getErrorIndex() to find out
961
* where in the string the parsing failed. The returned error
962
* index is the starting offset of the sub-patterns that the string
963
* is comparing with. For example, if the parsing string "AAA {0} BBB"
964
* is comparing against the pattern "AAD {0} BBB", the error index is
965
* 0. When an error occurs, the call to this method will return null.
966
* If the source is null, return an empty array.
967
*
968
* @param source the string to parse
969
* @param pos the parse position
970
* @return an array of parsed objects
971
* @throws NullPointerException if {@code pos} is {@code null}
972
* for a non-null {@code source} string.
973
*/
974
public Object[] parse(String source, ParsePosition pos) {
975
if (source == null) {
976
Object[] empty = {};
977
return empty;
978
}
979
980
int maximumArgumentNumber = -1;
981
for (int i = 0; i <= maxOffset; i++) {
982
if (argumentNumbers[i] > maximumArgumentNumber) {
983
maximumArgumentNumber = argumentNumbers[i];
984
}
985
}
986
Object[] resultArray = new Object[maximumArgumentNumber + 1];
987
988
int patternOffset = 0;
989
int sourceOffset = pos.index;
990
ParsePosition tempStatus = new ParsePosition(0);
991
for (int i = 0; i <= maxOffset; ++i) {
992
// match up to format
993
int len = offsets[i] - patternOffset;
994
if (len == 0 || pattern.regionMatches(patternOffset,
995
source, sourceOffset, len)) {
996
sourceOffset += len;
997
patternOffset += len;
998
} else {
999
pos.errorIndex = sourceOffset;
1000
return null; // leave index as is to signal error
1001
}
1002
1003
// now use format
1004
if (formats[i] == null) { // string format
1005
// if at end, use longest possible match
1006
// otherwise uses first match to intervening string
1007
// does NOT recursively try all possibilities
1008
int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
1009
1010
int next;
1011
if (patternOffset >= tempLength) {
1012
next = source.length();
1013
}else{
1014
next = source.indexOf(pattern.substring(patternOffset, tempLength),
1015
sourceOffset);
1016
}
1017
1018
if (next < 0) {
1019
pos.errorIndex = sourceOffset;
1020
return null; // leave index as is to signal error
1021
} else {
1022
String strValue= source.substring(sourceOffset,next);
1023
if (!strValue.equals("{"+argumentNumbers[i]+"}"))
1024
resultArray[argumentNumbers[i]]
1025
= source.substring(sourceOffset,next);
1026
sourceOffset = next;
1027
}
1028
} else {
1029
tempStatus.index = sourceOffset;
1030
resultArray[argumentNumbers[i]]
1031
= formats[i].parseObject(source,tempStatus);
1032
if (tempStatus.index == sourceOffset) {
1033
pos.errorIndex = sourceOffset;
1034
return null; // leave index as is to signal error
1035
}
1036
sourceOffset = tempStatus.index; // update
1037
}
1038
}
1039
int len = pattern.length() - patternOffset;
1040
if (len == 0 || pattern.regionMatches(patternOffset,
1041
source, sourceOffset, len)) {
1042
pos.index = sourceOffset + len;
1043
} else {
1044
pos.errorIndex = sourceOffset;
1045
return null; // leave index as is to signal error
1046
}
1047
return resultArray;
1048
}
1049
1050
/**
1051
* Parses text from the beginning of the given string to produce an object
1052
* array.
1053
* The method may not use the entire text of the given string.
1054
* <p>
1055
* See the {@link #parse(String, ParsePosition)} method for more information
1056
* on message parsing.
1057
*
1058
* @param source A {@code String} whose beginning should be parsed.
1059
* @return An {@code Object} array parsed from the string.
1060
* @throws ParseException if the beginning of the specified string
1061
* cannot be parsed.
1062
*/
1063
public Object[] parse(String source) throws ParseException {
1064
ParsePosition pos = new ParsePosition(0);
1065
Object[] result = parse(source, pos);
1066
if (pos.index == 0) // unchanged, returned object is null
1067
throw new ParseException("MessageFormat parse error!", pos.errorIndex);
1068
1069
return result;
1070
}
1071
1072
/**
1073
* Parses text from a string to produce an object array.
1074
* <p>
1075
* The method attempts to parse text starting at the index given by
1076
* {@code pos}.
1077
* If parsing succeeds, then the index of {@code pos} is updated
1078
* to the index after the last character used (parsing does not necessarily
1079
* use all characters up to the end of the string), and the parsed
1080
* object array is returned. The updated {@code pos} can be used to
1081
* indicate the starting point for the next call to this method.
1082
* If an error occurs, then the index of {@code pos} is not
1083
* changed, the error index of {@code pos} is set to the index of
1084
* the character where the error occurred, and null is returned.
1085
* <p>
1086
* See the {@link #parse(String, ParsePosition)} method for more information
1087
* on message parsing.
1088
*
1089
* @param source A {@code String}, part of which should be parsed.
1090
* @param pos A {@code ParsePosition} object with index and error
1091
* index information as described above.
1092
* @return An {@code Object} array parsed from the string. In case of
1093
* error, returns null.
1094
* @throws NullPointerException if {@code pos} is null.
1095
*/
1096
public Object parseObject(String source, ParsePosition pos) {
1097
return parse(source, pos);
1098
}
1099
1100
/**
1101
* Creates and returns a copy of this object.
1102
*
1103
* @return a clone of this instance.
1104
*/
1105
public Object clone() {
1106
MessageFormat other = (MessageFormat) super.clone();
1107
1108
// clone arrays. Can't do with utility because of bug in Cloneable
1109
other.formats = formats.clone(); // shallow clone
1110
for (int i = 0; i < formats.length; ++i) {
1111
if (formats[i] != null)
1112
other.formats[i] = (Format)formats[i].clone();
1113
}
1114
// for primitives or immutables, shallow clone is enough
1115
other.offsets = offsets.clone();
1116
other.argumentNumbers = argumentNumbers.clone();
1117
1118
return other;
1119
}
1120
1121
/**
1122
* Equality comparison between two message format objects
1123
*/
1124
public boolean equals(Object obj) {
1125
if (this == obj) // quick check
1126
return true;
1127
if (obj == null || getClass() != obj.getClass())
1128
return false;
1129
MessageFormat other = (MessageFormat) obj;
1130
return (maxOffset == other.maxOffset
1131
&& pattern.equals(other.pattern)
1132
&& ((locale != null && locale.equals(other.locale))
1133
|| (locale == null && other.locale == null))
1134
&& Arrays.equals(offsets,other.offsets)
1135
&& Arrays.equals(argumentNumbers,other.argumentNumbers)
1136
&& Arrays.equals(formats,other.formats));
1137
}
1138
1139
/**
1140
* Generates a hash code for the message format object.
1141
*/
1142
public int hashCode() {
1143
return pattern.hashCode(); // enough for reasonable distribution
1144
}
1145
1146
1147
/**
1148
* Defines constants that are used as attribute keys in the
1149
* {@code AttributedCharacterIterator} returned
1150
* from {@code MessageFormat.formatToCharacterIterator}.
1151
*
1152
* @since 1.4
1153
*/
1154
public static class Field extends Format.Field {
1155
1156
// Proclaim serial compatibility with 1.4 FCS
1157
@java.io.Serial
1158
private static final long serialVersionUID = 7899943957617360810L;
1159
1160
/**
1161
* Creates a Field with the specified name.
1162
*
1163
* @param name Name of the attribute
1164
*/
1165
protected Field(String name) {
1166
super(name);
1167
}
1168
1169
/**
1170
* Resolves instances being deserialized to the predefined constants.
1171
*
1172
* @throws InvalidObjectException if the constant could not be
1173
* resolved.
1174
* @return resolved MessageFormat.Field constant
1175
*/
1176
@java.io.Serial
1177
protected Object readResolve() throws InvalidObjectException {
1178
if (this.getClass() != MessageFormat.Field.class) {
1179
throw new InvalidObjectException("subclass didn't correctly implement readResolve");
1180
}
1181
1182
return ARGUMENT;
1183
}
1184
1185
//
1186
// The constants
1187
//
1188
1189
/**
1190
* Constant identifying a portion of a message that was generated
1191
* from an argument passed into {@code formatToCharacterIterator}.
1192
* The value associated with the key will be an {@code Integer}
1193
* indicating the index in the {@code arguments} array of the
1194
* argument from which the text was generated.
1195
*/
1196
public static final Field ARGUMENT =
1197
new Field("message argument field");
1198
}
1199
1200
// ===========================privates============================
1201
1202
/**
1203
* The locale to use for formatting numbers and dates.
1204
* @serial
1205
*/
1206
private Locale locale;
1207
1208
/**
1209
* The string that the formatted values are to be plugged into. In other words, this
1210
* is the pattern supplied on construction with all of the {} expressions taken out.
1211
* @serial
1212
*/
1213
private String pattern = "";
1214
1215
/** The initially expected number of subformats in the format */
1216
private static final int INITIAL_FORMATS = 10;
1217
1218
/**
1219
* An array of formatters, which are used to format the arguments.
1220
* @serial
1221
*/
1222
private Format[] formats = new Format[INITIAL_FORMATS];
1223
1224
/**
1225
* The positions where the results of formatting each argument are to be inserted
1226
* into the pattern.
1227
* @serial
1228
*/
1229
private int[] offsets = new int[INITIAL_FORMATS];
1230
1231
/**
1232
* The argument numbers corresponding to each formatter. (The formatters are stored
1233
* in the order they occur in the pattern, not in the order in which the arguments
1234
* are specified.)
1235
* @serial
1236
*/
1237
private int[] argumentNumbers = new int[INITIAL_FORMATS];
1238
1239
/**
1240
* One less than the number of entries in {@code offsets}. Can also be thought of
1241
* as the index of the highest-numbered element in {@code offsets} that is being used.
1242
* All of these arrays should have the same number of elements being used as {@code offsets}
1243
* does, and so this variable suffices to tell us how many entries are in all of them.
1244
* @serial
1245
*/
1246
private int maxOffset = -1;
1247
1248
/**
1249
* Internal routine used by format. If {@code characterIterators} is
1250
* {@code non-null}, AttributedCharacterIterator will be created from the
1251
* subformats as necessary. If {@code characterIterators} is {@code null}
1252
* and {@code fp} is {@code non-null} and identifies
1253
* {@code Field.ARGUMENT} as the field attribute, the location of
1254
* the first replaced argument will be set in it.
1255
*
1256
* @throws IllegalArgumentException if an argument in the
1257
* {@code arguments} array is not of the type
1258
* expected by the format element(s) that use it.
1259
*/
1260
private StringBuffer subformat(Object[] arguments, StringBuffer result,
1261
FieldPosition fp, List<AttributedCharacterIterator> characterIterators) {
1262
// note: this implementation assumes a fast substring & index.
1263
// if this is not true, would be better to append chars one by one.
1264
int lastOffset = 0;
1265
int last = result.length();
1266
for (int i = 0; i <= maxOffset; ++i) {
1267
result.append(pattern, lastOffset, offsets[i]);
1268
lastOffset = offsets[i];
1269
int argumentNumber = argumentNumbers[i];
1270
if (arguments == null || argumentNumber >= arguments.length) {
1271
result.append('{').append(argumentNumber).append('}');
1272
continue;
1273
}
1274
// int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
1275
if (false) { // if (argRecursion == 3){
1276
// prevent loop!!!
1277
result.append('\uFFFD');
1278
} else {
1279
Object obj = arguments[argumentNumber];
1280
String arg = null;
1281
Format subFormatter = null;
1282
if (obj == null) {
1283
arg = "null";
1284
} else if (formats[i] != null) {
1285
subFormatter = formats[i];
1286
if (subFormatter instanceof ChoiceFormat) {
1287
arg = formats[i].format(obj);
1288
if (arg.indexOf('{') >= 0) {
1289
subFormatter = new MessageFormat(arg, locale);
1290
obj = arguments;
1291
arg = null;
1292
}
1293
}
1294
} else if (obj instanceof Number) {
1295
// format number if can
1296
subFormatter = NumberFormat.getInstance(locale);
1297
} else if (obj instanceof Date) {
1298
// format a Date if can
1299
subFormatter = DateFormat.getDateTimeInstance(
1300
DateFormat.SHORT, DateFormat.SHORT, locale);//fix
1301
} else if (obj instanceof String) {
1302
arg = (String) obj;
1303
1304
} else {
1305
arg = obj.toString();
1306
if (arg == null) arg = "null";
1307
}
1308
1309
// At this point we are in two states, either subFormatter
1310
// is non-null indicating we should format obj using it,
1311
// or arg is non-null and we should use it as the value.
1312
1313
if (characterIterators != null) {
1314
// If characterIterators is non-null, it indicates we need
1315
// to get the CharacterIterator from the child formatter.
1316
if (last != result.length()) {
1317
characterIterators.add(
1318
createAttributedCharacterIterator(result.substring
1319
(last)));
1320
last = result.length();
1321
}
1322
if (subFormatter != null) {
1323
AttributedCharacterIterator subIterator =
1324
subFormatter.formatToCharacterIterator(obj);
1325
1326
append(result, subIterator);
1327
if (last != result.length()) {
1328
characterIterators.add(
1329
createAttributedCharacterIterator(
1330
subIterator, Field.ARGUMENT,
1331
Integer.valueOf(argumentNumber)));
1332
last = result.length();
1333
}
1334
arg = null;
1335
}
1336
if (arg != null && !arg.isEmpty()) {
1337
result.append(arg);
1338
characterIterators.add(
1339
createAttributedCharacterIterator(
1340
arg, Field.ARGUMENT,
1341
Integer.valueOf(argumentNumber)));
1342
last = result.length();
1343
}
1344
}
1345
else {
1346
if (subFormatter != null) {
1347
arg = subFormatter.format(obj);
1348
}
1349
last = result.length();
1350
result.append(arg);
1351
if (i == 0 && fp != null && Field.ARGUMENT.equals(
1352
fp.getFieldAttribute())) {
1353
fp.setBeginIndex(last);
1354
fp.setEndIndex(result.length());
1355
}
1356
last = result.length();
1357
}
1358
}
1359
}
1360
result.append(pattern, lastOffset, pattern.length());
1361
if (characterIterators != null && last != result.length()) {
1362
characterIterators.add(createAttributedCharacterIterator(
1363
result.substring(last)));
1364
}
1365
return result;
1366
}
1367
1368
/**
1369
* Convenience method to append all the characters in
1370
* {@code iterator} to the StringBuffer {@code result}.
1371
*/
1372
private void append(StringBuffer result, CharacterIterator iterator) {
1373
if (iterator.first() != CharacterIterator.DONE) {
1374
char aChar;
1375
1376
result.append(iterator.first());
1377
while ((aChar = iterator.next()) != CharacterIterator.DONE) {
1378
result.append(aChar);
1379
}
1380
}
1381
}
1382
1383
// Indices for segments
1384
private static final int SEG_RAW = 0;
1385
private static final int SEG_INDEX = 1;
1386
private static final int SEG_TYPE = 2;
1387
private static final int SEG_MODIFIER = 3; // modifier or subformat
1388
1389
// Indices for type keywords
1390
private static final int TYPE_NULL = 0;
1391
private static final int TYPE_NUMBER = 1;
1392
private static final int TYPE_DATE = 2;
1393
private static final int TYPE_TIME = 3;
1394
private static final int TYPE_CHOICE = 4;
1395
1396
private static final String[] TYPE_KEYWORDS = {
1397
"",
1398
"number",
1399
"date",
1400
"time",
1401
"choice"
1402
};
1403
1404
// Indices for number modifiers
1405
private static final int MODIFIER_DEFAULT = 0; // common in number and date-time
1406
private static final int MODIFIER_CURRENCY = 1;
1407
private static final int MODIFIER_PERCENT = 2;
1408
private static final int MODIFIER_INTEGER = 3;
1409
1410
private static final String[] NUMBER_MODIFIER_KEYWORDS = {
1411
"",
1412
"currency",
1413
"percent",
1414
"integer"
1415
};
1416
1417
// Indices for date-time modifiers
1418
private static final int MODIFIER_SHORT = 1;
1419
private static final int MODIFIER_MEDIUM = 2;
1420
private static final int MODIFIER_LONG = 3;
1421
private static final int MODIFIER_FULL = 4;
1422
1423
private static final String[] DATE_TIME_MODIFIER_KEYWORDS = {
1424
"",
1425
"short",
1426
"medium",
1427
"long",
1428
"full"
1429
};
1430
1431
// Date-time style values corresponding to the date-time modifiers.
1432
private static final int[] DATE_TIME_MODIFIERS = {
1433
DateFormat.DEFAULT,
1434
DateFormat.SHORT,
1435
DateFormat.MEDIUM,
1436
DateFormat.LONG,
1437
DateFormat.FULL,
1438
};
1439
1440
private void makeFormat(int position, int offsetNumber,
1441
StringBuilder[] textSegments)
1442
{
1443
String[] segments = new String[textSegments.length];
1444
for (int i = 0; i < textSegments.length; i++) {
1445
StringBuilder oneseg = textSegments[i];
1446
segments[i] = (oneseg != null) ? oneseg.toString() : "";
1447
}
1448
1449
// get the argument number
1450
int argumentNumber;
1451
try {
1452
argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized!
1453
} catch (NumberFormatException e) {
1454
throw new IllegalArgumentException("can't parse argument number: "
1455
+ segments[SEG_INDEX], e);
1456
}
1457
if (argumentNumber < 0) {
1458
throw new IllegalArgumentException("negative argument number: "
1459
+ argumentNumber);
1460
}
1461
1462
// resize format information arrays if necessary
1463
if (offsetNumber >= formats.length) {
1464
int newLength = formats.length * 2;
1465
Format[] newFormats = new Format[newLength];
1466
int[] newOffsets = new int[newLength];
1467
int[] newArgumentNumbers = new int[newLength];
1468
System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);
1469
System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);
1470
System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1);
1471
formats = newFormats;
1472
offsets = newOffsets;
1473
argumentNumbers = newArgumentNumbers;
1474
}
1475
int oldMaxOffset = maxOffset;
1476
maxOffset = offsetNumber;
1477
offsets[offsetNumber] = segments[SEG_RAW].length();
1478
argumentNumbers[offsetNumber] = argumentNumber;
1479
1480
// now get the format
1481
Format newFormat = null;
1482
if (!segments[SEG_TYPE].isEmpty()) {
1483
int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS);
1484
switch (type) {
1485
case TYPE_NULL:
1486
// Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}"
1487
// are treated as "{0}".
1488
break;
1489
1490
case TYPE_NUMBER:
1491
switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) {
1492
case MODIFIER_DEFAULT:
1493
newFormat = NumberFormat.getInstance(locale);
1494
break;
1495
case MODIFIER_CURRENCY:
1496
newFormat = NumberFormat.getCurrencyInstance(locale);
1497
break;
1498
case MODIFIER_PERCENT:
1499
newFormat = NumberFormat.getPercentInstance(locale);
1500
break;
1501
case MODIFIER_INTEGER:
1502
newFormat = NumberFormat.getIntegerInstance(locale);
1503
break;
1504
default: // DecimalFormat pattern
1505
try {
1506
newFormat = new DecimalFormat(segments[SEG_MODIFIER],
1507
DecimalFormatSymbols.getInstance(locale));
1508
} catch (IllegalArgumentException e) {
1509
maxOffset = oldMaxOffset;
1510
throw e;
1511
}
1512
break;
1513
}
1514
break;
1515
1516
case TYPE_DATE:
1517
case TYPE_TIME:
1518
int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS);
1519
if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) {
1520
if (type == TYPE_DATE) {
1521
newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod],
1522
locale);
1523
} else {
1524
newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod],
1525
locale);
1526
}
1527
} else {
1528
// SimpleDateFormat pattern
1529
try {
1530
newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale);
1531
} catch (IllegalArgumentException e) {
1532
maxOffset = oldMaxOffset;
1533
throw e;
1534
}
1535
}
1536
break;
1537
1538
case TYPE_CHOICE:
1539
try {
1540
// ChoiceFormat pattern
1541
newFormat = new ChoiceFormat(segments[SEG_MODIFIER]);
1542
} catch (Exception e) {
1543
maxOffset = oldMaxOffset;
1544
throw new IllegalArgumentException("Choice Pattern incorrect: "
1545
+ segments[SEG_MODIFIER], e);
1546
}
1547
break;
1548
1549
default:
1550
maxOffset = oldMaxOffset;
1551
throw new IllegalArgumentException("unknown format type: " +
1552
segments[SEG_TYPE]);
1553
}
1554
}
1555
formats[offsetNumber] = newFormat;
1556
}
1557
1558
private static final int findKeyword(String s, String[] list) {
1559
for (int i = 0; i < list.length; ++i) {
1560
if (s.equals(list[i]))
1561
return i;
1562
}
1563
1564
// Try trimmed lowercase.
1565
String ls = s.trim().toLowerCase(Locale.ROOT);
1566
if (ls != s) {
1567
for (int i = 0; i < list.length; ++i) {
1568
if (ls.equals(list[i]))
1569
return i;
1570
}
1571
}
1572
return -1;
1573
}
1574
1575
private static final void copyAndFixQuotes(String source, int start, int end,
1576
StringBuilder target) {
1577
boolean quoted = false;
1578
1579
for (int i = start; i < end; ++i) {
1580
char ch = source.charAt(i);
1581
if (ch == '{') {
1582
if (!quoted) {
1583
target.append('\'');
1584
quoted = true;
1585
}
1586
target.append(ch);
1587
} else if (ch == '\'') {
1588
target.append("''");
1589
} else {
1590
if (quoted) {
1591
target.append('\'');
1592
quoted = false;
1593
}
1594
target.append(ch);
1595
}
1596
}
1597
if (quoted) {
1598
target.append('\'');
1599
}
1600
}
1601
1602
/**
1603
* After reading an object from the input stream, do a simple verification
1604
* to maintain class invariants.
1605
* @throws InvalidObjectException if the objects read from the stream is invalid.
1606
*/
1607
@java.io.Serial
1608
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
1609
in.defaultReadObject();
1610
boolean isValid = maxOffset >= -1
1611
&& formats.length > maxOffset
1612
&& offsets.length > maxOffset
1613
&& argumentNumbers.length > maxOffset;
1614
if (isValid) {
1615
int lastOffset = pattern.length() + 1;
1616
for (int i = maxOffset; i >= 0; --i) {
1617
if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {
1618
isValid = false;
1619
break;
1620
} else {
1621
lastOffset = offsets[i];
1622
}
1623
}
1624
}
1625
if (!isValid) {
1626
throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");
1627
}
1628
}
1629
}
1630
1631