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