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