MessageFormatter xref

View Javadoc

    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 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 }