View Javadoc

1   /**
2    * Copyright (c) 2004-2011 QOS.ch
3    * All rights reserved.
4    *
5    * Permission is hereby granted, free  of charge, to any person obtaining
6    * a  copy  of this  software  and  associated  documentation files  (the
7    * "Software"), to  deal in  the Software without  restriction, including
8    * without limitation  the rights to  use, copy, modify,  merge, publish,
9    * distribute,  sublicense, and/or sell  copies of  the Software,  and to
10   * permit persons to whom the Software  is furnished to do so, subject to
11   * the following conditions:
12   *
13   * The  above  copyright  notice  and  this permission  notice  shall  be
14   * included in all copies or substantial portions of the Software.
15   *
16   * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
17   * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
18   * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21   * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
22   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23   *
24   */
25  package org.slf4j.helpers;
26  
27  import java.text.MessageFormat;
28  import java.util.HashMap;
29  import java.util.Map;
30  
31  // contributors: lizongbo: proposed special treatment of array parameter values
32  // Joern Huxhorn: pointed out double[] omission, suggested deep array copy
33  /**
34   * Formats messages according to very simple substitution rules. Substitutions
35   * can be made 1, 2 or more arguments.
36   * 
37   * <p>
38   * For example,
39   * 
40   * <pre>
41   * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;)
42   * </pre>
43   * 
44   * will return the string "Hi there.".
45   * <p>
46   * The {} pair is called the <em>formatting anchor</em>. It serves to designate
47   * the location where arguments need to be substituted within the message
48   * pattern.
49   * <p>
50   * In case your message contains the '{' or the '}' character, you do not have
51   * to do anything special unless the '}' character immediately follows '{'. For
52   * example,
53   * 
54   * <pre>
55   * MessageFormatter.format(&quot;Set {1,2,3} is not equal to {}.&quot;, &quot;1,2&quot;);
56   * </pre>
57   * 
58   * will return the string "Set {1,2,3} is not equal to 1,2.".
59   * 
60   * <p>
61   * If for whatever reason you need to place the string "{}" in the message
62   * without its <em>formatting anchor</em> meaning, then you need to escape the
63   * '{' character with '\', that is the backslash character. Only the '{'
64   * character should be escaped. There is no need to escape the '}' character.
65   * For example,
66   * 
67   * <pre>
68   * MessageFormatter.format(&quot;Set \\{} is not equal to {}.&quot;, &quot;1,2&quot;);
69   * </pre>
70   * 
71   * will return the string "Set {} is not equal to 1,2.".
72   * 
73   * <p>
74   * The escaping behavior just described can be overridden by escaping the escape
75   * character '\'. Calling
76   * 
77   * <pre>
78   * MessageFormatter.format(&quot;File name is C:\\\\{}.&quot;, &quot;file.zip&quot;);
79   * </pre>
80   * 
81   * will return the string "File name is C:\file.zip".
82   * 
83   * <p>
84   * The formatting conventions are different than those of {@link MessageFormat}
85   * which ships with the Java platform. This is justified by the fact that
86   * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}.
87   * This local performance difference is both measurable and significant in the
88   * larger context of the complete logging processing chain.
89   * 
90   * <p>
91   * See also {@link #format(String, Object)},
92   * {@link #format(String, Object, Object)} and
93   * {@link #arrayFormat(String, Object[])} methods for more details.
94   * 
95   * @author Ceki G&uuml;lc&uuml;
96   * @author Joern Huxhorn
97   */
98  final public class MessageFormatter {
99    static final char DELIM_START = '{';
100   static final char DELIM_STOP = '}';
101   static final String DELIM_STR = "{}";
102   private static final char ESCAPE_CHAR = '\\';
103 
104   /**
105    * Performs single argument substitution for the 'messagePattern' passed as
106    * parameter.
107    * <p>
108    * For example,
109    * 
110    * <pre>
111    * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);
112    * </pre>
113    * 
114    * will return the string "Hi there.".
115    * <p>
116    * 
117    * @param messagePattern
118    *          The message pattern which will be parsed and formatted
119    * @param argument
120    *          The argument to be substituted in place of the formatting anchor
121    * @return The formatted message
122    */
123   final public static FormattingTuple format(String messagePattern, Object arg) {
124     return arrayFormat(messagePattern, new Object[] { arg });
125   }
126 
127   /**
128    * 
129    * Performs a two argument substitution for the 'messagePattern' passed as
130    * parameter.
131    * <p>
132    * For example,
133    * 
134    * <pre>
135    * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);
136    * </pre>
137    * 
138    * will return the string "Hi Alice. My name is Bob.".
139    * 
140    * @param messagePattern
141    *          The message pattern which will be parsed and formatted
142    * @param arg1
143    *          The argument to be substituted in place of the first formatting
144    *          anchor
145    * @param arg2
146    *          The argument to be substituted in place of the second formatting
147    *          anchor
148    * @return The formatted message
149    */
150   final public static FormattingTuple format(final String messagePattern,
151       Object arg1, Object arg2) {
152     return arrayFormat(messagePattern, new Object[] { arg1, arg2 });
153   }
154 
155   static final Throwable getThrowableCandidate(Object[] argArray) {
156     if (argArray == null || argArray.length == 0) {
157       return null;
158     }
159 
160     final Object lastEntry = argArray[argArray.length - 1];
161     if (lastEntry instanceof Throwable) {
162       return (Throwable) lastEntry;
163     }
164     return null;
165   }
166 
167   /**
168    * Same principle as the {@link #format(String, Object)} and
169    * {@link #format(String, Object, Object)} methods except that any number of
170    * arguments can be passed in an array.
171    * 
172    * @param messagePattern
173    *          The message pattern which will be parsed and formatted
174    * @param argArray
175    *          An array of arguments to be substituted in place of formatting
176    *          anchors
177    * @return The formatted message
178    */
179   final public static FormattingTuple arrayFormat(final String messagePattern,
180       final Object[] argArray) {
181 
182     Throwable throwableCandidate = getThrowableCandidate(argArray);
183 
184     if (messagePattern == null) {
185       return new FormattingTuple(null, argArray, throwableCandidate);
186     }
187 
188     if (argArray == null) {
189       return new FormattingTuple(messagePattern);
190     }
191 
192     int i = 0;
193     int j;
194     // use string builder for better multicore performance 
195     StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
196 
197     int L;
198     for (L = 0; L < argArray.length; L++) {
199 
200       j = messagePattern.indexOf(DELIM_STR, i);
201 
202       if (j == -1) {
203         // no more variables
204         if (i == 0) { // this is a simple string
205           return new FormattingTuple(messagePattern, argArray,
206               throwableCandidate);
207         } else { // add the tail string which contains no variables and return
208           // the result.
209           sbuf.append(messagePattern.substring(i, messagePattern.length()));
210           return new FormattingTuple(sbuf.toString(), argArray,
211               throwableCandidate);
212         }
213       } else {
214         if (isEscapedDelimeter(messagePattern, j)) {
215           if (!isDoubleEscaped(messagePattern, j)) {
216             L--; // DELIM_START was escaped, thus should not be incremented
217             sbuf.append(messagePattern.substring(i, j - 1));
218             sbuf.append(DELIM_START);
219             i = j + 1;
220           } else {
221             // The escape character preceding the delimiter start is
222             // itself escaped: "abc x:\\{}"
223             // we have to consume one backward slash
224             sbuf.append(messagePattern.substring(i, j - 1));
225             deeplyAppendParameter(sbuf, argArray[L], new HashMap());
226             i = j + 2;
227           }
228         } else {
229           // normal case
230           sbuf.append(messagePattern.substring(i, j));
231           deeplyAppendParameter(sbuf, argArray[L], new HashMap());
232           i = j + 2;
233         }
234       }
235     }
236     // append the characters following the last {} pair.
237     sbuf.append(messagePattern.substring(i, messagePattern.length()));
238     if (L < argArray.length - 1) {
239       return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate);
240     } else {
241       return new FormattingTuple(sbuf.toString(), argArray, null);
242     }
243   }
244 
245   final static boolean isEscapedDelimeter(String messagePattern,
246       int delimeterStartIndex) {
247 
248     if (delimeterStartIndex == 0) {
249       return false;
250     }
251     char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
252     if (potentialEscape == ESCAPE_CHAR) {
253       return true;
254     } else {
255       return false;
256     }
257   }
258 
259   final static boolean isDoubleEscaped(String messagePattern,
260       int delimeterStartIndex) {
261     if (delimeterStartIndex >= 2
262         && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {
263       return true;
264     } else {
265       return false;
266     }
267   }
268 
269   // special treatment of array values was suggested by 'lizongbo'
270   private static void deeplyAppendParameter(StringBuilder sbuf, Object o,
271       Map seenMap) {
272     if (o == null) {
273       sbuf.append("null");
274       return;
275     }
276     if (!o.getClass().isArray()) {
277       safeObjectAppend(sbuf, o);
278     } else {
279       // check for primitive array types because they
280       // unfortunately cannot be cast to Object[]
281       if (o instanceof boolean[]) {
282         booleanArrayAppend(sbuf, (boolean[]) o);
283       } else if (o instanceof byte[]) {
284         byteArrayAppend(sbuf, (byte[]) o);
285       } else if (o instanceof char[]) {
286         charArrayAppend(sbuf, (char[]) o);
287       } else if (o instanceof short[]) {
288         shortArrayAppend(sbuf, (short[]) o);
289       } else if (o instanceof int[]) {
290         intArrayAppend(sbuf, (int[]) o);
291       } else if (o instanceof long[]) {
292         longArrayAppend(sbuf, (long[]) o);
293       } else if (o instanceof float[]) {
294         floatArrayAppend(sbuf, (float[]) o);
295       } else if (o instanceof double[]) {
296         doubleArrayAppend(sbuf, (double[]) o);
297       } else {
298         objectArrayAppend(sbuf, (Object[]) o, seenMap);
299       }
300     }
301   }
302 
303   private static void safeObjectAppend(StringBuilder sbuf, Object o) {
304     try {
305       String oAsString = o.toString();
306       sbuf.append(oAsString);
307     } catch (Throwable t) {
308       System.err
309           .println("SLF4J: Failed toString() invocation on an object of type ["
310               + o.getClass().getName() + "]");
311       t.printStackTrace();
312       sbuf.append("[FAILED toString()]");
313     }
314 
315   }
316 
317   private static void objectArrayAppend(StringBuilder sbuf, Object[] a,
318       Map seenMap) {
319     sbuf.append('[');
320     if (!seenMap.containsKey(a)) {
321       seenMap.put(a, null);
322       final int len = a.length;
323       for (int i = 0; i < len; i++) {
324         deeplyAppendParameter(sbuf, a[i], seenMap);
325         if (i != len - 1)
326           sbuf.append(", ");
327       }
328       // allow repeats in siblings
329       seenMap.remove(a);
330     } else {
331       sbuf.append("...");
332     }
333     sbuf.append(']');
334   }
335 
336   private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
337     sbuf.append('[');
338     final int len = a.length;
339     for (int i = 0; i < len; i++) {
340       sbuf.append(a[i]);
341       if (i != len - 1)
342         sbuf.append(", ");
343     }
344     sbuf.append(']');
345   }
346 
347   private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
348     sbuf.append('[');
349     final int len = a.length;
350     for (int i = 0; i < len; i++) {
351       sbuf.append(a[i]);
352       if (i != len - 1)
353         sbuf.append(", ");
354     }
355     sbuf.append(']');
356   }
357 
358   private static void charArrayAppend(StringBuilder sbuf, char[] a) {
359     sbuf.append('[');
360     final int len = a.length;
361     for (int i = 0; i < len; i++) {
362       sbuf.append(a[i]);
363       if (i != len - 1)
364         sbuf.append(", ");
365     }
366     sbuf.append(']');
367   }
368 
369   private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
370     sbuf.append('[');
371     final int len = a.length;
372     for (int i = 0; i < len; i++) {
373       sbuf.append(a[i]);
374       if (i != len - 1)
375         sbuf.append(", ");
376     }
377     sbuf.append(']');
378   }
379 
380   private static void intArrayAppend(StringBuilder sbuf, int[] a) {
381     sbuf.append('[');
382     final int len = a.length;
383     for (int i = 0; i < len; i++) {
384       sbuf.append(a[i]);
385       if (i != len - 1)
386         sbuf.append(", ");
387     }
388     sbuf.append(']');
389   }
390 
391   private static void longArrayAppend(StringBuilder sbuf, long[] a) {
392     sbuf.append('[');
393     final int len = a.length;
394     for (int i = 0; i < len; i++) {
395       sbuf.append(a[i]);
396       if (i != len - 1)
397         sbuf.append(", ");
398     }
399     sbuf.append(']');
400   }
401 
402   private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
403     sbuf.append('[');
404     final int len = a.length;
405     for (int i = 0; i < len; i++) {
406       sbuf.append(a[i]);
407       if (i != len - 1)
408         sbuf.append(", ");
409     }
410     sbuf.append(']');
411   }
412 
413   private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
414     sbuf.append('[');
415     final int len = a.length;
416     for (int i = 0; i < len; i++) {
417       sbuf.append(a[i]);
418       if (i != len - 1)
419         sbuf.append(", ");
420     }
421     sbuf.append(']');
422   }
423 }