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 }