View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2011, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.pattern.parser;
15  
16  import java.util.List;
17  import java.util.ArrayList;
18  
19  import ch.qos.logback.core.CoreConstants;
20  import static ch.qos.logback.core.CoreConstants.CURLY_LEFT;
21  import static ch.qos.logback.core.CoreConstants.ESCAPE_CHAR;
22  
23  import ch.qos.logback.core.pattern.util.IEscapeUtil;
24  import ch.qos.logback.core.pattern.util.RegularEscapeUtil;
25  import ch.qos.logback.core.pattern.util.RestrictedEscapeUtil;
26  
27  /**
28   * <p>
29   * Return a steady stream of tokens.
30   * <p/>
31   * <p/>
32   * <p>
33   * The returned tokens are one of: LITERAL, '%', FORMAT_MODIFIER, SIMPLE_KEYWORD, COMPOSITE_KEYWORD
34   * OPTION, LEFT_PARENTHESIS, and RIGHT_PARENTHESIS.
35   * </p>
36   * <p/>
37   * <p>
38   * The '\' character is used as escape. It can be used to escape '_', '%', '('
39   * and '('.
40   * <p>
41   * <p/>
42   * <p>
43   * Note that there is no EOS token returned.
44   * </p>
45   */
46  class TokenStream {
47  
48    enum TokenizerState { LITERAL_STATE,  FORMAT_MODIFIER_STATE, KEYWORD_STATE, OPTION_STATE,  RIGHT_PARENTHESIS_STATE}
49  
50    final String pattern;
51    final int patternLength;
52    final IEscapeUtil escapeUtil;
53  
54    final IEscapeUtil optionEscapeUtil = new RestrictedEscapeUtil();
55  
56    TokenizerState state = TokenizerState.LITERAL_STATE;
57    int pointer = 0;
58  
59    // this variant should be used for testing purposes only
60    TokenStream(String pattern) {
61      this(pattern, new RegularEscapeUtil());
62    }
63  
64    TokenStream(String pattern, IEscapeUtil escapeUtil) {
65      if (pattern == null || pattern.length() == 0) {
66        throw new IllegalArgumentException(
67                "null or empty pattern string not allowed");
68      }
69      this.pattern = pattern;
70      patternLength = pattern.length();
71      this.escapeUtil = escapeUtil;
72    }
73  
74    List tokenize() throws ScanException {
75      List<Token> tokenList = new ArrayList<Token>();
76      StringBuffer buf = new StringBuffer();
77  
78      while (pointer < patternLength) {
79        char c = pattern.charAt(pointer);
80        pointer++;
81  
82        switch (state) {
83          case LITERAL_STATE:
84            handleLiteralState(c, tokenList, buf);
85            break;
86          case FORMAT_MODIFIER_STATE:
87            handleFormatModifierState(c, tokenList, buf);
88            break;
89          case OPTION_STATE:
90            processOption(c, tokenList, buf);
91            break;
92          case KEYWORD_STATE:
93            handleKeywordState(c, tokenList, buf);
94            break;
95          case RIGHT_PARENTHESIS_STATE:
96            handleRightParenthesisState(c, tokenList, buf);
97            break;
98  
99          default:
100       }
101     }
102 
103     // EOS
104     switch (state) {
105       case LITERAL_STATE:
106         addValuedToken(Token.LITERAL, buf, tokenList);
107         break;
108       case KEYWORD_STATE:
109         tokenList.add(new Token(Token.SIMPLE_KEYWORD, buf.toString()));
110         break;
111       case RIGHT_PARENTHESIS_STATE:
112         tokenList.add(Token.RIGHT_PARENTHESIS_TOKEN);
113         break;
114 
115       case FORMAT_MODIFIER_STATE:
116       case OPTION_STATE:
117         throw new ScanException("Unexpected end of pattern string");
118     }
119 
120     return tokenList;
121   }
122 
123   private void handleRightParenthesisState(char c, List<Token> tokenList, StringBuffer buf) {
124     tokenList.add(Token.RIGHT_PARENTHESIS_TOKEN);
125     switch (c) {
126       case CoreConstants.RIGHT_PARENTHESIS_CHAR:
127         break;
128       case CURLY_LEFT:
129         state = TokenizerState.OPTION_STATE;
130         break;
131       case ESCAPE_CHAR:
132         escape("%{}", buf);
133         state = TokenizerState.LITERAL_STATE;
134         break;
135       default:
136         buf.append(c);
137         state = TokenizerState.LITERAL_STATE;
138     }
139   }
140 
141   private void processOption(char c, List<Token> tokenList, StringBuffer buf) throws ScanException {
142     OptionTokenizer ot = new OptionTokenizer(this);
143     ot.tokenize(c, tokenList);
144   }
145 
146   private void handleFormatModifierState(char c, List<Token> tokenList, StringBuffer buf) {
147     if (c == CoreConstants.LEFT_PARENTHESIS_CHAR) {
148       addValuedToken(Token.FORMAT_MODIFIER, buf, tokenList);
149       tokenList.add(Token.BARE_COMPOSITE_KEYWORD_TOKEN);
150       state = TokenizerState.LITERAL_STATE;
151     } else if (Character.isJavaIdentifierStart(c)) {
152       addValuedToken(Token.FORMAT_MODIFIER, buf, tokenList);
153       state = TokenizerState.KEYWORD_STATE;
154       buf.append(c);
155     } else {
156       buf.append(c);
157     }
158   }
159 
160   private void handleLiteralState(char c, List<Token> tokenList, StringBuffer buf) {
161     switch (c) {
162       case ESCAPE_CHAR:
163         escape("%()", buf);
164         break;
165 
166       case CoreConstants.PERCENT_CHAR:
167         addValuedToken(Token.LITERAL, buf, tokenList);
168         tokenList.add(Token.PERCENT_TOKEN);
169         state = TokenizerState.FORMAT_MODIFIER_STATE;
170         break;
171 
172       case CoreConstants.RIGHT_PARENTHESIS_CHAR:
173         addValuedToken(Token.LITERAL, buf, tokenList);
174         state = TokenizerState.RIGHT_PARENTHESIS_STATE;
175         break;
176 
177       default:
178         buf.append(c);
179     }
180   }
181 
182   private void handleKeywordState(char c, List<Token> tokenList, StringBuffer buf) {
183 
184     if (Character.isJavaIdentifierPart(c)) {
185       buf.append(c);
186     } else if (c == CURLY_LEFT) {
187       addValuedToken(Token.SIMPLE_KEYWORD, buf, tokenList);
188       state = TokenizerState.OPTION_STATE;
189     } else if (c == CoreConstants.LEFT_PARENTHESIS_CHAR) {
190       addValuedToken(Token.COMPOSITE_KEYWORD, buf, tokenList);
191       state = TokenizerState.LITERAL_STATE;
192     } else if (c == CoreConstants.PERCENT_CHAR) {
193       addValuedToken(Token.SIMPLE_KEYWORD, buf, tokenList);
194       tokenList.add(Token.PERCENT_TOKEN);
195       state = TokenizerState.FORMAT_MODIFIER_STATE;
196     } else if (c == CoreConstants.RIGHT_PARENTHESIS_CHAR) {
197       addValuedToken(Token.SIMPLE_KEYWORD, buf, tokenList);
198       state = TokenizerState.RIGHT_PARENTHESIS_STATE;
199     } else {
200       addValuedToken(Token.SIMPLE_KEYWORD, buf, tokenList);
201       if (c == ESCAPE_CHAR) {
202         if ((pointer < patternLength)) {
203           char next = pattern.charAt(pointer++);
204           escapeUtil.escape("%()", buf, next, pointer);
205         }
206       } else {
207         buf.append(c);
208       }
209       state = TokenizerState.LITERAL_STATE;
210     }
211   }
212 
213   void escape(String escapeChars, StringBuffer buf) {
214     if ((pointer < patternLength)) {
215       char next = pattern.charAt(pointer++);
216       escapeUtil.escape(escapeChars, buf, next, pointer);
217     }
218   }
219 
220   void optionEscape(String escapeChars, StringBuffer buf) {
221     if ((pointer < patternLength)) {
222       char next = pattern.charAt(pointer++);
223       optionEscapeUtil.escape(escapeChars, buf, next, pointer);
224     }
225   }
226 
227 
228 
229 
230   private void addValuedToken(int type, StringBuffer buf, List<Token> tokenList) {
231     if (buf.length() > 0) {
232       tokenList.add(new Token(type, buf.toString()));
233       buf.setLength(0);
234     }
235   }
236 }