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 format(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
257 final StringBuilder result = new StringBuilder();
258 int escapeCounter = 0;
259 int currentArgument = 0;
260 for (int i = 0; i < messagePattern.length(); i++) {
261 final char curChar = messagePattern.charAt(i);
262 if (curChar == ESCAPE_CHAR) {
263 escapeCounter++;
264 } else {
265 if (curChar == DELIM_START && i < messagePattern.length() - 1
266 && messagePattern.charAt(i + 1) == DELIM_STOP) {
267
268 final int escapedEscapes = escapeCounter / 2;
269 for (int j = 0; j < escapedEscapes; j++) {
270 result.append(ESCAPE_CHAR);
271 }
272
273 if (escapeCounter % 2 == 1) {
274
275
276 result.append(DELIM_START);
277 result.append(DELIM_STOP);
278 } else {
279
280 if (currentArgument < arguments.length) {
281 result.append(arguments[currentArgument]);
282 } else {
283 result.append(DELIM_START).append(DELIM_STOP);
284 }
285 currentArgument++;
286 }
287 i++;
288 escapeCounter = 0;
289 continue;
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 && i < messagePattern.length() - 1 && messagePattern.charAt(i + 1) == DELIM_STOP) {
330 result++;
331 i++;
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
358
359 public static String deepToString(final Object o) {
360 if (o == null) {
361 return null;
362 }
363 if (o instanceof String) {
364 return (String) o;
365 }
366 final StringBuilder str = new StringBuilder();
367 final Set<String> dejaVu = new HashSet<String>();
368 recursiveDeepToString(o, str, dejaVu);
369 return str.toString();
370 }
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396 private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set<String> dejaVu) {
397 if (o == null) {
398 str.append("null");
399 return;
400 }
401 if (o instanceof String) {
402 str.append(o);
403 return;
404 }
405
406 final Class<?> oClass = o.getClass();
407 if (oClass.isArray()) {
408 if (oClass == byte[].class) {
409 str.append(Arrays.toString((byte[]) o));
410 } else if (oClass == short[].class) {
411 str.append(Arrays.toString((short[]) o));
412 } else if (oClass == int[].class) {
413 str.append(Arrays.toString((int[]) o));
414 } else if (oClass == long[].class) {
415 str.append(Arrays.toString((long[]) o));
416 } else if (oClass == float[].class) {
417 str.append(Arrays.toString((float[]) o));
418 } else if (oClass == double[].class) {
419 str.append(Arrays.toString((double[]) o));
420 } else if (oClass == boolean[].class) {
421 str.append(Arrays.toString((boolean[]) o));
422 } else if (oClass == char[].class) {
423 str.append(Arrays.toString((char[]) o));
424 } else {
425
426 final String id = identityToString(o);
427 if (dejaVu.contains(id)) {
428 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
429 } else {
430 dejaVu.add(id);
431 final Object[] oArray = (Object[]) o;
432 str.append('[');
433 boolean first = true;
434 for (final Object current : oArray) {
435 if (first) {
436 first = false;
437 } else {
438 str.append(", ");
439 }
440 recursiveDeepToString(current, str, new HashSet<String>(dejaVu));
441 }
442 str.append(']');
443 }
444
445 }
446 } else if (o instanceof Map) {
447
448 final String id = identityToString(o);
449 if (dejaVu.contains(id)) {
450 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
451 } else {
452 dejaVu.add(id);
453 final Map<?, ?> oMap = (Map<?, ?>) o;
454 str.append('{');
455 boolean isFirst = true;
456 for (final Object o1 : oMap.entrySet()) {
457 final Map.Entry<?, ?> current = (Map.Entry<?, ?>) o1;
458 if (isFirst) {
459 isFirst = false;
460 } else {
461 str.append(", ");
462 }
463 final Object key = current.getKey();
464 final Object value = current.getValue();
465 recursiveDeepToString(key, str, new HashSet<String>(dejaVu));
466 str.append('=');
467 recursiveDeepToString(value, str, new HashSet<String>(dejaVu));
468 }
469 str.append('}');
470 }
471 } else if (o instanceof Collection) {
472
473 final String id = identityToString(o);
474 if (dejaVu.contains(id)) {
475 str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
476 } else {
477 dejaVu.add(id);
478 final Collection<?> oCol = (Collection<?>) o;
479 str.append('[');
480 boolean isFirst = true;
481 for (final Object anOCol : oCol) {
482 if (isFirst) {
483 isFirst = false;
484 } else {
485 str.append(", ");
486 }
487 recursiveDeepToString(anOCol, str, new HashSet<String>(dejaVu));
488 }
489 str.append(']');
490 }
491 } else if (o instanceof Date) {
492 final Date date = (Date) o;
493 final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
494
495 str.append(format.format(date));
496 } else {
497
498 try {
499 str.append(o.toString());
500 } catch (final Throwable t) {
501 str.append(ERROR_PREFIX);
502 str.append(identityToString(o));
503 str.append(ERROR_SEPARATOR);
504 final String msg = t.getMessage();
505 final String className = t.getClass().getName();
506 str.append(className);
507 if (!className.equals(msg)) {
508 str.append(ERROR_MSG_SEPARATOR);
509 str.append(msg);
510 }
511 str.append(ERROR_SUFFIX);
512 }
513 }
514 }
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536 public static String identityToString(final Object obj) {
537 if (obj == null) {
538 return null;
539 }
540 return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj));
541 }
542
543 @Override
544 public String toString() {
545 return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" +
546 Arrays.toString(stringArgs) + ", throwable=" + throwable + ']';
547 }
548 }