View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
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   * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and
30   * the parameters.
31   * <p/>
32   * This class was originally written for Lillith (http://mac.freshmeat.net/projects/lilith-viewer) by
33   * Joern Huxhorn where it is licensed under the LGPL. It has been relicensed here with his permission
34   * providing that this attribution remain.
35   */
36  public class ParameterizedMessage implements Message, Serializable {
37  
38      /**
39       * Prefix for recursion.
40       */
41      public static final String RECURSION_PREFIX = "[...";
42      /**
43       * Suffix for recursion.
44       */
45      public static final String RECURSION_SUFFIX = "...]";
46  
47      /**
48       * Prefix for errors.
49       */
50      public static final String ERROR_PREFIX = "[!!!";
51      /**
52       * Separator for errors.
53       */
54      public static final String ERROR_SEPARATOR = "=>";
55      /**
56       * Separator for error messages.
57       */
58      public static final String ERROR_MSG_SEPARATOR = ":";
59      /**
60       * Suffix for errors.
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       * Create the parameterizedMessage.
80       * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
81       * where parameters should be substituted.
82       * @param stringArgs The arguments for substitution.
83       * @param throwable A Throwable.
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      * <p>This method returns a ParameterizedMessage which contains the arguments converted to String
101      * as well as an optional Throwable.</p>
102      * <p/>
103      * <p>If the last argument is a Throwable and is NOT used up by a placeholder in the message pattern it is returned
104      * in ParameterizedMessage.getThrowable() and won't be contained in the created String[].<br/>
105      * If it is used up ParameterizedMessage.getThrowable() will return null even if the last argument was a
106      * Throwable!</p>
107      *
108      * @param messagePattern the message pattern that to be checked for placeholders.
109      * @param arguments      the argument array to be converted.
110      */
111     public ParameterizedMessage(String messagePattern, Object[] arguments) {
112         this.messagePattern = messagePattern;
113         if (arguments != null) {
114             parseArguments(arguments);
115         }
116     }
117 
118     /**
119      * Constructor with a pattern and a single parameter.
120      * @param messagePattern The message pattern.
121      * @param arg The parameter.
122      */
123     public ParameterizedMessage(String messagePattern, Object arg) {
124         this(messagePattern, new Object[]{arg});
125     }
126 
127     /**
128      * Constructor with a pattern and two parameters.
129      * @param messagePattern The message pattern.
130      * @param arg1 The first parameter.
131      * @param arg2 The second parameter.
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             // special case
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      * Returns the formatted message.
165      * @return the formatted message.
166      */
167     public String getFormattedMessage() {
168         if (formattedMessage == null) {
169             formattedMessage = formatMessage(messagePattern, stringArgs);
170         }
171         return formattedMessage;
172     }
173 
174     /**
175      * Returns the message pattern.
176      * @return the message pattern.
177      */
178     public String getFormat() {
179         return messagePattern;
180     }
181 
182     /**
183      * Returns the message parameters.
184      * @return the message parameters.
185      */
186     public Object[] getParameters() {
187         if (argArray != null) {
188             return argArray;
189         }
190         return stringArgs;
191     }
192 
193     /**
194      * Returns the Throwable that was given as the last argument, if any.
195      * It will not survive serialization. The Throwable exists as part of the message
196      * primarily so that it can be extracted from the end of the list of parameters
197      * and then be added to the LogEvent. As such, the Throwable in the event should
198      * not be used once the LogEvent has been constructed.
199      *
200      * @return the Throwable, if any.
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         //if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false;
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      * Replace placeholders in the given messagePattern with arguments.
241      *
242      * @param messagePattern the message pattern containing placeholders.
243      * @param arguments      the arguments to be used to replace placeholders.
244      * @return the formatted message.
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                             // write escaped escape chars
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                                 // i.e. escaped
270                                 // write escaped escape chars
271                                 result.append(DELIM_START);
272                                 result.append(DELIM_STOP);
273                             } else {
274                                 // unescaped
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                 // any other char beside ESCAPE or DELIM_START/STOP-combo
289                 // write unescaped escape chars
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      * Counts the number of unescaped placeholders in the given messagePattern.
304      *
305      * @param messagePattern the message pattern to be analyzed.
306      * @return the number of unescaped placeholders.
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             // special case, no placeholders at all.
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      * This method performs a deep toString of the given Object.
344      * Primitive arrays are converted using their respective Arrays.toString methods while
345      * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could
346      * contain themselves.
347      * <p/>
348      * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a
349      * behavior. They only check if the container is directly contained in itself, but not if a contained container
350      * contains the original one. Because of that, Arrays.toString(Object[]) isn't safe either.
351      * Confusing? Just read the last paragraph again and check the respective toString() implementation.
352      * <p/>
353      * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o)
354      * would produce a relatively hard-to-debug StackOverflowError.
355      * @param o The object.
356      * @return The String representation.
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>(); // that's actually a neat name ;)
367         recursiveDeepToString(o, str, dejaVu);
368         return str.toString();
369     }
370 
371     /**
372      * This method performs a deep toString of the given Object.
373      * Primitive arrays are converted using their respective Arrays.toString methods while
374      * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could
375      * contain themselves.
376      * <p/>
377      * dejaVu is used in case of those container types to prevent an endless recursion.
378      * <p/>
379      * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a
380      * behavior.
381      * They only check if the container is directly contained in itself, but not if a contained container contains the
382      * original one. Because of that, Arrays.toString(Object[]) isn't safe either.
383      * Confusing? Just read the last paragraph again and check the respective toString() implementation.
384      * <p/>
385      * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o)
386      * would produce a relatively hard-to-debug StackOverflowError.
387      *
388      * @param o      the Object to convert into a String
389      * @param str    the StringBuilder that o will be appended to
390      * @param dejaVu a list of container identities that were already used.
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                 // special handling of container Object[]
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                 //str.append(Arrays.deepToString((Object[]) o));
441             }
442         } else if (o instanceof Map) {
443             // special handling of container Map
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             // special handling of container Collection
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             // I'll leave it like this for the moment... this could probably be optimized using ThreadLocal...
491             str.append(format.format(date));
492         } else {
493             // it's just some other Object, we can only use toString().
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      * This method returns the same as if Object.toString() would not have been
514      * overridden in obj.
515      * <p/>
516      * Note that this isn't 100% secure as collisions can always happen with hash codes.
517      * <p/>
518      * Copied from Object.hashCode():
519      * As much as is reasonably practical, the hashCode method defined by
520      * class <tt>Object</tt> does return distinct integers for distinct
521      * objects. (This is typically implemented by converting the internal
522      * address of the object into an integer, but this implementation
523      * technique is not required by the
524      * Java<font size="-2"><sup>TM</sup></font>
525      * programming language.)
526      *
527      * @param obj the Object that is to be converted into an identity string.
528      * @return the identity string as also defined in Object.toString()
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 }