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 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,
    151       Object arg1, Object arg2) {
    152     return arrayFormat(messagePattern, new Object[] { arg1, arg2 });
    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   /**
    168    * Same principle as the {@link #format(String, Object)} and
    169    * {@link #format(String, Object, Object)} methods except that any number of
    170    * arguments can be passed in an array.
    171    *
    172    * @param messagePattern
    173    *          The message pattern which will be parsed and formatted
    174    * @param argArray
    175    *          An array of arguments to be substituted in place of formatting
    176    *          anchors
    177    * @return The formatted message
    178    */
    179   final public static FormattingTuple arrayFormat(final String messagePattern,
    180       final Object[] argArray) {
    181
    182     Throwable throwableCandidate = getThrowableCandidate(argArray);
    183
    184     if (messagePattern == null) {
    185       return new FormattingTuple(null, argArray, throwableCandidate);
    186     }
    187
    188     if (argArray == null) {
    189       return new FormattingTuple(messagePattern);
    190     }
    191
    192     int i = 0;
    193     int j;
    194     StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50);
    195
    196     int L;
    197     for (L = 0; L < argArray.length; L++) {
    198
    199       j = messagePattern.indexOf(DELIM_STR, i);
    200
    201       if (j == -1) {
    202         // no more variables
    203         if (i == 0) { // this is a simple string
    204           return new FormattingTuple(messagePattern, argArray,
    205               throwableCandidate);
    206         } else { // add the tail string which contains no variables and return
    207           // the result.
    208           sbuf.append(messagePattern.substring(i, messagePattern.length()));
    209           return new FormattingTuple(sbuf.toString(), argArray,
    210               throwableCandidate);
    211         }
    212       } else {
    213         if (isEscapedDelimeter(messagePattern, j)) {
    214           if (!isDoubleEscaped(messagePattern, j)) {
    215             L--; // DELIM_START was escaped, thus should not be incremented
    216             sbuf.append(messagePattern.substring(i, j - 1));
    217             sbuf.append(DELIM_START);
    218             i = j + 1;
    219           } else {
    220             // The escape character preceding the delimiter start is
    221             // itself escaped: "abc x:\\{}"
    222             // we have to consume one backward slash
    223             sbuf.append(messagePattern.substring(i, j - 1));
    224             deeplyAppendParameter(sbuf, argArray[L], new HashMap());
    225             i = j + 2;
    226           }
    227         } else {
    228           // normal case
    229           sbuf.append(messagePattern.substring(i, j));
    230           deeplyAppendParameter(sbuf, argArray[L], new HashMap());
    231           i = j + 2;
    232         }
    233       }
    234     }
    235     // append the characters following the last {} pair.
    236     sbuf.append(messagePattern.substring(i, messagePattern.length()));
    237     if (L < argArray.length - 1) {
    238       return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate);
    239     } else {
    240       return new FormattingTuple(sbuf.toString(), argArray, null);
    241     }
    242   }
    243
    244   final static boolean isEscapedDelimeter(String messagePattern,
    245       int delimeterStartIndex) {
    246
    247     if (delimeterStartIndex == 0) {
    248       return false;
    249     }
    250     char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
    251     if (potentialEscape == ESCAPE_CHAR) {
    252       return true;
    253     } else {
    254       return false;
    255     }
    256   }
    257
    258   final static boolean isDoubleEscaped(String messagePattern,
    259       int delimeterStartIndex) {
    260     if (delimeterStartIndex >= 2
    261         && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {
    262       return true;
    263     } else {
    264       return false;
    265     }
    266   }
    267
    268   // special treatment of array values was suggested by 'lizongbo'
    269   private static void deeplyAppendParameter(StringBuffer sbuf, Object o,
    270       Map seenMap) {
    271     if (o == null) {
    272       sbuf.append("null");
    273       return;
    274     }
    275     if (!o.getClass().isArray()) {
    276       safeObjectAppend(sbuf, o);
    277     } else {
    278       // check for primitive array types because they
    279       // unfortunately cannot be cast to Object[]
    280       if (o instanceof boolean[]) {
    281         booleanArrayAppend(sbuf, (boolean[]) o);
    282       } else if (o instanceof byte[]) {
    283         byteArrayAppend(sbuf, (byte[]) o);
    284       } else if (o instanceof char[]) {
    285         charArrayAppend(sbuf, (char[]) o);
    286       } else if (o instanceof short[]) {
    287         shortArrayAppend(sbuf, (short[]) o);
    288       } else if (o instanceof int[]) {
    289         intArrayAppend(sbuf, (int[]) o);
    290       } else if (o instanceof long[]) {
    291         longArrayAppend(sbuf, (long[]) o);
    292       } else if (o instanceof float[]) {
    293         floatArrayAppend(sbuf, (float[]) o);
    294       } else if (o instanceof double[]) {
    295         doubleArrayAppend(sbuf, (double[]) o);
    296       } else {
    297         objectArrayAppend(sbuf, (Object[]) o, seenMap);
    298       }
    299     }
    300   }
    301
    302   private static void safeObjectAppend(StringBuffer sbuf, Object o) {
    303     try {
    304       String oAsString = o.toString();
    305       sbuf.append(oAsString);
    306     } catch (Throwable t) {
    307       System.err
    308           .println("SLF4J: Failed toString() invocation on an object of type ["
    309               + o.getClass().getName() + "]");
    310       t.printStackTrace();
    311       sbuf.append("[FAILED toString()]");
    312     }
    313
    314   }
    315
    316   private static void objectArrayAppend(StringBuffer sbuf, Object[] a,
    317       Map seenMap) {
    318     sbuf.append('[');
    319     if (!seenMap.containsKey(a)) {
    320       seenMap.put(a, null);
    321       final int len = a.length;
    322       for (int i = 0; i < len; i++) {
    323         deeplyAppendParameter(sbuf, a[i], seenMap);
    324         if (i != len - 1)
    325           sbuf.append(", ");
    326       }
    327       // allow repeats in siblings
    328       seenMap.remove(a);
    329     } else {
    330       sbuf.append("...");
    331     }
    332     sbuf.append(']');
    333   }
    334
    335   private static void booleanArrayAppend(StringBuffer sbuf, boolean[] a) {
    336     sbuf.append('[');
    337     final int len = a.length;
    338     for (int i = 0; i < len; i++) {
    339       sbuf.append(a[i]);
    340       if (i != len - 1)
    341         sbuf.append(", ");
    342     }
    343     sbuf.append(']');
    344   }
    345
    346   private static void byteArrayAppend(StringBuffer sbuf, byte[] a) {
    347     sbuf.append('[');
    348     final int len = a.length;
    349     for (int i = 0; i < len; i++) {
    350       sbuf.append(a[i]);
    351       if (i != len - 1)
    352         sbuf.append(", ");
    353     }
    354     sbuf.append(']');
    355   }
    356
    357   private static void charArrayAppend(StringBuffer sbuf, char[] a) {
    358     sbuf.append('[');
    359     final int len = a.length;
    360     for (int i = 0; i < len; i++) {
    361       sbuf.append(a[i]);
    362       if (i != len - 1)
    363         sbuf.append(", ");
    364     }
    365     sbuf.append(']');
    366   }
    367
    368   private static void shortArrayAppend(StringBuffer sbuf, short[] a) {
    369     sbuf.append('[');
    370     final int len = a.length;
    371     for (int i = 0; i < len; i++) {
    372       sbuf.append(a[i]);
    373       if (i != len - 1)
    374         sbuf.append(", ");
    375     }
    376     sbuf.append(']');
    377   }
    378
    379   private static void intArrayAppend(StringBuffer sbuf, int[] a) {
    380     sbuf.append('[');
    381     final int len = a.length;
    382     for (int i = 0; i < len; i++) {
    383       sbuf.append(a[i]);
    384       if (i != len - 1)
    385         sbuf.append(", ");
    386     }
    387     sbuf.append(']');
    388   }
    389
    390   private static void longArrayAppend(StringBuffer sbuf, long[] a) {
    391     sbuf.append('[');
    392     final int len = a.length;
    393     for (int i = 0; i < len; i++) {
    394       sbuf.append(a[i]);
    395       if (i != len - 1)
    396         sbuf.append(", ");
    397     }
    398     sbuf.append(']');
    399   }
    400
    401   private static void floatArrayAppend(StringBuffer sbuf, float[] a) {
    402     sbuf.append('[');
    403     final int len = a.length;
    404     for (int i = 0; i < len; i++) {
    405       sbuf.append(a[i]);
    406       if (i != len - 1)
    407         sbuf.append(", ");
    408     }
    409     sbuf.append(']');
    410   }
    411
    412   private static void doubleArrayAppend(StringBuffer sbuf, double[] a) {
    413     sbuf.append('[');
    414     final int len = a.length;
    415     for (int i = 0; i < len; i++) {
    416       sbuf.append(a[i]);
    417       if (i != len - 1)
    418         sbuf.append(", ");
    419     }
    420     sbuf.append(']');
    421   }
    422 }