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