    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(&quot;Hi {}.&quot;, &quot;there&quot;)
    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(&quot;Set {1,2,3} is not equal to {}.&quot;, &quot;1,2&quot;);
    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(&quot;Set \\{} is not equal to {}.&quot;, &quot;1,2&quot;);
    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(&quot;File name is C:\\\\{}.&quot;, &quot;file.zip&quot;);
    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&uuml;lc&uuml;
    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(&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 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(&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
    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}