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