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