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