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 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 public ParameterizedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
91 this.messagePattern = messagePattern;
92 this.throwable = throwable;
93 if (arguments != null) {
94 parseArguments(arguments);
95 }
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109
110 public ParameterizedMessage(final String messagePattern, final Object[] arguments) {
111 this.messagePattern = messagePattern;
112 if (arguments != null) {
113 parseArguments(arguments);
114 }
115 }
116
117
118
119
120
121
122 public ParameterizedMessage(final String messagePattern, final Object arg) {
123 this(messagePattern, new Object[]{arg});
124 }
125
126
127
128
129
130
131
132 public ParameterizedMessage(final String messagePattern, final Object arg1, final Object arg2) {
133 this(messagePattern, new Object[]{arg1, arg2});
134 }
135
136 private void parseArguments(final Object[] arguments) {
137 final int argsCount = countArgumentPlaceholders(messagePattern);
138 int resultArgCount = arguments.length;
139 if (argsCount < arguments.length) {
140 if (throwable == null && arguments[arguments.length - 1] instanceof Throwable) {
141 throwable = (Throwable) arguments[arguments.length - 1];
142 resultArgCount--;
143 }
144 }
145 argArray = new Object[resultArgCount];
146 for (int i = 0; i < resultArgCount; ++i) {
147 argArray[i] = arguments[i];
148 }
149
150 if (argsCount == 1 && throwable == null && arguments.length > 1) {
151
152 stringArgs = new String[1];
153 stringArgs[0] = deepToString(arguments);
154 } else {
155 stringArgs = new String[resultArgCount];
156 for (int i = 0; i < stringArgs.length; i++) {
157 stringArgs[i] = deepToString(arguments[i]);
158 }
159 }
160 }
161
162
163
164
165
166 @Override
167 public String getFormattedMessage() {
168 if (formattedMessage == null) {
169 formattedMessage = formatMessage(messagePattern, stringArgs);
170 }
171 return formattedMessage;
172 }
173
174
175
176
177
178 @Override
179 public String getFormat() {
180 return messagePattern;
181 }
182
183
184
185
186
187 @Override
188 public Object[] getParameters() {
189 if (argArray != null) {
190 return argArray;
191 }
192 return stringArgs;
193 }
194
195
196
197
198
199
200
201
202
203
204 @Override
205 public Throwable getThrowable() {
206 return throwable;
207 }
208
209 protected String formatMessage(final String msgPattern, final String[] sArgs) {
210 return format(msgPattern, sArgs);
211 }
212
213 @Override
214 public boolean equals(final Object o) {
215 if (this == o) {
216 return true;
217 }
218 if (o == null || getClass() != o.getClass()) {
219 return false;
220 }
221
222 final ParameterizedMessage that = (ParameterizedMessage) o;
223
224 if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
225 return false;
226 }
227 if (!Arrays.equals(stringArgs, that.stringArgs)) {
228 return false;
229 }
230
231
232 return true;
233 }
234
235 @Override
236 public int hashCode() {
237 int result = messagePattern != null ? messagePattern.hashCode() : 0;
238 result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0);
239 return result;
240 }
241
242
243
244
245
246
247
248
249 public static String format(final String messagePattern, final Object[] arguments) {
250 if (messagePattern == null || arguments == null || arguments.length == 0) {
251 return messagePattern;
252 }
253
254 final StringBuilder result = new StringBuilder();
255 int escapeCounter = 0;
256 int currentArgument = 0;
257 for (int i = 0; i < messagePattern.length(); i++) {
258 final char curChar = messagePattern.charAt(i);
259 if (curChar == ESCAPE_CHAR) {
260 escapeCounter++;
261 } else {
262 if (curChar == DELIM_START) {
263 if (i < messagePattern.length() - 1) {
264 if (messagePattern.charAt(i + 1) == DELIM_STOP) {
265
266 final int escapedEscapes = escapeCounter / 2;
267 for (int j = 0; j < escapedEscapes; j++) {
268 result.append(ESCAPE_CHAR);
269 }
270
271 if (escapeCounter % 2 == 1) {
272
273
274 result.append(DELIM_START);
275 result.append(DELIM_STOP);
276 } else {
277
278 if (currentArgument < arguments.length) {
279 result.append(arguments[currentArgument]);
280 } else {
281 result.append(DELIM_START).append(DELIM_STOP);
282 }
283 currentArgument++;
284 }
285 i++;
286 escapeCounter = 0;
287 continue;
288 }
289 }
290 }
291
292
293 if (escapeCounter > 0) {
294 for (int j = 0; j < escapeCounter; j++) {
295 result.append(ESCAPE_CHAR);
296 }
297 escapeCounter = 0;
298 }
299 result.append(curChar);
300 }
301 }
302 return result.toString();
303 }
304
305
306
307
308
309
310
311 public static int countArgumentPlaceholders(final String messagePattern) {
312 if (messagePattern == null) {
313 return 0;
314 }
315
316 final int delim = messagePattern.indexOf(DELIM_START);
317
318 if (delim == -1) {
319
320 return 0;
321 }
322 int result = 0;
323 boolean isEscaped = false;
324 for (int i = 0; i < messagePattern.length(); i++) {
325 final char curChar = messagePattern.charAt(i);
326 if (curChar == ESCAPE_CHAR) {
327 isEscaped = !isEscaped;
328 } else if (curChar == DELIM_START) {
329 if (!isEscaped) {
330 if (i < messagePattern.length() - 1) {
331 if (messagePattern.charAt(i + 1) == DELIM_STOP) {
332 result++;
333 i++;
334 }
335 }
336 }
337 isEscaped = false;
338 } else {
339 isEscaped = false;
340 }
341 }
342 return result;
343 }
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361 public static String deepToString(final Object o) {
362 if (o == null) {
363 return null;
364 }
365 if (o instanceof String) {
366 return (String) o;
367 }
368 final StringBuilder str = new StringBuilder();
369 final Set<String> dejaVu = new HashSet<String>();
370 recursiveDeepToString(o, str, dejaVu);
371 return str.toString();
372 }
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395 private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set<String> dejaVu) {
396 if (o == null) {
397 str.append("null");
398 return;
399 }
400 if (o instanceof String) {
401 str.append(o);
402 return;
403 }
404
405 final Class<?> oClass = o.getClass();
406 if (oClass.isArray()) {
407 if (oClass == byte[].class) {
408 str.append(Arrays.toString((byte[]) o));
409 } else if (oClass == short[].class) {
410 str.append(Arrays.toString((short[]) o));
411 } else if (oClass == int[].class) {
412 str.append(Arrays.toString((int[]) o));
413 } else if (oClass == long[].class) {
414 str.append(Arrays.toString((long[]) o));
415 } else if (oClass == float[].class) {
416 str.append(Arrays.toString((float[]) o));
417 } else if (oClass == double[].class) {
418 str.append(Arrays.toString((double[]) o));
419 } else if (oClass == boolean[].class) {
420 str.append(Arrays.toString((boolean[]) o));
421 } else if (oClass == char[].class) {
422 str.append(Arrays.toString((char[]) o));
423 } else {
424
425 final String id = identityToString(o);
426 if (dejaVu.contains(id)) {
427 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
428 } else {
429 dejaVu.add(id);
430 final Object[] oArray = (Object[]) o;
431 str.append("[");
432 boolean first = true;
433 for (final Object current : oArray) {
434 if (first) {
435 first = false;
436 } else {
437 str.append(", ");
438 }
439 recursiveDeepToString(current, str, new HashSet<String>(dejaVu));
440 }
441 str.append("]");
442 }
443
444 }
445 } else if (o instanceof Map) {
446
447 final String id = identityToString(o);
448 if (dejaVu.contains(id)) {
449 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
450 } else {
451 dejaVu.add(id);
452 final Map<?, ?> oMap = (Map<?, ?>) o;
453 str.append("{");
454 boolean isFirst = true;
455 for (final Object o1 : oMap.entrySet()) {
456 final Map.Entry<?, ?> current = (Map.Entry<?, ?>) o1;
457 if (isFirst) {
458 isFirst = false;
459 } else {
460 str.append(", ");
461 }
462 final Object key = current.getKey();
463 final Object value = current.getValue();
464 recursiveDeepToString(key, str, new HashSet<String>(dejaVu));
465 str.append("=");
466 recursiveDeepToString(value, str, new HashSet<String>(dejaVu));
467 }
468 str.append("}");
469 }
470 } else if (o instanceof Collection) {
471
472 final String id = identityToString(o);
473 if (dejaVu.contains(id)) {
474 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
475 } else {
476 dejaVu.add(id);
477 final Collection<?> oCol = (Collection<?>) o;
478 str.append("[");
479 boolean isFirst = true;
480 for (final Object anOCol : oCol) {
481 if (isFirst) {
482 isFirst = false;
483 } else {
484 str.append(", ");
485 }
486 recursiveDeepToString(anOCol, str, new HashSet<String>(dejaVu));
487 }
488 str.append("]");
489 }
490 } else if (o instanceof Date) {
491 final Date date = (Date) o;
492 final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
493
494 str.append(format.format(date));
495 } else {
496
497 try {
498 str.append(o.toString());
499 } catch (final Throwable t) {
500 str.append(ERROR_PREFIX);
501 str.append(identityToString(o));
502 str.append(ERROR_SEPARATOR);
503 final String msg = t.getMessage();
504 final String className = t.getClass().getName();
505 str.append(className);
506 if (!className.equals(msg)) {
507 str.append(ERROR_MSG_SEPARATOR);
508 str.append(msg);
509 }
510 str.append(ERROR_SUFFIX);
511 }
512 }
513 }
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533 public static String identityToString(final Object obj) {
534 if (obj == null) {
535 return null;
536 }
537 return obj.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(obj));
538 }
539
540 @Override
541 public String toString() {
542 return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" +
543 Arrays.toString(stringArgs) + ", throwable=" + throwable + "]";
544 }
545 }