|
1 /* |
|
2 * Copyright (c) 1996, 2017, 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</code> 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</code> 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</code> differs from the other <code>Format</code> |
|
64 * classes in that you create a <code>MessageFormat</code> object with one |
|
65 * of its constructors (not with a <code>getInstance</code> style factory |
|
66 * method). The factory methods aren't necessary because <code>MessageFormat</code> |
|
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 * <h3><a id="patterns">Patterns and Their Interpretation</a></h3> |
|
72 * |
|
73 * <code>MessageFormat</code> 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>''</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 * <h4>Usage Information</h4> |
|
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</code>, |
|
232 * which internally creates a <code>MessageFormat</code> 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</code> 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</code>: |
|
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</code> |
|
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</code>: |
|
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</code> 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</code> in <code>MessageFormat</code> is treated as special; |
|
301 * occurrences of '{' are used to indicate subformats, and cause recursion. |
|
302 * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code> |
|
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 * <h4><a id="synchronization">Synchronization</a></h4> |
|
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 private static final long serialVersionUID = 6479157306784022952L; |
|
353 |
|
354 /** |
|
355 * Constructs a MessageFormat for the default |
|
356 * {@link java.util.Locale.Category#FORMAT FORMAT} locale and the |
|
357 * specified pattern. |
|
358 * The constructor first sets the locale, then parses the pattern and |
|
359 * creates a list of subformats for the format elements contained in it. |
|
360 * Patterns and their interpretation are specified in the |
|
361 * <a href="#patterns">class description</a>. |
|
362 * |
|
363 * @param pattern the pattern for this message format |
|
364 * @exception IllegalArgumentException if the pattern is invalid |
|
365 * @exception NullPointerException if {@code pattern} is |
|
366 * {@code null} |
|
367 */ |
|
368 public MessageFormat(String pattern) { |
|
369 this.locale = Locale.getDefault(Locale.Category.FORMAT); |
|
370 applyPattern(pattern); |
|
371 } |
|
372 |
|
373 /** |
|
374 * Constructs a MessageFormat for the specified locale and |
|
375 * pattern. |
|
376 * The constructor first sets the locale, then parses the pattern and |
|
377 * creates a list of subformats for the format elements contained in it. |
|
378 * Patterns and their interpretation are specified in the |
|
379 * <a href="#patterns">class description</a>. |
|
380 * |
|
381 * @param pattern the pattern for this message format |
|
382 * @param locale the locale for this message format |
|
383 * @exception IllegalArgumentException if the pattern is invalid |
|
384 * @exception NullPointerException if {@code pattern} is |
|
385 * {@code null} |
|
386 * @since 1.4 |
|
387 */ |
|
388 public MessageFormat(String pattern, Locale locale) { |
|
389 this.locale = locale; |
|
390 applyPattern(pattern); |
|
391 } |
|
392 |
|
393 /** |
|
394 * Sets the locale to be used when creating or comparing subformats. |
|
395 * This affects subsequent calls |
|
396 * <ul> |
|
397 * <li>to the {@link #applyPattern applyPattern} |
|
398 * and {@link #toPattern toPattern} methods if format elements specify |
|
399 * a format type and therefore have the subformats created in the |
|
400 * <code>applyPattern</code> method, as well as |
|
401 * <li>to the <code>format</code> and |
|
402 * {@link #formatToCharacterIterator formatToCharacterIterator} methods |
|
403 * if format elements do not specify a format type and therefore have |
|
404 * the subformats created in the formatting methods. |
|
405 * </ul> |
|
406 * Subformats that have already been created are not affected. |
|
407 * |
|
408 * @param locale the locale to be used when creating or comparing subformats |
|
409 */ |
|
410 public void setLocale(Locale locale) { |
|
411 this.locale = locale; |
|
412 } |
|
413 |
|
414 /** |
|
415 * Gets the locale that's used when creating or comparing subformats. |
|
416 * |
|
417 * @return the locale used when creating or comparing subformats |
|
418 */ |
|
419 public Locale getLocale() { |
|
420 return locale; |
|
421 } |
|
422 |
|
423 |
|
424 /** |
|
425 * Sets the pattern used by this message format. |
|
426 * The method parses the pattern and creates a list of subformats |
|
427 * for the format elements contained in it. |
|
428 * Patterns and their interpretation are specified in the |
|
429 * <a href="#patterns">class description</a>. |
|
430 * |
|
431 * @param pattern the pattern for this message format |
|
432 * @exception IllegalArgumentException if the pattern is invalid |
|
433 * @exception NullPointerException if {@code pattern} is |
|
434 * {@code null} |
|
435 */ |
|
436 @SuppressWarnings("fallthrough") // fallthrough in switch is expected, suppress it |
|
437 public void applyPattern(String pattern) { |
|
438 StringBuilder[] segments = new StringBuilder[4]; |
|
439 // Allocate only segments[SEG_RAW] here. The rest are |
|
440 // allocated on demand. |
|
441 segments[SEG_RAW] = new StringBuilder(); |
|
442 |
|
443 int part = SEG_RAW; |
|
444 int formatNumber = 0; |
|
445 boolean inQuote = false; |
|
446 int braceStack = 0; |
|
447 maxOffset = -1; |
|
448 for (int i = 0; i < pattern.length(); ++i) { |
|
449 char ch = pattern.charAt(i); |
|
450 if (part == SEG_RAW) { |
|
451 if (ch == '\'') { |
|
452 if (i + 1 < pattern.length() |
|
453 && pattern.charAt(i+1) == '\'') { |
|
454 segments[part].append(ch); // handle doubles |
|
455 ++i; |
|
456 } else { |
|
457 inQuote = !inQuote; |
|
458 } |
|
459 } else if (ch == '{' && !inQuote) { |
|
460 part = SEG_INDEX; |
|
461 if (segments[SEG_INDEX] == null) { |
|
462 segments[SEG_INDEX] = new StringBuilder(); |
|
463 } |
|
464 } else { |
|
465 segments[part].append(ch); |
|
466 } |
|
467 } else { |
|
468 if (inQuote) { // just copy quotes in parts |
|
469 segments[part].append(ch); |
|
470 if (ch == '\'') { |
|
471 inQuote = false; |
|
472 } |
|
473 } else { |
|
474 switch (ch) { |
|
475 case ',': |
|
476 if (part < SEG_MODIFIER) { |
|
477 if (segments[++part] == null) { |
|
478 segments[part] = new StringBuilder(); |
|
479 } |
|
480 } else { |
|
481 segments[part].append(ch); |
|
482 } |
|
483 break; |
|
484 case '{': |
|
485 ++braceStack; |
|
486 segments[part].append(ch); |
|
487 break; |
|
488 case '}': |
|
489 if (braceStack == 0) { |
|
490 part = SEG_RAW; |
|
491 makeFormat(i, formatNumber, segments); |
|
492 formatNumber++; |
|
493 // throw away other segments |
|
494 segments[SEG_INDEX] = null; |
|
495 segments[SEG_TYPE] = null; |
|
496 segments[SEG_MODIFIER] = null; |
|
497 } else { |
|
498 --braceStack; |
|
499 segments[part].append(ch); |
|
500 } |
|
501 break; |
|
502 case ' ': |
|
503 // Skip any leading space chars for SEG_TYPE. |
|
504 if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { |
|
505 segments[part].append(ch); |
|
506 } |
|
507 break; |
|
508 case '\'': |
|
509 inQuote = true; |
|
510 // fall through, so we keep quotes in other parts |
|
511 default: |
|
512 segments[part].append(ch); |
|
513 break; |
|
514 } |
|
515 } |
|
516 } |
|
517 } |
|
518 if (braceStack == 0 && part != 0) { |
|
519 maxOffset = -1; |
|
520 throw new IllegalArgumentException("Unmatched braces in the pattern."); |
|
521 } |
|
522 this.pattern = segments[0].toString(); |
|
523 } |
|
524 |
|
525 |
|
526 /** |
|
527 * Returns a pattern representing the current state of the message format. |
|
528 * The string is constructed from internal information and therefore |
|
529 * does not necessarily equal the previously applied pattern. |
|
530 * |
|
531 * @return a pattern representing the current state of the message format |
|
532 */ |
|
533 public String toPattern() { |
|
534 // later, make this more extensible |
|
535 int lastOffset = 0; |
|
536 StringBuilder result = new StringBuilder(); |
|
537 for (int i = 0; i <= maxOffset; ++i) { |
|
538 copyAndFixQuotes(pattern, lastOffset, offsets[i], result); |
|
539 lastOffset = offsets[i]; |
|
540 result.append('{').append(argumentNumbers[i]); |
|
541 Format fmt = formats[i]; |
|
542 if (fmt == null) { |
|
543 // do nothing, string format |
|
544 } else if (fmt instanceof NumberFormat) { |
|
545 if (fmt.equals(NumberFormat.getInstance(locale))) { |
|
546 result.append(",number"); |
|
547 } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) { |
|
548 result.append(",number,currency"); |
|
549 } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) { |
|
550 result.append(",number,percent"); |
|
551 } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) { |
|
552 result.append(",number,integer"); |
|
553 } else { |
|
554 if (fmt instanceof DecimalFormat) { |
|
555 result.append(",number,").append(((DecimalFormat)fmt).toPattern()); |
|
556 } else if (fmt instanceof ChoiceFormat) { |
|
557 result.append(",choice,").append(((ChoiceFormat)fmt).toPattern()); |
|
558 } else { |
|
559 // UNKNOWN |
|
560 } |
|
561 } |
|
562 } else if (fmt instanceof DateFormat) { |
|
563 int index; |
|
564 for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) { |
|
565 DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index], |
|
566 locale); |
|
567 if (fmt.equals(df)) { |
|
568 result.append(",date"); |
|
569 break; |
|
570 } |
|
571 df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index], |
|
572 locale); |
|
573 if (fmt.equals(df)) { |
|
574 result.append(",time"); |
|
575 break; |
|
576 } |
|
577 } |
|
578 if (index >= DATE_TIME_MODIFIERS.length) { |
|
579 if (fmt instanceof SimpleDateFormat) { |
|
580 result.append(",date,").append(((SimpleDateFormat)fmt).toPattern()); |
|
581 } else { |
|
582 // UNKNOWN |
|
583 } |
|
584 } else if (index != MODIFIER_DEFAULT) { |
|
585 result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]); |
|
586 } |
|
587 } else { |
|
588 //result.append(", unknown"); |
|
589 } |
|
590 result.append('}'); |
|
591 } |
|
592 copyAndFixQuotes(pattern, lastOffset, pattern.length(), result); |
|
593 return result.toString(); |
|
594 } |
|
595 |
|
596 /** |
|
597 * Sets the formats to use for the values passed into |
|
598 * <code>format</code> methods or returned from <code>parse</code> |
|
599 * methods. The indices of elements in <code>newFormats</code> |
|
600 * correspond to the argument indices used in the previously set |
|
601 * pattern string. |
|
602 * The order of formats in <code>newFormats</code> thus corresponds to |
|
603 * the order of elements in the <code>arguments</code> array passed |
|
604 * to the <code>format</code> methods or the result array returned |
|
605 * by the <code>parse</code> methods. |
|
606 * <p> |
|
607 * If an argument index is used for more than one format element |
|
608 * in the pattern string, then the corresponding new format is used |
|
609 * for all such format elements. If an argument index is not used |
|
610 * for any format element in the pattern string, then the |
|
611 * corresponding new format is ignored. If fewer formats are provided |
|
612 * than needed, then only the formats for argument indices less |
|
613 * than <code>newFormats.length</code> are replaced. |
|
614 * |
|
615 * @param newFormats the new formats to use |
|
616 * @exception NullPointerException if <code>newFormats</code> is null |
|
617 * @since 1.4 |
|
618 */ |
|
619 public void setFormatsByArgumentIndex(Format[] newFormats) { |
|
620 for (int i = 0; i <= maxOffset; i++) { |
|
621 int j = argumentNumbers[i]; |
|
622 if (j < newFormats.length) { |
|
623 formats[i] = newFormats[j]; |
|
624 } |
|
625 } |
|
626 } |
|
627 |
|
628 /** |
|
629 * Sets the formats to use for the format elements in the |
|
630 * previously set pattern string. |
|
631 * The order of formats in <code>newFormats</code> corresponds to |
|
632 * the order of format elements in the pattern string. |
|
633 * <p> |
|
634 * If more formats are provided than needed by the pattern string, |
|
635 * the remaining ones are ignored. If fewer formats are provided |
|
636 * than needed, then only the first <code>newFormats.length</code> |
|
637 * formats are replaced. |
|
638 * <p> |
|
639 * Since the order of format elements in a pattern string often |
|
640 * changes during localization, it is generally better to use the |
|
641 * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex} |
|
642 * method, which assumes an order of formats corresponding to the |
|
643 * order of elements in the <code>arguments</code> array passed to |
|
644 * the <code>format</code> methods or the result array returned by |
|
645 * the <code>parse</code> methods. |
|
646 * |
|
647 * @param newFormats the new formats to use |
|
648 * @exception NullPointerException if <code>newFormats</code> is null |
|
649 */ |
|
650 public void setFormats(Format[] newFormats) { |
|
651 int runsToCopy = newFormats.length; |
|
652 if (runsToCopy > maxOffset + 1) { |
|
653 runsToCopy = maxOffset + 1; |
|
654 } |
|
655 for (int i = 0; i < runsToCopy; i++) { |
|
656 formats[i] = newFormats[i]; |
|
657 } |
|
658 } |
|
659 |
|
660 /** |
|
661 * Sets the format to use for the format elements within the |
|
662 * previously set pattern string that use the given argument |
|
663 * index. |
|
664 * The argument index is part of the format element definition and |
|
665 * represents an index into the <code>arguments</code> array passed |
|
666 * to the <code>format</code> methods or the result array returned |
|
667 * by the <code>parse</code> methods. |
|
668 * <p> |
|
669 * If the argument index is used for more than one format element |
|
670 * in the pattern string, then the new format is used for all such |
|
671 * format elements. If the argument index is not used for any format |
|
672 * element in the pattern string, then the new format is ignored. |
|
673 * |
|
674 * @param argumentIndex the argument index for which to use the new format |
|
675 * @param newFormat the new format to use |
|
676 * @since 1.4 |
|
677 */ |
|
678 public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) { |
|
679 for (int j = 0; j <= maxOffset; j++) { |
|
680 if (argumentNumbers[j] == argumentIndex) { |
|
681 formats[j] = newFormat; |
|
682 } |
|
683 } |
|
684 } |
|
685 |
|
686 /** |
|
687 * Sets the format to use for the format element with the given |
|
688 * format element index within the previously set pattern string. |
|
689 * The format element index is the zero-based number of the format |
|
690 * element counting from the start of the pattern string. |
|
691 * <p> |
|
692 * Since the order of format elements in a pattern string often |
|
693 * changes during localization, it is generally better to use the |
|
694 * {@link #setFormatByArgumentIndex setFormatByArgumentIndex} |
|
695 * method, which accesses format elements based on the argument |
|
696 * index they specify. |
|
697 * |
|
698 * @param formatElementIndex the index of a format element within the pattern |
|
699 * @param newFormat the format to use for the specified format element |
|
700 * @exception ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or |
|
701 * larger than the number of format elements in the pattern string |
|
702 */ |
|
703 public void setFormat(int formatElementIndex, Format newFormat) { |
|
704 formats[formatElementIndex] = newFormat; |
|
705 } |
|
706 |
|
707 /** |
|
708 * Gets the formats used for the values passed into |
|
709 * <code>format</code> methods or returned from <code>parse</code> |
|
710 * methods. The indices of elements in the returned array |
|
711 * correspond to the argument indices used in the previously set |
|
712 * pattern string. |
|
713 * The order of formats in the returned array thus corresponds to |
|
714 * the order of elements in the <code>arguments</code> array passed |
|
715 * to the <code>format</code> methods or the result array returned |
|
716 * by the <code>parse</code> methods. |
|
717 * <p> |
|
718 * If an argument index is used for more than one format element |
|
719 * in the pattern string, then the format used for the last such |
|
720 * format element is returned in the array. If an argument index |
|
721 * is not used for any format element in the pattern string, then |
|
722 * null is returned in the array. |
|
723 * |
|
724 * @return the formats used for the arguments within the pattern |
|
725 * @since 1.4 |
|
726 */ |
|
727 public Format[] getFormatsByArgumentIndex() { |
|
728 int maximumArgumentNumber = -1; |
|
729 for (int i = 0; i <= maxOffset; i++) { |
|
730 if (argumentNumbers[i] > maximumArgumentNumber) { |
|
731 maximumArgumentNumber = argumentNumbers[i]; |
|
732 } |
|
733 } |
|
734 Format[] resultArray = new Format[maximumArgumentNumber + 1]; |
|
735 for (int i = 0; i <= maxOffset; i++) { |
|
736 resultArray[argumentNumbers[i]] = formats[i]; |
|
737 } |
|
738 return resultArray; |
|
739 } |
|
740 |
|
741 /** |
|
742 * Gets the formats used for the format elements in the |
|
743 * previously set pattern string. |
|
744 * The order of formats in the returned array corresponds to |
|
745 * the order of format elements in the pattern string. |
|
746 * <p> |
|
747 * Since the order of format elements in a pattern string often |
|
748 * changes during localization, it's generally better to use the |
|
749 * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex} |
|
750 * method, which assumes an order of formats corresponding to the |
|
751 * order of elements in the <code>arguments</code> array passed to |
|
752 * the <code>format</code> methods or the result array returned by |
|
753 * the <code>parse</code> methods. |
|
754 * |
|
755 * @return the formats used for the format elements in the pattern |
|
756 */ |
|
757 public Format[] getFormats() { |
|
758 Format[] resultArray = new Format[maxOffset + 1]; |
|
759 System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1); |
|
760 return resultArray; |
|
761 } |
|
762 |
|
763 /** |
|
764 * Formats an array of objects and appends the <code>MessageFormat</code>'s |
|
765 * pattern, with format elements replaced by the formatted objects, to the |
|
766 * provided <code>StringBuffer</code>. |
|
767 * <p> |
|
768 * The text substituted for the individual format elements is derived from |
|
769 * the current subformat of the format element and the |
|
770 * <code>arguments</code> element at the format element's argument index |
|
771 * as indicated by the first matching line of the following table. An |
|
772 * argument is <i>unavailable</i> if <code>arguments</code> is |
|
773 * <code>null</code> or has fewer than argumentIndex+1 elements. |
|
774 * |
|
775 * <table class="plain"> |
|
776 * <caption style="display:none">Examples of subformat,argument,and formatted text</caption> |
|
777 * <thead> |
|
778 * <tr> |
|
779 * <th scope="col">Subformat |
|
780 * <th scope="col">Argument |
|
781 * <th scope="col">Formatted Text |
|
782 * </thead> |
|
783 * <tbody> |
|
784 * <tr> |
|
785 * <th scope="row" style="text-weight-normal" rowspan=2><i>any</i> |
|
786 * <th scope="row" style="text-weight-normal"><i>unavailable</i> |
|
787 * <td><code>"{" + argumentIndex + "}"</code> |
|
788 * <tr> |
|
789 * <th scope="row" style="text-weight-normal"><code>null</code> |
|
790 * <td><code>"null"</code> |
|
791 * <tr> |
|
792 * <th scope="row" style="text-weight-normal"><code>instanceof ChoiceFormat</code> |
|
793 * <th scope="row" style="text-weight-normal"><i>any</i> |
|
794 * <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br> |
|
795 * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) : |
|
796 * subformat.format(argument)</code> |
|
797 * <tr> |
|
798 * <th scope="row" style="text-weight-normal"><code>!= null</code> |
|
799 * <th scope="row" style="text-weight-normal"><i>any</i> |
|
800 * <td><code>subformat.format(argument)</code> |
|
801 * <tr> |
|
802 * <th scope="row" style="text-weight-normal" rowspan=4><code>null</code> |
|
803 * <th scope="row" style="text-weight-normal"><code>instanceof Number</code> |
|
804 * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code> |
|
805 * <tr> |
|
806 * <th scope="row" style="text-weight-normal"><code>instanceof Date</code> |
|
807 * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code> |
|
808 * <tr> |
|
809 * <th scope="row" style="text-weight-normal"><code>instanceof String</code> |
|
810 * <td><code>argument</code> |
|
811 * <tr> |
|
812 * <th scope="row" style="text-weight-normal"><i>any</i> |
|
813 * <td><code>argument.toString()</code> |
|
814 * </tbody> |
|
815 * </table> |
|
816 * <p> |
|
817 * If <code>pos</code> is non-null, and refers to |
|
818 * <code>Field.ARGUMENT</code>, the location of the first formatted |
|
819 * string will be returned. |
|
820 * |
|
821 * @param arguments an array of objects to be formatted and substituted. |
|
822 * @param result where text is appended. |
|
823 * @param pos On input: an alignment field, if desired. |
|
824 * On output: the offsets of the alignment field. |
|
825 * @return the string buffer passed in as {@code result}, with formatted |
|
826 * text appended |
|
827 * @exception IllegalArgumentException if an argument in the |
|
828 * <code>arguments</code> array is not of the type |
|
829 * expected by the format element(s) that use it. |
|
830 * @exception NullPointerException if {@code result} is {@code null} |
|
831 */ |
|
832 public final StringBuffer format(Object[] arguments, StringBuffer result, |
|
833 FieldPosition pos) |
|
834 { |
|
835 return subformat(arguments, result, pos, null); |
|
836 } |
|
837 |
|
838 /** |
|
839 * Creates a MessageFormat with the given pattern and uses it |
|
840 * to format the given arguments. This is equivalent to |
|
841 * <blockquote> |
|
842 * <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> |
|
843 * </blockquote> |
|
844 * |
|
845 * @param pattern the pattern string |
|
846 * @param arguments object(s) to format |
|
847 * @return the formatted string |
|
848 * @exception IllegalArgumentException if the pattern is invalid, |
|
849 * or if an argument in the <code>arguments</code> array |
|
850 * is not of the type expected by the format element(s) |
|
851 * that use it. |
|
852 * @exception NullPointerException if {@code pattern} is {@code null} |
|
853 */ |
|
854 public static String format(String pattern, Object ... arguments) { |
|
855 MessageFormat temp = new MessageFormat(pattern); |
|
856 return temp.format(arguments); |
|
857 } |
|
858 |
|
859 // Overrides |
|
860 /** |
|
861 * Formats an array of objects and appends the <code>MessageFormat</code>'s |
|
862 * pattern, with format elements replaced by the formatted objects, to the |
|
863 * provided <code>StringBuffer</code>. |
|
864 * This is equivalent to |
|
865 * <blockquote> |
|
866 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code> |
|
867 * </blockquote> |
|
868 * |
|
869 * @param arguments an array of objects to be formatted and substituted. |
|
870 * @param result where text is appended. |
|
871 * @param pos On input: an alignment field, if desired. |
|
872 * On output: the offsets of the alignment field. |
|
873 * @exception IllegalArgumentException if an argument in the |
|
874 * <code>arguments</code> array is not of the type |
|
875 * expected by the format element(s) that use it. |
|
876 * @exception NullPointerException if {@code result} is {@code null} |
|
877 */ |
|
878 public final StringBuffer format(Object arguments, StringBuffer result, |
|
879 FieldPosition pos) |
|
880 { |
|
881 return subformat((Object[]) arguments, result, pos, null); |
|
882 } |
|
883 |
|
884 /** |
|
885 * Formats an array of objects and inserts them into the |
|
886 * <code>MessageFormat</code>'s pattern, producing an |
|
887 * <code>AttributedCharacterIterator</code>. |
|
888 * You can use the returned <code>AttributedCharacterIterator</code> |
|
889 * to build the resulting String, as well as to determine information |
|
890 * about the resulting String. |
|
891 * <p> |
|
892 * The text of the returned <code>AttributedCharacterIterator</code> is |
|
893 * the same that would be returned by |
|
894 * <blockquote> |
|
895 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code> |
|
896 * </blockquote> |
|
897 * <p> |
|
898 * In addition, the <code>AttributedCharacterIterator</code> contains at |
|
899 * least attributes indicating where text was generated from an |
|
900 * argument in the <code>arguments</code> array. The keys of these attributes are of |
|
901 * type <code>MessageFormat.Field</code>, their values are |
|
902 * <code>Integer</code> objects indicating the index in the <code>arguments</code> |
|
903 * array of the argument from which the text was generated. |
|
904 * <p> |
|
905 * The attributes/value from the underlying <code>Format</code> |
|
906 * instances that <code>MessageFormat</code> uses will also be |
|
907 * placed in the resulting <code>AttributedCharacterIterator</code>. |
|
908 * This allows you to not only find where an argument is placed in the |
|
909 * resulting String, but also which fields it contains in turn. |
|
910 * |
|
911 * @param arguments an array of objects to be formatted and substituted. |
|
912 * @return AttributedCharacterIterator describing the formatted value. |
|
913 * @exception NullPointerException if <code>arguments</code> is null. |
|
914 * @exception IllegalArgumentException if an argument in the |
|
915 * <code>arguments</code> array is not of the type |
|
916 * expected by the format element(s) that use it. |
|
917 * @since 1.4 |
|
918 */ |
|
919 public AttributedCharacterIterator formatToCharacterIterator(Object arguments) { |
|
920 StringBuffer result = new StringBuffer(); |
|
921 ArrayList<AttributedCharacterIterator> iterators = new ArrayList<>(); |
|
922 |
|
923 if (arguments == null) { |
|
924 throw new NullPointerException( |
|
925 "formatToCharacterIterator must be passed non-null object"); |
|
926 } |
|
927 subformat((Object[]) arguments, result, null, iterators); |
|
928 if (iterators.size() == 0) { |
|
929 return createAttributedCharacterIterator(""); |
|
930 } |
|
931 return createAttributedCharacterIterator( |
|
932 iterators.toArray( |
|
933 new AttributedCharacterIterator[iterators.size()])); |
|
934 } |
|
935 |
|
936 /** |
|
937 * Parses the string. |
|
938 * |
|
939 * <p>Caveats: The parse may fail in a number of circumstances. |
|
940 * For example: |
|
941 * <ul> |
|
942 * <li>If one of the arguments does not occur in the pattern. |
|
943 * <li>If the format of an argument loses information, such as |
|
944 * with a choice format where a large number formats to "many". |
|
945 * <li>Does not yet handle recursion (where |
|
946 * the substituted strings contain {n} references.) |
|
947 * <li>Will not always find a match (or the correct match) |
|
948 * if some part of the parse is ambiguous. |
|
949 * For example, if the pattern "{1},{2}" is used with the |
|
950 * string arguments {"a,b", "c"}, it will format as "a,b,c". |
|
951 * When the result is parsed, it will return {"a", "b,c"}. |
|
952 * <li>If a single argument is parsed more than once in the string, |
|
953 * then the later parse wins. |
|
954 * </ul> |
|
955 * When the parse fails, use ParsePosition.getErrorIndex() to find out |
|
956 * where in the string the parsing failed. The returned error |
|
957 * index is the starting offset of the sub-patterns that the string |
|
958 * is comparing with. For example, if the parsing string "AAA {0} BBB" |
|
959 * is comparing against the pattern "AAD {0} BBB", the error index is |
|
960 * 0. When an error occurs, the call to this method will return null. |
|
961 * If the source is null, return an empty array. |
|
962 * |
|
963 * @param source the string to parse |
|
964 * @param pos the parse position |
|
965 * @return an array of parsed objects |
|
966 * @exception NullPointerException if {@code pos} is {@code null} |
|
967 * for a non-null {@code source} string. |
|
968 */ |
|
969 public Object[] parse(String source, ParsePosition pos) { |
|
970 if (source == null) { |
|
971 Object[] empty = {}; |
|
972 return empty; |
|
973 } |
|
974 |
|
975 int maximumArgumentNumber = -1; |
|
976 for (int i = 0; i <= maxOffset; i++) { |
|
977 if (argumentNumbers[i] > maximumArgumentNumber) { |
|
978 maximumArgumentNumber = argumentNumbers[i]; |
|
979 } |
|
980 } |
|
981 Object[] resultArray = new Object[maximumArgumentNumber + 1]; |
|
982 |
|
983 int patternOffset = 0; |
|
984 int sourceOffset = pos.index; |
|
985 ParsePosition tempStatus = new ParsePosition(0); |
|
986 for (int i = 0; i <= maxOffset; ++i) { |
|
987 // match up to format |
|
988 int len = offsets[i] - patternOffset; |
|
989 if (len == 0 || pattern.regionMatches(patternOffset, |
|
990 source, sourceOffset, len)) { |
|
991 sourceOffset += len; |
|
992 patternOffset += len; |
|
993 } else { |
|
994 pos.errorIndex = sourceOffset; |
|
995 return null; // leave index as is to signal error |
|
996 } |
|
997 |
|
998 // now use format |
|
999 if (formats[i] == null) { // string format |
|
1000 // if at end, use longest possible match |
|
1001 // otherwise uses first match to intervening string |
|
1002 // does NOT recursively try all possibilities |
|
1003 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length(); |
|
1004 |
|
1005 int next; |
|
1006 if (patternOffset >= tempLength) { |
|
1007 next = source.length(); |
|
1008 }else{ |
|
1009 next = source.indexOf(pattern.substring(patternOffset, tempLength), |
|
1010 sourceOffset); |
|
1011 } |
|
1012 |
|
1013 if (next < 0) { |
|
1014 pos.errorIndex = sourceOffset; |
|
1015 return null; // leave index as is to signal error |
|
1016 } else { |
|
1017 String strValue= source.substring(sourceOffset,next); |
|
1018 if (!strValue.equals("{"+argumentNumbers[i]+"}")) |
|
1019 resultArray[argumentNumbers[i]] |
|
1020 = source.substring(sourceOffset,next); |
|
1021 sourceOffset = next; |
|
1022 } |
|
1023 } else { |
|
1024 tempStatus.index = sourceOffset; |
|
1025 resultArray[argumentNumbers[i]] |
|
1026 = formats[i].parseObject(source,tempStatus); |
|
1027 if (tempStatus.index == sourceOffset) { |
|
1028 pos.errorIndex = sourceOffset; |
|
1029 return null; // leave index as is to signal error |
|
1030 } |
|
1031 sourceOffset = tempStatus.index; // update |
|
1032 } |
|
1033 } |
|
1034 int len = pattern.length() - patternOffset; |
|
1035 if (len == 0 || pattern.regionMatches(patternOffset, |
|
1036 source, sourceOffset, len)) { |
|
1037 pos.index = sourceOffset + len; |
|
1038 } else { |
|
1039 pos.errorIndex = sourceOffset; |
|
1040 return null; // leave index as is to signal error |
|
1041 } |
|
1042 return resultArray; |
|
1043 } |
|
1044 |
|
1045 /** |
|
1046 * Parses text from the beginning of the given string to produce an object |
|
1047 * array. |
|
1048 * The method may not use the entire text of the given string. |
|
1049 * <p> |
|
1050 * See the {@link #parse(String, ParsePosition)} method for more information |
|
1051 * on message parsing. |
|
1052 * |
|
1053 * @param source A <code>String</code> whose beginning should be parsed. |
|
1054 * @return An <code>Object</code> array parsed from the string. |
|
1055 * @exception ParseException if the beginning of the specified string |
|
1056 * cannot be parsed. |
|
1057 */ |
|
1058 public Object[] parse(String source) throws ParseException { |
|
1059 ParsePosition pos = new ParsePosition(0); |
|
1060 Object[] result = parse(source, pos); |
|
1061 if (pos.index == 0) // unchanged, returned object is null |
|
1062 throw new ParseException("MessageFormat parse error!", pos.errorIndex); |
|
1063 |
|
1064 return result; |
|
1065 } |
|
1066 |
|
1067 /** |
|
1068 * Parses text from a string to produce an object array. |
|
1069 * <p> |
|
1070 * The method attempts to parse text starting at the index given by |
|
1071 * <code>pos</code>. |
|
1072 * If parsing succeeds, then the index of <code>pos</code> is updated |
|
1073 * to the index after the last character used (parsing does not necessarily |
|
1074 * use all characters up to the end of the string), and the parsed |
|
1075 * object array is returned. The updated <code>pos</code> can be used to |
|
1076 * indicate the starting point for the next call to this method. |
|
1077 * If an error occurs, then the index of <code>pos</code> is not |
|
1078 * changed, the error index of <code>pos</code> is set to the index of |
|
1079 * the character where the error occurred, and null is returned. |
|
1080 * <p> |
|
1081 * See the {@link #parse(String, ParsePosition)} method for more information |
|
1082 * on message parsing. |
|
1083 * |
|
1084 * @param source A <code>String</code>, part of which should be parsed. |
|
1085 * @param pos A <code>ParsePosition</code> object with index and error |
|
1086 * index information as described above. |
|
1087 * @return An <code>Object</code> array parsed from the string. In case of |
|
1088 * error, returns null. |
|
1089 * @throws NullPointerException if {@code pos} is null. |
|
1090 */ |
|
1091 public Object parseObject(String source, ParsePosition pos) { |
|
1092 return parse(source, pos); |
|
1093 } |
|
1094 |
|
1095 /** |
|
1096 * Creates and returns a copy of this object. |
|
1097 * |
|
1098 * @return a clone of this instance. |
|
1099 */ |
|
1100 public Object clone() { |
|
1101 MessageFormat other = (MessageFormat) super.clone(); |
|
1102 |
|
1103 // clone arrays. Can't do with utility because of bug in Cloneable |
|
1104 other.formats = formats.clone(); // shallow clone |
|
1105 for (int i = 0; i < formats.length; ++i) { |
|
1106 if (formats[i] != null) |
|
1107 other.formats[i] = (Format)formats[i].clone(); |
|
1108 } |
|
1109 // for primitives or immutables, shallow clone is enough |
|
1110 other.offsets = offsets.clone(); |
|
1111 other.argumentNumbers = argumentNumbers.clone(); |
|
1112 |
|
1113 return other; |
|
1114 } |
|
1115 |
|
1116 /** |
|
1117 * Equality comparison between two message format objects |
|
1118 */ |
|
1119 public boolean equals(Object obj) { |
|
1120 if (this == obj) // quick check |
|
1121 return true; |
|
1122 if (obj == null || getClass() != obj.getClass()) |
|
1123 return false; |
|
1124 MessageFormat other = (MessageFormat) obj; |
|
1125 return (maxOffset == other.maxOffset |
|
1126 && pattern.equals(other.pattern) |
|
1127 && ((locale != null && locale.equals(other.locale)) |
|
1128 || (locale == null && other.locale == null)) |
|
1129 && Arrays.equals(offsets,other.offsets) |
|
1130 && Arrays.equals(argumentNumbers,other.argumentNumbers) |
|
1131 && Arrays.equals(formats,other.formats)); |
|
1132 } |
|
1133 |
|
1134 /** |
|
1135 * Generates a hash code for the message format object. |
|
1136 */ |
|
1137 public int hashCode() { |
|
1138 return pattern.hashCode(); // enough for reasonable distribution |
|
1139 } |
|
1140 |
|
1141 |
|
1142 /** |
|
1143 * Defines constants that are used as attribute keys in the |
|
1144 * <code>AttributedCharacterIterator</code> returned |
|
1145 * from <code>MessageFormat.formatToCharacterIterator</code>. |
|
1146 * |
|
1147 * @since 1.4 |
|
1148 */ |
|
1149 public static class Field extends Format.Field { |
|
1150 |
|
1151 // Proclaim serial compatibility with 1.4 FCS |
|
1152 private static final long serialVersionUID = 7899943957617360810L; |
|
1153 |
|
1154 /** |
|
1155 * Creates a Field with the specified name. |
|
1156 * |
|
1157 * @param name Name of the attribute |
|
1158 */ |
|
1159 protected Field(String name) { |
|
1160 super(name); |
|
1161 } |
|
1162 |
|
1163 /** |
|
1164 * Resolves instances being deserialized to the predefined constants. |
|
1165 * |
|
1166 * @throws InvalidObjectException if the constant could not be |
|
1167 * resolved. |
|
1168 * @return resolved MessageFormat.Field constant |
|
1169 */ |
|
1170 protected Object readResolve() throws InvalidObjectException { |
|
1171 if (this.getClass() != MessageFormat.Field.class) { |
|
1172 throw new InvalidObjectException("subclass didn't correctly implement readResolve"); |
|
1173 } |
|
1174 |
|
1175 return ARGUMENT; |
|
1176 } |
|
1177 |
|
1178 // |
|
1179 // The constants |
|
1180 // |
|
1181 |
|
1182 /** |
|
1183 * Constant identifying a portion of a message that was generated |
|
1184 * from an argument passed into <code>formatToCharacterIterator</code>. |
|
1185 * The value associated with the key will be an <code>Integer</code> |
|
1186 * indicating the index in the <code>arguments</code> array of the |
|
1187 * argument from which the text was generated. |
|
1188 */ |
|
1189 public static final Field ARGUMENT = |
|
1190 new Field("message argument field"); |
|
1191 } |
|
1192 |
|
1193 // ===========================privates============================ |
|
1194 |
|
1195 /** |
|
1196 * The locale to use for formatting numbers and dates. |
|
1197 * @serial |
|
1198 */ |
|
1199 private Locale locale; |
|
1200 |
|
1201 /** |
|
1202 * The string that the formatted values are to be plugged into. In other words, this |
|
1203 * is the pattern supplied on construction with all of the {} expressions taken out. |
|
1204 * @serial |
|
1205 */ |
|
1206 private String pattern = ""; |
|
1207 |
|
1208 /** The initially expected number of subformats in the format */ |
|
1209 private static final int INITIAL_FORMATS = 10; |
|
1210 |
|
1211 /** |
|
1212 * An array of formatters, which are used to format the arguments. |
|
1213 * @serial |
|
1214 */ |
|
1215 private Format[] formats = new Format[INITIAL_FORMATS]; |
|
1216 |
|
1217 /** |
|
1218 * The positions where the results of formatting each argument are to be inserted |
|
1219 * into the pattern. |
|
1220 * @serial |
|
1221 */ |
|
1222 private int[] offsets = new int[INITIAL_FORMATS]; |
|
1223 |
|
1224 /** |
|
1225 * The argument numbers corresponding to each formatter. (The formatters are stored |
|
1226 * in the order they occur in the pattern, not in the order in which the arguments |
|
1227 * are specified.) |
|
1228 * @serial |
|
1229 */ |
|
1230 private int[] argumentNumbers = new int[INITIAL_FORMATS]; |
|
1231 |
|
1232 /** |
|
1233 * One less than the number of entries in <code>offsets</code>. Can also be thought of |
|
1234 * as the index of the highest-numbered element in <code>offsets</code> that is being used. |
|
1235 * All of these arrays should have the same number of elements being used as <code>offsets</code> |
|
1236 * does, and so this variable suffices to tell us how many entries are in all of them. |
|
1237 * @serial |
|
1238 */ |
|
1239 private int maxOffset = -1; |
|
1240 |
|
1241 /** |
|
1242 * Internal routine used by format. If <code>characterIterators</code> is |
|
1243 * non-null, AttributedCharacterIterator will be created from the |
|
1244 * subformats as necessary. If <code>characterIterators</code> is null |
|
1245 * and <code>fp</code> is non-null and identifies |
|
1246 * <code>Field.MESSAGE_ARGUMENT</code>, the location of |
|
1247 * the first replaced argument will be set in it. |
|
1248 * |
|
1249 * @exception IllegalArgumentException if an argument in the |
|
1250 * <code>arguments</code> array is not of the type |
|
1251 * expected by the format element(s) that use it. |
|
1252 */ |
|
1253 private StringBuffer subformat(Object[] arguments, StringBuffer result, |
|
1254 FieldPosition fp, List<AttributedCharacterIterator> characterIterators) { |
|
1255 // note: this implementation assumes a fast substring & index. |
|
1256 // if this is not true, would be better to append chars one by one. |
|
1257 int lastOffset = 0; |
|
1258 int last = result.length(); |
|
1259 for (int i = 0; i <= maxOffset; ++i) { |
|
1260 result.append(pattern, lastOffset, offsets[i]); |
|
1261 lastOffset = offsets[i]; |
|
1262 int argumentNumber = argumentNumbers[i]; |
|
1263 if (arguments == null || argumentNumber >= arguments.length) { |
|
1264 result.append('{').append(argumentNumber).append('}'); |
|
1265 continue; |
|
1266 } |
|
1267 // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3); |
|
1268 if (false) { // if (argRecursion == 3){ |
|
1269 // prevent loop!!! |
|
1270 result.append('\uFFFD'); |
|
1271 } else { |
|
1272 Object obj = arguments[argumentNumber]; |
|
1273 String arg = null; |
|
1274 Format subFormatter = null; |
|
1275 if (obj == null) { |
|
1276 arg = "null"; |
|
1277 } else if (formats[i] != null) { |
|
1278 subFormatter = formats[i]; |
|
1279 if (subFormatter instanceof ChoiceFormat) { |
|
1280 arg = formats[i].format(obj); |
|
1281 if (arg.indexOf('{') >= 0) { |
|
1282 subFormatter = new MessageFormat(arg, locale); |
|
1283 obj = arguments; |
|
1284 arg = null; |
|
1285 } |
|
1286 } |
|
1287 } else if (obj instanceof Number) { |
|
1288 // format number if can |
|
1289 subFormatter = NumberFormat.getInstance(locale); |
|
1290 } else if (obj instanceof Date) { |
|
1291 // format a Date if can |
|
1292 subFormatter = DateFormat.getDateTimeInstance( |
|
1293 DateFormat.SHORT, DateFormat.SHORT, locale);//fix |
|
1294 } else if (obj instanceof String) { |
|
1295 arg = (String) obj; |
|
1296 |
|
1297 } else { |
|
1298 arg = obj.toString(); |
|
1299 if (arg == null) arg = "null"; |
|
1300 } |
|
1301 |
|
1302 // At this point we are in two states, either subFormatter |
|
1303 // is non-null indicating we should format obj using it, |
|
1304 // or arg is non-null and we should use it as the value. |
|
1305 |
|
1306 if (characterIterators != null) { |
|
1307 // If characterIterators is non-null, it indicates we need |
|
1308 // to get the CharacterIterator from the child formatter. |
|
1309 if (last != result.length()) { |
|
1310 characterIterators.add( |
|
1311 createAttributedCharacterIterator(result.substring |
|
1312 (last))); |
|
1313 last = result.length(); |
|
1314 } |
|
1315 if (subFormatter != null) { |
|
1316 AttributedCharacterIterator subIterator = |
|
1317 subFormatter.formatToCharacterIterator(obj); |
|
1318 |
|
1319 append(result, subIterator); |
|
1320 if (last != result.length()) { |
|
1321 characterIterators.add( |
|
1322 createAttributedCharacterIterator( |
|
1323 subIterator, Field.ARGUMENT, |
|
1324 Integer.valueOf(argumentNumber))); |
|
1325 last = result.length(); |
|
1326 } |
|
1327 arg = null; |
|
1328 } |
|
1329 if (arg != null && arg.length() > 0) { |
|
1330 result.append(arg); |
|
1331 characterIterators.add( |
|
1332 createAttributedCharacterIterator( |
|
1333 arg, Field.ARGUMENT, |
|
1334 Integer.valueOf(argumentNumber))); |
|
1335 last = result.length(); |
|
1336 } |
|
1337 } |
|
1338 else { |
|
1339 if (subFormatter != null) { |
|
1340 arg = subFormatter.format(obj); |
|
1341 } |
|
1342 last = result.length(); |
|
1343 result.append(arg); |
|
1344 if (i == 0 && fp != null && Field.ARGUMENT.equals( |
|
1345 fp.getFieldAttribute())) { |
|
1346 fp.setBeginIndex(last); |
|
1347 fp.setEndIndex(result.length()); |
|
1348 } |
|
1349 last = result.length(); |
|
1350 } |
|
1351 } |
|
1352 } |
|
1353 result.append(pattern, lastOffset, pattern.length()); |
|
1354 if (characterIterators != null && last != result.length()) { |
|
1355 characterIterators.add(createAttributedCharacterIterator( |
|
1356 result.substring(last))); |
|
1357 } |
|
1358 return result; |
|
1359 } |
|
1360 |
|
1361 /** |
|
1362 * Convenience method to append all the characters in |
|
1363 * <code>iterator</code> to the StringBuffer <code>result</code>. |
|
1364 */ |
|
1365 private void append(StringBuffer result, CharacterIterator iterator) { |
|
1366 if (iterator.first() != CharacterIterator.DONE) { |
|
1367 char aChar; |
|
1368 |
|
1369 result.append(iterator.first()); |
|
1370 while ((aChar = iterator.next()) != CharacterIterator.DONE) { |
|
1371 result.append(aChar); |
|
1372 } |
|
1373 } |
|
1374 } |
|
1375 |
|
1376 // Indices for segments |
|
1377 private static final int SEG_RAW = 0; |
|
1378 private static final int SEG_INDEX = 1; |
|
1379 private static final int SEG_TYPE = 2; |
|
1380 private static final int SEG_MODIFIER = 3; // modifier or subformat |
|
1381 |
|
1382 // Indices for type keywords |
|
1383 private static final int TYPE_NULL = 0; |
|
1384 private static final int TYPE_NUMBER = 1; |
|
1385 private static final int TYPE_DATE = 2; |
|
1386 private static final int TYPE_TIME = 3; |
|
1387 private static final int TYPE_CHOICE = 4; |
|
1388 |
|
1389 private static final String[] TYPE_KEYWORDS = { |
|
1390 "", |
|
1391 "number", |
|
1392 "date", |
|
1393 "time", |
|
1394 "choice" |
|
1395 }; |
|
1396 |
|
1397 // Indices for number modifiers |
|
1398 private static final int MODIFIER_DEFAULT = 0; // common in number and date-time |
|
1399 private static final int MODIFIER_CURRENCY = 1; |
|
1400 private static final int MODIFIER_PERCENT = 2; |
|
1401 private static final int MODIFIER_INTEGER = 3; |
|
1402 |
|
1403 private static final String[] NUMBER_MODIFIER_KEYWORDS = { |
|
1404 "", |
|
1405 "currency", |
|
1406 "percent", |
|
1407 "integer" |
|
1408 }; |
|
1409 |
|
1410 // Indices for date-time modifiers |
|
1411 private static final int MODIFIER_SHORT = 1; |
|
1412 private static final int MODIFIER_MEDIUM = 2; |
|
1413 private static final int MODIFIER_LONG = 3; |
|
1414 private static final int MODIFIER_FULL = 4; |
|
1415 |
|
1416 private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { |
|
1417 "", |
|
1418 "short", |
|
1419 "medium", |
|
1420 "long", |
|
1421 "full" |
|
1422 }; |
|
1423 |
|
1424 // Date-time style values corresponding to the date-time modifiers. |
|
1425 private static final int[] DATE_TIME_MODIFIERS = { |
|
1426 DateFormat.DEFAULT, |
|
1427 DateFormat.SHORT, |
|
1428 DateFormat.MEDIUM, |
|
1429 DateFormat.LONG, |
|
1430 DateFormat.FULL, |
|
1431 }; |
|
1432 |
|
1433 private void makeFormat(int position, int offsetNumber, |
|
1434 StringBuilder[] textSegments) |
|
1435 { |
|
1436 String[] segments = new String[textSegments.length]; |
|
1437 for (int i = 0; i < textSegments.length; i++) { |
|
1438 StringBuilder oneseg = textSegments[i]; |
|
1439 segments[i] = (oneseg != null) ? oneseg.toString() : ""; |
|
1440 } |
|
1441 |
|
1442 // get the argument number |
|
1443 int argumentNumber; |
|
1444 try { |
|
1445 argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized! |
|
1446 } catch (NumberFormatException e) { |
|
1447 throw new IllegalArgumentException("can't parse argument number: " |
|
1448 + segments[SEG_INDEX], e); |
|
1449 } |
|
1450 if (argumentNumber < 0) { |
|
1451 throw new IllegalArgumentException("negative argument number: " |
|
1452 + argumentNumber); |
|
1453 } |
|
1454 |
|
1455 // resize format information arrays if necessary |
|
1456 if (offsetNumber >= formats.length) { |
|
1457 int newLength = formats.length * 2; |
|
1458 Format[] newFormats = new Format[newLength]; |
|
1459 int[] newOffsets = new int[newLength]; |
|
1460 int[] newArgumentNumbers = new int[newLength]; |
|
1461 System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1); |
|
1462 System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1); |
|
1463 System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1); |
|
1464 formats = newFormats; |
|
1465 offsets = newOffsets; |
|
1466 argumentNumbers = newArgumentNumbers; |
|
1467 } |
|
1468 int oldMaxOffset = maxOffset; |
|
1469 maxOffset = offsetNumber; |
|
1470 offsets[offsetNumber] = segments[SEG_RAW].length(); |
|
1471 argumentNumbers[offsetNumber] = argumentNumber; |
|
1472 |
|
1473 // now get the format |
|
1474 Format newFormat = null; |
|
1475 if (segments[SEG_TYPE].length() != 0) { |
|
1476 int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); |
|
1477 switch (type) { |
|
1478 case TYPE_NULL: |
|
1479 // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}" |
|
1480 // are treated as "{0}". |
|
1481 break; |
|
1482 |
|
1483 case TYPE_NUMBER: |
|
1484 switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { |
|
1485 case MODIFIER_DEFAULT: |
|
1486 newFormat = NumberFormat.getInstance(locale); |
|
1487 break; |
|
1488 case MODIFIER_CURRENCY: |
|
1489 newFormat = NumberFormat.getCurrencyInstance(locale); |
|
1490 break; |
|
1491 case MODIFIER_PERCENT: |
|
1492 newFormat = NumberFormat.getPercentInstance(locale); |
|
1493 break; |
|
1494 case MODIFIER_INTEGER: |
|
1495 newFormat = NumberFormat.getIntegerInstance(locale); |
|
1496 break; |
|
1497 default: // DecimalFormat pattern |
|
1498 try { |
|
1499 newFormat = new DecimalFormat(segments[SEG_MODIFIER], |
|
1500 DecimalFormatSymbols.getInstance(locale)); |
|
1501 } catch (IllegalArgumentException e) { |
|
1502 maxOffset = oldMaxOffset; |
|
1503 throw e; |
|
1504 } |
|
1505 break; |
|
1506 } |
|
1507 break; |
|
1508 |
|
1509 case TYPE_DATE: |
|
1510 case TYPE_TIME: |
|
1511 int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); |
|
1512 if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { |
|
1513 if (type == TYPE_DATE) { |
|
1514 newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod], |
|
1515 locale); |
|
1516 } else { |
|
1517 newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod], |
|
1518 locale); |
|
1519 } |
|
1520 } else { |
|
1521 // SimpleDateFormat pattern |
|
1522 try { |
|
1523 newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale); |
|
1524 } catch (IllegalArgumentException e) { |
|
1525 maxOffset = oldMaxOffset; |
|
1526 throw e; |
|
1527 } |
|
1528 } |
|
1529 break; |
|
1530 |
|
1531 case TYPE_CHOICE: |
|
1532 try { |
|
1533 // ChoiceFormat pattern |
|
1534 newFormat = new ChoiceFormat(segments[SEG_MODIFIER]); |
|
1535 } catch (Exception e) { |
|
1536 maxOffset = oldMaxOffset; |
|
1537 throw new IllegalArgumentException("Choice Pattern incorrect: " |
|
1538 + segments[SEG_MODIFIER], e); |
|
1539 } |
|
1540 break; |
|
1541 |
|
1542 default: |
|
1543 maxOffset = oldMaxOffset; |
|
1544 throw new IllegalArgumentException("unknown format type: " + |
|
1545 segments[SEG_TYPE]); |
|
1546 } |
|
1547 } |
|
1548 formats[offsetNumber] = newFormat; |
|
1549 } |
|
1550 |
|
1551 private static final int findKeyword(String s, String[] list) { |
|
1552 for (int i = 0; i < list.length; ++i) { |
|
1553 if (s.equals(list[i])) |
|
1554 return i; |
|
1555 } |
|
1556 |
|
1557 // Try trimmed lowercase. |
|
1558 String ls = s.trim().toLowerCase(Locale.ROOT); |
|
1559 if (ls != s) { |
|
1560 for (int i = 0; i < list.length; ++i) { |
|
1561 if (ls.equals(list[i])) |
|
1562 return i; |
|
1563 } |
|
1564 } |
|
1565 return -1; |
|
1566 } |
|
1567 |
|
1568 private static final void copyAndFixQuotes(String source, int start, int end, |
|
1569 StringBuilder target) { |
|
1570 boolean quoted = false; |
|
1571 |
|
1572 for (int i = start; i < end; ++i) { |
|
1573 char ch = source.charAt(i); |
|
1574 if (ch == '{') { |
|
1575 if (!quoted) { |
|
1576 target.append('\''); |
|
1577 quoted = true; |
|
1578 } |
|
1579 target.append(ch); |
|
1580 } else if (ch == '\'') { |
|
1581 target.append("''"); |
|
1582 } else { |
|
1583 if (quoted) { |
|
1584 target.append('\''); |
|
1585 quoted = false; |
|
1586 } |
|
1587 target.append(ch); |
|
1588 } |
|
1589 } |
|
1590 if (quoted) { |
|
1591 target.append('\''); |
|
1592 } |
|
1593 } |
|
1594 |
|
1595 /** |
|
1596 * After reading an object from the input stream, do a simple verification |
|
1597 * to maintain class invariants. |
|
1598 * @throws InvalidObjectException if the objects read from the stream is invalid. |
|
1599 */ |
|
1600 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { |
|
1601 in.defaultReadObject(); |
|
1602 boolean isValid = maxOffset >= -1 |
|
1603 && formats.length > maxOffset |
|
1604 && offsets.length > maxOffset |
|
1605 && argumentNumbers.length > maxOffset; |
|
1606 if (isValid) { |
|
1607 int lastOffset = pattern.length() + 1; |
|
1608 for (int i = maxOffset; i >= 0; --i) { |
|
1609 if ((offsets[i] < 0) || (offsets[i] > lastOffset)) { |
|
1610 isValid = false; |
|
1611 break; |
|
1612 } else { |
|
1613 lastOffset = offsets[i]; |
|
1614 } |
|
1615 } |
|
1616 } |
|
1617 if (!isValid) { |
|
1618 throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream."); |
|
1619 } |
|
1620 } |
|
1621 } |