/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* This is based on the Java1.0.2.jj grammar */ options { MULTI=true; // JAVA_UNICODE_ESCAPE = true; // LOOKAHEAD = 1; FORCE_LA_CHECK = false; NODE_EXTENDS="MyNode"; VISITOR = true; STATIC = false; VISITOR_DATA_TYPE = "SelectorEvalState"; VISITOR_EXCEPTION = "SelectorEvaluationException"; /* DEBUG_PARSER = true ; DEBUG_LOOKAHEAD = true ; DEBUG_TOKEN_MANAGER = true ; */ } PARSER_BEGIN(SelectorParser) /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hedwig.jms.selector; import org.apache.hedwig.jms.SessionImpl; import org.apache.hedwig.jms.message.MessageImpl; import org.apache.hedwig.jms.message.TextMessageImpl; import java.io.StringReader; import java.util.Set; import java.util.HashSet; import java.util.Map; import java.util.LinkedHashMap; public class SelectorParser { // It contains no state (as of now). private static final InterpretSelectorParserVisitor interpreter = new InterpretSelectorParserVisitor(); private static final TreeDumperSelectorParserVisitor treeDumper = new TreeDumperSelectorParserVisitor(); public static Boolean evaluateSelector(final Node ast, final MessageImpl message) { if (MyNode.logger.isTraceEnabled()) { MyNode.logger.trace("--- Dump AST START ---"); try { ast.jjtAccept(treeDumper, new SelectorEvalState(null)); } catch (SelectorEvaluationException e) { MyNode.logger.trace("Unable to run debug visitor " + message + ", exception : " + e + " ... ignoring.", e); } MyNode.logger.trace("--- Dump AST DONE---"); } final SelectorEvalState data = new SelectorEvalState(message); try { ast.jjtAccept(interpreter, data); if (1 != data.getStack().size() || SelectorConstant.SelectorDataType.BOOLEAN != data.getStack().peek().type){ if (MyNode.logger.isDebugEnabled()) MyNode.logger.debug("Expected only a single boolean in stack, obtained : " + data.getStack()); return null; } return data.getStack().peek().getBoolValue(); } catch (SelectorEvaluationException e) { if (MyNode.logger.isDebugEnabled()) MyNode.logger.debug("Unable to run interpreter on " + message + ", exception : " + e + " ... ignoring message", e); return null; } } private static final int CACHED_AST_SIZE = Integer.getInteger("CACHED_AST_SIZE", 128); private static final LinkedHashMap parsedSelectorCache = new LinkedHashMap(CACHED_AST_SIZE, 0.75f, true){ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > CACHED_AST_SIZE; } }; public static Node parseMessageSelector(String messageSelector) throws ParseException { if (MyNode.logger.isTraceEnabled()) { MyNode.logger.trace("Parse '" + messageSelector + "'"); } synchronized (parsedSelectorCache){ if (parsedSelectorCache.containsKey(messageSelector)) return parsedSelectorCache.get(messageSelector); } Node retval = null; try { SelectorParser parser = new SelectorParser(new StringReader(messageSelector)); parser.Expression(); retval = parser.jjtree.rootNode(); return retval; } catch (TokenMgrError rmErr){ // It throws an error ! seriously ? ... sigh ! if (MyNode.logger.isDebugEnabled()) { MyNode.logger.debug("Unable to parse selector expression - recieved error ", rmErr); } throw new ParseException(rmErr.toString()); } finally { synchronized (parsedSelectorCache){ parsedSelectorCache.put(messageSelector, retval); } } } public static void main(String[] args) throws ParseException, SelectorEvaluationException { MessageImpl message = new TextMessageImpl(null, "test"); SelectorEvalState data = new SelectorEvalState(message); SelectorParserVisitor visitor = new InterpretSelectorParserVisitor(); for (String arg : args){ Node node = parseMessageSelector(arg); node.jjtAccept(visitor, data); if (1 != data.getStack().size()){ throw new IllegalArgumentException("Invalid proposition '" + arg + "'. Unexpected result stack : " + data.getStack()); } else System.out.println("Result : " + data.getStack().peek()); } } private String unescapeSingleQuotes(String str){ final int len = str.length(); final StringBuilder retval = new StringBuilder(); int offset = 0; while (true){ int indx = str.indexOf('\'', offset); if (-1 == indx) break; if (indx + 1 >= len) break; retval.append(str.substring(offset, indx + 1)); offset = indx + 1; if ('\'' == str.charAt(indx + 1)) offset ++; } if (offset < len) retval.append(str.substring(offset)); return retval.toString(); } public String parseString(final Token token, boolean canBeNull) throws ParseException{ if (canBeNull && null == token) return null; final String str = token.image; final int len = str.length(); if (len >= 2 && '\'' == str.charAt(0) && '\'' == str.charAt(len - 1)){ String tstr = str.substring(1, len - 1); tstr = unescapeSingleQuotes(tstr); return tstr; } if (!"''".equals(str)) throw new ParseException("Unexpected string : " + str); return ""; } public String parseString(final Token token) throws ParseException{ return parseString(token, false); } public String parseIdentifier(Token identifier) throws ParseException{ // nothing to parse actually ... final String identifierName = identifier.image; if ("null".equalsIgnoreCase(identifierName) || "true".equalsIgnoreCase(identifierName) || "false".equalsIgnoreCase(identifierName)){ throw new ParseException("Invalid identifier name : " + identifierName); } if ("NOT".equalsIgnoreCase(identifierName) || "AND".equalsIgnoreCase(identifierName) || "OR".equalsIgnoreCase(identifierName) || "BETWEEN".equalsIgnoreCase(identifierName) || "LIKE".equalsIgnoreCase(identifierName) || "IN".equalsIgnoreCase(identifierName) || "IS".equalsIgnoreCase(identifierName) || "ESCAPE".equalsIgnoreCase(identifierName)){ throw new ParseException("Invalid identifier name : " + identifierName); } return identifierName; } } PARSER_END(SelectorParser) SKIP : /* WHITE SPACE */ { " " | "\t" | "\n" | "\r" | "\f" } /* SKIP : { " " | "\t" | "\f" } */ // As per 3.8.1.3 Special Notes, I SHOULD NOT be supporting SQL comments ... but what the heck :-) /* COMMENTS */ /* SKIP: { | } */ TOKEN : /* SEPARATORS */ { < LPAREN: "(" > | < RPAREN: ")" > } TOKEN : /* OPERATORS */ { < EQ: "=" > | < GT: ">" > | < LT: "<" > | < LE: "<=" > | < GE: ">=" > | < NE: "<>" > | < PLUS: "+" > | < MINUS: "-" > | < STAR: "*" > | < SLASH: "/" > | < COMMA: "," > } TOKEN[IGNORE_CASE] : { < NULL : "NULL" > | < NOT : "NOT" > | < AND : "AND" > | < OR : "OR" > | < BETWEEN : "BETWEEN" > | < LIKE : "LIKE" > | < IN : "IN" > | < IS : "IS" > | < ESCAPE : "ESCAPE" > } TOKEN[IGNORE_CASE] : /* Boolean literal - case-insensitive */ { < BOOLEAN_LITERAL: "true" | "false" > } TOKEN : /* IDENTIFIER */ { < IDENTIFIER: (|)* > | < #LETTER: [ "\u0024", "\u0041"-"\u005a", "\u005f", "\u0061"-"\u007a", "\u00c0"-"\u00d6", "\u00d8"-"\u00f6", "\u00f8"-"\u00ff", "\u0100"-"\u1fff", "\u3040"-"\u318f", "\u3300"-"\u337f", "\u3400"-"\u3d2d", "\u4e00"-"\u9fff", "\uf900"-"\ufaff" ] > | < #DIGIT: [ "\u0030"-"\u0039", "\u0660"-"\u0669", "\u06f0"-"\u06f9", "\u0966"-"\u096f", "\u09e6"-"\u09ef", "\u0a66"-"\u0a6f", "\u0ae6"-"\u0aef", "\u0b66"-"\u0b6f", "\u0be7"-"\u0bef", "\u0c66"-"\u0c6f", "\u0ce6"-"\u0cef", "\u0d66"-"\u0d6f", "\u0e50"-"\u0e59", "\u0ed0"-"\u0ed9", "\u1040"-"\u1049" ] > } TOKEN : /* LITERALS */ { < FLOATING_POINT_LITERAL: // (["+","-"])? ( (["0"-"9"])+ "." (["0"-"9"])* ()? (["f","F","d","D"])? | "." (["0"-"9"])+ ()? (["f","F","d","D"])? | (["0"-"9"])+ (["f","F","d","D"])? | (["0"-"9"])+ ()? ["f","F","d","D"] ) > | < #EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ > | // Scrapping the earlier more complicated definition of string listern. // This is not required on second thoughts - a simpler definition will suffice. < STRING_LITERAL: "'" (~["'"])* "'" ("'" (~["'"])* "'")*> | < INTEGER_LITERAL: // (["+","-"])? ["0"-"9"] (["0"-"9"])* > } void Expression () #void : {} { ExpressionChoice() } void ExpressionChoice() #void : {} { LOOKAHEAD(OrLogicalTerm()) OrLogicalTerm() | AndLogicalTermChoice() } void OrLogicalTerm () #void : {} { AndLogicalTermChoice() ( ( ( AndLogicalTermChoice() ) { jjtThis.setExprFunction(LogicalComparisonFunction.OR_FUNCTION); } ) #OrExpr(2) ) + } void AndLogicalTermChoice() #void : {} { LOOKAHEAD(AndLogicalTerm()) AndLogicalTerm() | NotLogicalTermChoice() } void AndLogicalTerm () #void : {} { NotLogicalTermChoice() ( ( ( NotLogicalTermChoice() ) { jjtThis.setExprFunction(LogicalComparisonFunction.AND_FUNCTION); } ) #AndExpr(2) ) + } void NotLogicalTermChoice() #void : {} { LOOKAHEAD(NotLogicalTerm()) NotLogicalTerm() | RelationTermChoice() } void NotLogicalTerm () #void : {} { ( ( RelationTermChoice() ) { jjtThis.setExprFunction(UnaryExprFunction.NOT_FUNCTION); } ) #NotExpr(1) } void RelationTermChoice () #void : {} { LOOKAHEAD(RelationTermOptions()) RelationTermOptions() | ArithmeticTermChoice() } void RelationTermOptions() #void : {} { ArithmeticTermChoice() ( ( ( ArithmeticTermChoice() ) { jjtThis.setExprFunction(ValueComparisonFunction.GREATER_THAN_FUNCTION); } ) #GreaterThan(2) | ( ( ArithmeticTermChoice() ) { jjtThis.setExprFunction(ValueComparisonFunction.LESS_THAN_FUNCTION); } ) #LessThan(2) | ( ( ArithmeticTermChoice() ) { jjtThis.setExprFunction(ValueComparisonFunction.LESS_THAN_EQUAL_TO_FUNCTION); } ) #LessThanEqualTo(2) | ( ( ArithmeticTermChoice() ) { jjtThis.setExprFunction(ValueComparisonFunction.GREATER_THAN_EQUAL_TO_FUNCTION); } ) #GreaterThanEqualTo(2) | ( ( ArithmeticTermChoice() ) { jjtThis.setExprFunction(ValueComparisonFunction.EQUAL_TO_FUNCTION); } ) #EqualTo(2) | ( ( ArithmeticTermChoice() ) { jjtThis.setExprFunction(ValueComparisonFunction.NOT_EQUAL_TO_FUNCTION); } ) #NotEqualTo(2) | LOOKAHEAD(IsNullTerm()) // LOOKAHEAD(2) IsNullTerm() | LOOKAHEAD(BetweenTerm()) // LOOKAHEAD(2) BetweenTerm() | LOOKAHEAD(InTerm()) // LOOKAHEAD(2) InTerm() | LOOKAHEAD(LikeTerm()) // LOOKAHEAD(2) LikeTerm() ) } void IsNullTerm() #void : { Token notToken = null; } { ( ( ( notToken= )? ) { jjtThis.setExprFunction(new PropertyExprFunction.IsNullExpr(null != notToken)); } ) #IsNullExpr(1) } void BetweenTerm() #void : { Token notToken = null; } { ( ( ( notToken= )? ArithmeticTermChoice() ArithmeticTermChoice() ) { jjtThis.setExprFunction(new PropertyExprFunction.BetweenExpr(null != notToken)); } ) #BetweenExpr(3) } void InTerm() #void : { Token notToken = null; } { ( ( ( notToken= )? StringVarargParams() ) { jjtThis.setExprFunction(new PropertyExprFunction.InExpr(null != notToken)); } ) #InExpr(2) } void LikeTerm() #void : { Token notToken = null; Token likePattern = null; Token escapeCharacter = null; } { ( ( ( notToken= )? likePattern= ( escapeCharacter= )? ) { jjtThis.setExprFunction(new PropertyExprFunction.LikeExpr( parseString(likePattern), parseString(escapeCharacter, true), null != notToken)); } ) #LikeExpr(1) } void ArithmeticTermChoice () #void : {} { LOOKAHEAD(AddTerm()) AddTerm() | MultTermChoice() } void AddTerm () #void : {} { MultTermChoice() ( ( ( MultTermChoice() ) { jjtThis.setExprFunction(BinaryArithmeticFunction.ADD_FUNCTION); } ) #AddExpr(2) | ( ( MultTermChoice() ) { jjtThis.setExprFunction(BinaryArithmeticFunction.SUB_FUNCTION); } ) #SubExpr(2) ) + } void MultTermChoice() #void : {} { LOOKAHEAD(MultTerm()) MultTerm() | UnaryUnitChoice() } void MultTerm() #void : {} { UnaryUnitChoice() ( ( ( UnaryUnitChoice() ) { jjtThis.setExprFunction(BinaryArithmeticFunction.DIVIDE_FUNCTION); } ) #DivideExpr(2) | ( ( UnaryUnitChoice() ) { jjtThis.setExprFunction(BinaryArithmeticFunction.MULTIPLY_FUNCTION); } ) #MultiplyExpr(2) ) + } void UnaryUnitChoice() #void : {} { LOOKAHEAD(UnaryUnit()) UnaryUnit() | BasicUnit() } void UnaryUnit() #void : {} { LOOKAHEAD( BasicUnit() ) ( ( BasicUnit() ) { jjtThis.setExprFunction(UnaryArithmeticFunction.NEGATE_FUNCTION); } ) #NegateExpr(1) | // Ignore if + value. LOOKAHEAD( BasicUnit() ) ( ( BasicUnit() ) { // noop } ) } void BasicUnit() #void : { Token identifier = null; } { // An expression within braces for grouping // LOOKAHEAD( ExpressionChoice() ) ( ExpressionChoice() ) | // LOOKAHEAD(Constant()) Constant() | // LOOKAHEAD() ( ( identifier= ) { jjtThis.setExprFunction(new PropertyExprFunction.LookupExpr(parseIdentifier(identifier))); } ) #LookupExpr(0) } void Constant() : { Token bt, it, ft; Token st; } { bt= { jjtThis.setConstantValue(new SelectorConstant(Boolean.parseBoolean(bt.image.toLowerCase()))); } | ft= { jjtThis.setConstantValue(new SelectorConstant(Double.parseDouble(ft.image))); } | st= { jjtThis.setConstantValue(new SelectorConstant(parseString(st))); } | it= { jjtThis.setConstantValue(new SelectorConstant(Integer.parseInt(it.image))); } } void StringVarargParams() : { Token st; } { st= { Set set = new HashSet(4); set.add(parseString(st)); jjtThis.setConstantValue(new SelectorConstant(set)); } ( ( st= ) { jjtThis.addToStringSet(parseString(st)); } )* }