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