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 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 }