1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.log4j.rule;
19
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Stack;
26 import java.util.Vector;
27
28 import org.apache.log4j.spi.LoggingEventFieldResolver;
29
30 /***
31 * A helper class which converts infix expressions to postfix expressions
32 * Currently grouping is supported, as well as all of the
33 * Rules supported by <code>RuleFactory</code>
34 *
35 * Supports grouping via parens, mult-word operands using single or double quotes,
36 * and these operators:
37 *
38 * ! NOT operator
39 * != NOT EQUALS operator
40 * == EQUALS operator
41 * ~= CASE-INSENSITIVE equals operator
42 * || OR operator
43 * && AND operator
44 * like REGEXP operator
45 * exists NOT NULL operator
46 * < LESS THAN operator
47 * > GREATER THAN operator
48 * <= LESS THAN EQUALS operator
49 * >= GREATER THAN EQUALS operator
50 *
51 * @author Scott Deboy (sdeboy@apache.org)
52 */
53
54 public class InFixToPostFix {
55 /***
56 * Precedence map.
57 */
58 private static final Map precedenceMap = new HashMap();
59 /***
60 * Operators.
61 */
62 private static final List operators = new Vector();
63
64
65 static {
66
67 operators.add("<=");
68 operators.add(">=");
69 operators.add("!=");
70 operators.add("==");
71 operators.add("~=");
72 operators.add("||");
73 operators.add("&&");
74 operators.add("like");
75 operators.add("exists");
76 operators.add("!");
77 operators.add("<");
78 operators.add(">");
79
80
81 precedenceMap.put("<", new Integer(3));
82 precedenceMap.put(">", new Integer(3));
83 precedenceMap.put("<=", new Integer(3));
84 precedenceMap.put(">=", new Integer(3));
85
86 precedenceMap.put("!", new Integer(3));
87 precedenceMap.put("!=", new Integer(3));
88 precedenceMap.put("==", new Integer(3));
89 precedenceMap.put("~=", new Integer(3));
90 precedenceMap.put("like", new Integer(3));
91 precedenceMap.put("exists", new Integer(3));
92
93 precedenceMap.put("||", new Integer(2));
94 precedenceMap.put("&&", new Integer(2));
95 }
96 /***
97 * Convert in-fix expression to post-fix.
98 * @param expression in-fix expression.
99 * @return post-fix expression.
100 */
101 public String convert(final String expression) {
102 return infixToPostFix(new CustomTokenizer(expression));
103 }
104
105 /***
106 * Evaluates whether symbol is operand.
107 * @param s symbol.
108 * @return true if operand.
109 */
110 public static boolean isOperand(final String s) {
111 String symbol = s.toLowerCase();
112 return (!operators.contains(symbol));
113 }
114
115 /***
116 * Determines whether one symbol precedes another.
117 * @param s1 symbol 1
118 * @param s2 symbol 2
119 * @return true if symbol 1 precedes symbol 2
120 */
121 boolean precedes(final String s1, final String s2) {
122 String symbol1 = s1.toLowerCase();
123 String symbol2 = s2.toLowerCase();
124
125 if (!precedenceMap.keySet().contains(symbol1)) {
126 return false;
127 }
128
129 if (!precedenceMap.keySet().contains(symbol2)) {
130 return false;
131 }
132
133 int index1 = ((Integer) precedenceMap.get(symbol1)).intValue();
134 int index2 = ((Integer) precedenceMap.get(symbol2)).intValue();
135
136 boolean precedesResult = (index1 < index2);
137
138 return precedesResult;
139 }
140
141 /***
142 * convert in-fix expression to post-fix.
143 * @param tokenizer tokenizer.
144 * @return post-fix expression.
145 */
146 String infixToPostFix(final CustomTokenizer tokenizer) {
147 final String space = " ";
148 StringBuffer postfix = new StringBuffer();
149
150 Stack stack = new Stack();
151
152 while (tokenizer.hasMoreTokens()) {
153 String token = tokenizer.nextToken();
154
155 boolean inText = (token.startsWith("'") && (!token.endsWith("'"))) || (token.startsWith("\"") && (!token.endsWith("\"")));
156 String quoteChar = token.substring(0, 1);
157 if (inText) {
158 while (inText && tokenizer.hasMoreTokens()) {
159 token = token + " " + tokenizer.nextToken();
160 inText = !(token.endsWith(quoteChar));
161 }
162 }
163
164 if ("(".equals(token)) {
165
166 postfix.append(infixToPostFix(tokenizer));
167 postfix.append(space);
168 } else if (")".equals(token)) {
169
170 while (stack.size() > 0) {
171 postfix.append(stack.pop().toString());
172 postfix.append(space);
173 }
174
175 return postfix.toString();
176 } else if (isOperand(token)) {
177 postfix.append(token);
178 postfix.append(space);
179 } else {
180
181
182
183
184
185
186 if (stack.size() > 0) {
187
188 String peek = stack.peek().toString();
189
190 if (precedes(peek, token)) {
191 stack.push(token);
192 } else {
193 boolean bypass = false;
194
195 do {
196 if (
197 (stack.size() > 0)
198 && !precedes(stack.peek().toString(), token)) {
199 postfix.append(stack.pop().toString());
200 postfix.append(space);
201 } else {
202 bypass = true;
203 }
204 } while (!bypass);
205
206 stack.push(token);
207 }
208 } else {
209 stack.push(token);
210 }
211 }
212 }
213
214 while (stack.size() > 0) {
215 postfix.append(stack.pop().toString());
216 postfix.append(space);
217 }
218
219 return postfix.toString();
220 }
221
222 public static class CustomTokenizer {
223 private LinkedList linkedList = new LinkedList();
224
225 public CustomTokenizer(String input) {
226 parseInput(input, linkedList);
227 }
228
229 public void parseInput(String input, LinkedList linkedList) {
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247 List keywords = LoggingEventFieldResolver.KEYWORD_LIST;
248
249 keywords.remove("PROP.");
250 int pos = 0;
251 while (pos < input.length()) {
252 if (nextValueIs(input, pos, "'") || nextValueIs(input, pos, "\"")) {
253 pos = handleQuotedString(input, pos, linkedList);
254 }
255 if (nextValueIs(input, pos, "PROP.")) {
256 pos = handleProperty(input, pos, linkedList);
257 }
258 boolean operatorFound = false;
259 for (Iterator iter = operators.iterator();iter.hasNext();) {
260 String operator = (String)iter.next();
261 if (nextValueIs(input, pos, operator)) {
262 operatorFound = true;
263 pos = handle(pos, linkedList, operator);
264 }
265 }
266 boolean keywordFound = false;
267 for (Iterator iter = keywords.iterator();iter.hasNext();) {
268 String keyword = (String)iter.next();
269 if (nextValueIs(input, pos, keyword)) {
270 keywordFound = true;
271 pos = handle(pos, linkedList, keyword);
272 }
273 }
274 if (operatorFound || keywordFound) {
275 continue;
276 }
277 if (nextValueIs(input, pos, ")")) {
278 pos = handle(pos, linkedList, ")");
279 } else if (nextValueIs(input, pos, "(")) {
280 pos = handle(pos, linkedList, "(");
281 } else if (nextValueIs(input, pos, " ")) {
282 pos++;
283 } else {
284 pos = handleText(input, pos, linkedList);
285 }
286 }
287 }
288
289 private boolean nextValueIs(String input, int pos, String value) {
290 return (input.length() >= (pos + value.length())) && (input.substring(pos, pos + value.length()).equalsIgnoreCase(value));
291 }
292
293 private int handle(int pos, LinkedList linkedList, String value) {
294 linkedList.add(value);
295 return pos + value.length();
296 }
297
298 private int handleQuotedString(String input, int pos, LinkedList linkedList) {
299 String quoteChar = input.substring(pos, pos + 1);
300 int nextSingleQuotePos = input.indexOf(quoteChar, pos + 1);
301 if (nextSingleQuotePos < 0) {
302 throw new IllegalArgumentException("Missing an end quote");
303 }
304 String result = input.substring(pos, nextSingleQuotePos + 1);
305 linkedList.add(result);
306 return nextSingleQuotePos + 1;
307 }
308
309 private int handleText(String input, int pos, LinkedList linkedList) {
310 StringBuffer text = new StringBuffer("");
311 int newPos = pos;
312 while (newPos < input.length()) {
313 if (nextValueIs(input, newPos, " ")) {
314 linkedList.add(text);
315 return newPos;
316 }
317 if (nextValueIs(input, newPos, "(")) {
318 linkedList.add(text);
319 return newPos;
320 }
321 if (nextValueIs(input, newPos, ")")) {
322 linkedList.add(text);
323 return newPos;
324 }
325 for (Iterator iter = operators.iterator();iter.hasNext();) {
326 String operator = (String)iter.next();
327 if (nextValueIs(input, newPos, operator)) {
328 linkedList.add(text);
329 return newPos;
330 }
331 }
332 text.append(input.substring(newPos, ++newPos));
333 }
334
335 if (!text.toString().trim().equals("")) {
336 linkedList.add(text);
337 }
338 return newPos;
339 }
340
341 private int handleProperty(String input, int pos, LinkedList linkedList) {
342 int propertyPos = pos + "PROP.".length();
343 StringBuffer propertyName = new StringBuffer("PROP.");
344 while (propertyPos < input.length()) {
345 if (nextValueIs(input, propertyPos, " ")) {
346 linkedList.add(propertyName);
347 return propertyPos;
348 }
349 if (nextValueIs(input, propertyPos, "(")) {
350 linkedList.add(propertyName);
351 return propertyPos;
352 }
353 if (nextValueIs(input, propertyPos, ")")) {
354 linkedList.add(propertyName);
355 return propertyPos;
356 }
357 for (Iterator iter = operators.iterator();iter.hasNext();) {
358 String operator = (String)iter.next();
359 if (nextValueIs(input, propertyPos, operator)) {
360 linkedList.add(propertyName);
361 return propertyPos;
362 }
363 }
364 propertyName.append(input.substring(propertyPos, ++propertyPos));
365 }
366 linkedList.add(propertyName);
367 return propertyPos;
368 }
369
370 public boolean hasMoreTokens() {
371 return linkedList.size() > 0;
372 }
373
374 public String nextToken() {
375 return linkedList.remove().toString();
376 }
377 }
378 }