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