View Javadoc

1   /** 
2    * LOGBack: the reliable, fast and flexible logging library for Java.
3    *
4    * Copyright (C) 1999-2005, QOS.ch, LOGBack.com
5    *
6    * This library is free software, you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation.
9    */
10  package ch.qos.logback.core.pattern.parser;
11  
12  import java.util.List;
13  import java.util.ArrayList;
14  
15  import ch.qos.logback.core.CoreConstants;
16  import ch.qos.logback.core.pattern.util.IEscapeUtil;
17  import ch.qos.logback.core.pattern.util.RegularEscapeUtil;
18  
19  
20  
21  /**
22   * <p>Return a steady stream of tokens. <p/>
23   * 
24   * <p>The returned tokens are one of:
25   * LITERAL, '%', FORMAT_MODIFIER, KEYWWORD, OPTION, LEFT_PARENTHESIS, and
26   * RIGHT_PARENTHESIS.</p>
27   * 
28   * <p>The '\' character is used as escape. It can be used to escape '_', '%', '(' and 
29   * '('.<p>
30   * 
31   * <p>Note that there is no EOS token returned.</p>
32   */
33  class TokenStream {
34  
35    private static final char ESCAPE_CHAR = '\\';
36    private static final char PERCENT_CHAR = CoreConstants.PERCENT_CHAR; 
37    private static final char LEFT_PARENTHESIS = '(';
38    private static final char RIGHT_PARENTHESIS = ')';
39    private static final char CURLY_LEFT = '{';
40    private static final char CURLY_RIGHT = '}';
41  
42    private static final int LITERAL_STATE = 0;
43    private static final int FORMAT_MODIFIER_STATE = 1;
44    private static final int KEYWORD_STATE = 2;
45    private static final int OPTION_STATE = 3;
46  
47    final String pattern;
48    final int patternLength;
49    final IEscapeUtil escapeUtil;
50    
51    int state = LITERAL_STATE;
52    int pointer = 0;
53  
54    // this variant should be used for testing purposes only
55    TokenStream(String pattern) {
56      this(pattern, new RegularEscapeUtil());
57    }
58    
59    TokenStream(String pattern, IEscapeUtil escapeUtil) {
60      if(pattern == null) {
61        throw new NullPointerException("null pattern string not allowed");
62      }
63      this.pattern = pattern;
64      patternLength = pattern.length();
65      this.escapeUtil = escapeUtil;
66    }
67  
68    List tokenize() throws ScanException {
69      List<Token> tokenList = new ArrayList<Token>();
70      StringBuffer buf = new StringBuffer();
71  
72      while (pointer < patternLength) {
73        char c = pattern.charAt(pointer);
74        pointer++;
75  
76        switch (state) {
77  
78        case LITERAL_STATE:
79          switch (c) {
80          case ESCAPE_CHAR:
81            escape("%()", buf);
82            break;
83          case PERCENT_CHAR:
84            addValuedToken(Token.LITERAL, buf, tokenList);
85            tokenList.add(Token.PERCENT_TOKEN);
86            state = FORMAT_MODIFIER_STATE;
87            break;
88  
89          case RIGHT_PARENTHESIS:
90            if (buf.length() >= 1 && buf.charAt(buf.length() - 1) == '\\') {
91              buf.deleteCharAt(buf.length() - 1);
92              buf.append(RIGHT_PARENTHESIS);
93            } else {
94              addValuedToken(Token.LITERAL, buf, tokenList);
95              tokenList.add(Token.RIGHT_PARENTHESIS_TOKEN);
96            }
97            break;
98  
99          default:
100           buf.append(c);
101         }
102         break;
103       //
104       case FORMAT_MODIFIER_STATE:
105         if (c == LEFT_PARENTHESIS) {
106           addValuedToken(Token.FORMAT_MODIFIER, buf, tokenList);
107           tokenList.add(Token.LEFT_PARENTHESIS_TOKEN);
108           state = LITERAL_STATE;
109         } else if (Character.isJavaIdentifierStart(c)) {
110           addValuedToken(Token.FORMAT_MODIFIER, buf, tokenList);
111           state = KEYWORD_STATE;
112           buf.append(c);
113         } else {
114           buf.append(c);
115         }
116         break;
117       case OPTION_STATE:
118         switch (c) {
119         case CURLY_RIGHT:
120           addValuedToken(Token.OPTION, buf, tokenList);
121           state = LITERAL_STATE;
122           break;
123         case ESCAPE_CHAR:
124           escape("%{}", buf);
125           break;
126         default:
127           buf.append(c);
128         }
129         break;
130       case KEYWORD_STATE:
131         if (c == CURLY_LEFT) {
132           addValuedToken(Token.KEYWORD, buf, tokenList);
133           state = OPTION_STATE;
134         } else if (Character.isJavaIdentifierPart(c)) {
135           buf.append(c);
136         } else if (c == PERCENT_CHAR) {
137           addValuedToken(Token.KEYWORD, buf, tokenList);
138           tokenList.add(Token.PERCENT_TOKEN);
139           state = FORMAT_MODIFIER_STATE;
140         } else {
141           addValuedToken(Token.KEYWORD, buf, tokenList);
142           if (c == RIGHT_PARENTHESIS) {
143             // if c is a right parenthesis, then add it as such
144             tokenList.add(Token.RIGHT_PARENTHESIS_TOKEN);
145           } else if (c == ESCAPE_CHAR) {
146             if ((pointer < patternLength)) {
147               char next = pattern.charAt(pointer++);
148               escapeUtil.escape("%()", buf, next, pointer);
149             }
150           } else {
151             buf.append(c);
152           }
153           state = LITERAL_STATE;
154         }
155         break;
156 
157       default:
158       }
159     }
160 
161     // EOS
162     switch (state) {
163     case LITERAL_STATE:
164       addValuedToken(Token.LITERAL, buf, tokenList);
165       break;
166     case KEYWORD_STATE:
167       tokenList.add(new Token(Token.KEYWORD, buf.toString()));
168       buf.setLength(0);
169       break;
170 
171     case FORMAT_MODIFIER_STATE:
172     case OPTION_STATE:
173       throw new ScanException("Unexpected end of pattern string");
174     }
175 
176     return tokenList;
177   }
178 
179   void escape(String escapeChars, StringBuffer buf) {
180     if ((pointer < patternLength)) {
181       char next = pattern.charAt(pointer++);
182       escapeUtil.escape(escapeChars, buf, next, pointer);
183     }
184   }
185 
186   private void addValuedToken(int type, StringBuffer buf, List<Token> tokenList) {
187     if (buf.length() > 0) {
188       tokenList.add(new Token(type, buf.toString()));
189       buf.setLength(0);
190     }
191   }
192 }