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