1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.message;
18
19 import org.apache.logging.log4j.util.StringBuilders;
20
21 import java.text.SimpleDateFormat;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Date;
26 import java.util.IdentityHashMap;
27 import java.util.Map;
28 import java.util.Set;
29
30
31
32
33 final class ParameterFormatter {
34
35
36
37 static final String RECURSION_PREFIX = "[...";
38
39
40
41 static final String RECURSION_SUFFIX = "...]";
42
43
44
45
46 static final String ERROR_PREFIX = "[!!!";
47
48
49
50 static final String ERROR_SEPARATOR = "=>";
51
52
53
54 static final String ERROR_MSG_SEPARATOR = ":";
55
56
57
58 static final String ERROR_SUFFIX = "!!!]";
59
60 private static final char DELIM_START = '{';
61 private static final char DELIM_STOP = '}';
62 private static final char ESCAPE_CHAR = '\\';
63
64 private static final ThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT_REF =
65 ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
66
67 private ParameterFormatter() {
68 }
69
70
71
72
73
74
75
76 static int countArgumentPlaceholders(final String messagePattern) {
77 if (messagePattern == null) {
78 return 0;
79 }
80 final int length = messagePattern.length();
81 int result = 0;
82 boolean isEscaped = false;
83 for (int i = 0; i < length - 1; i++) {
84 final char curChar = messagePattern.charAt(i);
85 if (curChar == ESCAPE_CHAR) {
86 isEscaped = !isEscaped;
87 } else if (curChar == DELIM_START) {
88 if (!isEscaped && messagePattern.charAt(i + 1) == DELIM_STOP) {
89 result++;
90 i++;
91 }
92 isEscaped = false;
93 } else {
94 isEscaped = false;
95 }
96 }
97 return result;
98 }
99
100
101
102
103
104
105
106 static int countArgumentPlaceholders2(final String messagePattern, final int[] indices) {
107 if (messagePattern == null) {
108 return 0;
109 }
110 final int length = messagePattern.length();
111 int result = 0;
112 boolean isEscaped = false;
113 for (int i = 0; i < length - 1; i++) {
114 final char curChar = messagePattern.charAt(i);
115 if (curChar == ESCAPE_CHAR) {
116 isEscaped = !isEscaped;
117 indices[0] = -1;
118 result++;
119 } else if (curChar == DELIM_START) {
120 if (!isEscaped && messagePattern.charAt(i + 1) == DELIM_STOP) {
121 indices[result] = i;
122 result++;
123 i++;
124 }
125 isEscaped = false;
126 } else {
127 isEscaped = false;
128 }
129 }
130 return result;
131 }
132
133
134
135
136
137
138
139 static int countArgumentPlaceholders3(final char[] messagePattern, final int length, final int[] indices) {
140 int result = 0;
141 boolean isEscaped = false;
142 for (int i = 0; i < length - 1; i++) {
143 final char curChar = messagePattern[i];
144 if (curChar == ESCAPE_CHAR) {
145 isEscaped = !isEscaped;
146 } else if (curChar == DELIM_START) {
147 if (!isEscaped && messagePattern[i + 1] == DELIM_STOP) {
148 indices[result] = i;
149 result++;
150 i++;
151 }
152 isEscaped = false;
153 } else {
154 isEscaped = false;
155 }
156 }
157 return result;
158 }
159
160
161
162
163
164
165
166
167 static String format(final String messagePattern, final Object[] arguments) {
168 final StringBuilder result = new StringBuilder();
169 final int argCount = arguments == null ? 0 : arguments.length;
170 formatMessage(result, messagePattern, arguments, argCount);
171 return result.toString();
172 }
173
174
175
176
177
178
179
180
181 static void formatMessage2(final StringBuilder buffer, final String messagePattern,
182 final Object[] arguments, final int argCount, final int[] indices) {
183 if (messagePattern == null || arguments == null || argCount == 0) {
184 buffer.append(messagePattern);
185 return;
186 }
187 int previous = 0;
188 for (int i = 0; i < argCount; i++) {
189 buffer.append(messagePattern, previous, indices[i]);
190 previous = indices[i] + 2;
191 recursiveDeepToString(arguments[i], buffer);
192 }
193 buffer.append(messagePattern, previous, messagePattern.length());
194 }
195
196
197
198
199
200
201
202
203 static void formatMessage3(final StringBuilder buffer, final char[] messagePattern, final int patternLength,
204 final Object[] arguments, final int argCount, final int[] indices) {
205 if (messagePattern == null) {
206 return;
207 }
208 if (arguments == null || argCount == 0) {
209 buffer.append(messagePattern);
210 return;
211 }
212 int previous = 0;
213 for (int i = 0; i < argCount; i++) {
214 buffer.append(messagePattern, previous, indices[i]);
215 previous = indices[i] + 2;
216 recursiveDeepToString(arguments[i], buffer);
217 }
218 buffer.append(messagePattern, previous, patternLength);
219 }
220
221
222
223
224
225
226
227
228 static void formatMessage(final StringBuilder buffer, final String messagePattern,
229 final Object[] arguments, final int argCount) {
230 if (messagePattern == null || arguments == null || argCount == 0) {
231 buffer.append(messagePattern);
232 return;
233 }
234 int escapeCounter = 0;
235 int currentArgument = 0;
236 int i = 0;
237 final int len = messagePattern.length();
238 for (; i < len - 1; i++) {
239 final char curChar = messagePattern.charAt(i);
240 if (curChar == ESCAPE_CHAR) {
241 escapeCounter++;
242 } else {
243 if (isDelimPair(curChar, messagePattern, i)) {
244 i++;
245
246
247 writeEscapedEscapeChars(escapeCounter, buffer);
248
249 if (isOdd(escapeCounter)) {
250
251 writeDelimPair(buffer);
252 } else {
253
254 writeArgOrDelimPair(arguments, argCount, currentArgument, buffer);
255 currentArgument++;
256 }
257 } else {
258 handleLiteralChar(buffer, escapeCounter, curChar);
259 }
260 escapeCounter = 0;
261 }
262 }
263 handleRemainingCharIfAny(messagePattern, len, buffer, escapeCounter, i);
264 }
265
266
267
268
269
270
271
272 private static boolean isDelimPair(final char curChar, final String messagePattern, final int curCharIndex) {
273 return curChar == DELIM_START && messagePattern.charAt(curCharIndex + 1) == DELIM_STOP;
274 }
275
276
277
278
279
280
281
282 private static void handleRemainingCharIfAny(final String messagePattern, final int len,
283 final StringBuilder buffer, final int escapeCounter, final int i) {
284 if (i == len - 1) {
285 final char curChar = messagePattern.charAt(i);
286 handleLastChar(buffer, escapeCounter, curChar);
287 }
288 }
289
290
291
292
293
294
295 private static void handleLastChar(final StringBuilder buffer, final int escapeCounter, final char curChar) {
296 if (curChar == ESCAPE_CHAR) {
297 writeUnescapedEscapeChars(escapeCounter + 1, buffer);
298 } else {
299 handleLiteralChar(buffer, escapeCounter, curChar);
300 }
301 }
302
303
304
305
306
307
308
309 private static void handleLiteralChar(final StringBuilder buffer, final int escapeCounter, final char curChar) {
310
311
312 writeUnescapedEscapeChars(escapeCounter, buffer);
313 buffer.append(curChar);
314 }
315
316
317
318
319
320
321 private static void writeDelimPair(final StringBuilder buffer) {
322 buffer.append(DELIM_START);
323 buffer.append(DELIM_STOP);
324 }
325
326
327
328
329
330
331 private static boolean isOdd(final int number) {
332 return (number & 1) == 1;
333 }
334
335
336
337
338
339
340
341 private static void writeEscapedEscapeChars(final int escapeCounter, final StringBuilder buffer) {
342 final int escapedEscapes = escapeCounter >> 1;
343 writeUnescapedEscapeChars(escapedEscapes, buffer);
344 }
345
346
347
348
349
350
351
352 private static void writeUnescapedEscapeChars(int escapeCounter, final StringBuilder buffer) {
353 while (escapeCounter > 0) {
354 buffer.append(ESCAPE_CHAR);
355 escapeCounter--;
356 }
357 }
358
359
360
361
362
363
364
365 private static void writeArgOrDelimPair(final Object[] arguments, final int argCount, final int currentArgument,
366 final StringBuilder buffer) {
367 if (currentArgument < argCount) {
368 recursiveDeepToString(arguments[currentArgument], buffer);
369 } else {
370 writeDelimPair(buffer);
371 }
372 }
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392 static String deepToString(final Object o) {
393 if (o == null) {
394 return null;
395 }
396
397 if (o instanceof String) {
398 return (String) o;
399 }
400 if (o instanceof Integer) {
401 return Integer.toString((Integer) o);
402 }
403 if (o instanceof Long) {
404 return Long.toString((Long) o);
405 }
406 if (o instanceof Double) {
407 return Double.toString((Double) o);
408 }
409 if (o instanceof Boolean) {
410 return Boolean.toString((Boolean) o);
411 }
412 if (o instanceof Character) {
413 return Character.toString((Character) o);
414 }
415 if (o instanceof Short) {
416 return Short.toString((Short) o);
417 }
418 if (o instanceof Float) {
419 return Float.toString((Float) o);
420 }
421 if (o instanceof Byte) {
422 return Byte.toString((Byte) o);
423 }
424 final StringBuilder str = new StringBuilder();
425 recursiveDeepToString(o, str);
426 return str.toString();
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447 static void recursiveDeepToString(final Object o, final StringBuilder str) {
448 recursiveDeepToString(o, str, null);
449 }
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472 private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set<Object> dejaVu) {
473 if (appendSpecialTypes(o, str)) {
474 return;
475 }
476 if (isMaybeRecursive(o)) {
477 appendPotentiallyRecursiveValue(o, str, dejaVu);
478 } else {
479 tryObjectToString(o, str);
480 }
481 }
482
483 private static boolean appendSpecialTypes(final Object o, final StringBuilder str) {
484 return StringBuilders.appendSpecificTypes(str, o) || appendDate(o, str);
485 }
486
487 private static boolean appendDate(final Object o, final StringBuilder str) {
488 if (!(o instanceof Date)) {
489 return false;
490 }
491 final Date date = (Date) o;
492 final SimpleDateFormat format = SIMPLE_DATE_FORMAT_REF.get();
493 str.append(format.format(date));
494 return true;
495 }
496
497
498
499
500 private static boolean isMaybeRecursive(final Object o) {
501 return o.getClass().isArray() || o instanceof Map || o instanceof Collection;
502 }
503
504 private static void appendPotentiallyRecursiveValue(
505 final Object o,
506 final StringBuilder str,
507 final Set<Object> dejaVu) {
508 final Class<?> oClass = o.getClass();
509 if (oClass.isArray()) {
510 appendArray(o, str, dejaVu, oClass);
511 } else if (o instanceof Map) {
512 appendMap(o, str, dejaVu);
513 } else if (o instanceof Collection) {
514 appendCollection(o, str, dejaVu);
515 } else {
516 throw new IllegalArgumentException("was expecting a container, found " + oClass);
517 }
518 }
519
520 private static void appendArray(
521 final Object o,
522 final StringBuilder str,
523 final Set<Object> dejaVu,
524 final Class<?> oClass) {
525 if (oClass == byte[].class) {
526 str.append(Arrays.toString((byte[]) o));
527 } else if (oClass == short[].class) {
528 str.append(Arrays.toString((short[]) o));
529 } else if (oClass == int[].class) {
530 str.append(Arrays.toString((int[]) o));
531 } else if (oClass == long[].class) {
532 str.append(Arrays.toString((long[]) o));
533 } else if (oClass == float[].class) {
534 str.append(Arrays.toString((float[]) o));
535 } else if (oClass == double[].class) {
536 str.append(Arrays.toString((double[]) o));
537 } else if (oClass == boolean[].class) {
538 str.append(Arrays.toString((boolean[]) o));
539 } else if (oClass == char[].class) {
540 str.append(Arrays.toString((char[]) o));
541 } else {
542
543 final Set<Object> effectiveDejaVu = getOrCreateDejaVu(dejaVu);
544 final boolean seen = !effectiveDejaVu.add(o);
545 if (seen) {
546 final String id = identityToString(o);
547 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
548 } else {
549 final Object[] oArray = (Object[]) o;
550 str.append('[');
551 boolean first = true;
552 for (final Object current : oArray) {
553 if (first) {
554 first = false;
555 } else {
556 str.append(", ");
557 }
558 recursiveDeepToString(current, str, cloneDejaVu(effectiveDejaVu));
559 }
560 str.append(']');
561 }
562 }
563 }
564
565
566
567
568 private static void appendMap(
569 final Object o,
570 final StringBuilder str,
571 final Set<Object> dejaVu) {
572 final Set<Object> effectiveDejaVu = getOrCreateDejaVu(dejaVu);
573 final boolean seen = !effectiveDejaVu.add(o);
574 if (seen) {
575 final String id = identityToString(o);
576 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
577 } else {
578 final Map<?, ?> oMap = (Map<?, ?>) o;
579 str.append('{');
580 boolean isFirst = true;
581 for (final Object o1 : oMap.entrySet()) {
582 final Map.Entry<?, ?> current = (Map.Entry<?, ?>) o1;
583 if (isFirst) {
584 isFirst = false;
585 } else {
586 str.append(", ");
587 }
588 final Object key = current.getKey();
589 final Object value = current.getValue();
590 recursiveDeepToString(key, str, cloneDejaVu(effectiveDejaVu));
591 str.append('=');
592 recursiveDeepToString(value, str, cloneDejaVu(effectiveDejaVu));
593 }
594 str.append('}');
595 }
596 }
597
598
599
600
601 private static void appendCollection(
602 final Object o,
603 final StringBuilder str,
604 final Set<Object> dejaVu) {
605 final Set<Object> effectiveDejaVu = getOrCreateDejaVu(dejaVu);
606 final boolean seen = !effectiveDejaVu.add(o);
607 if (seen) {
608 final String id = identityToString(o);
609 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
610 } else {
611 final Collection<?> oCol = (Collection<?>) o;
612 str.append('[');
613 boolean isFirst = true;
614 for (final Object anOCol : oCol) {
615 if (isFirst) {
616 isFirst = false;
617 } else {
618 str.append(", ");
619 }
620 recursiveDeepToString(anOCol, str, cloneDejaVu(effectiveDejaVu));
621 }
622 str.append(']');
623 }
624 }
625
626 private static Set<Object> getOrCreateDejaVu(Set<Object> dejaVu) {
627 return dejaVu == null
628 ? createDejaVu()
629 : dejaVu;
630 }
631
632 private static Set<Object> createDejaVu() {
633 return Collections.newSetFromMap(new IdentityHashMap<>());
634 }
635
636 private static Set<Object> cloneDejaVu(Set<Object> dejaVu) {
637 Set<Object> clonedDejaVu = createDejaVu();
638 clonedDejaVu.addAll(dejaVu);
639 return clonedDejaVu;
640 }
641
642 private static void tryObjectToString(final Object o, final StringBuilder str) {
643
644 try {
645 str.append(o.toString());
646 } catch (final Throwable t) {
647 handleErrorInObjectToString(o, str, t);
648 }
649 }
650
651 private static void handleErrorInObjectToString(final Object o, final StringBuilder str, final Throwable t) {
652 str.append(ERROR_PREFIX);
653 str.append(identityToString(o));
654 str.append(ERROR_SEPARATOR);
655 final String msg = t.getMessage();
656 final String className = t.getClass().getName();
657 str.append(className);
658 if (!className.equals(msg)) {
659 str.append(ERROR_MSG_SEPARATOR);
660 str.append(msg);
661 }
662 str.append(ERROR_SUFFIX);
663 }
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685 static String identityToString(final Object obj) {
686 if (obj == null) {
687 return null;
688 }
689 return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj));
690 }
691
692 }