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
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 || arguments == null || argCount == 0) {
204 buffer.append(messagePattern);
205 return;
206 }
207 int previous = 0;
208 for (int i = 0; i < argCount; i++) {
209 buffer.append(messagePattern, previous, indices[i]);
210 previous = indices[i] + 2;
211 recursiveDeepToString(arguments[i], buffer, null);
212 }
213 buffer.append(messagePattern, previous, patternLength);
214 }
215
216
217
218
219
220
221
222
223 static void formatMessage(final StringBuilder buffer, final String messagePattern,
224 final Object[] arguments, final int argCount) {
225 if (messagePattern == null || arguments == null || argCount == 0) {
226 buffer.append(messagePattern);
227 return;
228 }
229 int escapeCounter = 0;
230 int currentArgument = 0;
231 int i = 0;
232 final int len = messagePattern.length();
233 for (; i < len - 1; i++) {
234 final char curChar = messagePattern.charAt(i);
235 if (curChar == ESCAPE_CHAR) {
236 escapeCounter++;
237 } else {
238 if (isDelimPair(curChar, messagePattern, i)) {
239 i++;
240
241
242 writeEscapedEscapeChars(escapeCounter, buffer);
243
244 if (isOdd(escapeCounter)) {
245
246 writeDelimPair(buffer);
247 } else {
248
249 writeArgOrDelimPair(arguments, argCount, currentArgument, buffer);
250 currentArgument++;
251 }
252 } else {
253 handleLiteralChar(buffer, escapeCounter, curChar);
254 }
255 escapeCounter = 0;
256 }
257 }
258 handleRemainingCharIfAny(messagePattern, len, buffer, escapeCounter, i);
259 }
260
261
262
263
264
265
266
267 private static boolean isDelimPair(final char curChar, final String messagePattern, final int curCharIndex) {
268 return curChar == DELIM_START && messagePattern.charAt(curCharIndex + 1) == DELIM_STOP;
269 }
270
271
272
273
274
275
276
277 private static void handleRemainingCharIfAny(final String messagePattern, final int len,
278 final StringBuilder buffer, final int escapeCounter, final int i) {
279 if (i == len - 1) {
280 final char curChar = messagePattern.charAt(i);
281 handleLastChar(buffer, escapeCounter, curChar);
282 }
283 }
284
285
286
287
288
289
290 private static void handleLastChar(final StringBuilder buffer, final int escapeCounter, final char curChar) {
291 if (curChar == ESCAPE_CHAR) {
292 writeUnescapedEscapeChars(escapeCounter + 1, buffer);
293 } else {
294 handleLiteralChar(buffer, escapeCounter, curChar);
295 }
296 }
297
298
299
300
301
302
303
304 private static void handleLiteralChar(final StringBuilder buffer, final int escapeCounter, final char curChar) {
305
306
307 writeUnescapedEscapeChars(escapeCounter, buffer);
308 buffer.append(curChar);
309 }
310
311
312
313
314
315
316 private static void writeDelimPair(final StringBuilder buffer) {
317 buffer.append(DELIM_START);
318 buffer.append(DELIM_STOP);
319 }
320
321
322
323
324
325
326 private static boolean isOdd(final int number) {
327 return (number & 1) == 1;
328 }
329
330
331
332
333
334
335
336 private static void writeEscapedEscapeChars(final int escapeCounter, final StringBuilder buffer) {
337 final int escapedEscapes = escapeCounter >> 1;
338 writeUnescapedEscapeChars(escapedEscapes, buffer);
339 }
340
341
342
343
344
345
346
347 private static void writeUnescapedEscapeChars(int escapeCounter, final StringBuilder buffer) {
348 while (escapeCounter > 0) {
349 buffer.append(ESCAPE_CHAR);
350 escapeCounter--;
351 }
352 }
353
354
355
356
357
358
359
360 private static void writeArgOrDelimPair(final Object[] arguments, final int argCount, final int currentArgument,
361 final StringBuilder buffer) {
362 if (currentArgument < argCount) {
363 recursiveDeepToString(arguments[currentArgument], buffer, null);
364 } else {
365 writeDelimPair(buffer);
366 }
367 }
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387 static String deepToString(final Object o) {
388 if (o == null) {
389 return null;
390 }
391 if (o instanceof String) {
392 return (String) o;
393 }
394 final StringBuilder str = new StringBuilder();
395 final Set<String> dejaVu = new HashSet<>();
396 recursiveDeepToString(o, str, dejaVu);
397 return str.toString();
398 }
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424 private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set<String> dejaVu) {
425 if (appendSpecialTypes(o, str)) {
426 return;
427 }
428 if (isMaybeRecursive(o)) {
429 appendPotentiallyRecursiveValue(o, str, dejaVu);
430 } else {
431 tryObjectToString(o, str);
432 }
433 }
434
435 private static boolean appendSpecialTypes(final Object o, final StringBuilder str) {
436 if (o == null || o instanceof String) {
437 str.append((String) o);
438 return true;
439 } else if (o instanceof CharSequence) {
440 str.append((CharSequence) o);
441 return true;
442 } else if (o instanceof StringBuilderFormattable) {
443 ((StringBuilderFormattable) o).formatTo(str);
444 return true;
445 }
446 return appendDate(o, str);
447 }
448
449 private static boolean appendDate(final Object o, final StringBuilder str) {
450 if (!(o instanceof Date)) {
451 return false;
452 }
453 final Date date = (Date) o;
454 final SimpleDateFormat format = getSimpleDateFormat();
455 str.append(format.format(date));
456 return true;
457 }
458
459 private static SimpleDateFormat getSimpleDateFormat() {
460 SimpleDateFormat result = threadLocalSimpleDateFormat.get();
461 if (result == null) {
462 result = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
463 threadLocalSimpleDateFormat.set(result);
464 }
465 return result;
466 }
467
468
469
470
471 private static boolean isMaybeRecursive(final Object o) {
472 return o.getClass().isArray() || o instanceof Map || o instanceof Collection;
473 }
474
475 private static void appendPotentiallyRecursiveValue(final Object o, final StringBuilder str,
476 final Set<String> dejaVu) {
477 final Class<?> oClass = o.getClass();
478 if (oClass.isArray()) {
479 appendArray(o, str, dejaVu, oClass);
480 } else if (o instanceof Map) {
481 appendMap(o, str, dejaVu);
482 } else if (o instanceof Collection) {
483 appendCollection(o, str, dejaVu);
484 }
485 }
486
487 private static void appendArray(final Object o, final StringBuilder str, Set<String> dejaVu,
488 final Class<?> oClass) {
489 if (oClass == byte[].class) {
490 str.append(Arrays.toString((byte[]) o));
491 } else if (oClass == short[].class) {
492 str.append(Arrays.toString((short[]) o));
493 } else if (oClass == int[].class) {
494 str.append(Arrays.toString((int[]) o));
495 } else if (oClass == long[].class) {
496 str.append(Arrays.toString((long[]) o));
497 } else if (oClass == float[].class) {
498 str.append(Arrays.toString((float[]) o));
499 } else if (oClass == double[].class) {
500 str.append(Arrays.toString((double[]) o));
501 } else if (oClass == boolean[].class) {
502 str.append(Arrays.toString((boolean[]) o));
503 } else if (oClass == char[].class) {
504 str.append(Arrays.toString((char[]) o));
505 } else {
506 if (dejaVu == null) {
507 dejaVu = new HashSet<>();
508 }
509
510 final String id = identityToString(o);
511 if (dejaVu.contains(id)) {
512 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
513 } else {
514 dejaVu.add(id);
515 final Object[] oArray = (Object[]) o;
516 str.append('[');
517 boolean first = true;
518 for (final Object current : oArray) {
519 if (first) {
520 first = false;
521 } else {
522 str.append(", ");
523 }
524 recursiveDeepToString(current, str, new HashSet<>(dejaVu));
525 }
526 str.append(']');
527 }
528
529 }
530 }
531
532 private static void appendMap(final Object o, final StringBuilder str, Set<String> dejaVu) {
533
534 if (dejaVu == null) {
535 dejaVu = new HashSet<>();
536 }
537 final String id = identityToString(o);
538 if (dejaVu.contains(id)) {
539 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
540 } else {
541 dejaVu.add(id);
542 final Map<?, ?> oMap = (Map<?, ?>) o;
543 str.append('{');
544 boolean isFirst = true;
545 for (final Object o1 : oMap.entrySet()) {
546 final Map.Entry<?, ?> current = (Map.Entry<?, ?>) o1;
547 if (isFirst) {
548 isFirst = false;
549 } else {
550 str.append(", ");
551 }
552 final Object key = current.getKey();
553 final Object value = current.getValue();
554 recursiveDeepToString(key, str, new HashSet<>(dejaVu));
555 str.append('=');
556 recursiveDeepToString(value, str, new HashSet<>(dejaVu));
557 }
558 str.append('}');
559 }
560 }
561
562 private static void appendCollection(final Object o, final StringBuilder str, Set<String> dejaVu) {
563
564 if (dejaVu == null) {
565 dejaVu = new HashSet<>();
566 }
567 final String id = identityToString(o);
568 if (dejaVu.contains(id)) {
569 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
570 } else {
571 dejaVu.add(id);
572 final Collection<?> oCol = (Collection<?>) o;
573 str.append('[');
574 boolean isFirst = true;
575 for (final Object anOCol : oCol) {
576 if (isFirst) {
577 isFirst = false;
578 } else {
579 str.append(", ");
580 }
581 recursiveDeepToString(anOCol, str, new HashSet<>(dejaVu));
582 }
583 str.append(']');
584 }
585 }
586
587 private static void tryObjectToString(final Object o, final StringBuilder str) {
588
589 try {
590 str.append(o.toString());
591 } catch (final Throwable t) {
592 handleErrorInObjectToString(o, str, t);
593 }
594 }
595
596 private static void handleErrorInObjectToString(final Object o, final StringBuilder str, final Throwable t) {
597 str.append(ERROR_PREFIX);
598 str.append(identityToString(o));
599 str.append(ERROR_SEPARATOR);
600 final String msg = t.getMessage();
601 final String className = t.getClass().getName();
602 str.append(className);
603 if (!className.equals(msg)) {
604 str.append(ERROR_MSG_SEPARATOR);
605 str.append(msg);
606 }
607 str.append(ERROR_SUFFIX);
608 }
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630 static String identityToString(final Object obj) {
631 if (obj == null) {
632 return null;
633 }
634 return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj));
635 }
636
637 }