1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.lang3.text;
18
19 import java.text.Format;
20 import java.text.MessageFormat;
21 import java.text.ParsePosition;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Locale;
25 import java.util.Map;
26 import java.util.Objects;
27
28 import org.apache.commons.lang3.LocaleUtils;
29 import org.apache.commons.lang3.StringUtils;
30 import org.apache.commons.lang3.Validate;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 @Deprecated
73 public class ExtendedMessageFormat extends MessageFormat {
74
75 private static final long serialVersionUID = -2362048321261811743L;
76 private static final String EMPTY_PATTERN = StringUtils.EMPTY;
77 private static final char START_FMT = ',';
78 private static final char END_FE = '}';
79 private static final char START_FE = '{';
80 private static final char QUOTE = '\'';
81
82
83
84
85 private String toPattern;
86
87
88
89
90 private final Map<String, ? extends FormatFactory> registry;
91
92
93
94
95
96
97
98 public ExtendedMessageFormat(final String pattern) {
99 this(pattern, Locale.getDefault());
100 }
101
102
103
104
105
106
107
108
109 public ExtendedMessageFormat(final String pattern, final Locale locale) {
110 this(pattern, locale, null);
111 }
112
113
114
115
116
117
118
119
120
121 public ExtendedMessageFormat(final String pattern, final Locale locale, final Map<String, ? extends FormatFactory> registry) {
122 super(EMPTY_PATTERN);
123 setLocale(LocaleUtils.toLocale(locale));
124 this.registry = registry;
125 applyPattern(pattern);
126 }
127
128
129
130
131
132
133
134
135 public ExtendedMessageFormat(final String pattern, final Map<String, ? extends FormatFactory> registry) {
136 this(pattern, Locale.getDefault(), registry);
137 }
138
139
140
141
142
143
144
145
146
147
148 private StringBuilder appendQuotedString(final String pattern, final ParsePosition pos,
149 final StringBuilder appendTo) {
150 assert pattern.toCharArray()[pos.getIndex()] == QUOTE :
151 "Quoted string must start with quote character";
152
153
154 if (appendTo != null) {
155 appendTo.append(QUOTE);
156 }
157 next(pos);
158
159 final int start = pos.getIndex();
160 final char[] c = pattern.toCharArray();
161 for (int i = pos.getIndex(); i < pattern.length(); i++) {
162 if (c[pos.getIndex()] == QUOTE) {
163 next(pos);
164 return appendTo == null ? null : appendTo.append(c, start,
165 pos.getIndex() - start);
166 }
167 next(pos);
168 }
169 throw new IllegalArgumentException(
170 "Unterminated quoted string at position " + start);
171 }
172
173
174
175
176
177
178 @Override
179 public final void applyPattern(final String pattern) {
180 if (registry == null) {
181 super.applyPattern(pattern);
182 toPattern = super.toPattern();
183 return;
184 }
185 final ArrayList<Format> foundFormats = new ArrayList<>();
186 final ArrayList<String> foundDescriptions = new ArrayList<>();
187 final StringBuilder stripCustom = new StringBuilder(pattern.length());
188
189 final ParsePosition pos = new ParsePosition(0);
190 final char[] c = pattern.toCharArray();
191 int fmtCount = 0;
192 while (pos.getIndex() < pattern.length()) {
193 switch (c[pos.getIndex()]) {
194 case QUOTE:
195 appendQuotedString(pattern, pos, stripCustom);
196 break;
197 case START_FE:
198 fmtCount++;
199 seekNonWs(pattern, pos);
200 final int start = pos.getIndex();
201 final int index = readArgumentIndex(pattern, next(pos));
202 stripCustom.append(START_FE).append(index);
203 seekNonWs(pattern, pos);
204 Format format = null;
205 String formatDescription = null;
206 if (c[pos.getIndex()] == START_FMT) {
207 formatDescription = parseFormatDescription(pattern,
208 next(pos));
209 format = getFormat(formatDescription);
210 if (format == null) {
211 stripCustom.append(START_FMT).append(formatDescription);
212 }
213 }
214 foundFormats.add(format);
215 foundDescriptions.add(format == null ? null : formatDescription);
216 Validate.isTrue(foundFormats.size() == fmtCount);
217 Validate.isTrue(foundDescriptions.size() == fmtCount);
218 if (c[pos.getIndex()] != END_FE) {
219 throw new IllegalArgumentException(
220 "Unreadable format element at position " + start);
221 }
222
223 default:
224 stripCustom.append(c[pos.getIndex()]);
225 next(pos);
226 }
227 }
228 super.applyPattern(stripCustom.toString());
229 toPattern = insertFormats(super.toPattern(), foundDescriptions);
230 if (containsElements(foundFormats)) {
231 final Format[] origFormats = getFormats();
232
233
234 int i = 0;
235 for (final Format f : foundFormats) {
236 if (f != null) {
237 origFormats[i] = f;
238 }
239 i++;
240 }
241 super.setFormats(origFormats);
242 }
243 }
244
245
246
247
248
249
250 private boolean containsElements(final Collection<?> coll) {
251 if (coll == null || coll.isEmpty()) {
252 return false;
253 }
254 return coll.stream().anyMatch(Objects::nonNull);
255 }
256
257 @Override
258 public boolean equals(final Object obj) {
259 if (this == obj) {
260 return true;
261 }
262 if (!super.equals(obj)) {
263 return false;
264 }
265 if (!(obj instanceof ExtendedMessageFormat)) {
266 return false;
267 }
268 final ExtendedMessageFormat other = (ExtendedMessageFormat) obj;
269 return Objects.equals(registry, other.registry) && Objects.equals(toPattern, other.toPattern);
270 }
271
272
273
274
275
276
277
278 private Format getFormat(final String desc) {
279 if (registry != null) {
280 String name = desc;
281 String args = null;
282 final int i = desc.indexOf(START_FMT);
283 if (i > 0) {
284 name = desc.substring(0, i).trim();
285 args = desc.substring(i + 1).trim();
286 }
287 final FormatFactory factory = registry.get(name);
288 if (factory != null) {
289 return factory.getFormat(name, args, getLocale());
290 }
291 }
292 return null;
293 }
294
295
296
297
298
299
300
301 private void getQuotedString(final String pattern, final ParsePosition pos) {
302 appendQuotedString(pattern, pos, null);
303 }
304
305 @Override
306 public int hashCode() {
307 final int prime = 31;
308 final int result = super.hashCode();
309 return prime * result + Objects.hash(registry, toPattern);
310 }
311
312
313
314
315
316
317
318
319 private String insertFormats(final String pattern, final ArrayList<String> customPatterns) {
320 if (!containsElements(customPatterns)) {
321 return pattern;
322 }
323 final StringBuilder sb = new StringBuilder(pattern.length() * 2);
324 final ParsePosition pos = new ParsePosition(0);
325 int fe = -1;
326 int depth = 0;
327 while (pos.getIndex() < pattern.length()) {
328 final char c = pattern.charAt(pos.getIndex());
329 switch (c) {
330 case QUOTE:
331 appendQuotedString(pattern, pos, sb);
332 break;
333 case START_FE:
334 depth++;
335 sb.append(START_FE).append(readArgumentIndex(pattern, next(pos)));
336
337 if (depth == 1) {
338 fe++;
339 final String customPattern = customPatterns.get(fe);
340 if (customPattern != null) {
341 sb.append(START_FMT).append(customPattern);
342 }
343 }
344 break;
345 case END_FE:
346 depth--;
347
348 default:
349 sb.append(c);
350 next(pos);
351 }
352 }
353 return sb.toString();
354 }
355
356
357
358
359
360
361
362 private ParsePosition next(final ParsePosition pos) {
363 pos.setIndex(pos.getIndex() + 1);
364 return pos;
365 }
366
367
368
369
370
371
372
373
374 private String parseFormatDescription(final String pattern, final ParsePosition pos) {
375 final int start = pos.getIndex();
376 seekNonWs(pattern, pos);
377 final int text = pos.getIndex();
378 int depth = 1;
379 for (; pos.getIndex() < pattern.length(); next(pos)) {
380 switch (pattern.charAt(pos.getIndex())) {
381 case START_FE:
382 depth++;
383 break;
384 case END_FE:
385 depth--;
386 if (depth == 0) {
387 return pattern.substring(text, pos.getIndex());
388 }
389 break;
390 case QUOTE:
391 getQuotedString(pattern, pos);
392 break;
393 default:
394 break;
395 }
396 }
397 throw new IllegalArgumentException(
398 "Unterminated format element at position " + start);
399 }
400
401
402
403
404
405
406
407
408 private int readArgumentIndex(final String pattern, final ParsePosition pos) {
409 final int start = pos.getIndex();
410 seekNonWs(pattern, pos);
411 final StringBuilder result = new StringBuilder();
412 boolean error = false;
413 for (; !error && pos.getIndex() < pattern.length(); next(pos)) {
414 char c = pattern.charAt(pos.getIndex());
415 if (Character.isWhitespace(c)) {
416 seekNonWs(pattern, pos);
417 c = pattern.charAt(pos.getIndex());
418 if (c != START_FMT && c != END_FE) {
419 error = true;
420 continue;
421 }
422 }
423 if ((c == START_FMT || c == END_FE) && result.length() > 0) {
424 try {
425 return Integer.parseInt(result.toString());
426 } catch (final NumberFormatException ignored) {
427
428
429 }
430 }
431 error = !Character.isDigit(c);
432 result.append(c);
433 }
434 if (error) {
435 throw new IllegalArgumentException(
436 "Invalid format argument index at position " + start + ": "
437 + pattern.substring(start, pos.getIndex()));
438 }
439 throw new IllegalArgumentException(
440 "Unterminated format element at position " + start);
441 }
442
443
444
445
446
447
448
449 private void seekNonWs(final String pattern, final ParsePosition pos) {
450 int len;
451 final char[] buffer = pattern.toCharArray();
452 do {
453 len = StrMatcher.splitMatcher().isMatch(buffer, pos.getIndex());
454 pos.setIndex(pos.getIndex() + len);
455 } while (len > 0 && pos.getIndex() < pattern.length());
456 }
457
458
459
460
461
462
463
464
465 @Override
466 public void setFormat(final int formatElementIndex, final Format newFormat) {
467 throw new UnsupportedOperationException();
468 }
469
470
471
472
473
474
475
476
477 @Override
478 public void setFormatByArgumentIndex(final int argumentIndex, final Format newFormat) {
479 throw new UnsupportedOperationException();
480 }
481
482
483
484
485
486
487
488 @Override
489 public void setFormats(final Format[] newFormats) {
490 throw new UnsupportedOperationException();
491 }
492
493
494
495
496
497
498
499 @Override
500 public void setFormatsByArgumentIndex(final Format[] newFormats) {
501 throw new UnsupportedOperationException();
502 }
503
504
505
506
507 @Override
508 public String toPattern() {
509 return toPattern;
510 }
511 }