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.util.HashMap;
28  import java.util.Map;
29  
30  // contributors: lizongbo: proposed special treatment of array parameter values
31  // Jörn Huxhorn: pointed out double[] omission, suggested deep array copy
32  /**
33   * Formats messages according to very simple substitution rules. Substitutions
34   * can be made 1, 2 or more arguments.
35   * <p>
36   * For example,
37   * 
38   * <pre>MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;)</pre>
39   * 
40   * will return the string "Hi there.".
41   * <p>
42   * The {} pair is called the <em>formatting anchor</em>. It serves to
43   * designate the location where arguments need to be substituted within the
44   * message pattern.
45   * <p>
46   * In case your message contains the '{' or the '}' character, you do not have
47   * to do anything special unless the '}' character immediately follows '{'. For
48   * example,
49   * 
50   * <pre>
51   * MessageFormatter.format(&quot;Set {1,2,3} is not equal to {}.&quot;, &quot;1,2&quot;);
52   * </pre>
53   * 
54   * will return the string "Set {1,2,3} is not equal to 1,2.".
55   * 
56   * <p>If for whatever reason you need to place the string "{}" in the message
57   * without its <em>formatting anchor</em> meaning, then you need to escape the
58   * '{' character with '\', that is the backslash character. Only the '{'
59   * character should be escaped. There is no need to escape the '}' character.
60   * For example,
61   * 
62   * <pre>
63   * MessageFormatter.format(&quot;Set \\{} is not equal to {}.&quot;, &quot;1,2&quot;);
64   * </pre>
65   * 
66   * will return the string "Set {} is not equal to 1,2.".
67   * 
68   * <p>
69   * The escaping behavior just described can be overridden by escaping the escape
70   * character '\'. Calling
71   * 
72   * <pre>
73   * MessageFormatter.format(&quot;File name is C:\\\\{}.&quot;, &quot;file.zip&quot;);
74   * </pre>
75   * 
76   * will return the string "File name is C:\file.zip".
77   * 
78   * <p>
79   * See {@link #format(String, Object)}, {@link #format(String, Object, Object)}
80   * and {@link #arrayFormat(String, Object[])} methods for more details.
81   * 
82   * @author Ceki G&uuml;lc&uuml;
83   */
84  final public class MessageFormatter {
85    static final char DELIM_START = '{';
86    static final char DELIM_STOP = '}';
87    static final String DELIM_STR = "{}";
88    private static final char ESCAPE_CHAR = '\\';
89  
90    /**
91     * Performs single argument substitution for the 'messagePattern' passed as
92     * parameter.
93     * <p>
94     * For example,
95     * 
96     * <pre>
97     * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);
98     * </pre>
99     * 
100    * will return the string "Hi there.".
101    * <p>
102    * 
103    * @param messagePattern
104    *                The message pattern which will be parsed and formatted
105    * @param argument
106    *                The argument to be substituted in place of the formatting
107    *                anchor
108    * @return The formatted message
109    */
110   final public static String format(String messagePattern, Object arg) {
111     return arrayFormat(messagePattern, new Object[] { arg });
112   }
113 
114   /**
115    * 
116    * Performs a two argument substitution for the 'messagePattern' passed as
117    * parameter.
118    * <p>
119    * For example,
120    * 
121    * <pre>
122    * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);
123    * </pre>
124    * 
125    * will return the string "Hi Alice. My name is Bob.".
126    * 
127    * @param messagePattern
128    *                The message pattern which will be parsed and formatted
129    * @param arg1
130    *                The argument to be substituted in place of the first
131    *                formatting anchor
132    * @param arg2
133    *                The argument to be substituted in place of the second
134    *                formatting anchor
135    * @return The formatted message
136    */
137   final public static String format(final String messagePattern, Object arg1, Object arg2) {
138     return arrayFormat(messagePattern, new Object[] { arg1, arg2 });
139   }
140 
141   /**
142    * Same principle as the {@link #format(String, Object)} and
143    * {@link #format(String, Object, Object)} methods except that any number of
144    * arguments can be passed in an array.
145    * 
146    * @param messagePattern
147    *                The message pattern which will be parsed and formatted
148    * @param argArray
149    *                An array of arguments to be substituted in place of
150    *                formatting anchors
151    * @return The formatted message
152    */
153   final public static String arrayFormat(final String messagePattern,
154       final Object[] argArray) {
155     if (messagePattern == null) {
156       return null;
157     }
158     if (argArray == null) {
159       return messagePattern;
160     }
161     int i = 0;
162     int j;
163     StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50);
164 
165     for (int L = 0; L < argArray.length; L++) {
166 
167       j = messagePattern.indexOf(DELIM_STR, i);
168 
169       if (j == -1) {
170         // no more variables
171         if (i == 0) { // this is a simple string
172           return messagePattern;
173         } else { // add the tail string which contains no variables and return
174           // the result.
175           sbuf.append(messagePattern.substring(i, messagePattern.length()));
176           return sbuf.toString();
177         }
178       } else {
179         if (isEscapedDelimeter(messagePattern, j)) {
180           if (!isDoubleEscaped(messagePattern, j)) {
181             L--; // DELIM_START was escaped, thus should not be incremented
182             sbuf.append(messagePattern.substring(i, j - 1));
183             sbuf.append(DELIM_START);
184             i = j + 1;
185           } else {
186             // The escape character preceding the delemiter start is
187             // itself escaped: "abc x:\\{}"
188             // we have to consume one backward slash
189             sbuf.append(messagePattern.substring(i, j - 1));
190             deeplyAppendParameter(sbuf, argArray[L], new HashMap());
191             i = j + 2;
192           }
193         } else {
194           // normal case
195           sbuf.append(messagePattern.substring(i, j));
196           deeplyAppendParameter(sbuf, argArray[L], new HashMap());
197           i = j + 2;
198         }
199       }
200     }
201     // append the characters following the last {} pair.
202     sbuf.append(messagePattern.substring(i, messagePattern.length()));
203     return sbuf.toString();
204   }
205 
206   final static boolean isEscapedDelimeter(String messagePattern,
207       int delimeterStartIndex) {
208 
209     if (delimeterStartIndex == 0) {
210       return false;
211     }
212     char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
213     if (potentialEscape == ESCAPE_CHAR) {
214       return true;
215     } else {
216       return false;
217     }
218   }
219 
220   final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {
221     if (delimeterStartIndex >= 2
222         && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {
223       return true;
224     } else {
225       return false;
226     }
227   }
228 
229   // special treatment of array values was suggested by 'lizongbo'
230   private static void deeplyAppendParameter(StringBuffer sbuf, Object o,
231       Map seenMap) {
232     if (o == null) {
233       sbuf.append("null");
234       return;
235     }
236     if (!o.getClass().isArray()) {
237       sbuf.append(o);
238     } else {
239       // check for primitive array types because they
240       // unfortunately cannot be cast to Object[]
241       if (o instanceof boolean[]) {
242         booleanArrayAppend(sbuf, (boolean[]) o);
243       } else if (o instanceof byte[]) {
244         byteArrayAppend(sbuf, (byte[]) o);
245       } else if (o instanceof char[]) {
246         charArrayAppend(sbuf, (char[]) o);
247       } else if (o instanceof short[]) {
248         shortArrayAppend(sbuf, (short[]) o);
249       } else if (o instanceof int[]) {
250         intArrayAppend(sbuf, (int[]) o);
251       } else if (o instanceof long[]) {
252         longArrayAppend(sbuf, (long[]) o);
253       } else if (o instanceof float[]) {
254         floatArrayAppend(sbuf, (float[]) o);
255       } else if (o instanceof double[]) {
256         doubleArrayAppend(sbuf, (double[]) o);
257       } else {
258         objectArrayAppend(sbuf, (Object[]) o, seenMap);
259       }
260     }
261   }
262 
263   private static void objectArrayAppend(StringBuffer sbuf, Object[] a,
264       Map seenMap) {
265     sbuf.append('[');
266     if (!seenMap.containsKey(a)) {
267       seenMap.put(a, null);
268       final int len = a.length;
269       for (int i = 0; i < len; i++) {
270         deeplyAppendParameter(sbuf, a[i], seenMap);
271         if (i != len - 1)
272           sbuf.append(", ");
273       }
274       // allow repeats in siblings
275       seenMap.remove(a);
276     } else {
277       sbuf.append("...");
278     }
279     sbuf.append(']');
280   }
281 
282   private static void booleanArrayAppend(StringBuffer sbuf, boolean[] a) {
283     sbuf.append('[');
284     final int len = a.length;
285     for (int i = 0; i < len; i++) {
286       sbuf.append(a[i]);
287       if (i != len - 1)
288         sbuf.append(", ");
289     }
290     sbuf.append(']');
291   }
292 
293   private static void byteArrayAppend(StringBuffer sbuf, byte[] a) {
294     sbuf.append('[');
295     final int len = a.length;
296     for (int i = 0; i < len; i++) {
297       sbuf.append(a[i]);
298       if (i != len - 1)
299         sbuf.append(", ");
300     }
301     sbuf.append(']');
302   }
303 
304   private static void charArrayAppend(StringBuffer sbuf, char[] a) {
305     sbuf.append('[');
306     final int len = a.length;
307     for (int i = 0; i < len; i++) {
308       sbuf.append(a[i]);
309       if (i != len - 1)
310         sbuf.append(", ");
311     }
312     sbuf.append(']');
313   }
314 
315   private static void shortArrayAppend(StringBuffer sbuf, short[] a) {
316     sbuf.append('[');
317     final int len = a.length;
318     for (int i = 0; i < len; i++) {
319       sbuf.append(a[i]);
320       if (i != len - 1)
321         sbuf.append(", ");
322     }
323     sbuf.append(']');
324   }
325 
326   private static void intArrayAppend(StringBuffer sbuf, int[] a) {
327     sbuf.append('[');
328     final int len = a.length;
329     for (int i = 0; i < len; i++) {
330       sbuf.append(a[i]);
331       if (i != len - 1)
332         sbuf.append(", ");
333     }
334     sbuf.append(']');
335   }
336 
337   private static void longArrayAppend(StringBuffer sbuf, long[] a) {
338     sbuf.append('[');
339     final int len = a.length;
340     for (int i = 0; i < len; i++) {
341       sbuf.append(a[i]);
342       if (i != len - 1)
343         sbuf.append(", ");
344     }
345     sbuf.append(']');
346   }
347 
348   private static void floatArrayAppend(StringBuffer sbuf, float[] a) {
349     sbuf.append('[');
350     final int len = a.length;
351     for (int i = 0; i < len; i++) {
352       sbuf.append(a[i]);
353       if (i != len - 1)
354         sbuf.append(", ");
355     }
356     sbuf.append(']');
357   }
358 
359   private static void doubleArrayAppend(StringBuffer sbuf, double[] a) {
360     sbuf.append('[');
361     final int len = a.length;
362     for (int i = 0; i < len; i++) {
363       sbuf.append(a[i]);
364       if (i != len - 1)
365         sbuf.append(", ");
366     }
367     sbuf.append(']');
368   }
369 }