View Javadoc

1   /* 
2    * Copyright (c) 2004-2007 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>MessageFormatter.format(&quot;Set \\{} is not equal to {}.&quot;, &quot;1,2&quot;);</pre>
68   * 
69   * will return the string "Set {} is not equal to 1,2.".
70   * 
71   * <p>
72   * The escaping behavior just described can be overridden by escaping the escape
73   * character '\'. Calling
74   * <pre>
75   * MessageFormatter.format(&quot;File name is C:\\\\{}.&quot;, &quot;file.zip&quot;);
76   * </pre>
77   * 
78   * will return the string "File name is C:\file.zip".
79   * 
80   * <p>
81   * The formatting conventions are different than those of {@link MessageFormat}
82   * which ships with the Java platform. This is justified by the fact that
83   * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}.
84   * This local performance difference is both measurable and significant in the
85   * larger context of the complete logging processing chain.
86   * 
87   * <p>
88   * See also {@link #format(String, Object)},
89   * {@link #format(String, Object, Object)} and
90   * {@link #arrayFormat(String, Object[])} methods for more details.
91   * 
92   * @author Ceki G&uuml;lc&uuml;
93   */
94  final public class MessageFormatter {
95    static final char DELIM_START = '{';
96    static final char DELIM_STOP = '}';
97    static final String DELIM_STR = "{}";
98    private static final char ESCAPE_CHAR = '\\';
99  
100   /**
101    * Performs single argument substitution for the 'messagePattern' passed as
102    * parameter.
103    * <p>
104    * For example,
105    * 
106    * <pre>
107    * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);
108    * </pre>
109    * 
110    * will return the string "Hi there.".
111    * <p>
112    * 
113    * @param messagePattern
114    *          The message pattern which will be parsed and formatted
115    * @param argument
116    *          The argument to be substituted in place of the formatting anchor
117    * @return The formatted message
118    */
119   final public static String format(String messagePattern, Object arg) {
120     return arrayFormat(messagePattern, new Object[] { arg });
121   }
122 
123   /**
124    * 
125    * Performs a two argument substitution for the 'messagePattern' passed as
126    * parameter.
127    * <p>
128    * For example,
129    * 
130    * <pre>
131    * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);
132    * </pre>
133    * 
134    * will return the string "Hi Alice. My name is Bob.".
135    * 
136    * @param messagePattern
137    *          The message pattern which will be parsed and formatted
138    * @param arg1
139    *          The argument to be substituted in place of the first formatting
140    *          anchor
141    * @param arg2
142    *          The argument to be substituted in place of the second formatting
143    *          anchor
144    * @return The formatted message
145    */
146   final public static String format(final String messagePattern, Object arg1,
147       Object arg2) {
148     return arrayFormat(messagePattern, new Object[] { arg1, arg2 });
149   }
150 
151   /**
152    * Same principle as the {@link #format(String, Object)} and
153    * {@link #format(String, Object, Object)} methods except that any number of
154    * arguments can be passed in an array.
155    * 
156    * @param messagePattern
157    *          The message pattern which will be parsed and formatted
158    * @param argArray
159    *          An array of arguments to be substituted in place of formatting
160    *          anchors
161    * @return The formatted message
162    */
163   final public static String arrayFormat(final String messagePattern,
164       final Object[] argArray) {
165     if (messagePattern == null) {
166       return null;
167     }
168     if (argArray == null) {
169       return messagePattern;
170     }
171     int i = 0;
172     int j;
173     StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50);
174 
175     for (int L = 0; L < argArray.length; L++) {
176 
177       j = messagePattern.indexOf(DELIM_STR, i);
178 
179       if (j == -1) {
180         // no more variables
181         if (i == 0) { // this is a simple string
182           return messagePattern;
183         } else { // add the tail string which contains no variables and return
184           // the result.
185           sbuf.append(messagePattern.substring(i, messagePattern.length()));
186           return sbuf.toString();
187         }
188       } else {
189         if (isEscapedDelimeter(messagePattern, j)) {
190           if (!isDoubleEscaped(messagePattern, j)) {
191             L--; // DELIM_START was escaped, thus should not be incremented
192             sbuf.append(messagePattern.substring(i, j - 1));
193             sbuf.append(DELIM_START);
194             i = j + 1;
195           } else {
196             // The escape character preceding the delimiter start is
197             // itself escaped: "abc x:\\{}"
198             // we have to consume one backward slash
199             sbuf.append(messagePattern.substring(i, j - 1));
200             deeplyAppendParameter(sbuf, argArray[L], new HashMap());
201             i = j + 2;
202           }
203         } else {
204           // normal case
205           sbuf.append(messagePattern.substring(i, j));
206           deeplyAppendParameter(sbuf, argArray[L], new HashMap());
207           i = j + 2;
208         }
209       }
210     }
211     // append the characters following the last {} pair.
212     sbuf.append(messagePattern.substring(i, messagePattern.length()));
213     return sbuf.toString();
214   }
215 
216   final static boolean isEscapedDelimeter(String messagePattern,
217       int delimeterStartIndex) {
218 
219     if (delimeterStartIndex == 0) {
220       return false;
221     }
222     char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
223     if (potentialEscape == ESCAPE_CHAR) {
224       return true;
225     } else {
226       return false;
227     }
228   }
229 
230   final static boolean isDoubleEscaped(String messagePattern,
231       int delimeterStartIndex) {
232     if (delimeterStartIndex >= 2
233         && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {
234       return true;
235     } else {
236       return false;
237     }
238   }
239 
240   // special treatment of array values was suggested by 'lizongbo'
241   private static void deeplyAppendParameter(StringBuffer sbuf, Object o,
242       Map seenMap) {
243     if (o == null) {
244       sbuf.append("null");
245       return;
246     }
247     if (!o.getClass().isArray()) {
248       safeObjectAppend(sbuf, o);
249     } else {
250       // check for primitive array types because they
251       // unfortunately cannot be cast to Object[]
252       if (o instanceof boolean[]) {
253         booleanArrayAppend(sbuf, (boolean[]) o);
254       } else if (o instanceof byte[]) {
255         byteArrayAppend(sbuf, (byte[]) o);
256       } else if (o instanceof char[]) {
257         charArrayAppend(sbuf, (char[]) o);
258       } else if (o instanceof short[]) {
259         shortArrayAppend(sbuf, (short[]) o);
260       } else if (o instanceof int[]) {
261         intArrayAppend(sbuf, (int[]) o);
262       } else if (o instanceof long[]) {
263         longArrayAppend(sbuf, (long[]) o);
264       } else if (o instanceof float[]) {
265         floatArrayAppend(sbuf, (float[]) o);
266       } else if (o instanceof double[]) {
267         doubleArrayAppend(sbuf, (double[]) o);
268       } else {
269         objectArrayAppend(sbuf, (Object[]) o, seenMap);
270       }
271     }
272   }
273 
274   private static void safeObjectAppend(StringBuffer sbuf, Object o) {
275     try {
276       String oAsString = o.toString();
277       sbuf.append(oAsString);
278     } catch (Throwable t) {
279       System.err
280           .println("SLF4J: Failed toString() invocation on an object of type ["
281               + o.getClass().getName() + "]");
282       t.printStackTrace();
283       sbuf.append("[FAILED toString()]");
284     }
285 
286   }
287 
288   private static void objectArrayAppend(StringBuffer sbuf, Object[] a,
289       Map seenMap) {
290     sbuf.append('[');
291     if (!seenMap.containsKey(a)) {
292       seenMap.put(a, null);
293       final int len = a.length;
294       for (int i = 0; i < len; i++) {
295         deeplyAppendParameter(sbuf, a[i], seenMap);
296         if (i != len - 1)
297           sbuf.append(", ");
298       }
299       // allow repeats in siblings
300       seenMap.remove(a);
301     } else {
302       sbuf.append("...");
303     }
304     sbuf.append(']');
305   }
306 
307   private static void booleanArrayAppend(StringBuffer sbuf, boolean[] a) {
308     sbuf.append('[');
309     final int len = a.length;
310     for (int i = 0; i < len; i++) {
311       sbuf.append(a[i]);
312       if (i != len - 1)
313         sbuf.append(", ");
314     }
315     sbuf.append(']');
316   }
317 
318   private static void byteArrayAppend(StringBuffer sbuf, byte[] a) {
319     sbuf.append('[');
320     final int len = a.length;
321     for (int i = 0; i < len; i++) {
322       sbuf.append(a[i]);
323       if (i != len - 1)
324         sbuf.append(", ");
325     }
326     sbuf.append(']');
327   }
328 
329   private static void charArrayAppend(StringBuffer sbuf, char[] a) {
330     sbuf.append('[');
331     final int len = a.length;
332     for (int i = 0; i < len; i++) {
333       sbuf.append(a[i]);
334       if (i != len - 1)
335         sbuf.append(", ");
336     }
337     sbuf.append(']');
338   }
339 
340   private static void shortArrayAppend(StringBuffer sbuf, short[] a) {
341     sbuf.append('[');
342     final int len = a.length;
343     for (int i = 0; i < len; i++) {
344       sbuf.append(a[i]);
345       if (i != len - 1)
346         sbuf.append(", ");
347     }
348     sbuf.append(']');
349   }
350 
351   private static void intArrayAppend(StringBuffer sbuf, int[] a) {
352     sbuf.append('[');
353     final int len = a.length;
354     for (int i = 0; i < len; i++) {
355       sbuf.append(a[i]);
356       if (i != len - 1)
357         sbuf.append(", ");
358     }
359     sbuf.append(']');
360   }
361 
362   private static void longArrayAppend(StringBuffer sbuf, long[] a) {
363     sbuf.append('[');
364     final int len = a.length;
365     for (int i = 0; i < len; i++) {
366       sbuf.append(a[i]);
367       if (i != len - 1)
368         sbuf.append(", ");
369     }
370     sbuf.append(']');
371   }
372 
373   private static void floatArrayAppend(StringBuffer sbuf, float[] a) {
374     sbuf.append('[');
375     final int len = a.length;
376     for (int i = 0; i < len; i++) {
377       sbuf.append(a[i]);
378       if (i != len - 1)
379         sbuf.append(", ");
380     }
381     sbuf.append(']');
382   }
383 
384   private static void doubleArrayAppend(StringBuffer sbuf, double[] a) {
385     sbuf.append('[');
386     final int len = a.length;
387     for (int i = 0; i < len; i++) {
388       sbuf.append(a[i]);
389       if (i != len - 1)
390         sbuf.append(", ");
391     }
392     sbuf.append(']');
393   }
394 }