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