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    *   https://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.HashSet;
44  import java.util.Set;
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 public final class MessageFormatter {
112     private static final String DELIM_STR = "{}";
113     private static final char ESCAPE_CHAR = '\\';
114 
115     /**
116      * Performs single argument substitution for the 'messagePattern' passed as
117      * parameter.
118      * <p/>
119      * For example,
120      * <p/>
121      * <pre>
122      * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);
123      * </pre>
124      * <p/>
125      * will return the string "Hi there.".
126      * <p/>
127      *
128      * @param messagePattern The message pattern which will be parsed and formatted
129      * @param arg            The argument to be substituted in place of the formatting anchor
130      * @return The formatted message
131      */
132     public static FormattingTuple format(String messagePattern, Object arg) {
133         return arrayFormat(messagePattern, new Object[]{arg});
134     }
135 
136     /**
137      * Performs a two argument substitution for the 'messagePattern' passed as
138      * parameter.
139      * <p/>
140      * For example,
141      * <p/>
142      * <pre>
143      * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);
144      * </pre>
145      * <p/>
146      * will return the string "Hi Alice. My name is Bob.".
147      *
148      * @param messagePattern The message pattern which will be parsed and formatted
149      * @param argA           The argument to be substituted in place of the first formatting
150      *                       anchor
151      * @param argB           The argument to be substituted in place of the second formatting
152      *                       anchor
153      * @return The formatted message
154      */
155     public static FormattingTuple format(final String messagePattern,
156                                   Object argA, Object argB) {
157         return arrayFormat(messagePattern, new Object[]{argA, argB});
158     }
159 
160     /**
161      * Same principle as the {@link #format(String, Object)} and
162      * {@link #format(String, Object, Object)} methods except that any number of
163      * arguments can be passed in an array.
164      *
165      * @param messagePattern The message pattern which will be parsed and formatted
166      * @param argArray       An array of arguments to be substituted in place of formatting
167      *                       anchors
168      * @return The formatted message
169      */
170     public static FormattingTuple arrayFormat(final String messagePattern,
171                                        final Object[] argArray) {
172         if (argArray == null || argArray.length == 0) {
173             return new FormattingTuple(messagePattern, null);
174         }
175 
176         int lastArrIdx = argArray.length - 1;
177         Object lastEntry = argArray[lastArrIdx];
178         Throwable throwable = lastEntry instanceof Throwable? (Throwable) lastEntry : null;
179 
180         if (messagePattern == null) {
181             return new FormattingTuple(null, throwable);
182         }
183 
184         int j = messagePattern.indexOf(DELIM_STR);
185         if (j == -1) {
186             // this is a simple string
187             return new FormattingTuple(messagePattern, throwable);
188         }
189 
190         StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
191         int i = 0;
192         int L = 0;
193         do {
194             boolean notEscaped = j == 0 || messagePattern.charAt(j - 1) != ESCAPE_CHAR;
195             if (notEscaped) {
196                 // normal case
197                 sbuf.append(messagePattern, i, j);
198             } else {
199                 sbuf.append(messagePattern, i, j - 1);
200                 // check that escape char is not is escaped: "abc x:\\{}"
201                 notEscaped = j >= 2 && messagePattern.charAt(j - 2) == ESCAPE_CHAR;
202             }
203 
204             i = j + 2;
205             if (notEscaped) {
206                 deeplyAppendParameter(sbuf, argArray[L], null);
207                 L++;
208                 if (L > lastArrIdx) {
209                     break;
210                 }
211             } else {
212                 sbuf.append(DELIM_STR);
213             }
214             j = messagePattern.indexOf(DELIM_STR, i);
215         } while (j != -1);
216 
217         // append the characters following the last {} pair.
218         sbuf.append(messagePattern, i, messagePattern.length());
219         return new FormattingTuple(sbuf.toString(), L <= lastArrIdx? throwable : null);
220     }
221 
222     // special treatment of array values was suggested by 'lizongbo'
223     private static void deeplyAppendParameter(StringBuilder sbuf, Object o,
224                                               Set<Object[]> seenSet) {
225         if (o == null) {
226             sbuf.append("null");
227             return;
228         }
229         Class<?> objClass = o.getClass();
230         if (!objClass.isArray()) {
231             if (Number.class.isAssignableFrom(objClass)) {
232                 // Prevent String instantiation for some number types
233                 if (objClass == Long.class) {
234                     sbuf.append(((Long) o).longValue());
235                 } else if (objClass == Integer.class || objClass == Short.class || objClass == Byte.class) {
236                     sbuf.append(((Number) o).intValue());
237                 } else if (objClass == Double.class) {
238                     sbuf.append(((Double) o).doubleValue());
239                 } else if (objClass == Float.class) {
240                     sbuf.append(((Float) o).floatValue());
241                 } else {
242                     safeObjectAppend(sbuf, o);
243                 }
244             } else {
245                 safeObjectAppend(sbuf, o);
246             }
247         } else {
248             // check for primitive array types because they
249             // unfortunately cannot be cast to Object[]
250             sbuf.append('[');
251             if (objClass == boolean[].class) {
252                 booleanArrayAppend(sbuf, (boolean[]) o);
253             } else if (objClass == byte[].class) {
254                 byteArrayAppend(sbuf, (byte[]) o);
255             } else if (objClass == char[].class) {
256                 charArrayAppend(sbuf, (char[]) o);
257             } else if (objClass == short[].class) {
258                 shortArrayAppend(sbuf, (short[]) o);
259             } else if (objClass == int[].class) {
260                 intArrayAppend(sbuf, (int[]) o);
261             } else if (objClass == long[].class) {
262                 longArrayAppend(sbuf, (long[]) o);
263             } else if (objClass == float[].class) {
264                 floatArrayAppend(sbuf, (float[]) o);
265             } else if (objClass == double[].class) {
266                 doubleArrayAppend(sbuf, (double[]) o);
267             } else {
268                 objectArrayAppend(sbuf, (Object[]) o, seenSet);
269             }
270             sbuf.append(']');
271         }
272     }
273 
274     private static void safeObjectAppend(StringBuilder 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     private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Set<Object[]> seenSet) {
288         if (a.length == 0) {
289             return;
290         }
291         if (seenSet == null) {
292             seenSet = new HashSet<Object[]>(a.length);
293         }
294         if (seenSet.add(a)) {
295             deeplyAppendParameter(sbuf, a[0], seenSet);
296             for (int i = 1; i < a.length; i++) {
297                 sbuf.append(", ");
298                 deeplyAppendParameter(sbuf, a[i], seenSet);
299             }
300             // allow repeats in siblings
301             seenSet.remove(a);
302         } else {
303             sbuf.append("...");
304         }
305     }
306 
307     private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
308         if (a.length == 0) {
309             return;
310         }
311         sbuf.append(a[0]);
312         for (int i = 1; i < a.length; i++) {
313             sbuf.append(", ");
314             sbuf.append(a[i]);
315         }
316     }
317 
318     private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
319         if (a.length == 0) {
320             return;
321         }
322         sbuf.append(a[0]);
323         for (int i = 1; i < a.length; i++) {
324             sbuf.append(", ");
325             sbuf.append(a[i]);
326         }
327     }
328 
329     private static void charArrayAppend(StringBuilder sbuf, char[] a) {
330         if (a.length == 0) {
331             return;
332         }
333         sbuf.append(a[0]);
334         for (int i = 1; i < a.length; i++) {
335             sbuf.append(", ");
336             sbuf.append(a[i]);
337         }
338     }
339 
340     private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
341         if (a.length == 0) {
342             return;
343         }
344         sbuf.append(a[0]);
345         for (int i = 1; i < a.length; i++) {
346             sbuf.append(", ");
347             sbuf.append(a[i]);
348         }
349     }
350 
351     private static void intArrayAppend(StringBuilder sbuf, int[] a) {
352         if (a.length == 0) {
353             return;
354         }
355         sbuf.append(a[0]);
356         for (int i = 1; i < a.length; i++) {
357             sbuf.append(", ");
358             sbuf.append(a[i]);
359         }
360     }
361 
362     private static void longArrayAppend(StringBuilder sbuf, long[] a) {
363         if (a.length == 0) {
364             return;
365         }
366         sbuf.append(a[0]);
367         for (int i = 1; i < a.length; i++) {
368             sbuf.append(", ");
369             sbuf.append(a[i]);
370         }
371     }
372 
373     private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
374         if (a.length == 0) {
375             return;
376         }
377         sbuf.append(a[0]);
378         for (int i = 1; i < a.length; i++) {
379             sbuf.append(", ");
380             sbuf.append(a[i]);
381         }
382     }
383 
384     private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
385         if (a.length == 0) {
386             return;
387         }
388         sbuf.append(a[0]);
389         for (int i = 1; i < a.length; i++) {
390             sbuf.append(", ");
391             sbuf.append(a[i]);
392         }
393     }
394 
395     private MessageFormatter() {
396     }
397 }