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