View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * &lt      LESS THAN operator
47   * &gt      GREATER THAN operator
48   * &lt=     LESS THAN EQUALS operator
49   * &gt=     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      //order multi-char operators before single-char operators (will use this order during parsing)
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      //boolean precedence
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         //recurse
166         postfix.append(infixToPostFix(tokenizer));
167         postfix.append(space);
168       } else if (")".equals(token)) {
169         //exit recursion level
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         //operator..
181         //peek the stack..if the top element has a lower precedence than token
182         //(peeked + has lower precedence than token *),
183         // push token onto the stack
184         //otherwise, pop top element off stack and add to postfix string
185         //in a loop until lower precedence or empty..then push token
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      Operators:
232          !
233          !=
234          ==
235          ~=
236          ||
237          &&
238          like
239          exists
240          <
241          <=
242          >
243          >=
244       */
245       //for operators, handle multi-char matches before single-char matches
246       //order does not matter for keywords
247       List keywords = LoggingEventFieldResolver.KEYWORD_LIST;
248       //remove PROP. from the keyword list...it is a keyword in that list, but is handled separately here from the other keywords since its name is not fixed
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      //don't add empty text entry (may be at end)
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 }