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 com.foxinmy.weixin4j.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("Hi {}.", "there")
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("Set {1,2,3} is not equal to {}.", "1,2");
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("Set \\{} is not equal to {}.", "1,2");
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("File name is C:\\\\{}.", "file.zip");
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("Hi {}.", "there");
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("Hi {}. My name is {}.", "Alice", "Bob");
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 }