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