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  // Jörn 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     StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50);
195 
196     int L;
197     for (L = 0; L < argArray.length; L++) {
198 
199       j = messagePattern.indexOf(DELIM_STR, i);
200 
201       if (j == -1) {
202         // no more variables
203         if (i == 0) { // this is a simple string
204           return new FormattingTuple(messagePattern, argArray,
205               throwableCandidate);
206         } else { // add the tail string which contains no variables and return
207           // the result.
208           sbuf.append(messagePattern.substring(i, messagePattern.length()));
209           return new FormattingTuple(sbuf.toString(), argArray,
210               throwableCandidate);
211         }
212       } else {
213         if (isEscapedDelimeter(messagePattern, j)) {
214           if (!isDoubleEscaped(messagePattern, j)) {
215             L--; // DELIM_START was escaped, thus should not be incremented
216             sbuf.append(messagePattern.substring(i, j - 1));
217             sbuf.append(DELIM_START);
218             i = j + 1;
219           } else {
220             // The escape character preceding the delimiter start is
221             // itself escaped: "abc x:\\{}"
222             // we have to consume one backward slash
223             sbuf.append(messagePattern.substring(i, j - 1));
224             deeplyAppendParameter(sbuf, argArray[L], new HashMap());
225             i = j + 2;
226           }
227         } else {
228           // normal case
229           sbuf.append(messagePattern.substring(i, j));
230           deeplyAppendParameter(sbuf, argArray[L], new HashMap());
231           i = j + 2;
232         }
233       }
234     }
235     // append the characters following the last {} pair.
236     sbuf.append(messagePattern.substring(i, messagePattern.length()));
237     if (L < argArray.length - 1) {
238       return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate);
239     } else {
240       return new FormattingTuple(sbuf.toString(), argArray, null);
241     }
242   }
243 
244   final static boolean isEscapedDelimeter(String messagePattern,
245       int delimeterStartIndex) {
246 
247     if (delimeterStartIndex == 0) {
248       return false;
249     }
250     char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
251     if (potentialEscape == ESCAPE_CHAR) {
252       return true;
253     } else {
254       return false;
255     }
256   }
257 
258   final static boolean isDoubleEscaped(String messagePattern,
259       int delimeterStartIndex) {
260     if (delimeterStartIndex >= 2
261         && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {
262       return true;
263     } else {
264       return false;
265     }
266   }
267 
268   // special treatment of array values was suggested by 'lizongbo'
269   private static void deeplyAppendParameter(StringBuffer sbuf, Object o,
270       Map seenMap) {
271     if (o == null) {
272       sbuf.append("null");
273       return;
274     }
275     if (!o.getClass().isArray()) {
276       safeObjectAppend(sbuf, o);
277     } else {
278       // check for primitive array types because they
279       // unfortunately cannot be cast to Object[]
280       if (o instanceof boolean[]) {
281         booleanArrayAppend(sbuf, (boolean[]) o);
282       } else if (o instanceof byte[]) {
283         byteArrayAppend(sbuf, (byte[]) o);
284       } else if (o instanceof char[]) {
285         charArrayAppend(sbuf, (char[]) o);
286       } else if (o instanceof short[]) {
287         shortArrayAppend(sbuf, (short[]) o);
288       } else if (o instanceof int[]) {
289         intArrayAppend(sbuf, (int[]) o);
290       } else if (o instanceof long[]) {
291         longArrayAppend(sbuf, (long[]) o);
292       } else if (o instanceof float[]) {
293         floatArrayAppend(sbuf, (float[]) o);
294       } else if (o instanceof double[]) {
295         doubleArrayAppend(sbuf, (double[]) o);
296       } else {
297         objectArrayAppend(sbuf, (Object[]) o, seenMap);
298       }
299     }
300   }
301 
302   private static void safeObjectAppend(StringBuffer sbuf, Object o) {
303     try {
304       String oAsString = o.toString();
305       sbuf.append(oAsString);
306     } catch (Throwable t) {
307       System.err
308           .println("SLF4J: Failed toString() invocation on an object of type ["
309               + o.getClass().getName() + "]");
310       t.printStackTrace();
311       sbuf.append("[FAILED toString()]");
312     }
313 
314   }
315 
316   private static void objectArrayAppend(StringBuffer sbuf, Object[] a,
317       Map seenMap) {
318     sbuf.append('[');
319     if (!seenMap.containsKey(a)) {
320       seenMap.put(a, null);
321       final int len = a.length;
322       for (int i = 0; i < len; i++) {
323         deeplyAppendParameter(sbuf, a[i], seenMap);
324         if (i != len - 1)
325           sbuf.append(", ");
326       }
327       // allow repeats in siblings
328       seenMap.remove(a);
329     } else {
330       sbuf.append("...");
331     }
332     sbuf.append(']');
333   }
334 
335   private static void booleanArrayAppend(StringBuffer sbuf, boolean[] a) {
336     sbuf.append('[');
337     final int len = a.length;
338     for (int i = 0; i < len; i++) {
339       sbuf.append(a[i]);
340       if (i != len - 1)
341         sbuf.append(", ");
342     }
343     sbuf.append(']');
344   }
345 
346   private static void byteArrayAppend(StringBuffer sbuf, byte[] a) {
347     sbuf.append('[');
348     final int len = a.length;
349     for (int i = 0; i < len; i++) {
350       sbuf.append(a[i]);
351       if (i != len - 1)
352         sbuf.append(", ");
353     }
354     sbuf.append(']');
355   }
356 
357   private static void charArrayAppend(StringBuffer sbuf, char[] a) {
358     sbuf.append('[');
359     final int len = a.length;
360     for (int i = 0; i < len; i++) {
361       sbuf.append(a[i]);
362       if (i != len - 1)
363         sbuf.append(", ");
364     }
365     sbuf.append(']');
366   }
367 
368   private static void shortArrayAppend(StringBuffer sbuf, short[] a) {
369     sbuf.append('[');
370     final int len = a.length;
371     for (int i = 0; i < len; i++) {
372       sbuf.append(a[i]);
373       if (i != len - 1)
374         sbuf.append(", ");
375     }
376     sbuf.append(']');
377   }
378 
379   private static void intArrayAppend(StringBuffer sbuf, int[] a) {
380     sbuf.append('[');
381     final int len = a.length;
382     for (int i = 0; i < len; i++) {
383       sbuf.append(a[i]);
384       if (i != len - 1)
385         sbuf.append(", ");
386     }
387     sbuf.append(']');
388   }
389 
390   private static void longArrayAppend(StringBuffer sbuf, long[] a) {
391     sbuf.append('[');
392     final int len = a.length;
393     for (int i = 0; i < len; i++) {
394       sbuf.append(a[i]);
395       if (i != len - 1)
396         sbuf.append(", ");
397     }
398     sbuf.append(']');
399   }
400 
401   private static void floatArrayAppend(StringBuffer sbuf, float[] a) {
402     sbuf.append('[');
403     final int len = a.length;
404     for (int i = 0; i < len; i++) {
405       sbuf.append(a[i]);
406       if (i != len - 1)
407         sbuf.append(", ");
408     }
409     sbuf.append(']');
410   }
411 
412   private static void doubleArrayAppend(StringBuffer sbuf, double[] a) {
413     sbuf.append('[');
414     final int len = a.length;
415     for (int i = 0; i < len; i++) {
416       sbuf.append(a[i]);
417       if (i != len - 1)
418         sbuf.append(", ");
419     }
420     sbuf.append(']');
421   }
422 }