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