001: /*
002: * Copyright (c) 2004-2007 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:
025: package org.slf4j.helpers;
026:
027: /**
028: * Formats messages according to very simple substitution rules. Substitutions
029: * can be made 1, 2 or more arguments.
030: * <p>
031: * For example,
032: * <pre>MessageFormatter.format("Hi {}.", "there");</pre>
033: * will return the string "Hi there.".
034: * <p>
035: * The {} pair is called the <em>formatting anchor</em>. It serves to
036: * designate the location where arguments need to be substituted within the
037: * message pattern.
038: * <p>
039: * In the rare case where you need to place the '{' or '}' in the message
040: * pattern itself but do not want them to be interpreted as a formatting
041: * anchors, you can espace the '{' character with '\', that is the backslash
042: * character. Only the '{' character should be escaped. There is no need to
043: * escape the '}' character. For example,
044: * <pre>MessageFormatter.format("Set \\{1,2,3} is not equal to {}.", "1,2");</pre>
045: * will return the string "Set {1,2,3} is not equal to 1,2.".
046: *
047: * <p>
048: * The escaping behaviour just described can be overridden by
049: * escaping the escape character '\'. Calling
050: * <pre>MessageFormatter.format("File name is C:\\\\{}.", "file.zip");</pre>
051: * will return the string "File name is C:\file.zip".
052: *
053: * <p>
054: * See {@link #format(String, Object)}, {@link #format(String, Object, Object)}
055: * and {@link #arrayFormat(String, Object[])} methods for more details.
056: *
057: * @author Ceki Gülcü
058: */
059: public class MessageFormatter {
060: static final char DELIM_START = '{';
061: static final char DELIM_STOP = '}';
062: private static final char ESCAPE_CHAR = '\\';
063:
064: /**
065: * Performs single argument substitution for the 'messagePattern' passed as
066: * parameter.
067: * <p>
068: * For example,
069: *
070: * <pre>
071: * MessageFormatter.format("Hi {}.", "there");
072: * </pre>
073: *
074: * will return the string "Hi there.".
075: * <p>
076: *
077: * @param messagePattern
078: * The message pattern which will be parsed and formatted
079: * @param argument
080: * The argument to be substituted in place of the formatting anchor
081: * @return The formatted message
082: */
083: public static String format(String messagePattern, Object arg) {
084: return arrayFormat(messagePattern, new Object[] { arg });
085: }
086:
087: /**
088: *
089: * Performs a two argument substitution for the 'messagePattern' passed as
090: * parameter.
091: * <p>
092: * For example,
093: *
094: * <pre>
095: * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
096: * </pre>
097: *
098: * will return the string "Hi Alice. My name is Bob.".
099: *
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,
111: Object arg2) {
112: return arrayFormat(messagePattern, new Object[] { arg1, arg2 });
113: }
114:
115: /**
116: * Same principle as the {@link #format(String, Object)} and
117: * {@link #format(String, Object, Object)} methods except that any number of
118: * arguments can be passed in an array.
119: *
120: * @param messagePattern
121: * The message pattern which will be parsed and formatted
122: * @param argArray
123: * An array of arguments to be substituted in place of formatting
124: * anchors
125: * @return The formatted message
126: */
127: public static String arrayFormat(String messagePattern,
128: Object[] argArray) {
129: if (messagePattern == null) {
130: return null;
131: }
132: int i = 0;
133: int len = messagePattern.length();
134: int j = messagePattern.indexOf(DELIM_START);
135:
136: StringBuffer sbuf = new StringBuffer(
137: messagePattern.length() + 50);
138:
139: for (int L = 0; L < argArray.length; L++) {
140:
141: j = messagePattern.indexOf(DELIM_START, i);
142:
143: if (j == -1 || (j + 1 == len)) {
144: // no more variables
145: if (i == 0) { // this is a simple string
146: return messagePattern;
147: } else { // add the tail string which contains no variables and return
148: // the result.
149: sbuf.append(messagePattern.substring(i,
150: 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,
173: messagePattern.length()));
174: return sbuf.toString();
175: } else {
176: // normal case
177: sbuf.append(messagePattern.substring(i, j));
178: sbuf.append(argArray[L]);
179: i = j + 2;
180: }
181: }
182: }
183: // append the characters following the second {} pair.
184: sbuf.append(messagePattern
185: .substring(i, messagePattern.length()));
186: return sbuf.toString();
187: }
188:
189: static boolean isEscapedDelimeter(String messagePattern,
190: int delimeterStartIndex) {
191:
192: if (delimeterStartIndex == 0) {
193: return false;
194: }
195: char potentialEscape = messagePattern
196: .charAt(delimeterStartIndex - 1);
197: if (potentialEscape == ESCAPE_CHAR) {
198: return true;
199: } else {
200: return false;
201: }
202: }
203:
204: static boolean isDoubleEscaped(String messagePattern,
205: int delimeterStartIndex) {
206: if (delimeterStartIndex >= 2
207: && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {
208: return true;
209: } else {
210: return false;
211: }
212: }
213: }
|