001/** 002 * Copyright (c) 2004-2011 QOS.ch 003 * All rights reserved. 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining 006 * a copy of this software and associated documentation files (the 007 * "Software"), to deal in the Software without restriction, including 008 * without limitation the rights to use, copy, modify, merge, publish, 009 * distribute, sublicense, and/or sell copies of the Software, and to 010 * permit persons to whom the Software is furnished to do so, subject to 011 * the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be 014 * included in all copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 017 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 018 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 021 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 023 * 024 */ 025package org.slf4j.helpers; 026 027import java.text.MessageFormat; 028import java.util.HashMap; 029import java.util.Map; 030 031// contributors: lizongbo: proposed special treatment of array parameter values 032// Joern Huxhorn: pointed out double[] omission, suggested deep array copy 033/** 034 * Formats messages according to very simple substitution rules. Substitutions 035 * can be made 1, 2 or more arguments. 036 * 037 * <p> 038 * For example, 039 * 040 * <pre> 041 * MessageFormatter.format("Hi {}.", "there") 042 * </pre> 043 * 044 * will return the string "Hi there.". 045 * <p> 046 * The {} pair is called the <em>formatting anchor</em>. It serves to designate 047 * the location where arguments need to be substituted within the message 048 * pattern. 049 * <p> 050 * In case your message contains the '{' or the '}' character, you do not have 051 * to do anything special unless the '}' character immediately follows '{'. For 052 * example, 053 * 054 * <pre> 055 * MessageFormatter.format("Set {1,2,3} is not equal to {}.", "1,2"); 056 * </pre> 057 * 058 * will return the string "Set {1,2,3} is not equal to 1,2.". 059 * 060 * <p> 061 * If for whatever reason you need to place the string "{}" in the message 062 * without its <em>formatting anchor</em> meaning, then you need to escape the 063 * '{' character with '\', that is the backslash character. Only the '{' 064 * character should be escaped. There is no need to escape the '}' character. 065 * For example, 066 * 067 * <pre> 068 * MessageFormatter.format("Set \\{} is not equal to {}.", "1,2"); 069 * </pre> 070 * 071 * will return the string "Set {} is not equal to 1,2.". 072 * 073 * <p> 074 * The escaping behavior just described can be overridden by escaping the escape 075 * character '\'. Calling 076 * 077 * <pre> 078 * MessageFormatter.format("File name is C:\\\\{}.", "file.zip"); 079 * </pre> 080 * 081 * will return the string "File name is C:\file.zip". 082 * 083 * <p> 084 * The formatting conventions are different than those of {@link MessageFormat} 085 * which ships with the Java platform. This is justified by the fact that 086 * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}. 087 * This local performance difference is both measurable and significant in the 088 * larger context of the complete logging processing chain. 089 * 090 * <p> 091 * See also {@link #format(String, Object)}, 092 * {@link #format(String, Object, Object)} and 093 * {@link #arrayFormat(String, Object[])} methods for more details. 094 * 095 * @author Ceki Gülcü 096 * @author Joern Huxhorn 097 */ 098final public class MessageFormatter { 099 static final char DELIM_START = '{'; 100 static final char DELIM_STOP = '}'; 101 static final String DELIM_STR = "{}"; 102 private static final char ESCAPE_CHAR = '\\'; 103 104 /** 105 * Performs single argument substitution for the 'messagePattern' passed as 106 * parameter. 107 * <p> 108 * For example, 109 * 110 * <pre> 111 * MessageFormatter.format("Hi {}.", "there"); 112 * </pre> 113 * 114 * will return the string "Hi there.". 115 * <p> 116 * 117 * @param messagePattern 118 * The message pattern which will be parsed and formatted 119 * @param arg 120 * The argument to be substituted in place of the formatting anchor 121 * @return The formatted message 122 */ 123 final public static FormattingTuple format(String messagePattern, Object arg) { 124 return arrayFormat(messagePattern, new Object[] { arg }); 125 } 126 127 /** 128 * 129 * Performs a two argument substitution for the 'messagePattern' passed as 130 * parameter. 131 * <p> 132 * For example, 133 * 134 * <pre> 135 * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob"); 136 * </pre> 137 * 138 * will return the string "Hi Alice. My name is Bob.". 139 * 140 * @param messagePattern 141 * The message pattern which will be parsed and formatted 142 * @param arg1 143 * The argument to be substituted in place of the first formatting 144 * anchor 145 * @param arg2 146 * The argument to be substituted in place of the second formatting 147 * anchor 148 * @return The formatted message 149 */ 150 final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) { 151 return arrayFormat(messagePattern, new Object[] { arg1, arg2 }); 152 } 153 154 155 static final Throwable getThrowableCandidate(Object[] argArray) { 156 if (argArray == null || argArray.length == 0) { 157 return null; 158 } 159 160 final Object lastEntry = argArray[argArray.length - 1]; 161 if (lastEntry instanceof Throwable) { 162 return (Throwable) lastEntry; 163 } 164 return null; 165 } 166 167 final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { 168 Throwable throwableCandidate = getThrowableCandidate(argArray); 169 Object[] args = argArray; 170 if (throwableCandidate != null) { 171 args = trimmedCopy(argArray); 172 } 173 return arrayFormat(messagePattern, args, throwableCandidate); 174 } 175 176 private static Object[] trimmedCopy(Object[] argArray) { 177 if (argArray == null || argArray.length == 0) { 178 throw new IllegalStateException("non-sensical empty or null argument array"); 179 } 180 final int trimemdLen = argArray.length - 1; 181 Object[] trimmed = new Object[trimemdLen]; 182 System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); 183 return trimmed; 184 } 185 186 final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) { 187 188 if (messagePattern == null) { 189 return new FormattingTuple(null, argArray, throwable); 190 } 191 192 if (argArray == null) { 193 return new FormattingTuple(messagePattern); 194 } 195 196 int i = 0; 197 int j; 198 // use string builder for better multicore performance 199 StringBuilder sbuf = new StringBuilder(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, throwable); 210 } else { // add the tail string which contains no variables and return 211 // the result. 212 sbuf.append(messagePattern, i, messagePattern.length()); 213 return new FormattingTuple(sbuf.toString(), argArray, throwable); 214 } 215 } else { 216 if (isEscapedDelimeter(messagePattern, j)) { 217 if (!isDoubleEscaped(messagePattern, j)) { 218 L--; // DELIM_START was escaped, thus should not be incremented 219 sbuf.append(messagePattern, i, j - 1); 220 sbuf.append(DELIM_START); 221 i = j + 1; 222 } else { 223 // The escape character preceding the delimiter start is 224 // itself escaped: "abc x:\\{}" 225 // we have to consume one backward slash 226 sbuf.append(messagePattern, i, j - 1); 227 deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>()); 228 i = j + 2; 229 } 230 } else { 231 // normal case 232 sbuf.append(messagePattern, i, j); 233 deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>()); 234 i = j + 2; 235 } 236 } 237 } 238 // append the characters following the last {} pair. 239 sbuf.append(messagePattern, i, messagePattern.length()); 240 return new FormattingTuple(sbuf.toString(), argArray, throwable); 241 } 242 243 final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) { 244 245 if (delimeterStartIndex == 0) { 246 return false; 247 } 248 char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); 249 if (potentialEscape == ESCAPE_CHAR) { 250 return true; 251 } else { 252 return false; 253 } 254 } 255 256 final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { 257 if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) { 258 return true; 259 } else { 260 return false; 261 } 262 } 263 264 // special treatment of array values was suggested by 'lizongbo' 265 private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> 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(StringBuilder sbuf, Object o) { 298 try { 299 String oAsString = o.toString(); 300 sbuf.append(oAsString); 301 } catch (Throwable t) { 302 Util.report("SLF4J: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t); 303 sbuf.append("[FAILED toString()]"); 304 } 305 306 } 307 308 private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) { 309 sbuf.append('['); 310 if (!seenMap.containsKey(a)) { 311 seenMap.put(a, null); 312 final int len = a.length; 313 for (int i = 0; i < len; i++) { 314 deeplyAppendParameter(sbuf, a[i], seenMap); 315 if (i != len - 1) 316 sbuf.append(", "); 317 } 318 // allow repeats in siblings 319 seenMap.remove(a); 320 } else { 321 sbuf.append("..."); 322 } 323 sbuf.append(']'); 324 } 325 326 private static void booleanArrayAppend(StringBuilder sbuf, boolean[] 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 byteArrayAppend(StringBuilder sbuf, byte[] 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 charArrayAppend(StringBuilder sbuf, char[] 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 shortArrayAppend(StringBuilder sbuf, short[] 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 370 private static void intArrayAppend(StringBuilder sbuf, int[] a) { 371 sbuf.append('['); 372 final int len = a.length; 373 for (int i = 0; i < len; i++) { 374 sbuf.append(a[i]); 375 if (i != len - 1) 376 sbuf.append(", "); 377 } 378 sbuf.append(']'); 379 } 380 381 private static void longArrayAppend(StringBuilder sbuf, long[] a) { 382 sbuf.append('['); 383 final int len = a.length; 384 for (int i = 0; i < len; i++) { 385 sbuf.append(a[i]); 386 if (i != len - 1) 387 sbuf.append(", "); 388 } 389 sbuf.append(']'); 390 } 391 392 private static void floatArrayAppend(StringBuilder sbuf, float[] a) { 393 sbuf.append('['); 394 final int len = a.length; 395 for (int i = 0; i < len; i++) { 396 sbuf.append(a[i]); 397 if (i != len - 1) 398 sbuf.append(", "); 399 } 400 sbuf.append(']'); 401 } 402 403 private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { 404 sbuf.append('['); 405 final int len = a.length; 406 for (int i = 0; i < len; i++) { 407 sbuf.append(a[i]); 408 if (i != len - 1) 409 sbuf.append(", "); 410 } 411 sbuf.append(']'); 412 } 413 414}