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 /** 28 * Formats messages according to very simple substitution rules. Substitutions 29 * can be made 1, 2 or more arguments. 30 * <p> 31 * For example, 32 * <pre>MessageFormatter.format("Hi {}.", "there");</pre> 33 * will return the string "Hi there.". 34 * <p> 35 * The {} pair is called the <em>formatting anchor</em>. It serves to 36 * designate the location where arguments need to be substituted within the 37 * message pattern. 38 * <p> 39 * In the rare case where you need to place the '{' or '}' in the message 40 * pattern itself but do not want them to be interpreted as a formatting 41 * anchors, you can espace the '{' character with '\', that is the backslash 42 * character. Only the '{' character should be escaped. There is no need to 43 * escape the '}' character. For example, 44 * <pre>MessageFormatter.format("Set \\{1,2,3} is not equal to {}.", "1,2");</pre> 45 * will return the string "Set {1,2,3} is not equal to 1,2.". 46 * 47 * <p> 48 * The escaping behaviour just described can be overridden by 49 * escaping the escape character '\'. Calling 50 * <pre>MessageFormatter.format("File name is C:\\\\{}.", "file.zip");</pre> 51 * will return the string "File name is C:\file.zip". 52 * 53 * <p> 54 * See {@link #format(String, Object)}, {@link #format(String, Object, Object)} 55 * and {@link #arrayFormat(String, Object[])} methods for more details. 56 * 57 * @author Ceki Gülcü 58 */ 59 public class MessageFormatter { 60 static final char DELIM_START = '{'; 61 static final char DELIM_STOP = '}'; 62 private static final char ESCAPE_CHAR = '\\'; 63 64 /** 65 * Performs single argument substitution for the 'messagePattern' passed as 66 * parameter. 67 * <p> 68 * For example, 69 * 70 * <pre> 71 * MessageFormatter.format("Hi {}.", "there"); 72 * </pre> 73 * 74 * will return the string "Hi there.". 75 * <p> 76 * 77 * @param messagePattern 78 * The message pattern which will be parsed and formatted 79 * @param argument 80 * The argument to be substituted in place of the formatting anchor 81 * @return The formatted message 82 */ 83 public static String format(String messagePattern, Object arg) { 84 return arrayFormat(messagePattern, new Object[] { arg }); 85 } 86 87 /** 88 * 89 * Performs a two argument substitution for the 'messagePattern' passed as 90 * parameter. 91 * <p> 92 * For example, 93 * 94 * <pre> 95 * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob"); 96 * </pre> 97 * 98 * will return the string "Hi Alice. My name is Bob.". 99 * 100 * @param messagePattern 101 * The message pattern which will be parsed and formatted 102 * @param arg1 103 * The argument to be substituted in place of the first formatting 104 * anchor 105 * @param arg2 106 * The argument to be substituted in place of the second formatting 107 * anchor 108 * @return The formatted message 109 */ 110 public static String format(String messagePattern, Object arg1, Object arg2) { 111 return arrayFormat(messagePattern, new Object[] { arg1, arg2 }); 112 } 113 114 /** 115 * Same principle as the {@link #format(String, Object)} and 116 * {@link #format(String, Object, Object)} methods except that any number of 117 * arguments can be passed in an array. 118 * 119 * @param messagePattern 120 * The message pattern which will be parsed and formatted 121 * @param argArray 122 * An array of arguments to be substituted in place of formatting 123 * anchors 124 * @return The formatted message 125 */ 126 public static String arrayFormat(String messagePattern, Object[] argArray) { 127 if (messagePattern == null) { 128 return null; 129 } 130 int i = 0; 131 int len = messagePattern.length(); 132 int j = messagePattern.indexOf(DELIM_START); 133 134 if(argArray == null) { 135 return messagePattern; 136 } 137 138 StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50); 139 140 for (int L = 0; L < argArray.length; L++) { 141 142 j = messagePattern.indexOf(DELIM_START, i); 143 144 if (j == -1 || (j + 1 == len)) { 145 // no more variables 146 if (i == 0) { // this is a simple string 147 return messagePattern; 148 } else { // add the tail string which contains no variables and return 149 // the result. 150 sbuf.append(messagePattern.substring(i, messagePattern.length())); 151 return sbuf.toString(); 152 } 153 } else { 154 char delimStop = messagePattern.charAt(j + 1); 155 156 if (isEscapedDelimeter(messagePattern, j)) { 157 if(!isDoubleEscaped(messagePattern, j)) { 158 L--; // DELIM_START was escaped, thus should not be incremented 159 sbuf.append(messagePattern.substring(i, j - 1)); 160 sbuf.append(DELIM_START); 161 i = j + 1; 162 } else { 163 // The escape character preceding the delemiter start is 164 // itself escaped: "abc x:\\{}" 165 // we have to consume one backward slash 166 sbuf.append(messagePattern.substring(i, j-1)); 167 sbuf.append(argArray[L]); 168 i = j + 2; 169 } 170 } else if ((delimStop != DELIM_STOP)) { 171 // invalid DELIM_START/DELIM_STOP pair 172 sbuf.append(messagePattern.substring(i, messagePattern.length())); 173 return sbuf.toString(); 174 } else { 175 // normal case 176 sbuf.append(messagePattern.substring(i, j)); 177 sbuf.append(argArray[L]); 178 i = j + 2; 179 } 180 } 181 } 182 // append the characters following the last {} pair. 183 sbuf.append(messagePattern.substring(i, messagePattern.length())); 184 return sbuf.toString(); 185 } 186 187 static boolean isEscapedDelimeter(String messagePattern, 188 int delimeterStartIndex) { 189 190 if (delimeterStartIndex == 0) { 191 return false; 192 } 193 char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); 194 if (potentialEscape == ESCAPE_CHAR) { 195 return true; 196 } else { 197 return false; 198 } 199 } 200 201 static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { 202 if (delimeterStartIndex >= 2 203 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) { 204 return true; 205 } else { 206 return false; 207 } 208 } 209 }