Home | History | Annotate | Download | only in helpers
      1 /**
      2  * Copyright (c) 2004-2011 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.text.MessageFormat;
     28 import java.util.HashMap;
     29 import java.util.Map;
     30 
     31 // contributors: lizongbo: proposed special treatment of array parameter values
     32 // Joern Huxhorn: pointed out double[] omission, suggested deep array copy
     33 /**
     34  * Formats messages according to very simple substitution rules. Substitutions
     35  * can be made 1, 2 or more arguments.
     36  *
     37  * <p>
     38  * For example,
     39  *
     40  * <pre>
     41  * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;)
     42  * </pre>
     43  *
     44  * will return the string "Hi there.".
     45  * <p>
     46  * The {} pair is called the <em>formatting anchor</em>. It serves to designate
     47  * the location where arguments need to be substituted within the message
     48  * pattern.
     49  * <p>
     50  * In case your message contains the '{' or the '}' character, you do not have
     51  * to do anything special unless the '}' character immediately follows '{'. For
     52  * example,
     53  *
     54  * <pre>
     55  * MessageFormatter.format(&quot;Set {1,2,3} is not equal to {}.&quot;, &quot;1,2&quot;);
     56  * </pre>
     57  *
     58  * will return the string "Set {1,2,3} is not equal to 1,2.".
     59  *
     60  * <p>
     61  * If for whatever reason you need to place the string "{}" in the message
     62  * without its <em>formatting anchor</em> meaning, then you need to escape the
     63  * '{' character with '\', that is the backslash character. Only the '{'
     64  * character should be escaped. There is no need to escape the '}' character.
     65  * For example,
     66  *
     67  * <pre>
     68  * MessageFormatter.format(&quot;Set \\{} is not equal to {}.&quot;, &quot;1,2&quot;);
     69  * </pre>
     70  *
     71  * will return the string "Set {} is not equal to 1,2.".
     72  *
     73  * <p>
     74  * The escaping behavior just described can be overridden by escaping the escape
     75  * character '\'. Calling
     76  *
     77  * <pre>
     78  * MessageFormatter.format(&quot;File name is C:\\\\{}.&quot;, &quot;file.zip&quot;);
     79  * </pre>
     80  *
     81  * will return the string "File name is C:\file.zip".
     82  *
     83  * <p>
     84  * The formatting conventions are different than those of {@link MessageFormat}
     85  * which ships with the Java platform. This is justified by the fact that
     86  * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}.
     87  * This local performance difference is both measurable and significant in the
     88  * larger context of the complete logging processing chain.
     89  *
     90  * <p>
     91  * See also {@link #format(String, Object)},
     92  * {@link #format(String, Object, Object)} and
     93  * {@link #arrayFormat(String, Object[])} methods for more details.
     94  *
     95  * @author Ceki G&uuml;lc&uuml;
     96  * @author Joern Huxhorn
     97  */
     98 final public class MessageFormatter {
     99     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(&quot;Hi {}.&quot;, &quot;there&quot;);
    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 argument
    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(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);
    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     static final Throwable getThrowableCandidate(Object[] argArray) {
    155         if (argArray == null || argArray.length == 0) {
    156             return null;
    157         }
    158 
    159         final Object lastEntry = argArray[argArray.length - 1];
    160         if (lastEntry instanceof Throwable) {
    161             return (Throwable) lastEntry;
    162         }
    163         return null;
    164     }
    165 
    166     /**
    167      * Same principle as the {@link #format(String, Object)} and
    168      * {@link #format(String, Object, Object)} methods except that any number of
    169      * arguments can be passed in an array.
    170      *
    171      * @param messagePattern
    172      *          The message pattern which will be parsed and formatted
    173      * @param argArray
    174      *          An array of arguments to be substituted in place of formatting
    175      *          anchors
    176      * @return The formatted message
    177      */
    178     final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {
    179 
    180         Throwable throwableCandidate = getThrowableCandidate(argArray);
    181 
    182         if (messagePattern == null) {
    183             return new FormattingTuple(null, argArray, throwableCandidate);
    184         }
    185 
    186         if (argArray == null) {
    187             return new FormattingTuple(messagePattern);
    188         }
    189 
    190         int i = 0;
    191         int j;
    192         // use string builder for better multicore performance
    193         StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
    194 
    195         int L;
    196         for (L = 0; L < argArray.length; L++) {
    197 
    198             j = messagePattern.indexOf(DELIM_STR, i);
    199 
    200             if (j == -1) {
    201                 // no more variables
    202                 if (i == 0) { // this is a simple string
    203                     return new FormattingTuple(messagePattern, argArray, throwableCandidate);
    204                 } else { // add the tail string which contains no variables and return
    205                     // the result.
    206                     sbuf.append(messagePattern.substring(i, messagePattern.length()));
    207                     return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate);
    208                 }
    209             } else {
    210                 if (isEscapedDelimeter(messagePattern, j)) {
    211                     if (!isDoubleEscaped(messagePattern, j)) {
    212                         L--; // DELIM_START was escaped, thus should not be incremented
    213                         sbuf.append(messagePattern.substring(i, j - 1));
    214                         sbuf.append(DELIM_START);
    215                         i = j + 1;
    216                     } else {
    217                         // The escape character preceding the delimiter start is
    218                         // itself escaped: "abc x:\\{}"
    219                         // we have to consume one backward slash
    220                         sbuf.append(messagePattern.substring(i, j - 1));
    221                         deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
    222                         i = j + 2;
    223                     }
    224                 } else {
    225                     // normal case
    226                     sbuf.append(messagePattern.substring(i, j));
    227                     deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
    228                     i = j + 2;
    229                 }
    230             }
    231         }
    232         // append the characters following the last {} pair.
    233         sbuf.append(messagePattern.substring(i, messagePattern.length()));
    234         if (L < argArray.length - 1) {
    235             return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate);
    236         } else {
    237             return new FormattingTuple(sbuf.toString(), argArray, null);
    238         }
    239     }
    240 
    241     final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {
    242 
    243         if (delimeterStartIndex == 0) {
    244             return false;
    245         }
    246         char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
    247         if (potentialEscape == ESCAPE_CHAR) {
    248             return true;
    249         } else {
    250             return false;
    251         }
    252     }
    253 
    254     final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {
    255         if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {
    256             return true;
    257         } else {
    258             return false;
    259         }
    260     }
    261 
    262     // special treatment of array values was suggested by 'lizongbo'
    263     private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {
    264         if (o == null) {
    265             sbuf.append("null");
    266             return;
    267         }
    268         if (!o.getClass().isArray()) {
    269             safeObjectAppend(sbuf, o);
    270         } else {
    271             // check for primitive array types because they
    272             // unfortunately cannot be cast to Object[]
    273             if (o instanceof boolean[]) {
    274                 booleanArrayAppend(sbuf, (boolean[]) o);
    275             } else if (o instanceof byte[]) {
    276                 byteArrayAppend(sbuf, (byte[]) o);
    277             } else if (o instanceof char[]) {
    278                 charArrayAppend(sbuf, (char[]) o);
    279             } else if (o instanceof short[]) {
    280                 shortArrayAppend(sbuf, (short[]) o);
    281             } else if (o instanceof int[]) {
    282                 intArrayAppend(sbuf, (int[]) o);
    283             } else if (o instanceof long[]) {
    284                 longArrayAppend(sbuf, (long[]) o);
    285             } else if (o instanceof float[]) {
    286                 floatArrayAppend(sbuf, (float[]) o);
    287             } else if (o instanceof double[]) {
    288                 doubleArrayAppend(sbuf, (double[]) o);
    289             } else {
    290                 objectArrayAppend(sbuf, (Object[]) o, seenMap);
    291             }
    292         }
    293     }
    294 
    295     private static void safeObjectAppend(StringBuilder sbuf, Object o) {
    296         try {
    297             String oAsString = o.toString();
    298             sbuf.append(oAsString);
    299         } catch (Throwable t) {
    300             System.err.println("SLF4J: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]");
    301             t.printStackTrace();
    302             sbuf.append("[FAILED toString()]");
    303         }
    304 
    305     }
    306 
    307     private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {
    308         sbuf.append('[');
    309         if (!seenMap.containsKey(a)) {
    310             seenMap.put(a, null);
    311             final int len = a.length;
    312             for (int i = 0; i < len; i++) {
    313                 deeplyAppendParameter(sbuf, a[i], seenMap);
    314                 if (i != len - 1)
    315                     sbuf.append(", ");
    316             }
    317             // allow repeats in siblings
    318             seenMap.remove(a);
    319         } else {
    320             sbuf.append("...");
    321         }
    322         sbuf.append(']');
    323     }
    324 
    325     private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
    326         sbuf.append('[');
    327         final int len = a.length;
    328         for (int i = 0; i < len; i++) {
    329             sbuf.append(a[i]);
    330             if (i != len - 1)
    331                 sbuf.append(", ");
    332         }
    333         sbuf.append(']');
    334     }
    335 
    336     private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
    337         sbuf.append('[');
    338         final int len = a.length;
    339         for (int i = 0; i < len; i++) {
    340             sbuf.append(a[i]);
    341             if (i != len - 1)
    342                 sbuf.append(", ");
    343         }
    344         sbuf.append(']');
    345     }
    346 
    347     private static void charArrayAppend(StringBuilder sbuf, char[] a) {
    348         sbuf.append('[');
    349         final int len = a.length;
    350         for (int i = 0; i < len; i++) {
    351             sbuf.append(a[i]);
    352             if (i != len - 1)
    353                 sbuf.append(", ");
    354         }
    355         sbuf.append(']');
    356     }
    357 
    358     private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
    359         sbuf.append('[');
    360         final int len = a.length;
    361         for (int i = 0; i < len; i++) {
    362             sbuf.append(a[i]);
    363             if (i != len - 1)
    364                 sbuf.append(", ");
    365         }
    366         sbuf.append(']');
    367     }
    368 
    369     private static void intArrayAppend(StringBuilder sbuf, int[] a) {
    370         sbuf.append('[');
    371         final int len = a.length;
    372         for (int i = 0; i < len; i++) {
    373             sbuf.append(a[i]);
    374             if (i != len - 1)
    375                 sbuf.append(", ");
    376         }
    377         sbuf.append(']');
    378     }
    379 
    380     private static void longArrayAppend(StringBuilder sbuf, long[] a) {
    381         sbuf.append('[');
    382         final int len = a.length;
    383         for (int i = 0; i < len; i++) {
    384             sbuf.append(a[i]);
    385             if (i != len - 1)
    386                 sbuf.append(", ");
    387         }
    388         sbuf.append(']');
    389     }
    390 
    391     private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
    392         sbuf.append('[');
    393         final int len = a.length;
    394         for (int i = 0; i < len; i++) {
    395             sbuf.append(a[i]);
    396             if (i != len - 1)
    397                 sbuf.append(", ");
    398         }
    399         sbuf.append(']');
    400     }
    401 
    402     private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
    403         sbuf.append('[');
    404         final int len = a.length;
    405         for (int i = 0; i < len; i++) {
    406             sbuf.append(a[i]);
    407             if (i != len - 1)
    408                 sbuf.append(", ");
    409         }
    410         sbuf.append(']');
    411     }
    412 }
    413