001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.log4j.helpers;
018
019import org.apache.log4j.Layout;
020import org.apache.log4j.spi.LoggingEvent;
021import org.apache.log4j.spi.LocationInfo;
022import java.text.DateFormat;
023import java.text.SimpleDateFormat;
024import java.util.Date;
025import java.util.Map;
026import java.util.Arrays;
027
028// Contributors:   Nelson Minar <(nelson@monkey.org>
029//                 Igor E. Poteryaev <jah@mail.ru>
030//                 Reinhard Deschler <reinhard.deschler@web.de>
031
032/**
033   Most of the work of the {@link org.apache.log4j.PatternLayout} class
034   is delegated to the PatternParser class.
035
036   <p>It is this class that parses conversion patterns and creates
037   a chained list of {@link OptionConverter OptionConverters}.
038
039   @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
040   @author Ceki G&uuml;lc&uuml;
041   @author Anders Kristensen
042
043   @since 0.8.2
044*/
045public class PatternParser {
046
047  private static final char ESCAPE_CHAR = '%';
048
049  private static final int LITERAL_STATE = 0;
050  private static final int CONVERTER_STATE = 1;
051  private static final int DOT_STATE = 3;
052  private static final int MIN_STATE = 4;
053  private static final int MAX_STATE = 5;
054
055  static final int FULL_LOCATION_CONVERTER = 1000;
056  static final int METHOD_LOCATION_CONVERTER = 1001;
057  static final int CLASS_LOCATION_CONVERTER = 1002;
058  static final int LINE_LOCATION_CONVERTER = 1003;
059  static final int FILE_LOCATION_CONVERTER = 1004;
060
061  static final int RELATIVE_TIME_CONVERTER = 2000;
062  static final int THREAD_CONVERTER = 2001;
063  static final int LEVEL_CONVERTER = 2002;
064  static final int NDC_CONVERTER = 2003;
065  static final int MESSAGE_CONVERTER = 2004;
066
067  int state;
068  protected StringBuffer currentLiteral = new StringBuffer(32);
069  protected int patternLength;
070  protected int i;
071  PatternConverter head;
072  PatternConverter tail;
073  protected FormattingInfo formattingInfo = new FormattingInfo();
074  protected String pattern;
075
076  public
077  PatternParser(String pattern) {
078    this.pattern = pattern;
079    patternLength =  pattern.length();
080    state = LITERAL_STATE;
081  }
082
083  private
084  void  addToList(PatternConverter pc) {
085    if(head == null) {
086      head = tail = pc;
087    } else {
088      tail.next = pc;
089      tail = pc;
090    }
091  }
092
093  protected
094  String extractOption() {
095    if((i < patternLength) && (pattern.charAt(i) == '{')) {
096      int end = pattern.indexOf('}', i);
097      if (end > i) {
098        String r = pattern.substring(i + 1, end);
099        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.class, null);
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      default: return 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      default: return 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