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, 
308               formattingInfo.isRightTruncated(),
309               formattingInfo.getMinLength(),
310               formattingInfo.getMaxLength());
311           break;
312 
313         case '!':
314           formattingInfo =
315             new FormattingInfo(
316               formattingInfo.isLeftAligned(), 
317               true,
318               formattingInfo.getMinLength(),
319               formattingInfo.getMaxLength());
320           break;
321 
322 
323         case '.':
324           state = DOT_STATE;
325 
326           break;
327 
328         default:
329 
330           if ((c >= '0') && (c <= '9')) {
331             formattingInfo =
332               new FormattingInfo(
333                 formattingInfo.isLeftAligned(), 
334                 formattingInfo.isRightTruncated(),
335                 c - '0',
336                 formattingInfo.getMaxLength());
337             state = MIN_STATE;
338           } else {
339             i = finalizeConverter(
340                 c, pattern, i, currentLiteral, formattingInfo,
341                 converterRegistry, rules, patternConverters, formattingInfos);
342 
343             // Next pattern is assumed to be a literal.
344             state = LITERAL_STATE;
345             formattingInfo = FormattingInfo.getDefault();
346             currentLiteral.setLength(0);
347           }
348         } // switch
349 
350         break;
351 
352       case MIN_STATE:
353         currentLiteral.append(c);
354 
355         if ((c >= '0') && (c <= '9')) {
356           formattingInfo =
357             new FormattingInfo(
358               formattingInfo.isLeftAligned(),
359               formattingInfo.isRightTruncated(),
360               (formattingInfo.getMinLength() * 10) + (c - '0'),
361               formattingInfo.getMaxLength());
362         } else if (c == '.') {
363           state = DOT_STATE;
364         } else {
365           i = finalizeConverter(
366               c, pattern, i, currentLiteral, formattingInfo,
367               converterRegistry, rules, patternConverters, formattingInfos);
368           state = LITERAL_STATE;
369           formattingInfo = FormattingInfo.getDefault();
370           currentLiteral.setLength(0);
371         }
372 
373         break;
374 
375       case DOT_STATE:
376         currentLiteral.append(c);
377 
378         if ((c >= '0') && (c <= '9')) {
379           formattingInfo =
380             new FormattingInfo(
381               formattingInfo.isLeftAligned(), 
382               formattingInfo.isRightTruncated(),
383               formattingInfo.getMinLength(),
384               c - '0');
385           state = MAX_STATE;
386         } else {
387             LogLog.error(
388               "Error occured in position " + i
389               + ".\n Was expecting digit, instead got char \"" + c + "\".");
390 
391           state = LITERAL_STATE;
392         }
393 
394         break;
395 
396       case MAX_STATE:
397         currentLiteral.append(c);
398 
399         if ((c >= '0') && (c <= '9')) {
400           formattingInfo =
401             new FormattingInfo(
402               formattingInfo.isLeftAligned(), 
403               formattingInfo.isRightTruncated(),
404               formattingInfo.getMinLength(),
405               (formattingInfo.getMaxLength() * 10) + (c - '0'));
406         } else {
407           i = finalizeConverter(
408               c, pattern, i, currentLiteral, formattingInfo,
409               converterRegistry, rules, patternConverters, formattingInfos);
410           state = LITERAL_STATE;
411           formattingInfo = FormattingInfo.getDefault();
412           currentLiteral.setLength(0);
413         }
414 
415         break;
416       } // switch
417     }
418 
419     // while
420     if (currentLiteral.length() != 0) {
421       patternConverters.add(
422         new LiteralPatternConverter(currentLiteral.toString()));
423       formattingInfos.add(FormattingInfo.getDefault());
424     }
425   }
426 
427   /***
428    * Creates a new PatternConverter.
429    *
430    *
431    * @param converterId converterId.
432    * @param currentLiteral literal to be used if converter is unrecognized or following converter
433    *    if converterId contains extra characters.
434    * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
435    * @param rules map of stock pattern converters keyed by format specifier.
436    * @param options converter options.
437    * @return  converter or null.
438    */
439   private static PatternConverter createConverter(
440     final String converterId, final StringBuffer currentLiteral,
441     final Map converterRegistry, final Map rules, final List options) {
442     String converterName = converterId;
443     Object converterObj = null;
444 
445     for (int i = converterId.length(); (i > 0) && (converterObj == null);
446         i--) {
447       converterName = converterName.substring(0, i);
448 
449       if (converterRegistry != null) {
450         converterObj = converterRegistry.get(converterName);
451       }
452 
453       if ((converterObj == null) && (rules != null)) {
454         converterObj = rules.get(converterName);
455       }
456     }
457 
458     if (converterObj == null) {
459         LogLog.error("Unrecognized format specifier [" + converterId + "]");
460 
461       return null;
462     }
463 
464     Class converterClass = null;
465 
466     if (converterObj instanceof Class) {
467       converterClass = (Class) converterObj;
468     } else {
469       if (converterObj instanceof String) {
470         try {
471           converterClass = Loader.loadClass((String) converterObj);
472         } catch (ClassNotFoundException ex) {
473             LogLog.warn(
474               "Class for conversion pattern %" + converterName + " not found",
475               ex);
476 
477           return null;
478         }
479       } else {
480           LogLog.warn(
481             "Bad map entry for conversion pattern %" +  converterName + ".");
482 
483         return null;
484       }
485     }
486 
487     try {
488       Method factory =
489         converterClass.getMethod(
490           "newInstance",
491           new Class[] {
492             Class.forName("[Ljava.lang.String;")
493           });
494       String[] optionsArray = new String[options.size()];
495       optionsArray = (String[]) options.toArray(optionsArray);
496 
497       Object newObj =
498         factory.invoke(null, new Object[] { optionsArray });
499 
500       if (newObj instanceof PatternConverter) {
501         currentLiteral.delete(
502           0,
503           currentLiteral.length()
504           - (converterId.length() - converterName.length()));
505 
506         return (PatternConverter) newObj;
507       } else {
508           LogLog.warn(
509             "Class " + converterClass.getName()
510             + " does not extend PatternConverter.");
511       }
512     } catch (Exception ex) {
513         LogLog.error("Error creating converter for " + converterId, ex);
514 
515       try {
516         //
517         //  try default constructor
518         PatternConverter pc = (PatternConverter) converterClass.newInstance();
519         currentLiteral.delete(
520           0,
521           currentLiteral.length()
522           - (converterId.length() - converterName.length()));
523 
524         return pc;
525       } catch (Exception ex2) {
526           LogLog.error("Error creating converter for " + converterId, ex2);
527       }
528     }
529 
530     return null;
531   }
532 
533   /***
534    * Processes a format specifier sequence.
535    *
536    * @param c initial character of format specifier.
537    * @param pattern conversion pattern
538    * @param i current position in conversion pattern.
539    * @param currentLiteral current literal.
540    * @param formattingInfo current field specifier.
541    * @param converterRegistry map of user-provided pattern converters keyed by format specifier, may be null.
542    * @param rules map of stock pattern converters keyed by format specifier.
543    * @param patternConverters list to receive parsed pattern converter.
544    * @param formattingInfos list to receive corresponding field specifier.
545    * @return position after format specifier sequence.
546    */
547   private static int finalizeConverter(
548     char c, String pattern, int i,
549     final StringBuffer currentLiteral, final FormattingInfo formattingInfo,
550     final Map converterRegistry, final Map rules, final List patternConverters,
551     final List formattingInfos) {
552     StringBuffer convBuf = new StringBuffer();
553     i = extractConverter(c, pattern, i, convBuf, currentLiteral);
554 
555     String converterId = convBuf.toString();
556 
557     List options = new ArrayList();
558     i = extractOptions(pattern, i, options);
559 
560     PatternConverter pc =
561       createConverter(
562         converterId, currentLiteral, converterRegistry, rules, options);
563 
564     if (pc == null) {
565       StringBuffer msg;
566 
567       if ((converterId == null) || (converterId.length() == 0)) {
568         msg =
569           new StringBuffer("Empty conversion specifier starting at position ");
570       } else {
571         msg = new StringBuffer("Unrecognized conversion specifier [");
572         msg.append(converterId);
573         msg.append("] starting at position ");
574       }
575 
576       msg.append(Integer.toString(i));
577       msg.append(" in conversion pattern.");
578 
579         LogLog.error(msg.toString());
580 
581       patternConverters.add(
582         new LiteralPatternConverter(currentLiteral.toString()));
583       formattingInfos.add(FormattingInfo.getDefault());
584     } else {
585       patternConverters.add(pc);
586       formattingInfos.add(formattingInfo);
587 
588       if (currentLiteral.length() > 0) {
589         patternConverters.add(
590           new LiteralPatternConverter(currentLiteral.toString()));
591         formattingInfos.add(FormattingInfo.getDefault());
592       }
593     }
594 
595     currentLiteral.setLength(0);
596 
597     return i;
598   }
599 
600   /***
601    * The class wraps another Map but throws exceptions on any attempt to modify the map.
602    */
603   private static class ReadOnlyMap implements Map {
604     /***
605      * Wrapped map.
606      */
607     private final Map map;
608 
609     /***
610      * Constructor
611      * @param src source map.
612      */
613     public ReadOnlyMap(Map src) {
614       map = src;
615     }
616 
617     /***
618      * {@inheritDoc}
619      */
620     public void clear() {
621       throw new UnsupportedOperationException();
622     }
623 
624     /***
625      * {@inheritDoc}
626      */
627     public boolean containsKey(Object key) {
628       return map.containsKey(key);
629     }
630 
631     /***
632      * {@inheritDoc}
633      */
634     public boolean containsValue(Object value) {
635       return map.containsValue(value);
636     }
637 
638     /***
639      * {@inheritDoc}
640      */
641     public Set entrySet() {
642       return map.entrySet();
643     }
644 
645     /***
646      * {@inheritDoc}
647      */
648     public Object get(Object key) {
649       return map.get(key);
650     }
651 
652     /***
653      * {@inheritDoc}
654      */
655     public boolean isEmpty() {
656       return map.isEmpty();
657     }
658 
659     /***
660      * {@inheritDoc}
661      */
662     public Set keySet() {
663       return map.keySet();
664     }
665 
666     /***
667      * {@inheritDoc}
668      */
669     public Object put(Object key, Object value) {
670       throw new UnsupportedOperationException();
671     }
672 
673     /***
674      * {@inheritDoc}
675      */
676     public void putAll(Map t) {
677       throw new UnsupportedOperationException();
678     }
679 
680     /***
681      * {@inheritDoc}
682      */
683     public Object remove(Object key) {
684       throw new UnsupportedOperationException();
685     }
686 
687     /***
688      * {@inheritDoc}
689      */
690     public int size() {
691       return map.size();
692     }
693 
694     /***
695      * {@inheritDoc}
696      */
697     public Collection values() {
698       return map.values();
699     }
700   }
701 }