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 package org.apache.log4j.helpers;
18
19 import org.apache.log4j.Layout;
20 import org.apache.log4j.spi.LoggingEvent;
21 import org.apache.log4j.spi.LocationInfo;
22 import java.text.DateFormat;
23 import java.text.SimpleDateFormat;
24 import java.util.Date;
25 import java.util.Map;
26 import java.util.Arrays;
27
28 // Contributors:   Nelson Minar <(nelson@monkey.org>
29 //                 Igor E. Poteryaev <jah@mail.ru>
30 //                 Reinhard Deschler <reinhard.deschler@web.de>
31
32 /**
33    Most of the work of the {@link org.apache.log4j.PatternLayout} class
34    is delegated to the PatternParser class.
35
36    <p>It is this class that parses conversion patterns and creates
37    a chained list of {@link OptionConverter OptionConverters}.
38
39    @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
40    @author Ceki G&uuml;lc&uuml;
41    @author Anders Kristensen
42
43    @since 0.8.2
44 */

45 public class PatternParser {
46
47   private static final char ESCAPE_CHAR = '%';
48
49   private static final int LITERAL_STATE = 0;
50   private static final int CONVERTER_STATE = 1;
51   private static final int DOT_STATE = 3;
52   private static final int MIN_STATE = 4;
53   private static final int MAX_STATE = 5;
54
55   static final int FULL_LOCATION_CONVERTER = 1000;
56   static final int METHOD_LOCATION_CONVERTER = 1001;
57   static final int CLASS_LOCATION_CONVERTER = 1002;
58   static final int LINE_LOCATION_CONVERTER = 1003;
59   static final int FILE_LOCATION_CONVERTER = 1004;
60
61   static final int RELATIVE_TIME_CONVERTER = 2000;
62   static final int THREAD_CONVERTER = 2001;
63   static final int LEVEL_CONVERTER = 2002;
64   static final int NDC_CONVERTER = 2003;
65   static final int MESSAGE_CONVERTER = 2004;
66
67   int state;
68   protected StringBuffer currentLiteral = new StringBuffer(32);
69   protected int patternLength;
70   protected int i;
71   PatternConverter head;
72   PatternConverter tail;
73   protected FormattingInfo formattingInfo = new FormattingInfo();
74   protected String pattern;
75
76   public
77   PatternParser(String pattern) {
78     this.pattern = pattern;
79     patternLength =  pattern.length();
80     state = LITERAL_STATE;
81   }
82
83   private
84   void  addToList(PatternConverter pc) {
85     if(head == null) {
86       head = tail = pc;
87     } else {
88       tail.next = pc;
89       tail = pc;
90     }
91   }
92
93   protected
94   String extractOption() {
95     if((i < patternLength) && (pattern.charAt(i) == '{')) {
96       int end = pattern.indexOf('}', i);
97       if (end > i) {
98     String r = pattern.substring(i + 1, end);
99     i = end+1;
100     return r;
101       }
102     }
103     return null;
104   }
105
106
107   /**
108      The option is expected to be in decimal and positive. In case of
109      error, zero is returned.  */

110   protected
111   int extractPrecisionOption() {
112     String opt = extractOption();
113     int r = 0;
114     if(opt != null) {
115       try {
116     r = Integer.parseInt(opt);
117     if(r <= 0) {
118         LogLog.error(
119             "Precision option (" + opt + ") isn't a positive integer.");
120         r = 0;
121     }
122       }
123       catch (NumberFormatException e) {
124     LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
125       }
126     }
127     return r;
128   }
129
130   public
131   PatternConverter parse() {
132     char c;
133     i = 0;
134     while(i < patternLength) {
135       c = pattern.charAt(i++);
136       switch(state) {
137       case LITERAL_STATE:
138         // In literal state, the last char is always a literal.
139         if(i == patternLength) {
140           currentLiteral.append(c);
141           continue;
142         }
143         if(c == ESCAPE_CHAR) {
144           // peek at the next char.
145           switch(pattern.charAt(i)) {
146           case ESCAPE_CHAR:
147             currentLiteral.append(c);
148             i++; // move pointer
149             break;
150           case 'n':
151             currentLiteral.append(Layout.LINE_SEP);
152             i++; // move pointer
153             break;
154           default:
155             if(currentLiteral.length() != 0) {
156               addToList(new LiteralPatternConverter(
157                                                   currentLiteral.toString()));
158               //LogLog.debug("Parsed LITERAL converter: \""
159               //           +currentLiteral+"\".");
160             }
161             currentLiteral.setLength(0);
162             currentLiteral.append(c); // append %
163             state = CONVERTER_STATE;
164             formattingInfo.reset();
165           }
166         }
167         else {
168           currentLiteral.append(c);
169         }
170         break;
171       case CONVERTER_STATE:
172     currentLiteral.append(c);
173     switch(c) {
174     case '-':
175       formattingInfo.leftAlign = true;
176       break;
177     case '.':
178       state = DOT_STATE;
179       break;
180     default:
181       if(c >= '0' && c <= '9') {
182         formattingInfo.min = c - '0';
183         state = MIN_STATE;
184       }
185       else
186         finalizeConverter(c);
187     } // switch
188     break;
189       case MIN_STATE:
190     currentLiteral.append(c);
191     if(c >= '0' && c <= '9')
192       formattingInfo.min = formattingInfo.min*10 + (c - '0');
193     else if(c == '.')
194       state = DOT_STATE;
195     else {
196       finalizeConverter(c);
197     }
198     break;
199       case DOT_STATE:
200     currentLiteral.append(c);
201     if(c >= '0' && c <= '9') {
202       formattingInfo.max = c - '0';
203        state = MAX_STATE;
204     }
205     else {
206       LogLog.error("Error occured in position "+i
207              +".\n Was expecting digit, instead got char \""+c+"\".");
208       state = LITERAL_STATE;
209     }
210     break;
211       case MAX_STATE:
212     currentLiteral.append(c);
213     if(c >= '0' && c <= '9')
214       formattingInfo.max = formattingInfo.max*10 + (c - '0');
215     else {
216       finalizeConverter(c);
217       state = LITERAL_STATE;
218     }
219     break;
220       } // switch
221     } // while
222     if(currentLiteral.length() != 0) {
223       addToList(new LiteralPatternConverter(currentLiteral.toString()));
224       //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
225     }
226     return head;
227   }
228
229   protected
230   void finalizeConverter(char c) {
231     PatternConverter pc = null;
232     switch(c) {
233     case 'c':
234       pc = new CategoryPatternConverter(formattingInfo,
235                     extractPrecisionOption());
236       //LogLog.debug("CATEGORY converter.");
237       //formattingInfo.dump();
238       currentLiteral.setLength(0);
239       break;
240     case 'C':
241       pc = new ClassNamePatternConverter(formattingInfo,
242                      extractPrecisionOption());
243       //LogLog.debug("CLASS_NAME converter.");
244       //formattingInfo.dump();
245       currentLiteral.setLength(0);
246       break;
247     case 'd':
248       String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
249       DateFormat df;
250       String dOpt = extractOption();
251       if(dOpt != null)
252     dateFormatStr = dOpt;
253
254       if(dateFormatStr.equalsIgnoreCase(
255                                     AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
256     df = new  ISO8601DateFormat();
257       else if(dateFormatStr.equalsIgnoreCase(
258                                    AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
259     df = new AbsoluteTimeDateFormat();
260       else if(dateFormatStr.equalsIgnoreCase(
261                               AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
262     df = new DateTimeDateFormat();
263       else {
264     try {
265       df = new SimpleDateFormat(dateFormatStr);
266     }
267     catch (IllegalArgumentException e) {
268       LogLog.error("Could not instantiate SimpleDateFormat with " +
269                dateFormatStr, e);
270       df = (DateFormat) OptionConverter.instantiateByClassName(
271                        "org.apache.log4j.helpers.ISO8601DateFormat",
272                    DateFormat.classnull);
273     }
274       }
275       pc = new DatePatternConverter(formattingInfo, df);
276       //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
277       //formattingInfo.dump();
278       currentLiteral.setLength(0);
279       break;
280     case 'F':
281       pc = new LocationPatternConverter(formattingInfo,
282                     FILE_LOCATION_CONVERTER);
283       //LogLog.debug("File name converter.");
284       //formattingInfo.dump();
285       currentLiteral.setLength(0);
286       break;
287     case 'l':
288       pc = new LocationPatternConverter(formattingInfo,
289                     FULL_LOCATION_CONVERTER);
290       //LogLog.debug("Location converter.");
291       //formattingInfo.dump();
292       currentLiteral.setLength(0);
293       break;
294     case 'L':
295       pc = new LocationPatternConverter(formattingInfo,
296                     LINE_LOCATION_CONVERTER);
297       //LogLog.debug("LINE NUMBER converter.");
298       //formattingInfo.dump();
299       currentLiteral.setLength(0);
300       break;
301     case 'm':
302       pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
303       //LogLog.debug("MESSAGE converter.");
304       //formattingInfo.dump();
305       currentLiteral.setLength(0);
306       break;
307     case 'M':
308       pc = new LocationPatternConverter(formattingInfo,
309                     METHOD_LOCATION_CONVERTER);
310       //LogLog.debug("METHOD converter.");
311       //formattingInfo.dump();
312       currentLiteral.setLength(0);
313       break;
314     case 'p':
315       pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
316       //LogLog.debug("LEVEL converter.");
317       //formattingInfo.dump();
318       currentLiteral.setLength(0);
319       break;
320     case 'r':
321       pc = new BasicPatternConverter(formattingInfo,
322                      RELATIVE_TIME_CONVERTER);
323       //LogLog.debug("RELATIVE time converter.");
324       //formattingInfo.dump();
325       currentLiteral.setLength(0);
326       break;
327     case 't':
328       pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
329       //LogLog.debug("THREAD converter.");
330       //formattingInfo.dump();
331       currentLiteral.setLength(0);
332       break;
333       /*case 'u':
334       if(i < patternLength) {
335     char cNext = pattern.charAt(i);
336     if(cNext >= '0' && cNext <= '9') {
337       pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
338       LogLog.debug("USER converter ["+cNext+"].");
339       formattingInfo.dump();
340       currentLiteral.setLength(0);
341       i++;
342     }
343     else
344       LogLog.error("Unexpected char" +cNext+" at position "+i);
345       }
346       break;*/

347     case 'x':
348       pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
349       //LogLog.debug("NDC converter.");
350       currentLiteral.setLength(0);
351       break;
352     case 'X':
353       String xOpt = extractOption();
354       pc = new MDCPatternConverter(formattingInfo, xOpt);
355       currentLiteral.setLength(0);
356       break;
357     default:
358       LogLog.error("Unexpected char [" +c+"] at position "+i
359            +" in conversion patterrn.");
360       pc = new LiteralPatternConverter(currentLiteral.toString());
361       currentLiteral.setLength(0);
362     }
363
364     addConverter(pc);
365   }
366
367   protected
368   void addConverter(PatternConverter pc) {
369     currentLiteral.setLength(0);
370     // Add the pattern converter to the list.
371     addToList(pc);
372     // Next pattern is assumed to be a literal.
373     state = LITERAL_STATE;
374     // Reset formatting info
375     formattingInfo.reset();
376   }
377
378   // ---------------------------------------------------------------------
379   //                      PatternConverters
380   // ---------------------------------------------------------------------
381
382   private static class BasicPatternConverter extends PatternConverter {
383     int type;
384
385     BasicPatternConverter(FormattingInfo formattingInfo, int type) {
386       super(formattingInfo);
387       this.type = type;
388     }
389
390     public
391     String convert(LoggingEvent event) {
392       switch(type) {
393       case RELATIVE_TIME_CONVERTER:
394     return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
395       case THREAD_CONVERTER:
396     return event.getThreadName();
397       case LEVEL_CONVERTER:
398     return event.getLevel().toString();
399       case NDC_CONVERTER:
400     return event.getNDC();
401       case MESSAGE_CONVERTER: {
402     return event.getRenderedMessage();
403       }
404       defaultreturn null;
405       }
406     }
407   }
408
409   private static class LiteralPatternConverter extends PatternConverter {
410     private String literal;
411
412     LiteralPatternConverter(String value) {
413       literal = value;
414     }
415
416     public
417     final
418     void format(StringBuffer sbuf, LoggingEvent event) {
419       sbuf.append(literal);
420     }
421
422     public
423     String convert(LoggingEvent event) {
424       return literal;
425     }
426   }
427
428   private static class DatePatternConverter extends PatternConverter {
429     private DateFormat df;
430     private Date date;
431
432     DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
433       super(formattingInfo);
434       date = new Date();
435       this.df = df;
436     }
437
438     public
439     String convert(LoggingEvent event) {
440       date.setTime(event.timeStamp);
441       String converted = null;
442       try {
443         converted = df.format(date);
444       }
445       catch (Exception ex) {
446         LogLog.error("Error occured while converting date.", ex);
447       }
448       return converted;
449     }
450   }
451
452   private static class MDCPatternConverter extends PatternConverter {
453     private String key;
454
455     MDCPatternConverter(FormattingInfo formattingInfo, String key) {
456       super(formattingInfo);
457       this.key = key;
458     }
459
460     public
461     String convert(LoggingEvent event) {
462       if (key == null) {
463           StringBuffer buf = new StringBuffer("{");
464           Map properties = event.getProperties();
465           if (properties.size() > 0) {
466             Object[] keys = properties.keySet().toArray();
467             Arrays.sort(keys);
468             for (int i = 0; i < keys.length; i++) {
469                 buf.append('{');
470                 buf.append(keys[i]);
471                 buf.append(',');
472                 buf.append(properties.get(keys[i]));
473                 buf.append('}');
474             }
475           }
476           buf.append('}');
477           return buf.toString();
478       } else {
479         Object val = event.getMDC(key);
480         if(val == null) {
481             return null;
482         } else {
483             return val.toString();
484         }
485       }
486     }
487   }
488
489
490   private class LocationPatternConverter extends PatternConverter {
491     int type;
492
493     LocationPatternConverter(FormattingInfo formattingInfo, int type) {
494       super(formattingInfo);
495       this.type = type;
496     }
497
498     public
499     String convert(LoggingEvent event) {
500       LocationInfo locationInfo = event.getLocationInformation();
501       switch(type) {
502       case FULL_LOCATION_CONVERTER:
503     return locationInfo.fullInfo;
504       case METHOD_LOCATION_CONVERTER:
505     return locationInfo.getMethodName();
506       case LINE_LOCATION_CONVERTER:
507     return locationInfo.getLineNumber();
508       case FILE_LOCATION_CONVERTER:
509     return locationInfo.getFileName();
510       defaultreturn null;
511       }
512     }
513   }
514
515   private static abstract class NamedPatternConverter extends PatternConverter {
516     int precision;
517
518     NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
519       super(formattingInfo);
520       this.precision =  precision;
521     }
522
523     abstract
524     String getFullyQualifiedName(LoggingEvent event);
525
526     public
527     String convert(LoggingEvent event) {
528       String n = getFullyQualifiedName(event);
529       if(precision <= 0)
530     return n;
531       else {
532     int len = n.length();
533
534     // We substract 1 from 'len' when assigning to 'end' to avoid out of
535     // bounds exception in return r.substring(end+1, len). This can happen if
536     // precision is 1 and the category name ends with a dot.
537     int end = len -1 ;
538     for(int i = precision; i > 0; i--) {
539       end = n.lastIndexOf('.', end-1);
540       if(end == -1)
541         return n;
542     }
543     return n.substring(end+1, len);
544       }
545     }
546   }
547
548   private class ClassNamePatternConverter extends NamedPatternConverter {
549
550     ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
551       super(formattingInfo, precision);
552     }
553
554     String getFullyQualifiedName(LoggingEvent event) {
555       return event.getLocationInformation().getClassName();
556     }
557   }
558
559   private class CategoryPatternConverter extends NamedPatternConverter {
560
561     CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
562       super(formattingInfo, precision);
563     }
564
565     String getFullyQualifiedName(LoggingEvent event) {
566       return event.getLoggerName();
567     }
568   }
569 }
570
571