View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.log4j.pattern;
    19
    20  import org.apache.log4j.helpers.Loader;
    21  import org.apache.log4j.helpers.LogLog;
    22
    23  import java.lang.reflect.Method;
    24  import java.util.ArrayList;
    25  import java.util.Collection;
    26  import java.util.HashMap;
    27  import java.util.List;
    28  import java.util.Map;
    29  import java.util.Set;
    30
    31  // Contributors:   Nelson Minar <(nelson@monkey.org>
    32  //                 Igor E. Poteryaev <jah@mail.ru>
    33  //                 Reinhard Deschler <reinhard.deschler@web.de>
    34
    35  /**
    36   * Most of the work of the {@link org.apache.log4j.EnhancedPatternLayout} class
    37   * is delegated to the PatternParser class.
    38   * <p>It is this class that parses conversion patterns and creates
    39   * a chained list of {@link PatternConverter PatternConverters}.
    40   *
    41   * @author James P. Cakalic
    42   * @author Ceki G&uuml;lc&uuml;
    43   * @author Anders Kristensen
    44   * @author Paul Smith
    45   * @author Curt Arnold
    46   *
    47  */
    48  public final class PatternParser {
    49    /**
    50     * Escape character for format specifier.
    51     */
    52    private static final char ESCAPE_CHAR = '%';
    53
    54    /**
    55     * Literal state.
    56     */
    57    private static final int LITERAL_STATE = 0;
    58
    59    /**
    60     * In converter name state.
    61     */
    62    private static final int CONVERTER_STATE = 1;
    63
    64    /**
    65     * Dot state.
    66     */
    67    private static final int DOT_STATE = 3;
    68
    69    /**
    70     * Min state.
    71     */
    72    private static final int MIN_STATE = 4;
    73
    74    /**
    75     * Max state.
    76     */
    77    private static final int MAX_STATE = 5;
    78
    79    /**
    80     * Standard format specifiers for EnhancedPatternLayout.
    81     */
    82    private static final Map PATTERN_LAYOUT_RULES;
    83
    84    /**
    85     * Standard format specifiers for rolling file appenders.
    86     */
    87    private static final Map FILENAME_PATTERN_RULES;
    88
    89    static {
    90      // We set the global rules in the static initializer of PatternParser class
    91      Map rules = new HashMap(17);
    92      rules.put("c", LoggerPatternConverter.class);
    93      rules.put("logger", LoggerPatternConverter.class);
    94
    95      rules.put("C", ClassNamePatternConverter.class);
    96      rules.put("class", ClassNamePatternConverter.class);
    97
    98      rules.put("d", DatePatternConverter.class);
    99      rules.put("date", DatePatternConverter.class);
    100
    101     rules.put("F", FileLocationPatternConverter.class);
    102     rules.put("file", FileLocationPatternConverter.class);
    103
    104     rules.put("l", FullLocationPatternConverter.class);
    105
    106     rules.put("L", LineLocationPatternConverter.class);
    107     rules.put("line", LineLocationPatternConverter.class);
    108
    109     rules.put("m", MessagePatternConverter.class);
    110     rules.put("message", MessagePatternConverter.class);
    111
    112     rules.put("n", LineSeparatorPatternConverter.class);
    113
    114     rules.put("M", MethodLocationPatternConverter.class);
    115     rules.put("method", MethodLocationPatternConverter.class);
    116
    117     rules.put("p", LevelPatternConverter.class);
    118     rules.put("level", LevelPatternConverter.class);
    119
    120     rules.put("r", RelativeTimePatternConverter.class);
    121     rules.put("relative", RelativeTimePatternConverter.class);
    122
    123     rules.put("t", ThreadPatternConverter.class);
    124     rules.put("thread", ThreadPatternConverter.class);
    125
    126     rules.put("x", NDCPatternConverter.class);
    127     rules.put("ndc", NDCPatternConverter.class);
    128
    129     rules.put("X", PropertiesPatternConverter.class);
    130     rules.put("properties", PropertiesPatternConverter.class);
    131
    132     rules.put("sn", SequenceNumberPatternConverter.class);
    133     rules.put("sequenceNumber", SequenceNumberPatternConverter.class);
    134
    135     rules.put("throwable", ThrowableInformationPatternConverter.class);
    136     PATTERN_LAYOUT_RULES = new ReadOnlyMap(rules);
    137
    138     Map fnameRules = new HashMap(4);
    139     fnameRules.put("d", FileDatePatternConverter.class);
    140     fnameRules.put("date", FileDatePatternConverter.class);
    141     fnameRules.put("i", IntegerPatternConverter.class);
    142     fnameRules.put("index", IntegerPatternConverter.class);
    143
    144     FILENAME_PATTERN_RULES = new ReadOnlyMap(fnameRules);
    145   }
    146
    147   /**
    148    * Private constructor.
    149    */
    150   private PatternParser() {
    151   }
    152
    153   /**
    154    * Get standard format specifiers for EnhancedPatternLayout.
    155    * @return read-only map of format converter classes keyed by format specifier strings.
    156    */
    157   public static Map getPatternLayoutRules() {
    158     return PATTERN_LAYOUT_RULES;
    159   }
    160
    161   /**
    162    * Get standard format specifiers for rolling file appender file specification.
    163    * @return read-only map of format converter classes keyed by format specifier strings.
    164    */
    165   public static Map getFileNamePatternRules() {
    166     return FILENAME_PATTERN_RULES;
    167   }
    168
    169   /** Extract the converter identifier found at position i.
    170    *
    171    * After this function returns, the variable i will point to the
    172    * first char after the end of the converter identifier.
    173    *
    174    * If i points to a char which is not a character acceptable at the
    175    * start of a unicode identifier, the value null is returned.
    176    *
    177    * @param lastChar last processed character.
    178    * @param pattern format string.
    179    * @param i current index into pattern format.
    180    * @param convBuf buffer to receive conversion specifier.
    181    * @param currentLiteral literal to be output in case format specifier in unrecognized.
    182    * @return position in pattern after converter.
    183    */
    184   private static int extractConverter(
    185     char lastChar, final String pattern, int i, final StringBuffer convBuf,
    186     final StringBuffer currentLiteral) {
    187     convBuf.setLength(0);
    188
    189     // When this method is called, lastChar points to the first character of the
    190     // conversion word. For example:
    191     // For "%hello"     lastChar = 'h'
    192     // For "%-5hello"   lastChar = 'h'
    193     //System.out.println("lastchar is "+lastChar);
    194     if (!Character.isUnicodeIdentifierStart(lastChar)) {
    195       return i;
    196     }
    197
    198     convBuf.append(lastChar);
    199
    200     while (
    201       (i < pattern.length())
    202         && Character.isUnicodeIdentifierPart(pattern.charAt(i))) {
    203       convBuf.append(pattern.charAt(i));
    204       currentLiteral.append(pattern.charAt(i));
    205
    206       //System.out.println("conv buffer is now ["+convBuf+"].");
    207       i++;
    208     }
    209
    210     return i;
    211   }
    212
    213   /**
    214    * Extract options.
    215    * @param pattern conversion pattern.
    216    * @param i start of options.
    217    * @param options array to receive extracted options
    218    * @return position in pattern after options.
    219    */
    220   private static int extractOptions(String pattern, int i, List options) {
    221     while ((i < pattern.length()) && (pattern.charAt(i) == '{')) {
    222       int end = pattern.indexOf('}', i);
    223
    224       if (end == -1) {
    225         break;
    226       }
    227
    228       String r = pattern.substring(i + 1, end);
    229       options.add(r);
    230       i = end + 1;
    231     }
    232
    233     return i;
    234   }
    235
    236   /**
    237    * Parse a format specifier.
    238    * @param pattern pattern to parse.
    239    * @param patternConverters list to receive pattern converters.
    240    * @param formattingInfos list to receive field specifiers corresponding to pattern converters.
    241    * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
    242    * @param rules map of stock pattern converters keyed by format specifier.
    243    */
    244   public static void parse(
    245     final String pattern, final List patternConverters,
    246     final List formattingInfos, final Map converterRegistry, final Map rules) {
    247     if (pattern == null) {
    248       throw new NullPointerException("pattern");
    249     }
    250
    251     StringBuffer currentLiteral = new StringBuffer(32);
    252
    253     int patternLength = pattern.length();
    254     int state = LITERAL_STATE;
    255     char c;
    256     int i = 0;
    257     FormattingInfo formattingInfo = FormattingInfo.getDefault();
    258
    259     while (i < patternLength) {
    260       c = pattern.charAt(i++);
    261
    262       switch (state) {
    263       case LITERAL_STATE:
    264
    265         // In literal state, the last char is always a literal.
    266         if (i == patternLength) {
    267           currentLiteral.append(c);
    268
    269           continue;
    270         }
    271
    272         if (c == ESCAPE_CHAR) {
    273           // peek at the next char.
    274           switch (pattern.charAt(i)) {
    275           case ESCAPE_CHAR:
    276             currentLiteral.append(c);
    277             i++; // move pointer
    278
    279             break;
    280
    281           default:
    282
    283             if (currentLiteral.length() != 0) {
    284               patternConverters.add(
    285                 new LiteralPatternConverter(currentLiteral.toString()));
    286               formattingInfos.add(FormattingInfo.getDefault());
    287             }
    288
    289             currentLiteral.setLength(0);
    290             currentLiteral.append(c); // append %
    291             state = CONVERTER_STATE;
    292             formattingInfo = FormattingInfo.getDefault();
    293           }
    294         } else {
    295           currentLiteral.append(c);
    296         }
    297
    298         break;
    299
    300       case CONVERTER_STATE:
    301         currentLiteral.append(c);
    302
    303         switch (c) {
    304         case '-':
    305           formattingInfo =
    306             new FormattingInfo(
    307               true, formattingInfo.getMinLength(),
    308               formattingInfo.getMaxLength());
    309
    310           break;
    311
    312         case '.':
    313           state = DOT_STATE;
    314
    315           break;
    316
    317         default:
    318
    319           if ((c >= '0') && (c <= '9')) {
    320             formattingInfo =
    321               new FormattingInfo(
    322                 formattingInfo.isLeftAligned(), c - '0',
    323                 formattingInfo.getMaxLength());
    324             state = MIN_STATE;
    325           } else {
    326             i = finalizeConverter(
    327                 c, pattern, i, currentLiteral, formattingInfo,
    328                 converterRegistry, rules, patternConverters, formattingInfos);
    329
    330             // Next pattern is assumed to be a literal.
    331             state = LITERAL_STATE;
    332             formattingInfo = FormattingInfo.getDefault();
    333             currentLiteral.setLength(0);
    334           }
    335         } // switch
    336
    337         break;
    338
    339       case MIN_STATE:
    340         currentLiteral.append(c);
    341
    342         if ((c >= '0') && (c <= '9')) {
    343           formattingInfo =
    344             new FormattingInfo(
    345               formattingInfo.isLeftAligned(),
    346               (formattingInfo.getMinLength() * 10) + (c - '0'),
    347               formattingInfo.getMaxLength());
    348         } else if (c == '.') {
    349           state = DOT_STATE;
    350         } else {
    351           i = finalizeConverter(
    352               c, pattern, i, currentLiteral, formattingInfo,
    353               converterRegistry, rules, patternConverters, formattingInfos);
    354           state = LITERAL_STATE;
    355           formattingInfo = FormattingInfo.getDefault();
    356           currentLiteral.setLength(0);
    357         }
    358
    359         break;
    360
    361       case DOT_STATE:
    362         currentLiteral.append(c);
    363
    364         if ((c >= '0') && (c <= '9')) {
    365           formattingInfo =
    366             new FormattingInfo(
    367               formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
    368               c - '0');
    369           state = MAX_STATE;
    370         } else {
    371             LogLog.error(
    372               "Error occured in position " + i
    373               + ".\n Was expecting digit, instead got char \"" + c + "\".");
    374
    375           state = LITERAL_STATE;
    376         }
    377
    378         break;
    379
    380       case MAX_STATE:
    381         currentLiteral.append(c);
    382
    383         if ((c >= '0') && (c <= '9')) {
    384           formattingInfo =
    385             new FormattingInfo(
    386               formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
    387               (formattingInfo.getMaxLength() * 10) + (c - '0'));
    388         } else {
    389           i = finalizeConverter(
    390               c, pattern, i, currentLiteral, formattingInfo,
    391               converterRegistry, rules, patternConverters, formattingInfos);
    392           state = LITERAL_STATE;
    393           formattingInfo = FormattingInfo.getDefault();
    394           currentLiteral.setLength(0);
    395         }
    396
    397         break;
    398       } // switch
    399     }
    400
    401     // while
    402     if (currentLiteral.length() != 0) {
    403       patternConverters.add(
    404         new LiteralPatternConverter(currentLiteral.toString()));
    405       formattingInfos.add(FormattingInfo.getDefault());
    406     }
    407   }
    408
    409   /**
    410    * Creates a new PatternConverter.
    411    *
    412    *
    413    * @param converterId converterId.
    414    * @param currentLiteral literal to be used if converter is unrecognized or following converter
    415    *    if converterId contains extra characters.
    416    * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
    417    * @param rules map of stock pattern converters keyed by format specifier.
    418    * @param options converter options.
    419    * @return  converter or null.
    420    */
    421   private static PatternConverter createConverter(
    422     final String converterId, final StringBuffer currentLiteral,
    423     final Map converterRegistry, final Map rules, final List options) {
    424     String converterName = converterId;
    425     Object converterObj = null;
    426
    427     for (int i = converterId.length(); (i > 0) && (converterObj == null);
    428         i--) {
    429       converterName = converterName.substring(0, i);
    430
    431       if (converterRegistry != null) {
    432         converterObj = converterRegistry.get(converterName);
    433       }
    434
    435       if ((converterObj == null) && (rules != null)) {
    436         converterObj = rules.get(converterName);
    437       }
    438     }
    439
    440     if (converterObj == null) {
    441         LogLog.error("Unrecognized format specifier [" + converterId + "]");
    442
    443       return null;
    444     }
    445
    446     Class converterClass = null;
    447
    448     if (converterObj instanceof Class) {
    449       converterClass = (Class) converterObj;
    450     } else {
    451       if (converterObj instanceof String) {
    452         try {
    453           converterClass = Loader.loadClass((String) converterObj);
    454         } catch (ClassNotFoundException ex) {
    455             LogLog.warn(
    456               "Class for conversion pattern %" + converterName + " not found",
    457               ex);
    458
    459           return null;
    460         }
    461       } else {
    462           LogLog.warn(
    463             "Bad map entry for conversion pattern %" +  converterName + ".");
    464
    465         return null;
    466       }
    467     }
    468
    469     try {
    470       Method factory =
    471         converterClass.getMethod(
    472           "newInstance",
    473           new Class[] {
    474             Class.forName("[Ljava.lang.String;")
    475           });
    476       String[] optionsArray = new String[options.size()];
    477       optionsArray = (String[]) options.toArray(optionsArray);
    478
    479       Object newObj =
    480         factory.invoke(null, new Object[] { optionsArray });
    481
    482       if (newObj instanceof PatternConverter) {
    483         currentLiteral.delete(
    484           0,
    485           currentLiteral.length()
    486           - (converterId.length() - converterName.length()));
    487
    488         return (PatternConverter) newObj;
    489       } else {
    490           LogLog.warn(
    491             "Class " + converterClass.getName()
    492             + " does not extend PatternConverter.");
    493       }
    494     } catch (Exception ex) {
    495         LogLog.error("Error creating converter for " + converterId, ex);
    496
    497       try {
    498         //
    499         //  try default constructor
    500         PatternConverter pc = (PatternConverter) converterClass.newInstance();
    501         currentLiteral.delete(
    502           0,
    503           currentLiteral.length()
    504           - (converterId.length() - converterName.length()));
    505
    506         return pc;
    507       } catch (Exception ex2) {
    508           LogLog.error("Error creating converter for " + converterId, ex2);
    509       }
    510     }
    511
    512     return null;
    513   }
    514
    515   /**
    516    * Processes a format specifier sequence.
    517    *
    518    * @param c initial character of format specifier.
    519    * @param pattern conversion pattern
    520    * @param i current position in conversion pattern.
    521    * @param currentLiteral current literal.
    522    * @param formattingInfo current field specifier.
    523    * @param converterRegistry map of user-provided pattern converters keyed by format specifier, may be null.
    524    * @param rules map of stock pattern converters keyed by format specifier.
    525    * @param patternConverters list to receive parsed pattern converter.
    526    * @param formattingInfos list to receive corresponding field specifier.
    527    * @return position after format specifier sequence.
    528    */
    529   private static int finalizeConverter(
    530     char c, String pattern, int i,
    531     final StringBuffer currentLiteral, final FormattingInfo formattingInfo,
    532     final Map converterRegistry, final Map rules, final List patternConverters,
    533     final List formattingInfos) {
    534     StringBuffer convBuf = new StringBuffer();
    535     i = extractConverter(c, pattern, i, convBuf, currentLiteral);
    536
    537     String converterId = convBuf.toString();
    538
    539     List options = new ArrayList();
    540     i = extractOptions(pattern, i, options);
    541
    542     PatternConverter pc =
    543       createConverter(
    544         converterId, currentLiteral, converterRegistry, rules, options);
    545
    546     if (pc == null) {
    547       StringBuffer msg;
    548
    549       if ((converterId == null) || (converterId.length() == 0)) {
    550         msg =
    551           new StringBuffer("Empty conversion specifier starting at position ");
    552       } else {
    553         msg = new StringBuffer("Unrecognized conversion specifier [");
    554         msg.append(converterId);
    555         msg.append("] starting at position ");
    556       }
    557
    558       msg.append(Integer.toString(i));
    559       msg.append(" in conversion pattern.");
    560
    561         LogLog.error(msg.toString());
    562
    563       patternConverters.add(
    564         new LiteralPatternConverter(currentLiteral.toString()));
    565       formattingInfos.add(FormattingInfo.getDefault());
    566     } else {
    567       patternConverters.add(pc);
    568       formattingInfos.add(formattingInfo);
    569
    570       if (currentLiteral.length() > 0) {
    571         patternConverters.add(
    572           new LiteralPatternConverter(currentLiteral.toString()));
    573         formattingInfos.add(FormattingInfo.getDefault());
    574       }
    575     }
    576
    577     currentLiteral.setLength(0);
    578
    579     return i;
    580   }
    581
    582   /**
    583    * The class wraps another Map but throws exceptions on any attempt to modify the map.
    584    */
    585   private static class ReadOnlyMap implements Map {
    586     /**
    587      * Wrapped map.
    588      */
    589     private final Map map;
    590
    591     /**
    592      * Constructor
    593      * @param src source map.
    594      */
    595     public ReadOnlyMap(Map src) {
    596       map = src;
    597     }
    598
    599     /**
    600      * {@inheritDoc}
    601      */
    602     public void clear() {
    603       throw new UnsupportedOperationException();
    604     }
    605
    606     /**
    607      * {@inheritDoc}
    608      */
    609     public boolean containsKey(Object key) {
    610       return map.containsKey(key);
    611     }
    612
    613     /**
    614      * {@inheritDoc}
    615      */
    616     public boolean containsValue(Object value) {
    617       return map.containsValue(value);
    618     }
    619
    620     /**
    621      * {@inheritDoc}
    622      */
    623     public Set entrySet() {
    624       return map.entrySet();
    625     }
    626
    627     /**
    628      * {@inheritDoc}
    629      */
    630     public Object get(Object key) {
    631       return map.get(key);
    632     }
    633
    634     /**
    635      * {@inheritDoc}
    636      */
    637     public boolean isEmpty() {
    638       return map.isEmpty();
    639     }
    640
    641     /**
    642      * {@inheritDoc}
    643      */
    644     public Set keySet() {
    645       return map.keySet();
    646     }
    647
    648     /**
    649      * {@inheritDoc}
    650      */
    651     public Object put(Object key, Object value) {
    652       throw new UnsupportedOperationException();
    653     }
    654
    655     /**
    656      * {@inheritDoc}
    657      */
    658     public void putAll(Map t) {
    659       throw new UnsupportedOperationException();
    660     }
    661
    662     /**
    663      * {@inheritDoc}
    664      */
    665     public Object remove(Object key) {
    666       throw new UnsupportedOperationException();
    667     }
    668
    669     /**
    670      * {@inheritDoc}
    671      */
    672     public int size() {
    673       return map.size();
    674     }
    675
    676     /**
    677      * {@inheritDoc}
    678      */
    679     public Collection values() {
    680       return map.values();
    681     }
    682   }
    683 }