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