You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ta...@apache.org on 2014/09/26 21:52:17 UTC
[1/2] Create our own selector parser lib based on apollo-selector
Repository: qpid-jms
Updated Branches:
refs/heads/master 4c17e5e47 -> a5fe3018d
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XalanXPathEvaluator.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XalanXPathEvaluator.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XalanXPathEvaluator.java
new file mode 100644
index 0000000..b09961e
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XalanXPathEvaluator.java
@@ -0,0 +1,116 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+import org.apache.xpath.CachedXPathAPI;
+import org.apache.xpath.objects.XObject;
+import org.w3c.dom.Document;
+import org.w3c.dom.traversal.NodeIterator;
+import org.xml.sax.InputSource;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+
+public class XalanXPathEvaluator implements XPathExpression.XPathEvaluator {
+ public static final String DOCUMENT_BUILDER_FACTORY_FEATURE = "org.apache.activemq.apollo.documentBuilderFactory.feature";
+ private final String xpath;
+
+ public XalanXPathEvaluator(String xpath) {
+ this.xpath = xpath;
+ }
+
+ public boolean evaluate(Filterable m) throws FilterException {
+ String stringBody = m.getBodyAs(String.class);
+ if (stringBody!=null) {
+ return evaluate(stringBody);
+ }
+ return false;
+ }
+
+ protected boolean evaluate(String text) {
+ return evaluate(new InputSource(new StringReader(text)));
+ }
+
+ protected boolean evaluate(InputSource inputSource) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ setupFeatures(factory);
+ factory.setNamespaceAware(true);
+ factory.setIgnoringElementContentWhitespace(true);
+ factory.setIgnoringComments(true);
+ DocumentBuilder dbuilder = factory.newDocumentBuilder();
+ Document doc = dbuilder.parse(inputSource);
+
+ //An XPath expression could return a true or false value instead of a node.
+ //eval() is a better way to determine the boolean value of the exp.
+ //For compliance with legacy behavior where selecting an empty node returns true,
+ //selectNodeIterator is attempted in case of a failure.
+
+ CachedXPathAPI cachedXPathAPI = new CachedXPathAPI();
+ XObject result = cachedXPathAPI.eval(doc, xpath);
+ if (result.bool())
+ return true;
+ else {
+ NodeIterator iterator = cachedXPathAPI.selectNodeIterator(doc, xpath);
+ return (iterator.nextNode() != null);
+ }
+
+ } catch (Throwable e) {
+ return false;
+ }
+ }
+
+ protected void setupFeatures(DocumentBuilderFactory factory) {
+ Properties properties = System.getProperties();
+ List<String> features = new ArrayList<String>();
+ for (Map.Entry<Object, Object> prop : properties.entrySet()) {
+ String key = (String) prop.getKey();
+ if (key.startsWith(DOCUMENT_BUILDER_FACTORY_FEATURE)) {
+ String uri = key.split(DOCUMENT_BUILDER_FACTORY_FEATURE + ":")[1];
+ Boolean value = Boolean.valueOf((String)prop.getValue());
+ try {
+ factory.setFeature(uri, value);
+ features.add("feature " + uri + " value " + value);
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException("DocumentBuilderFactory doesn't support the feature " + uri + " with value " + value + ", due to " + e);
+ }
+ }
+ }
+ if (features.size() > 0) {
+ StringBuffer featureString = new StringBuffer();
+ // just log the configured feature
+ for (String feature : features) {
+ if (featureString.length() != 0) {
+ featureString.append(", ");
+ }
+ featureString.append(feature);
+ }
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/package.html
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/package.html b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/package.html
new file mode 100755
index 0000000..836ddd9
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/package.html
@@ -0,0 +1,27 @@
+<!--
+ 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.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+ Filter implementations for wildcards & JMS selectors
+</p>
+
+</body>
+</html>
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/javacc/HyphenatedParser.jj
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/javacc/HyphenatedParser.jj b/qpid-jms-selector/src/main/javacc/HyphenatedParser.jj
new file mode 100755
index 0000000..5966027
--- /dev/null
+++ b/qpid-jms-selector/src/main/javacc/HyphenatedParser.jj
@@ -0,0 +1,563 @@
+/**
+ * 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.
+ */
+
+// ----------------------------------------------------------------------------
+// OPTIONS
+// ----------------------------------------------------------------------------
+options {
+ STATIC = false;
+ UNICODE_INPUT = true;
+ // some performance optimizations
+ ERROR_REPORTING = false;
+}
+
+// ----------------------------------------------------------------------------
+// PARSER
+// ----------------------------------------------------------------------------
+
+PARSER_BEGIN(HyphenatedParser)
+/**
+ * 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.qpid.jms.selector.hyphenated;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.qpid.jms.selector.filter.*;
+
+/**
+ * JMS Selector Parser generated by JavaCC
+ *
+ * Do not edit this .java file directly - it is autogenerated from HyphenatedParser.jj
+ */
+public class HyphenatedParser {
+ private BooleanExpression asBooleanExpression(Expression value) throws ParseException {
+ if (value instanceof BooleanExpression) {
+ return (BooleanExpression) value;
+ }
+ if (value instanceof PropertyExpression) {
+ return UnaryExpression.createBooleanCast( value );
+ }
+ throw new ParseException("Expression will not result in a boolean value: " + value);
+ }
+}
+
+PARSER_END(HyphenatedParser)
+
+// ----------------------------------------------------------------------------
+// Tokens
+// ----------------------------------------------------------------------------
+
+/* White Space */
+SPECIAL_TOKEN :
+{
+ " " | "\t" | "\n" | "\r" | "\f"
+}
+
+/* Comments */
+SKIP:
+{
+ <LINE_COMMENT: "--" (~["\n","\r"])* ("\n"|"\r"|"\r\n") >
+}
+
+SKIP:
+{
+ <BLOCK_COMMENT: "/*" (~["*"])* "*" ("*" | (~["*","/"] (~["*"])* "*"))* "/">
+}
+
+/* Reserved Words */
+TOKEN [IGNORE_CASE] :
+{
+ < NOT : "NOT">
+ | < AND : "AND">
+ | < OR : "OR">
+ | < BETWEEN : "BETWEEN">
+ | < LIKE : "LIKE">
+ | < ESCAPE : "ESCAPE">
+ | < IN : "IN">
+ | < IS : "IS">
+ | < TRUE : "TRUE" >
+ | < FALSE : "FALSE" >
+ | < NULL : "NULL" >
+ | < XPATH : "XPATH" >
+ | < XQUERY : "XQUERY" >
+}
+
+/* Literals */
+TOKEN [IGNORE_CASE] :
+{
+
+ < DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])* (["l","L"])? >
+ | < HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+ >
+ | < OCTAL_LITERAL: "0" (["0"-"7"])* >
+ | < FLOATING_POINT_LITERAL:
+ (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? // matches: 5.5 or 5. or 5.5E10 or 5.E10
+ | "." (["0"-"9"])+ (<EXPONENT>)? // matches: .5 or .5E10
+ | (["0"-"9"])+ <EXPONENT> // matches: 5E10
+ >
+ | < #EXPONENT: "E" (["+","-"])? (["0"-"9"])+ >
+ | < STRING_LITERAL: "'" ( ("''") | ~["'"] )* "'" >
+}
+
+TOKEN [IGNORE_CASE] :
+{
+ < ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$", "-"])* >
+}
+
+// ----------------------------------------------------------------------------
+// Grammer
+// ----------------------------------------------------------------------------
+BooleanExpression JmsSelector() :
+{
+ Expression left=null;
+}
+{
+ (
+ left = orExpression()
+ )
+ {
+ return asBooleanExpression(left);
+ }
+
+}
+
+Expression orExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = andExpression()
+ (
+ <OR> right = andExpression()
+ {
+ left = LogicExpression.createOR(asBooleanExpression(left), asBooleanExpression(right));
+ }
+ )*
+ )
+ {
+ return left;
+ }
+
+}
+
+
+Expression andExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = equalityExpression()
+ (
+ <AND> right = equalityExpression()
+ {
+ left = LogicExpression.createAND(asBooleanExpression(left), asBooleanExpression(right));
+ }
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression equalityExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = comparisonExpression()
+ (
+
+ "=" right = comparisonExpression()
+ {
+ left = ComparisonExpression.createEqual(left, right);
+ }
+ |
+ "<>" right = comparisonExpression()
+ {
+ left = ComparisonExpression.createNotEqual(left, right);
+ }
+ |
+ LOOKAHEAD(2)
+ <IS> <NULL>
+ {
+ left = ComparisonExpression.createIsNull(left);
+ }
+ |
+ <IS> <NOT> <NULL>
+ {
+ left = ComparisonExpression.createIsNotNull(left);
+ }
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression comparisonExpression() :
+{
+ Expression left;
+ Expression right;
+ Expression low;
+ Expression high;
+ String t, u;
+ boolean not;
+ ArrayList list;
+}
+{
+ (
+ left = addExpression()
+ (
+
+ ">" right = addExpression()
+ {
+ left = ComparisonExpression.createGreaterThan(left, right);
+ }
+ |
+ ">=" right = addExpression()
+ {
+ left = ComparisonExpression.createGreaterThanEqual(left, right);
+ }
+ |
+ "<" right = addExpression()
+ {
+ left = ComparisonExpression.createLessThan(left, right);
+ }
+ |
+ "<=" right = addExpression()
+ {
+ left = ComparisonExpression.createLessThanEqual(left, right);
+ }
+ |
+ {
+ u=null;
+ }
+ <LIKE> t = stringLitteral()
+ [ <ESCAPE> u = stringLitteral() ]
+ {
+ left = ComparisonExpression.createLike(left, t, u);
+ }
+ |
+ LOOKAHEAD(2)
+ {
+ u=null;
+ }
+ <NOT> <LIKE> t = stringLitteral() [ <ESCAPE> u = stringLitteral() ]
+ {
+ left = ComparisonExpression.createNotLike(left, t, u);
+ }
+ |
+ <BETWEEN> low = addExpression() <AND> high = addExpression()
+ {
+ left = ComparisonExpression.createBetween(left, low, high);
+ }
+ |
+ LOOKAHEAD(2)
+ <NOT> <BETWEEN> low = addExpression() <AND> high = addExpression()
+ {
+ left = ComparisonExpression.createNotBetween(left, low, high);
+ }
+ |
+ <IN>
+ "("
+ t = stringLitteral()
+ {
+ list = new ArrayList();
+ list.add( t );
+ }
+ (
+ ","
+ t = stringLitteral()
+ {
+ list.add( t );
+ }
+
+ )*
+ ")"
+ {
+ left = ComparisonExpression.createInFilter(left, list);
+ }
+ |
+ LOOKAHEAD(2)
+ <NOT> <IN>
+ "("
+ t = stringLitteral()
+ {
+ list = new ArrayList();
+ list.add( t );
+ }
+ (
+ ","
+ t = stringLitteral()
+ {
+ list.add( t );
+ }
+
+ )*
+ ")"
+ {
+ left = ComparisonExpression.createNotInFilter(left, list);
+ }
+
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression addExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ left = multExpr()
+ (
+ LOOKAHEAD( ("+"|"-") multExpr())
+ (
+ "+" right = multExpr()
+ {
+ left = ArithmeticExpression.createPlus(left, right);
+ }
+ |
+ "-" right = multExpr()
+ {
+ left = ArithmeticExpression.createMinus(left, right);
+ }
+ )
+
+ )*
+ {
+ return left;
+ }
+}
+
+Expression multExpr() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ left = unaryExpr()
+ (
+ "*" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createMultiply(left, right);
+ }
+ |
+ "/" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createDivide(left, right);
+ }
+ |
+ "%" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createMod(left, right);
+ }
+
+ )*
+ {
+ return left;
+ }
+}
+
+
+Expression unaryExpr() :
+{
+ String s=null;
+ Expression left=null;
+}
+{
+ (
+ LOOKAHEAD( "+" unaryExpr() )
+ "+" left=unaryExpr()
+ |
+ "-" left=unaryExpr()
+ {
+ left = UnaryExpression.createNegate(left);
+ }
+ |
+ <NOT> left=unaryExpr()
+ {
+ left = UnaryExpression.createNOT( asBooleanExpression(left) );
+ }
+ |
+ <XPATH> s=stringLitteral()
+ {
+ left = UnaryExpression.createXPath( s );
+ }
+ |
+ <XQUERY> s=stringLitteral()
+ {
+ left = UnaryExpression.createXQuery( s );
+ }
+ |
+ left = primaryExpr()
+ )
+ {
+ return left;
+ }
+
+}
+
+Expression primaryExpr() :
+{
+ Expression left=null;
+}
+{
+ (
+ left = literal()
+ |
+ left = variable()
+ |
+ "(" left = orExpression() ")"
+ )
+ {
+ return left;
+ }
+}
+
+
+
+ConstantExpression literal() :
+{
+ Token t;
+ String s;
+ ConstantExpression left=null;
+}
+{
+ (
+ (
+ s = stringLitteral()
+ {
+ left = new ConstantExpression(s);
+ }
+ )
+ |
+ (
+ t = <DECIMAL_LITERAL>
+ {
+ left = ConstantExpression.createFromDecimal(t.image);
+ }
+ )
+ |
+ (
+ t = <HEX_LITERAL>
+ {
+ left = ConstantExpression.createFromHex(t.image);
+ }
+ )
+ |
+ (
+ t = <OCTAL_LITERAL>
+ {
+ left = ConstantExpression.createFromOctal(t.image);
+ }
+ )
+ |
+ (
+ t = <FLOATING_POINT_LITERAL>
+ {
+ left = ConstantExpression.createFloat(t.image);
+ }
+ )
+ |
+ (
+ <TRUE>
+ {
+ left = ConstantExpression.TRUE;
+ }
+ )
+ |
+ (
+ <FALSE>
+ {
+ left = ConstantExpression.FALSE;
+ }
+ )
+ |
+ (
+ <NULL>
+ {
+ left = ConstantExpression.NULL;
+ }
+ )
+ )
+ {
+ return left;
+ }
+}
+
+String stringLitteral() :
+{
+ Token t;
+ StringBuffer rc = new StringBuffer();
+ boolean first=true;
+}
+{
+ t = <STRING_LITERAL>
+ {
+ // Decode the sting value.
+ String image = t.image;
+ for( int i=1; i < image.length()-1; i++ ) {
+ char c = image.charAt(i);
+ if( c == '\'' )
+ i++;
+ rc.append(c);
+ }
+ return rc.toString();
+ }
+}
+
+PropertyExpression variable() :
+{
+ Token t;
+ PropertyExpression left=null;
+}
+{
+ (
+ t=<ID>
+ {
+ left = new PropertyExpression(t.image);
+ }
+ )
+ {
+ return left;
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/javacc/StrictParser.jj
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/javacc/StrictParser.jj b/qpid-jms-selector/src/main/javacc/StrictParser.jj
new file mode 100755
index 0000000..dfe8dab
--- /dev/null
+++ b/qpid-jms-selector/src/main/javacc/StrictParser.jj
@@ -0,0 +1,563 @@
+/**
+ * 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.
+ */
+
+// ----------------------------------------------------------------------------
+// OPTIONS
+// ----------------------------------------------------------------------------
+options {
+ STATIC = false;
+ UNICODE_INPUT = true;
+ // some performance optimizations
+ ERROR_REPORTING = false;
+}
+
+// ----------------------------------------------------------------------------
+// PARSER
+// ----------------------------------------------------------------------------
+
+PARSER_BEGIN(StrictParser)
+/**
+ * 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.qpid.jms.selector.strict;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.qpid.jms.selector.filter.*;
+
+/**
+ * JMS Selector Parser generated by JavaCC
+ *
+ * Do not edit this .java file directly - it is autogenerated from StrictParser.jj
+ */
+public class StrictParser {
+ private BooleanExpression asBooleanExpression(Expression value) throws ParseException {
+ if (value instanceof BooleanExpression) {
+ return (BooleanExpression) value;
+ }
+ if (value instanceof PropertyExpression) {
+ return UnaryExpression.createBooleanCast( value );
+ }
+ throw new ParseException("Expression will not result in a boolean value: " + value);
+ }
+}
+
+PARSER_END(StrictParser)
+
+// ----------------------------------------------------------------------------
+// Tokens
+// ----------------------------------------------------------------------------
+
+/* White Space */
+SPECIAL_TOKEN :
+{
+ " " | "\t" | "\n" | "\r" | "\f"
+}
+
+/* Comments */
+SKIP:
+{
+ <LINE_COMMENT: "--" (~["\n","\r"])* ("\n"|"\r"|"\r\n") >
+}
+
+SKIP:
+{
+ <BLOCK_COMMENT: "/*" (~["*"])* "*" ("*" | (~["*","/"] (~["*"])* "*"))* "/">
+}
+
+/* Reserved Words */
+TOKEN [IGNORE_CASE] :
+{
+ < NOT : "NOT">
+ | < AND : "AND">
+ | < OR : "OR">
+ | < BETWEEN : "BETWEEN">
+ | < LIKE : "LIKE">
+ | < ESCAPE : "ESCAPE">
+ | < IN : "IN">
+ | < IS : "IS">
+ | < TRUE : "TRUE" >
+ | < FALSE : "FALSE" >
+ | < NULL : "NULL" >
+ | < XPATH : "XPATH" >
+ | < XQUERY : "XQUERY" >
+}
+
+/* Literals */
+TOKEN [IGNORE_CASE] :
+{
+
+ < DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])* (["l","L"])? >
+ | < HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+ >
+ | < OCTAL_LITERAL: "0" (["0"-"7"])* >
+ | < FLOATING_POINT_LITERAL:
+ (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? // matches: 5.5 or 5. or 5.5E10 or 5.E10
+ | "." (["0"-"9"])+ (<EXPONENT>)? // matches: .5 or .5E10
+ | (["0"-"9"])+ <EXPONENT> // matches: 5E10
+ >
+ | < #EXPONENT: "E" (["+","-"])? (["0"-"9"])+ >
+ | < STRING_LITERAL: "'" ( ("''") | ~["'"] )* "'" >
+}
+
+TOKEN [IGNORE_CASE] :
+{
+ < ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$"])* >
+}
+
+// ----------------------------------------------------------------------------
+// Grammer
+// ----------------------------------------------------------------------------
+BooleanExpression JmsSelector() :
+{
+ Expression left=null;
+}
+{
+ (
+ left = orExpression()
+ )
+ {
+ return asBooleanExpression(left);
+ }
+
+}
+
+Expression orExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = andExpression()
+ (
+ <OR> right = andExpression()
+ {
+ left = LogicExpression.createOR(asBooleanExpression(left), asBooleanExpression(right));
+ }
+ )*
+ )
+ {
+ return left;
+ }
+
+}
+
+
+Expression andExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = equalityExpression()
+ (
+ <AND> right = equalityExpression()
+ {
+ left = LogicExpression.createAND(asBooleanExpression(left), asBooleanExpression(right));
+ }
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression equalityExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = comparisonExpression()
+ (
+
+ "=" right = comparisonExpression()
+ {
+ left = ComparisonExpression.createEqual(left, right);
+ }
+ |
+ "<>" right = comparisonExpression()
+ {
+ left = ComparisonExpression.createNotEqual(left, right);
+ }
+ |
+ LOOKAHEAD(2)
+ <IS> <NULL>
+ {
+ left = ComparisonExpression.createIsNull(left);
+ }
+ |
+ <IS> <NOT> <NULL>
+ {
+ left = ComparisonExpression.createIsNotNull(left);
+ }
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression comparisonExpression() :
+{
+ Expression left;
+ Expression right;
+ Expression low;
+ Expression high;
+ String t, u;
+ boolean not;
+ ArrayList list;
+}
+{
+ (
+ left = addExpression()
+ (
+
+ ">" right = addExpression()
+ {
+ left = ComparisonExpression.createGreaterThan(left, right);
+ }
+ |
+ ">=" right = addExpression()
+ {
+ left = ComparisonExpression.createGreaterThanEqual(left, right);
+ }
+ |
+ "<" right = addExpression()
+ {
+ left = ComparisonExpression.createLessThan(left, right);
+ }
+ |
+ "<=" right = addExpression()
+ {
+ left = ComparisonExpression.createLessThanEqual(left, right);
+ }
+ |
+ {
+ u=null;
+ }
+ <LIKE> t = stringLitteral()
+ [ <ESCAPE> u = stringLitteral() ]
+ {
+ left = ComparisonExpression.createLike(left, t, u);
+ }
+ |
+ LOOKAHEAD(2)
+ {
+ u=null;
+ }
+ <NOT> <LIKE> t = stringLitteral() [ <ESCAPE> u = stringLitteral() ]
+ {
+ left = ComparisonExpression.createNotLike(left, t, u);
+ }
+ |
+ <BETWEEN> low = addExpression() <AND> high = addExpression()
+ {
+ left = ComparisonExpression.createBetween(left, low, high);
+ }
+ |
+ LOOKAHEAD(2)
+ <NOT> <BETWEEN> low = addExpression() <AND> high = addExpression()
+ {
+ left = ComparisonExpression.createNotBetween(left, low, high);
+ }
+ |
+ <IN>
+ "("
+ t = stringLitteral()
+ {
+ list = new ArrayList();
+ list.add( t );
+ }
+ (
+ ","
+ t = stringLitteral()
+ {
+ list.add( t );
+ }
+
+ )*
+ ")"
+ {
+ left = ComparisonExpression.createInFilter(left, list);
+ }
+ |
+ LOOKAHEAD(2)
+ <NOT> <IN>
+ "("
+ t = stringLitteral()
+ {
+ list = new ArrayList();
+ list.add( t );
+ }
+ (
+ ","
+ t = stringLitteral()
+ {
+ list.add( t );
+ }
+
+ )*
+ ")"
+ {
+ left = ComparisonExpression.createNotInFilter(left, list);
+ }
+
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression addExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ left = multExpr()
+ (
+ LOOKAHEAD( ("+"|"-") multExpr())
+ (
+ "+" right = multExpr()
+ {
+ left = ArithmeticExpression.createPlus(left, right);
+ }
+ |
+ "-" right = multExpr()
+ {
+ left = ArithmeticExpression.createMinus(left, right);
+ }
+ )
+
+ )*
+ {
+ return left;
+ }
+}
+
+Expression multExpr() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ left = unaryExpr()
+ (
+ "*" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createMultiply(left, right);
+ }
+ |
+ "/" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createDivide(left, right);
+ }
+ |
+ "%" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createMod(left, right);
+ }
+
+ )*
+ {
+ return left;
+ }
+}
+
+
+Expression unaryExpr() :
+{
+ String s=null;
+ Expression left=null;
+}
+{
+ (
+ LOOKAHEAD( "+" unaryExpr() )
+ "+" left=unaryExpr()
+ |
+ "-" left=unaryExpr()
+ {
+ left = UnaryExpression.createNegate(left);
+ }
+ |
+ <NOT> left=unaryExpr()
+ {
+ left = UnaryExpression.createNOT( asBooleanExpression(left) );
+ }
+ |
+ <XPATH> s=stringLitteral()
+ {
+ left = UnaryExpression.createXPath( s );
+ }
+ |
+ <XQUERY> s=stringLitteral()
+ {
+ left = UnaryExpression.createXQuery( s );
+ }
+ |
+ left = primaryExpr()
+ )
+ {
+ return left;
+ }
+
+}
+
+Expression primaryExpr() :
+{
+ Expression left=null;
+}
+{
+ (
+ left = literal()
+ |
+ left = variable()
+ |
+ "(" left = orExpression() ")"
+ )
+ {
+ return left;
+ }
+}
+
+
+
+ConstantExpression literal() :
+{
+ Token t;
+ String s;
+ ConstantExpression left=null;
+}
+{
+ (
+ (
+ s = stringLitteral()
+ {
+ left = new ConstantExpression(s);
+ }
+ )
+ |
+ (
+ t = <DECIMAL_LITERAL>
+ {
+ left = ConstantExpression.createFromDecimal(t.image);
+ }
+ )
+ |
+ (
+ t = <HEX_LITERAL>
+ {
+ left = ConstantExpression.createFromHex(t.image);
+ }
+ )
+ |
+ (
+ t = <OCTAL_LITERAL>
+ {
+ left = ConstantExpression.createFromOctal(t.image);
+ }
+ )
+ |
+ (
+ t = <FLOATING_POINT_LITERAL>
+ {
+ left = ConstantExpression.createFloat(t.image);
+ }
+ )
+ |
+ (
+ <TRUE>
+ {
+ left = ConstantExpression.TRUE;
+ }
+ )
+ |
+ (
+ <FALSE>
+ {
+ left = ConstantExpression.FALSE;
+ }
+ )
+ |
+ (
+ <NULL>
+ {
+ left = ConstantExpression.NULL;
+ }
+ )
+ )
+ {
+ return left;
+ }
+}
+
+String stringLitteral() :
+{
+ Token t;
+ StringBuffer rc = new StringBuffer();
+ boolean first=true;
+}
+{
+ t = <STRING_LITERAL>
+ {
+ // Decode the sting value.
+ String image = t.image;
+ for( int i=1; i < image.length()-1; i++ ) {
+ char c = image.charAt(i);
+ if( c == '\'' )
+ i++;
+ rc.append(c);
+ }
+ return rc.toString();
+ }
+}
+
+PropertyExpression variable() :
+{
+ Token t;
+ PropertyExpression left=null;
+}
+{
+ (
+ t=<ID>
+ {
+ left = new PropertyExpression(t.image);
+ }
+ )
+ {
+ return left;
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/test/java/org/apache/activemq/apollo/selector/SelectorParserTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/test/java/org/apache/activemq/apollo/selector/SelectorParserTest.java b/qpid-jms-selector/src/test/java/org/apache/activemq/apollo/selector/SelectorParserTest.java
new file mode 100755
index 0000000..e48fa4e
--- /dev/null
+++ b/qpid-jms-selector/src/test/java/org/apache/activemq/apollo/selector/SelectorParserTest.java
@@ -0,0 +1,74 @@
+/**
+ * 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.activemq.apollo.selector;
+
+import junit.framework.TestCase;
+
+import org.apache.qpid.jms.selector.SelectorParser;
+import org.apache.qpid.jms.selector.filter.BooleanExpression;
+import org.apache.qpid.jms.selector.filter.ComparisonExpression;
+import org.apache.qpid.jms.selector.filter.Expression;
+import org.apache.qpid.jms.selector.filter.LogicExpression;
+import org.apache.qpid.jms.selector.filter.PropertyExpression;
+import org.apache.qpid.jms.selector.filter.XPathExpression;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @version $Revision: 1.2 $
+ */
+public class SelectorParserTest extends TestCase {
+ private static final Logger LOG = LoggerFactory.getLogger(SelectorParserTest.class);
+
+ public void testParseXPath() throws Exception {
+ BooleanExpression filter = parse("XPATH '//title[@lang=''eng'']'");
+ assertTrue("Created XPath expression", filter instanceof XPathExpression);
+ LOG.info("Expression: " + filter);
+ }
+
+ public void testParseWithParensAround() throws Exception {
+ String[] values = {"x = 1 and y = 2", "(x = 1) and (y = 2)", "((x = 1) and (y = 2))"};
+
+ for (int i = 0; i < values.length; i++) {
+ String value = values[i];
+ LOG.info("Parsing: " + value);
+
+ BooleanExpression andExpression = parse(value);
+ assertTrue("Created LogicExpression expression", andExpression instanceof LogicExpression);
+ LogicExpression logicExpression = (LogicExpression)andExpression;
+ Expression left = logicExpression.getLeft();
+ Expression right = logicExpression.getRight();
+
+ assertTrue("Left is a binary filter", left instanceof ComparisonExpression);
+ assertTrue("Right is a binary filter", right instanceof ComparisonExpression);
+ ComparisonExpression leftCompare = (ComparisonExpression)left;
+ ComparisonExpression rightCompare = (ComparisonExpression)right;
+ assertPropertyExpression("left", leftCompare.getLeft(), "x");
+ assertPropertyExpression("right", rightCompare.getLeft(), "y");
+ }
+ }
+
+ protected void assertPropertyExpression(String message, Expression expression, String expected) {
+ assertTrue(message + ". Must be PropertyExpression", expression instanceof PropertyExpression);
+ PropertyExpression propExp = (PropertyExpression)expression;
+ assertEquals(message + ". Property name", expected, propExp.getName());
+ }
+
+ protected BooleanExpression parse(String text) throws Exception {
+ return SelectorParser.parse(text);
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/test/java/org/apache/activemq/apollo/selector/SelectorTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/test/java/org/apache/activemq/apollo/selector/SelectorTest.java b/qpid-jms-selector/src/test/java/org/apache/activemq/apollo/selector/SelectorTest.java
new file mode 100755
index 0000000..398548f
--- /dev/null
+++ b/qpid-jms-selector/src/test/java/org/apache/activemq/apollo/selector/SelectorTest.java
@@ -0,0 +1,484 @@
+/**
+ * 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.activemq.apollo.selector;
+
+import java.util.HashMap;
+
+import junit.framework.TestCase;
+
+import org.apache.qpid.jms.selector.SelectorParser;
+import org.apache.qpid.jms.selector.filter.BooleanExpression;
+import org.apache.qpid.jms.selector.filter.FilterException;
+import org.apache.qpid.jms.selector.filter.Filterable;
+
+/**
+ * @version $Revision: 1.7 $
+ */
+@SuppressWarnings("unchecked")
+public class SelectorTest extends TestCase {
+
+ class MockMessage implements Filterable {
+
+ HashMap<String, Object> properties = new HashMap<String, Object>();
+ private String text;
+ private Object destination;
+ private String messageId;
+ private String type;
+ private Object localConnectionId;
+
+ public void setDestination(Object destination) {
+ this.destination=destination;
+ }
+ public void setJMSMessageID(String messageId) {
+ this.messageId = messageId;
+ }
+ public void setJMSType(String type) {
+ this.type = type;
+ }
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public void setBooleanProperty(String key, boolean value) {
+ properties.put(key, value);
+ }
+
+ public void setStringProperty(String key, String value) {
+ properties.put(key, value);
+ }
+
+ public void setByteProperty(String key, byte value) {
+ properties.put(key, value);
+ }
+
+ public void setDoubleProperty(String key, double value) {
+ properties.put(key, value);
+ }
+
+ public void setFloatProperty(String key, float value) {
+ properties.put(key, value);
+ }
+
+ public void setLongProperty(String key, long value) {
+ properties.put(key, value);
+ }
+
+ public void setIntProperty(String key, int value) {
+ properties.put(key, value);
+ }
+
+ public void setShortProperty(String key, short value) {
+ properties.put(key, value);
+ }
+
+ public void setObjectProperty(String key, Object value) {
+ properties.put(key, value);
+ }
+
+ public <T> T getBodyAs(Class<T> type) throws FilterException {
+ if( type == String.class ) {
+ return type.cast(text);
+ }
+ return null;
+ }
+
+ public Object getProperty(String name) {
+ if( "JMSType".equals(name) ) {
+ return type;
+ }
+ if( "JMSMessageID".equals(name) ) {
+ return messageId;
+ }
+ return properties.get(name);
+ }
+
+ public <T> T getDestination() {
+ return (T)destination;
+ }
+
+ public Object getLocalConnectionId() {
+ return localConnectionId;
+ }
+
+
+ }
+
+
+ public void testBooleanSelector() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "(trueProp OR falseProp) AND trueProp", true);
+ assertSelector(message, "(trueProp OR falseProp) AND falseProp", false);
+
+ }
+
+ public void testXPathSelectors() throws Exception {
+ MockMessage message = new MockMessage();
+
+ message.setJMSType("xml");
+ message.setText("<root><a key='first' num='1'/><b key='second' num='2'>b</b></root>");
+
+ assertSelector(message, "XPATH 'root/a'", true);
+ assertSelector(message, "XPATH '//root/b'", true);
+ assertSelector(message, "XPATH 'root/c'", false);
+ assertSelector(message, "XPATH '//root/b/text()=\"b\"'", true);
+ assertSelector(message, "XPATH '//root/b=\"b\"'", true);
+ assertSelector(message, "XPATH '//root/b=\"c\"'", false);
+ assertSelector(message, "XPATH '//root/b!=\"c\"'", true);
+
+ assertSelector(message, "XPATH '//root/*[@key=''second'']'", true);
+ assertSelector(message, "XPATH '//root/*[@key=''third'']'", false);
+ assertSelector(message, "XPATH '//root/a[@key=''first'']'", true);
+ assertSelector(message, "XPATH '//root/a[@num=1]'", true);
+ assertSelector(message, "XPATH '//root/a[@key=''second'']'", false);
+
+ assertSelector(message, "XPATH '/root/*[@key=''first'' or @key=''third'']'", true);
+ assertSelector(message, "XPATH '//root/*[@key=''third'' or @key=''forth'']'", false);
+
+ assertSelector(message, "XPATH '/root/b=''b'' and /root/b[@key=''second'']'", true);
+ assertSelector(message, "XPATH '/root/b=''b'' and /root/b[@key=''first'']'", false);
+
+ assertSelector(message, "XPATH 'not(//root/a)'", false);
+ assertSelector(message, "XPATH 'not(//root/c)'", true);
+ assertSelector(message, "XPATH '//root/a[not(@key=''first'')]'", false);
+ assertSelector(message, "XPATH '//root/a[not(not(@key=''first''))]'", true);
+
+ assertSelector(message, "XPATH 'string(//root/b)'", true);
+ assertSelector(message, "XPATH 'string(//root/a)'", false);
+
+ assertSelector(message, "XPATH 'sum(//@num) < 10'", true);
+ assertSelector(message, "XPATH 'sum(//@num) > 10'", false);
+
+ assertSelector(message, "XPATH '//root/a[@num > 1]'", false);
+ assertSelector(message, "XPATH '//root/b[@num > 1]'", true);
+
+ }
+
+ public void testJMSPropertySelectors() throws Exception {
+ MockMessage message = createMessage();
+ message.setJMSType("selector-test");
+ message.setJMSMessageID("id:test:1:1:1:1");
+
+ assertSelector(message, "JMSType = 'selector-test'", true);
+ assertSelector(message, "JMSType = 'crap'", false);
+
+ assertSelector(message, "JMSMessageID = 'id:test:1:1:1:1'", true);
+ assertSelector(message, "JMSMessageID = 'id:not-test:1:1:1:1'", false);
+
+ message = createMessage();
+ message.setJMSType("1001");
+
+ assertSelector(message, "JMSType='1001'", true);
+ assertSelector(message, "JMSType='1001' OR JMSType='1002'", true);
+ assertSelector(message, "JMSType = 'crap'", false);
+ }
+
+ public void testBasicSelectors() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "name = 'James'", true);
+ assertSelector(message, "rank > 100", true);
+ assertSelector(message, "rank >= 123", true);
+ assertSelector(message, "rank >= 124", false);
+
+ }
+
+ public void testPropertyTypes() throws Exception {
+ MockMessage message = createMessage();
+ assertSelector(message, "byteProp = 123", true);
+ assertSelector(message, "byteProp = 10", false);
+ assertSelector(message, "byteProp2 = 33", true);
+ assertSelector(message, "byteProp2 = 10", false);
+
+ assertSelector(message, "shortProp = 123", true);
+ assertSelector(message, "shortProp = 10", false);
+
+ assertSelector(message, "shortProp = 123", true);
+ assertSelector(message, "shortProp = 10", false);
+
+ assertSelector(message, "intProp = 123", true);
+ assertSelector(message, "intProp = 10", false);
+
+ assertSelector(message, "longProp = 123", true);
+ assertSelector(message, "longProp = 10", false);
+
+ assertSelector(message, "floatProp = 123", true);
+ assertSelector(message, "floatProp = 10", false);
+
+ assertSelector(message, "doubleProp = 123", true);
+ assertSelector(message, "doubleProp = 10", false);
+ }
+
+ public void testAndSelectors() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "name = 'James' and rank < 200", true);
+ assertSelector(message, "name = 'James' and rank > 200", false);
+ assertSelector(message, "name = 'Foo' and rank < 200", false);
+ assertSelector(message, "unknown = 'Foo' and anotherUnknown < 200", false);
+ }
+
+ public void testOrSelectors() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "name = 'James' or rank < 200", true);
+ assertSelector(message, "name = 'James' or rank > 200", true);
+ assertSelector(message, "name = 'Foo' or rank < 200", true);
+ assertSelector(message, "name = 'Foo' or rank > 200", false);
+ assertSelector(message, "unknown = 'Foo' or anotherUnknown < 200", false);
+ }
+
+ public void testPlus() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "rank + 2 = 125", true);
+ assertSelector(message, "(rank + 2) = 125", true);
+ assertSelector(message, "125 = (rank + 2)", true);
+ assertSelector(message, "rank + version = 125", true);
+ assertSelector(message, "rank + 2 < 124", false);
+ assertSelector(message, "name + '!' = 'James!'", true);
+ }
+
+ public void testMinus() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "rank - 2 = 121", true);
+ assertSelector(message, "rank - version = 121", true);
+ assertSelector(message, "rank - 2 > 122", false);
+ }
+
+ public void testMultiply() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "rank * 2 = 246", true);
+ assertSelector(message, "rank * version = 246", true);
+ assertSelector(message, "rank * 2 < 130", false);
+ }
+
+ public void testDivide() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "rank / version = 61.5", true);
+ assertSelector(message, "rank / 3 > 100.0", false);
+ assertSelector(message, "rank / 3 > 100", false);
+ assertSelector(message, "version / 2 = 1", true);
+
+ }
+
+ public void testBetween() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "rank between 100 and 150", true);
+ assertSelector(message, "rank between 10 and 120", false);
+ }
+
+ public void testIn() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "name in ('James', 'Bob', 'Gromit')", true);
+ assertSelector(message, "name in ('Bob', 'James', 'Gromit')", true);
+ assertSelector(message, "name in ('Gromit', 'Bob', 'James')", true);
+
+ assertSelector(message, "name in ('Gromit', 'Bob', 'Cheddar')", false);
+ assertSelector(message, "name not in ('Gromit', 'Bob', 'Cheddar')", true);
+ }
+
+ public void testIsNull() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "dummy is null", true);
+ assertSelector(message, "dummy is not null", false);
+ assertSelector(message, "name is not null", true);
+ assertSelector(message, "name is null", false);
+ }
+
+ public void testLike() throws Exception {
+ MockMessage message = createMessage();
+ message.setStringProperty("modelClassId", "com.whatever.something.foo.bar");
+ message.setStringProperty("modelInstanceId", "170");
+ message.setStringProperty("modelRequestError", "abc");
+ message.setStringProperty("modelCorrelatedClientId", "whatever");
+
+ assertSelector(message, "modelClassId LIKE 'com.whatever.something.%' AND modelInstanceId = '170' AND (modelRequestError IS NULL OR modelCorrelatedClientId = 'whatever')",
+ true);
+
+ message.setStringProperty("modelCorrelatedClientId", "shouldFailNow");
+
+ assertSelector(message, "modelClassId LIKE 'com.whatever.something.%' AND modelInstanceId = '170' AND (modelRequestError IS NULL OR modelCorrelatedClientId = 'whatever')",
+ false);
+
+ message = createMessage();
+ message.setStringProperty("modelClassId", "com.whatever.something.foo.bar");
+ message.setStringProperty("modelInstanceId", "170");
+ message.setStringProperty("modelCorrelatedClientId", "shouldNotMatch");
+
+ assertSelector(message, "modelClassId LIKE 'com.whatever.something.%' AND modelInstanceId = '170' AND (modelRequestError IS NULL OR modelCorrelatedClientId = 'whatever')",
+ true);
+ }
+
+ /**
+ * Test cases from Mats Henricson
+ */
+ public void testMatsHenricsonUseCases() throws Exception {
+ MockMessage message = createMessage();
+ assertSelector(message, "SessionserverId=1870414179", false);
+
+ message.setLongProperty("SessionserverId", 1870414179);
+ assertSelector(message, "SessionserverId=1870414179", true);
+
+ message.setLongProperty("SessionserverId", 1234);
+ assertSelector(message, "SessionserverId=1870414179", false);
+
+ assertSelector(message, "Command NOT IN ('MirrorLobbyRequest', 'MirrorLobbyReply')", false);
+
+ message.setStringProperty("Command", "Cheese");
+ assertSelector(message, "Command NOT IN ('MirrorLobbyRequest', 'MirrorLobbyReply')", true);
+
+ message.setStringProperty("Command", "MirrorLobbyRequest");
+ assertSelector(message, "Command NOT IN ('MirrorLobbyRequest', 'MirrorLobbyReply')", false);
+ message.setStringProperty("Command", "MirrorLobbyReply");
+ assertSelector(message, "Command NOT IN ('MirrorLobbyRequest', 'MirrorLobbyReply')", false);
+ }
+
+ public void testFloatComparisons() throws Exception {
+ MockMessage message = createMessage();
+
+ // JMS 1.1 Section 3.8.1.1 : Approximate literals use the Java
+ // floating-point literal syntax.
+ // We will use the java varible x to demo valid floating point syntaxs.
+ double x;
+
+ // test decimals like x.x
+ x = 1.0;
+ x = -1.1;
+ x = 1.0E1;
+ x = 1.1E1;
+ x = -1.1E1;
+ assertSelector(message, "1.0 < 1.1", true);
+ assertSelector(message, "-1.1 < 1.0", true);
+ assertSelector(message, "1.0E1 < 1.1E1", true);
+ assertSelector(message, "-1.1E1 < 1.0E1", true);
+
+ // test decimals like x.
+ x = 1.;
+ x = 1.E1;
+ assertSelector(message, "1. < 1.1", true);
+ assertSelector(message, "-1.1 < 1.", true);
+ assertSelector(message, "1.E1 < 1.1E1", true);
+ assertSelector(message, "-1.1E1 < 1.E1", true);
+
+ // test decimals like .x
+ x = .5;
+ x = -.5;
+ x = .5E1;
+ assertSelector(message, ".1 < .5", true);
+ assertSelector(message, "-.5 < .1", true);
+ assertSelector(message, ".1E1 < .5E1", true);
+ assertSelector(message, "-.5E1 < .1E1", true);
+
+ // test exponents
+ x = 4E10;
+ x = -4E10;
+ x = 5E+10;
+ x = 5E-10;
+ assertSelector(message, "4E10 < 5E10", true);
+ assertSelector(message, "5E8 < 5E10", true);
+ assertSelector(message, "-4E10 < 2E10", true);
+ assertSelector(message, "-5E8 < 2E2", true);
+ assertSelector(message, "4E+10 < 5E+10", true);
+ assertSelector(message, "4E-10 < 5E-10", true);
+ }
+
+ public void testStringQuoteParsing() throws Exception {
+ MockMessage message = createMessage();
+ assertSelector(message, "quote = '''In God We Trust'''", true);
+ }
+
+ public void testLikeComparisons() throws Exception {
+ MockMessage message = createMessage();
+
+ assertSelector(message, "quote LIKE '''In G_d We Trust'''", true);
+ assertSelector(message, "quote LIKE '''In Gd_ We Trust'''", false);
+ assertSelector(message, "quote NOT LIKE '''In G_d We Trust'''", false);
+ assertSelector(message, "quote NOT LIKE '''In Gd_ We Trust'''", true);
+
+ assertSelector(message, "foo LIKE '%oo'", true);
+ assertSelector(message, "foo LIKE '%ar'", false);
+ assertSelector(message, "foo NOT LIKE '%oo'", false);
+ assertSelector(message, "foo NOT LIKE '%ar'", true);
+
+ assertSelector(message, "foo LIKE '!_%' ESCAPE '!'", true);
+ assertSelector(message, "quote LIKE '!_%' ESCAPE '!'", false);
+ assertSelector(message, "foo NOT LIKE '!_%' ESCAPE '!'", false);
+ assertSelector(message, "quote NOT LIKE '!_%' ESCAPE '!'", true);
+
+ assertSelector(message, "punctuation LIKE '!#$&()*+,-./:;<=>?@[\\]^`{|}~'", true);
+ }
+
+ public void testInvalidSelector() throws Exception {
+ MockMessage message = createMessage();
+ assertInvalidSelector(message, "3+5");
+ assertInvalidSelector(message, "True AND 3+5");
+ assertInvalidSelector(message, "=TEST 'test'");
+ }
+
+ protected MockMessage createMessage() {
+ MockMessage message = createMessage("FOO.BAR");
+ message.setJMSType("selector-test");
+ message.setJMSMessageID("connection:1:1:1:1");
+ message.setObjectProperty("name", "James");
+ message.setObjectProperty("location", "London");
+
+ message.setByteProperty("byteProp", (byte)123);
+ message.setByteProperty("byteProp2", (byte)33);
+ message.setShortProperty("shortProp", (short)123);
+ message.setIntProperty("intProp", (int)123);
+ message.setLongProperty("longProp", (long)123);
+ message.setFloatProperty("floatProp", (float)123);
+ message.setDoubleProperty("doubleProp", (double)123);
+
+ message.setIntProperty("rank", 123);
+ message.setIntProperty("version", 2);
+ message.setStringProperty("quote", "'In God We Trust'");
+ message.setStringProperty("foo", "_foo");
+ message.setStringProperty("punctuation", "!#$&()*+,-./:;<=>?@[\\]^`{|}~");
+ message.setBooleanProperty("trueProp", true);
+ message.setBooleanProperty("falseProp", false);
+ return message;
+ }
+
+ protected void assertInvalidSelector(MockMessage message, String text) {
+ try {
+ SelectorParser.parse(text);
+ fail("Created a valid selector");
+ } catch (FilterException e) {
+ }
+ }
+
+ protected void assertSelector(MockMessage message, String text, boolean expected) throws FilterException {
+ BooleanExpression selector = SelectorParser.parse(text);
+ assertTrue("Created a valid selector", selector != null);
+ boolean value = selector.matches(message);
+ assertEquals("Selector for: " + text, expected, value);
+ }
+
+ protected MockMessage createMessage(String subject) {
+ MockMessage message = new MockMessage();
+ message.setDestination(subject);
+ return message;
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/test/resources/log4j.properties b/qpid-jms-selector/src/test/resources/log4j.properties
new file mode 100755
index 0000000..8df5a9e
--- /dev/null
+++ b/qpid-jms-selector/src/test/resources/log4j.properties
@@ -0,0 +1,38 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+#
+# The logging properties used during tests..
+#
+log4j.rootLogger=INFO, out, stdout
+
+log4j.logger.org.apache.qpid.jms=DEBUG
+
+# Tune the TestPeer as needed for debugging.
+log4j.logger.org.apache.qpid.jms.test.testpeer=TRACE
+
+# CONSOLE appender not used by default
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d [%-15.15t] - %-5p %-30.30c{1} - %m%n
+
+# File appender
+log4j.appender.out=org.apache.log4j.FileAppender
+log4j.appender.out.layout=org.apache.log4j.PatternLayout
+log4j.appender.out.layout.ConversionPattern=%d [%-15.15t] - %-5p %-30.30c{1} - %m%n
+log4j.appender.out.file=target/activemq-test.log
+log4j.appender.out.append=true
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[2/2] git commit: Create our own selector parser lib based on
apollo-selector
Posted by ta...@apache.org.
Create our own selector parser lib based on apollo-selector
Project: http://git-wip-us.apache.org/repos/asf/qpid-jms/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-jms/commit/a5fe3018
Tree: http://git-wip-us.apache.org/repos/asf/qpid-jms/tree/a5fe3018
Diff: http://git-wip-us.apache.org/repos/asf/qpid-jms/diff/a5fe3018
Branch: refs/heads/master
Commit: a5fe3018d12021f4b61cc63e791e70bf63910361
Parents: 4c17e5e
Author: Timothy Bish <ta...@gmail.com>
Authored: Fri Sep 26 15:52:05 2014 -0400
Committer: Timothy Bish <ta...@gmail.com>
Committed: Fri Sep 26 15:52:05 2014 -0400
----------------------------------------------------------------------
pom.xml | 31 +-
qpid-jms-client/pom.xml | 6 +-
.../java/org/apache/qpid/jms/JmsSession.java | 6 +-
.../qpid-jms-activemq-tests/pom.xml | 10 +-
qpid-jms-selector/pom.xml | 129 +++++
.../org/apache/qpid/jms/selector/LRUCache.java | 91 +++
.../qpid/jms/selector/SelectorParser.java | 100 ++++
.../selector/filter/ArithmeticExpression.java | 204 +++++++
.../jms/selector/filter/BinaryExpression.java | 96 ++++
.../jms/selector/filter/BooleanExpression.java | 36 ++
.../selector/filter/ComparisonExpression.java | 478 ++++++++++++++++
.../jms/selector/filter/ConstantExpression.java | 163 ++++++
.../qpid/jms/selector/filter/Expression.java | 34 ++
.../jms/selector/filter/FilterException.java | 40 ++
.../qpid/jms/selector/filter/Filterable.java | 53 ++
.../jms/selector/filter/LogicExpression.java | 88 +++
.../jms/selector/filter/PropertyExpression.java | 67 +++
.../jms/selector/filter/UnaryExpression.java | 256 +++++++++
.../jms/selector/filter/XPathExpression.java | 75 +++
.../jms/selector/filter/XQueryExpression.java | 49 ++
.../selector/filter/XalanXPathEvaluator.java | 116 ++++
.../qpid/jms/selector/filter/package.html | 27 +
.../src/main/javacc/HyphenatedParser.jj | 563 +++++++++++++++++++
.../src/main/javacc/StrictParser.jj | 563 +++++++++++++++++++
.../apollo/selector/SelectorParserTest.java | 74 +++
.../activemq/apollo/selector/SelectorTest.java | 484 ++++++++++++++++
.../src/test/resources/log4j.properties | 38 ++
27 files changed, 3859 insertions(+), 18 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 2035569..3918759 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,8 +14,7 @@
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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+--><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.apache</groupId>
<artifactId>apache</artifactId>
@@ -48,15 +47,16 @@
<slf4j-version>1.7.7</slf4j-version>
<hawtbuf-version>1.11</hawtbuf-version>
<activemq-version>5.11-SNAPSHOT</activemq-version>
- <apollo-version>1.7</apollo-version>
<proton-version>1.0-SNAPSHOT</proton-version>
<fuse-joram-tests-version>1.0</fuse-joram-tests-version>
<jetty-version>8.1.15.v20140411</jetty-version>
<vertex-version>2.0.2-final</vertex-version>
<mockito-version>1.9.5</mockito-version>
<hamcrest-version>1.3</hamcrest-version>
+ <xalan-version>2.6.0</xalan-version>
<!-- Maven Plugin Version for this Project -->
+ <maven-javacc-plugin-version>2.6</maven-javacc-plugin-version>
<maven-surefire-plugin-version>2.16</maven-surefire-plugin-version>
<maven-assembly-plugin-version>2.4</maven-assembly-plugin-version>
<maven-release-plugin-version>2.4.1</maven-release-plugin-version>
@@ -64,6 +64,7 @@
<maven-idea-plugin-version>2.5</maven-idea-plugin-version>
<maven-compiler-plugin-version>3.1</maven-compiler-plugin-version>
<findbugs-maven-plugin-version>3.0.0</findbugs-maven-plugin-version>
+ <build-helper-maven-plugin-version>1.8</build-helper-maven-plugin-version>
<!-- Test properties -->
<maven.test.redirectTestOutputToFile>true</maven.test.redirectTestOutputToFile>
@@ -90,6 +91,7 @@
<module>qpid-jms-discovery</module>
<module>qpid-jms-interop-tests</module>
<module>qpid-jms-examples</module>
+ <module>qpid-jms-selector</module>
</modules>
<dependencyManagement>
@@ -109,6 +111,11 @@
</dependency>
<dependency>
<groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-jms-selector</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.qpid</groupId>
<artifactId>proton-jms</artifactId>
<version>${proton-version}</version>
</dependency>
@@ -156,9 +163,9 @@
<version>${hawtbuf-version}</version>
</dependency>
<dependency>
- <groupId>org.apache.activemq</groupId>
- <artifactId>apollo-selector</artifactId>
- <version>${apollo-version}</version>
+ <groupId>xalan</groupId>
+ <artifactId>xalan</artifactId>
+ <version>${xalan-version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
@@ -217,6 +224,16 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin-version}</version>
</plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>javacc-maven-plugin</artifactId>
+ <version>${maven-javacc-plugin-version}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>${build-helper-maven-plugin-version}</version>
+ </plugin>
</plugins>
</pluginManagement>
<plugins>
@@ -234,4 +251,4 @@
</plugin>
</plugins>
</build>
-</project>
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-client/pom.xml
----------------------------------------------------------------------
diff --git a/qpid-jms-client/pom.xml b/qpid-jms-client/pom.xml
index 15820ce..85f5b99 100644
--- a/qpid-jms-client/pom.xml
+++ b/qpid-jms-client/pom.xml
@@ -30,7 +30,7 @@
<dependencies>
<!-- =================================== -->
- <!-- Required Dependencies -->
+ <!-- Client Dependencies -->
<!-- =================================== -->
<dependency>
<groupId>org.slf4j</groupId>
@@ -45,8 +45,8 @@
<artifactId>hawtbuf</artifactId>
</dependency>
<dependency>
- <groupId>org.apache.activemq</groupId>
- <artifactId>apollo-selector</artifactId>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-jms-selector</artifactId>
<optional>true</optional>
</dependency>
<dependency>
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
index 0c8343c..3dbbaf2 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java
@@ -59,8 +59,6 @@ import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
-import org.apache.activemq.apollo.filter.FilterException;
-import org.apache.activemq.apollo.selector.SelectorParser;
import org.apache.qpid.jms.message.JmsInboundMessageDispatch;
import org.apache.qpid.jms.message.JmsMessage;
import org.apache.qpid.jms.message.JmsMessageFactory;
@@ -71,8 +69,10 @@ import org.apache.qpid.jms.meta.JmsProducerId;
import org.apache.qpid.jms.meta.JmsSessionId;
import org.apache.qpid.jms.meta.JmsSessionInfo;
import org.apache.qpid.jms.provider.Provider;
-import org.apache.qpid.jms.provider.ProviderFuture;
import org.apache.qpid.jms.provider.ProviderConstants.ACK_TYPE;
+import org.apache.qpid.jms.provider.ProviderFuture;
+import org.apache.qpid.jms.selector.SelectorParser;
+import org.apache.qpid.jms.selector.filter.FilterException;
/**
* JMS Session implementation
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-interop-tests/qpid-jms-activemq-tests/pom.xml
----------------------------------------------------------------------
diff --git a/qpid-jms-interop-tests/qpid-jms-activemq-tests/pom.xml b/qpid-jms-interop-tests/qpid-jms-activemq-tests/pom.xml
index 0087cd0..afb51e3 100644
--- a/qpid-jms-interop-tests/qpid-jms-activemq-tests/pom.xml
+++ b/qpid-jms-interop-tests/qpid-jms-activemq-tests/pom.xml
@@ -43,16 +43,16 @@
<groupId>org.apache.qpid</groupId>
<artifactId>qpid-jms-discovery</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-jms-selector</artifactId>
+ <optional>true</optional>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
- <dependency>
- <groupId>org.apache.activemq</groupId>
- <artifactId>apollo-selector</artifactId>
- <optional>true</optional>
- </dependency>
<!-- =================================== -->
<!-- Testing Dependencies -->
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/pom.xml
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/pom.xml b/qpid-jms-selector/pom.xml
new file mode 100644
index 0000000..94b8b4d
--- /dev/null
+++ b/qpid-jms-selector/pom.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.qpid</groupId>
+ <artifactId>qpid-jms-parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+
+ <packaging>jar</packaging>
+ <artifactId>qpid-jms-selector</artifactId>
+ <name>QpidJMS Selector</name>
+ <description>The Qpid JMS optional message selector validation library</description>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>xalan</groupId>
+ <artifactId>xalan</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>javacc-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <configuration>
+ <sourceDirectory>${basedir}/src/main/javacc</sourceDirectory>
+ <outputDirectory>${basedir}/target/generated-sources/javacc</outputDirectory>
+ </configuration>
+ <goals>
+ <goal>javacc</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${basedir}/target/generated-sources/javacc</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence
+ on the Maven build itself. -->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>
+ javacc-maven-plugin
+ </artifactId>
+ <versionRange>[2.6,)</versionRange>
+ <goals>
+ <goal>javacc</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <execute>
+ <runOnIncremental>true</runOnIncremental>
+ </execute>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/LRUCache.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/LRUCache.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/LRUCache.java
new file mode 100755
index 0000000..6e9858d
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/LRUCache.java
@@ -0,0 +1,91 @@
+/**
+ * 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.qpid.jms.selector;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A Simple LRU Cache
+ *
+ * @param <K>
+ * @param <V>
+ */
+
+public class LRUCache<K, V> extends LinkedHashMap<K, V> {
+ private static final long serialVersionUID = -342098639681884413L;
+ protected int maxCacheSize = 10000;
+
+ /**
+ * Default constructor for an LRU Cache The default capacity is 10000
+ */
+ public LRUCache() {
+ this(0,10000, 0.75f, true);
+ }
+
+ /**
+ * Constructs a LRUCache with a maximum capacity
+ *
+ * @param maximumCacheSize
+ */
+ public LRUCache(int maximumCacheSize) {
+ this(0, maximumCacheSize, 0.75f, true);
+ }
+
+ /**
+ * Constructs an empty <tt>LRUCache</tt> instance with the specified
+ * initial capacity, maximumCacheSize,load factor and ordering mode.
+ *
+ * @param initialCapacity the initial capacity.
+ * @param maximumCacheSize
+ * @param loadFactor the load factor.
+ * @param accessOrder the ordering mode - <tt>true</tt> for access-order,
+ * <tt>false</tt> for insertion-order.
+ * @throws IllegalArgumentException if the initial capacity is negative or
+ * the load factor is non-positive.
+ */
+
+ public LRUCache(int initialCapacity, int maximumCacheSize, float loadFactor, boolean accessOrder) {
+ super(initialCapacity, loadFactor, accessOrder);
+ this.maxCacheSize = maximumCacheSize;
+ }
+
+ /**
+ * @return Returns the maxCacheSize.
+ */
+ public int getMaxCacheSize() {
+ return maxCacheSize;
+ }
+
+ /**
+ * @param maxCacheSize The maxCacheSize to set.
+ */
+ public void setMaxCacheSize(int maxCacheSize) {
+ this.maxCacheSize = maxCacheSize;
+ }
+
+ protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
+ if( size() > maxCacheSize ) {
+ onCacheEviction(eldest);
+ return true;
+ }
+ return false;
+ }
+
+ protected void onCacheEviction(Map.Entry<K,V> eldest) {
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/SelectorParser.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/SelectorParser.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/SelectorParser.java
new file mode 100644
index 0000000..9e68112
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/SelectorParser.java
@@ -0,0 +1,100 @@
+/**
+ * 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.qpid.jms.selector;
+
+import org.apache.qpid.jms.selector.filter.BooleanExpression;
+import org.apache.qpid.jms.selector.filter.ComparisonExpression;
+import org.apache.qpid.jms.selector.filter.FilterException;
+import org.apache.qpid.jms.selector.hyphenated.HyphenatedParser;
+import org.apache.qpid.jms.selector.strict.StrictParser;
+
+import java.io.StringReader;
+
+/**
+ */
+public class SelectorParser {
+
+ private static final LRUCache cache = new LRUCache(100);
+ private static final String CONVERT_STRING_EXPRESSIONS_PREFIX = "convert_string_expressions:";
+ private static final String HYPHENATED_PROPS_PREFIX = "hyphenated_props:";
+ private static final String NO_CONVERT_STRING_EXPRESSIONS_PREFIX = "no_convert_string_expressions:";
+ private static final String NO_HYPHENATED_PROPS_PREFIX = "no_hyphenated_props:";
+
+ public static BooleanExpression parse(String sql) throws FilterException {
+ Object result = cache.get(sql);
+ if (result instanceof FilterException) {
+ throw (FilterException) result;
+ } else if (result instanceof BooleanExpression) {
+ return (BooleanExpression) result;
+ } else {
+ String actual = sql;
+ boolean convertStringExpressions = false;
+ boolean hyphenatedProps=false;
+ while(true) {
+ if( actual.startsWith(CONVERT_STRING_EXPRESSIONS_PREFIX)) {
+ convertStringExpressions = true;
+ actual = actual.substring(CONVERT_STRING_EXPRESSIONS_PREFIX.length());
+ continue;
+ }
+ if( actual.startsWith(HYPHENATED_PROPS_PREFIX)) {
+ hyphenatedProps = true;
+ actual = actual.substring(HYPHENATED_PROPS_PREFIX.length());
+ continue;
+ }
+ if( actual.startsWith(NO_CONVERT_STRING_EXPRESSIONS_PREFIX)) {
+ convertStringExpressions = false;
+ actual = actual.substring(NO_CONVERT_STRING_EXPRESSIONS_PREFIX.length());
+ continue;
+ }
+ if( actual.startsWith(NO_HYPHENATED_PROPS_PREFIX)) {
+ hyphenatedProps = false;
+ actual = actual.substring(NO_HYPHENATED_PROPS_PREFIX.length());
+ continue;
+ }
+ break;
+ }
+
+ if( convertStringExpressions ) {
+ ComparisonExpression.CONVERT_STRING_EXPRESSIONS.set(true);
+ }
+ try {
+ BooleanExpression e = null;
+ if( hyphenatedProps ) {
+ HyphenatedParser parser = new HyphenatedParser(new StringReader(actual));
+ e = parser.JmsSelector();
+ } else {
+ StrictParser parser = new StrictParser(new StringReader(actual));
+ e = parser.JmsSelector();
+ }
+ cache.put(sql, e);
+ return e;
+ } catch (Throwable e) {
+ FilterException fe = new FilterException(actual, e);
+ cache.put(sql, fe);
+ throw fe;
+ } finally {
+ if( convertStringExpressions ) {
+ ComparisonExpression.CONVERT_STRING_EXPRESSIONS.remove();
+ }
+ }
+ }
+ }
+
+ public static void clearCache() {
+ cache.clear();
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ArithmeticExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ArithmeticExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ArithmeticExpression.java
new file mode 100755
index 0000000..a4700c6
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ArithmeticExpression.java
@@ -0,0 +1,204 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+
+/**
+ * An expression which performs an operation on two expression values
+ *
+ * @version $Revision: 1.2 $
+ */
+public abstract class ArithmeticExpression extends BinaryExpression {
+
+ protected static final int INTEGER = 1;
+ protected static final int LONG = 2;
+ protected static final int DOUBLE = 3;
+ boolean convertStringExpressions = false;
+
+ /**
+ * @param left
+ * @param right
+ */
+ public ArithmeticExpression(Expression left, Expression right) {
+ super(left, right);
+ convertStringExpressions = ComparisonExpression.CONVERT_STRING_EXPRESSIONS.get()!=null;
+ }
+
+ public static Expression createPlus(Expression left, Expression right) {
+ return new ArithmeticExpression(left, right) {
+ protected Object evaluate(Object lvalue, Object rvalue) {
+ if (lvalue instanceof String) {
+ String text = (String)lvalue;
+ String answer = text + rvalue;
+ return answer;
+ } else {
+ return plus(asNumber(lvalue), asNumber(rvalue));
+ }
+ }
+
+ public String getExpressionSymbol() {
+ return "+";
+ }
+ };
+ }
+
+ public static Expression createMinus(Expression left, Expression right) {
+ return new ArithmeticExpression(left, right) {
+ protected Object evaluate(Object lvalue, Object rvalue) {
+ return minus(asNumber(lvalue), asNumber(rvalue));
+ }
+
+ public String getExpressionSymbol() {
+ return "-";
+ }
+ };
+ }
+
+ public static Expression createMultiply(Expression left, Expression right) {
+ return new ArithmeticExpression(left, right) {
+
+ protected Object evaluate(Object lvalue, Object rvalue) {
+ return multiply(asNumber(lvalue), asNumber(rvalue));
+ }
+
+ public String getExpressionSymbol() {
+ return "*";
+ }
+ };
+ }
+
+ public static Expression createDivide(Expression left, Expression right) {
+ return new ArithmeticExpression(left, right) {
+
+ protected Object evaluate(Object lvalue, Object rvalue) {
+ return divide(asNumber(lvalue), asNumber(rvalue));
+ }
+
+ public String getExpressionSymbol() {
+ return "/";
+ }
+ };
+ }
+
+ public static Expression createMod(Expression left, Expression right) {
+ return new ArithmeticExpression(left, right) {
+
+ protected Object evaluate(Object lvalue, Object rvalue) {
+ return mod(asNumber(lvalue), asNumber(rvalue));
+ }
+
+ public String getExpressionSymbol() {
+ return "%";
+ }
+ };
+ }
+
+ protected Number plus(Number left, Number right) {
+ switch (numberType(left, right)) {
+ case INTEGER:
+ return new Integer(left.intValue() + right.intValue());
+ case LONG:
+ return new Long(left.longValue() + right.longValue());
+ default:
+ return new Double(left.doubleValue() + right.doubleValue());
+ }
+ }
+
+ protected Number minus(Number left, Number right) {
+ switch (numberType(left, right)) {
+ case INTEGER:
+ return new Integer(left.intValue() - right.intValue());
+ case LONG:
+ return new Long(left.longValue() - right.longValue());
+ default:
+ return new Double(left.doubleValue() - right.doubleValue());
+ }
+ }
+
+ protected Number multiply(Number left, Number right) {
+ switch (numberType(left, right)) {
+ case INTEGER:
+ return new Integer(left.intValue() * right.intValue());
+ case LONG:
+ return new Long(left.longValue() * right.longValue());
+ default:
+ return new Double(left.doubleValue() * right.doubleValue());
+ }
+ }
+
+ protected Number divide(Number left, Number right) {
+ return new Double(left.doubleValue() / right.doubleValue());
+ }
+
+ protected Number mod(Number left, Number right) {
+ return new Double(left.doubleValue() % right.doubleValue());
+ }
+
+ private int numberType(Number left, Number right) {
+ if (isDouble(left) || isDouble(right)) {
+ return DOUBLE;
+ } else if (left instanceof Long || right instanceof Long) {
+ return LONG;
+ } else {
+ return INTEGER;
+ }
+ }
+
+ private boolean isDouble(Number n) {
+ return n instanceof Float || n instanceof Double;
+ }
+
+ protected Number asNumber(Object value) {
+ if (value instanceof Number) {
+ return (Number)value;
+ } else {
+ if( convertStringExpressions && value instanceof String) {
+ String v = (String) value;
+ try {
+ if( v.contains(".") ) {
+ return new Double(v);
+ } else {
+ return new Long(v);
+ }
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("Cannot convert value: " + value + " into a number");
+ }
+ }
+ throw new RuntimeException("Cannot convert value: " + value + " into a number");
+ }
+ }
+
+ public Object evaluate(Filterable message) throws FilterException {
+ Object lvalue = left.evaluate(message);
+ if (lvalue == null) {
+ return null;
+ }
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null) {
+ return null;
+ }
+ return evaluate(lvalue, rvalue);
+ }
+
+ /**
+ * @param lvalue
+ * @param rvalue
+ * @return
+ */
+ protected abstract Object evaluate(Object lvalue, Object rvalue);
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/BinaryExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/BinaryExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/BinaryExpression.java
new file mode 100755
index 0000000..2bb1a95
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/BinaryExpression.java
@@ -0,0 +1,96 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+
+
+/**
+ * An expression which performs an operation on two expression values.
+ *
+ * @version $Revision: 1.2 $
+ */
+public abstract class BinaryExpression implements Expression {
+ protected Expression left;
+ protected Expression right;
+
+ public BinaryExpression(Expression left, Expression right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ public Expression getLeft() {
+ return left;
+ }
+
+ public Expression getRight() {
+ return right;
+ }
+
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "(" + left.toString() + " " + getExpressionSymbol() + " " + right.toString() + ")";
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o) {
+
+ if (o == null || !this.getClass().equals(o.getClass())) {
+ return false;
+ }
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Returns the symbol that represents this binary expression. For example, addition is
+ * represented by "+"
+ *
+ * @return
+ */
+ public abstract String getExpressionSymbol();
+
+ /**
+ * @param expression
+ */
+ public void setRight(Expression expression) {
+ right = expression;
+ }
+
+ /**
+ * @param expression
+ */
+ public void setLeft(Expression expression) {
+ left = expression;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/BooleanExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/BooleanExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/BooleanExpression.java
new file mode 100755
index 0000000..3225c56
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/BooleanExpression.java
@@ -0,0 +1,36 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+
+
+/**
+ * A BooleanExpression is an expression that always
+ * produces a Boolean result.
+ *
+ * @version $Revision: 1.2 $
+ */
+public interface BooleanExpression extends Expression {
+
+ /**
+ * @param message
+ * @return true if the expression evaluates to Boolean.TRUE.
+ * @throws FilterException
+ */
+ boolean matches(Filterable message) throws FilterException;
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ComparisonExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ComparisonExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ComparisonExpression.java
new file mode 100755
index 0000000..4179943
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ComparisonExpression.java
@@ -0,0 +1,478 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+
+/**
+ * A filter performing a comparison of two objects
+ *
+ * @version $Revision: 1.2 $
+ */
+public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression {
+
+ public static final ThreadLocal<Boolean> CONVERT_STRING_EXPRESSIONS = new ThreadLocal<Boolean>();
+
+ boolean convertStringExpressions = false;
+ private static final Set<Character> REGEXP_CONTROL_CHARS = new HashSet<Character>();
+
+ /**
+ * @param left
+ * @param right
+ */
+ public ComparisonExpression(Expression left, Expression right) {
+ super(left, right);
+ convertStringExpressions = CONVERT_STRING_EXPRESSIONS.get()!=null;
+ }
+
+ public static BooleanExpression createBetween(Expression value, Expression left, Expression right) {
+ return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
+ }
+
+ public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) {
+ return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right));
+ }
+
+ static {
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('.'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('\\'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('['));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf(']'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('^'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('$'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('?'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('*'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('+'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('{'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('}'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('|'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('('));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf(')'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf(':'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('&'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('<'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('>'));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('='));
+ REGEXP_CONTROL_CHARS.add(Character.valueOf('!'));
+ }
+
+ static class LikeExpression extends UnaryExpression implements BooleanExpression {
+
+ Pattern likePattern;
+
+ /**
+ */
+ public LikeExpression(Expression right, String like, int escape) {
+ super(right);
+
+ StringBuffer regexp = new StringBuffer(like.length() * 2);
+ regexp.append("\\A"); // The beginning of the input
+ for (int i = 0; i < like.length(); i++) {
+ char c = like.charAt(i);
+ if (escape == (0xFFFF & c)) {
+ i++;
+ if (i >= like.length()) {
+ // nothing left to escape...
+ break;
+ }
+
+ char t = like.charAt(i);
+ regexp.append("\\x");
+ regexp.append(Integer.toHexString(0xFFFF & t));
+ } else if (c == '%') {
+ regexp.append(".*?"); // Do a non-greedy match
+ } else if (c == '_') {
+ regexp.append("."); // match one
+ } else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) {
+ regexp.append("\\x");
+ regexp.append(Integer.toHexString(0xFFFF & c));
+ } else {
+ regexp.append(c);
+ }
+ }
+ regexp.append("\\z"); // The end of the input
+
+ likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL);
+ }
+
+ /**
+ * @see org.apache.activemq.filter.UnaryExpression#getExpressionSymbol()
+ */
+ public String getExpressionSymbol() {
+ return "LIKE";
+ }
+
+ /**
+ * @see org.apache.activemq.filter.Expression#evaluate(Filterable)
+ */
+ public Object evaluate(Filterable message) throws FilterException {
+
+ Object rv = this.getRight().evaluate(message);
+
+ if (rv == null) {
+ return null;
+ }
+
+ if (!(rv instanceof String)) {
+ return Boolean.FALSE;
+ // throw new RuntimeException("LIKE can only operate on String
+ // identifiers. LIKE attemped on: '" + rv.getClass());
+ }
+
+ return likePattern.matcher((String)rv).matches() ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public boolean matches(Filterable message) throws FilterException {
+ Object object = evaluate(message);
+ return object != null && object == Boolean.TRUE;
+ }
+ }
+
+ public static BooleanExpression createLike(Expression left, String right, String escape) {
+ if (escape != null && escape.length() != 1) {
+ throw new RuntimeException("The ESCAPE string litteral is invalid. It can only be one character. Litteral used: " + escape);
+ }
+ int c = -1;
+ if (escape != null) {
+ c = 0xFFFF & escape.charAt(0);
+ }
+
+ return new LikeExpression(left, right, c);
+ }
+
+ public static BooleanExpression createNotLike(Expression left, String right, String escape) {
+ return UnaryExpression.createNOT(createLike(left, right, escape));
+ }
+
+ public static BooleanExpression createInFilter(Expression left, List elements) {
+
+ if (!(left instanceof PropertyExpression)) {
+ throw new RuntimeException("Expected a property for In expression, got: " + left);
+ }
+ return UnaryExpression.createInExpression((PropertyExpression)left, elements, false);
+
+ }
+
+ public static BooleanExpression createNotInFilter(Expression left, List elements) {
+
+ if (!(left instanceof PropertyExpression)) {
+ throw new RuntimeException("Expected a property for In expression, got: " + left);
+ }
+ return UnaryExpression.createInExpression((PropertyExpression)left, elements, true);
+
+ }
+
+ public static BooleanExpression createIsNull(Expression left) {
+ return doCreateEqual(left, ConstantExpression.NULL);
+ }
+
+ public static BooleanExpression createIsNotNull(Expression left) {
+ return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL));
+ }
+
+ public static BooleanExpression createNotEqual(Expression left, Expression right) {
+ return UnaryExpression.createNOT(createEqual(left, right));
+ }
+
+ public static BooleanExpression createEqual(Expression left, Expression right) {
+ checkEqualOperand(left);
+ checkEqualOperand(right);
+ checkEqualOperandCompatability(left, right);
+ return doCreateEqual(left, right);
+ }
+
+ private static BooleanExpression doCreateEqual(Expression left, Expression right) {
+ return new ComparisonExpression(left, right) {
+
+ public Object evaluate(Filterable message) throws FilterException {
+ Object lv = left.evaluate(message);
+ Object rv = right.evaluate(message);
+
+ // Iff one of the values is null
+ if (lv == null ^ rv == null) {
+ return Boolean.FALSE;
+ }
+ if (lv == rv || lv.equals(rv)) {
+ return Boolean.TRUE;
+ }
+ if (lv instanceof Comparable && rv instanceof Comparable) {
+ return compare((Comparable)lv, (Comparable)rv);
+ }
+ return Boolean.FALSE;
+ }
+
+ protected boolean asBoolean(int answer) {
+ return answer == 0;
+ }
+
+ public String getExpressionSymbol() {
+ return "=";
+ }
+ };
+ }
+
+ public static BooleanExpression createGreaterThan(final Expression left, final Expression right) {
+ checkLessThanOperand(left);
+ checkLessThanOperand(right);
+ return new ComparisonExpression(left, right) {
+ protected boolean asBoolean(int answer) {
+ return answer > 0;
+ }
+
+ public String getExpressionSymbol() {
+ return ">";
+ }
+ };
+ }
+
+ public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) {
+ checkLessThanOperand(left);
+ checkLessThanOperand(right);
+ return new ComparisonExpression(left, right) {
+ protected boolean asBoolean(int answer) {
+ return answer >= 0;
+ }
+
+ public String getExpressionSymbol() {
+ return ">=";
+ }
+ };
+ }
+
+ public static BooleanExpression createLessThan(final Expression left, final Expression right) {
+ checkLessThanOperand(left);
+ checkLessThanOperand(right);
+ return new ComparisonExpression(left, right) {
+
+ protected boolean asBoolean(int answer) {
+ return answer < 0;
+ }
+
+ public String getExpressionSymbol() {
+ return "<";
+ }
+
+ };
+ }
+
+ public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) {
+ checkLessThanOperand(left);
+ checkLessThanOperand(right);
+ return new ComparisonExpression(left, right) {
+
+ protected boolean asBoolean(int answer) {
+ return answer <= 0;
+ }
+
+ public String getExpressionSymbol() {
+ return "<=";
+ }
+ };
+ }
+
+ /**
+ * Only Numeric expressions can be used in >, >=, < or <= expressions.s
+ *
+ * @param expr
+ */
+ public static void checkLessThanOperand(Expression expr) {
+ if (expr instanceof ConstantExpression) {
+ Object value = ((ConstantExpression)expr).getValue();
+ if (value instanceof Number) {
+ return;
+ }
+
+ // Else it's boolean or a String..
+ throw new RuntimeException("Value '" + expr + "' cannot be compared.");
+ }
+ if (expr instanceof BooleanExpression) {
+ throw new RuntimeException("Value '" + expr + "' cannot be compared.");
+ }
+ }
+
+ /**
+ * Validates that the expression can be used in == or <> expression. Cannot
+ * not be NULL TRUE or FALSE litterals.
+ *
+ * @param expr
+ */
+ public static void checkEqualOperand(Expression expr) {
+ if (expr instanceof ConstantExpression) {
+ Object value = ((ConstantExpression)expr).getValue();
+ if (value == null) {
+ throw new RuntimeException("'" + expr + "' cannot be compared.");
+ }
+ }
+ }
+
+ /**
+ * @param left
+ * @param right
+ */
+ private static void checkEqualOperandCompatability(Expression left, Expression right) {
+ if (left instanceof ConstantExpression && right instanceof ConstantExpression) {
+ if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) {
+ throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'");
+ }
+ }
+ }
+
+ public Object evaluate(Filterable message) throws FilterException {
+ Comparable<Comparable> lv = (Comparable)left.evaluate(message);
+ if (lv == null) {
+ return null;
+ }
+ Comparable rv = (Comparable)right.evaluate(message);
+ if (rv == null) {
+ return null;
+ }
+ return compare(lv, rv);
+ }
+
+ protected Boolean compare(Comparable lv, Comparable rv) {
+ Class<? extends Comparable> lc = lv.getClass();
+ Class<? extends Comparable> rc = rv.getClass();
+ // If the the objects are not of the same type,
+ // try to convert up to allow the comparison.
+ if (lc != rc) {
+ try {
+ if (lc == Boolean.class) {
+ if (convertStringExpressions && rc == String.class) {
+ rv = Boolean.valueOf((String)rv);
+ } else {
+ return Boolean.FALSE;
+ }
+ } else if (lc == Byte.class) {
+ if (rc == Short.class) {
+ lv = Short.valueOf(((Number)lv).shortValue());
+ } else if (rc == Integer.class) {
+ lv = Integer.valueOf(((Number)lv).intValue());
+ } else if (rc == Long.class) {
+ lv = Long.valueOf(((Number)lv).longValue());
+ } else if (rc == Float.class) {
+ lv = new Float(((Number)lv).floatValue());
+ } else if (rc == Double.class) {
+ lv = new Double(((Number)lv).doubleValue());
+ } else if (convertStringExpressions && rc == String.class) {
+ rv = Byte.valueOf((String)rv);
+ } else {
+ return Boolean.FALSE;
+ }
+ } else if (lc == Short.class) {
+ if (rc == Integer.class) {
+ lv = Integer.valueOf(((Number)lv).intValue());
+ } else if (rc == Long.class) {
+ lv = Long.valueOf(((Number)lv).longValue());
+ } else if (rc == Float.class) {
+ lv = new Float(((Number)lv).floatValue());
+ } else if (rc == Double.class) {
+ lv = new Double(((Number)lv).doubleValue());
+ } else if (convertStringExpressions && rc == String.class) {
+ rv = Short.valueOf((String)rv);
+ } else {
+ return Boolean.FALSE;
+ }
+ } else if (lc == Integer.class) {
+ if (rc == Long.class) {
+ lv = Long.valueOf(((Number)lv).longValue());
+ } else if (rc == Float.class) {
+ lv = new Float(((Number)lv).floatValue());
+ } else if (rc == Double.class) {
+ lv = new Double(((Number)lv).doubleValue());
+ } else if (convertStringExpressions && rc == String.class) {
+ rv = Integer.valueOf((String)rv);
+ } else {
+ return Boolean.FALSE;
+ }
+ } else if (lc == Long.class) {
+ if (rc == Integer.class) {
+ rv = Long.valueOf(((Number)rv).longValue());
+ } else if (rc == Float.class) {
+ lv = new Float(((Number)lv).floatValue());
+ } else if (rc == Double.class) {
+ lv = new Double(((Number)lv).doubleValue());
+ } else if (convertStringExpressions && rc == String.class) {
+ rv = Long.valueOf((String)rv);
+ } else {
+ return Boolean.FALSE;
+ }
+ } else if (lc == Float.class) {
+ if (rc == Integer.class) {
+ rv = new Float(((Number)rv).floatValue());
+ } else if (rc == Long.class) {
+ rv = new Float(((Number)rv).floatValue());
+ } else if (rc == Double.class) {
+ lv = new Double(((Number)lv).doubleValue());
+ } else if (convertStringExpressions && rc == String.class) {
+ rv = Float.valueOf((String)rv);
+ } else {
+ return Boolean.FALSE;
+ }
+ } else if (lc == Double.class) {
+ if (rc == Integer.class) {
+ rv = new Double(((Number)rv).doubleValue());
+ } else if (rc == Long.class) {
+ rv = new Double(((Number)rv).doubleValue());
+ } else if (rc == Float.class) {
+ rv = new Double(((Number)rv).doubleValue());
+ } else if (convertStringExpressions && rc == String.class) {
+ rv = Double.valueOf((String)rv);
+ } else {
+ return Boolean.FALSE;
+ }
+ } else if (convertStringExpressions && lc == String.class) {
+
+ if (rc == Boolean.class) {
+ lv = Boolean.valueOf((String)lv);
+ } else if (rc == Byte.class) {
+ lv = Byte.valueOf((String)lv);
+ } else if (rc == Short.class) {
+ lv = Short.valueOf((String)lv);
+ } else if (rc == Integer.class) {
+ lv = Integer.valueOf((String)lv);
+ } else if (rc == Long.class) {
+ lv = Long.valueOf((String)lv);
+ } else if (rc == Float.class) {
+ lv = Float.valueOf((String)lv);
+ } else if (rc == Double.class) {
+ lv = Double.valueOf((String)lv);
+ } else {
+ return Boolean.FALSE;
+ }
+
+ } else {
+ return Boolean.FALSE;
+ }
+ } catch (NumberFormatException e) {
+ return Boolean.FALSE;
+ }
+ }
+ return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ protected abstract boolean asBoolean(int answer);
+
+ public boolean matches(Filterable message) throws FilterException {
+ Object object = evaluate(message);
+ return object != null && object == Boolean.TRUE;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ConstantExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ConstantExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ConstantExpression.java
new file mode 100755
index 0000000..53d300d
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/ConstantExpression.java
@@ -0,0 +1,163 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+import java.math.BigDecimal;
+
+
+/**
+ * Represents a constant expression
+ *
+ * @version $Revision: 1.2 $
+ */
+public class ConstantExpression implements Expression {
+
+ static class BooleanConstantExpression extends ConstantExpression implements BooleanExpression {
+ public BooleanConstantExpression(Object value) {
+ super(value);
+ }
+
+ public boolean matches(Filterable message) throws FilterException {
+ Object object = evaluate(message);
+ return object != null && object == Boolean.TRUE;
+ }
+ }
+
+ public static final BooleanConstantExpression NULL = new BooleanConstantExpression(null);
+ public static final BooleanConstantExpression TRUE = new BooleanConstantExpression(Boolean.TRUE);
+ public static final BooleanConstantExpression FALSE = new BooleanConstantExpression(Boolean.FALSE);
+
+ private Object value;
+
+ public ConstantExpression(Object value) {
+ this.value = value;
+ }
+
+ public static ConstantExpression createFromDecimal(String text) {
+
+ // Strip off the 'l' or 'L' if needed.
+ if (text.endsWith("l") || text.endsWith("L")) {
+ text = text.substring(0, text.length() - 1);
+ }
+
+ Number value;
+ try {
+ value = new Long(text);
+ } catch (NumberFormatException e) {
+ // The number may be too big to fit in a long.
+ value = new BigDecimal(text);
+ }
+
+ long l = value.longValue();
+ if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE) {
+ value = Integer.valueOf(value.intValue());
+ }
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFromHex(String text) {
+ Number value = Long.valueOf(Long.parseLong(text.substring(2), 16));
+ long l = value.longValue();
+ if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE) {
+ value = Integer.valueOf(value.intValue());
+ }
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFromOctal(String text) {
+ Number value = Long.valueOf(Long.parseLong(text, 8));
+ long l = value.longValue();
+ if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE) {
+ value = Integer.valueOf(value.intValue());
+ }
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFloat(String text) {
+ Number value = new Double(text);
+ return new ConstantExpression(value);
+ }
+
+ public Object evaluate(Filterable message) throws FilterException {
+ return value;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ if (value == null) {
+ return "NULL";
+ }
+ if (value instanceof Boolean) {
+ return ((Boolean)value).booleanValue() ? "TRUE" : "FALSE";
+ }
+ if (value instanceof String) {
+ return encodeString((String)value);
+ }
+ return value.toString();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o) {
+
+ if (o == null || !this.getClass().equals(o.getClass())) {
+ return false;
+ }
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Encodes the value of string so that it looks like it would look like when
+ * it was provided in a selector.
+ *
+ * @param string
+ * @return
+ */
+ public static String encodeString(String s) {
+ StringBuffer b = new StringBuffer();
+ b.append('\'');
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == '\'') {
+ b.append(c);
+ }
+ b.append(c);
+ }
+ b.append('\'');
+ return b.toString();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/Expression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/Expression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/Expression.java
new file mode 100755
index 0000000..081eddf
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/Expression.java
@@ -0,0 +1,34 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+
+
+/**
+ * Represents an expression
+ *
+ * @version $Revision: 1.2 $
+ */
+public interface Expression {
+
+ /**
+ * @return the value of this expression
+ */
+ Object evaluate(Filterable message) throws FilterException;
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/FilterException.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/FilterException.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/FilterException.java
new file mode 100644
index 0000000..6c4830e
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/FilterException.java
@@ -0,0 +1,40 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+public class FilterException extends Exception {
+
+ private static final long serialVersionUID = -6892363158919485507L;
+
+ public FilterException() {
+ super();
+ }
+
+ public FilterException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FilterException(String message) {
+ super(message);
+ }
+
+ public FilterException(Throwable cause) {
+ super(cause);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/Filterable.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/Filterable.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/Filterable.java
new file mode 100755
index 0000000..106b0e9
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/Filterable.java
@@ -0,0 +1,53 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+/**
+ * A Filterable is the object being evaluated by the filters. It provides
+ * access to filtered properties.
+ *
+ * @version $Revision: 1.4 $
+ */
+public interface Filterable {
+
+ /**
+ * This method is used by message filters which do content based routing (Like the XPath
+ * based selectors).
+ *
+ * @param <T>
+ * @param type
+ * @return
+ * @throws FilterException
+ */
+ <T> T getBodyAs(Class<T> type) throws FilterException;
+
+ /**
+ * Extracts the named message property
+ *
+ * @param name
+ * @return
+ */
+ Object getProperty(String name);
+
+ /**
+ * Used by the NoLocal filter.
+ *
+ * @return a unique id for the connection that produced the message.
+ */
+ Object getLocalConnectionId();
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/LogicExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/LogicExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/LogicExpression.java
new file mode 100755
index 0000000..aabd35c
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/LogicExpression.java
@@ -0,0 +1,88 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+
+/**
+ * A filter performing a comparison of two objects
+ *
+ * @version $Revision: 1.2 $
+ */
+public abstract class LogicExpression extends BinaryExpression implements BooleanExpression {
+
+ /**
+ * @param left
+ * @param right
+ */
+ public LogicExpression(BooleanExpression left, BooleanExpression right) {
+ super(left, right);
+ }
+
+ public static BooleanExpression createOR(BooleanExpression lvalue, BooleanExpression rvalue) {
+ return new LogicExpression(lvalue, rvalue) {
+
+ public Object evaluate(Filterable message) throws FilterException {
+
+ Boolean lv = (Boolean)left.evaluate(message);
+ // Can we do an OR shortcut??
+ if (lv != null && lv.booleanValue()) {
+ return Boolean.TRUE;
+ }
+
+ Boolean rv = (Boolean)right.evaluate(message);
+ return rv == null ? null : rv;
+ }
+
+ public String getExpressionSymbol() {
+ return "OR";
+ }
+ };
+ }
+
+ public static BooleanExpression createAND(BooleanExpression lvalue, BooleanExpression rvalue) {
+ return new LogicExpression(lvalue, rvalue) {
+
+ public Object evaluate(Filterable message) throws FilterException {
+
+ Boolean lv = (Boolean)left.evaluate(message);
+
+ // Can we do an AND shortcut??
+ if (lv == null) {
+ return null;
+ }
+ if (!lv.booleanValue()) {
+ return Boolean.FALSE;
+ }
+
+ Boolean rv = (Boolean)right.evaluate(message);
+ return rv == null ? null : rv;
+ }
+
+ public String getExpressionSymbol() {
+ return "AND";
+ }
+ };
+ }
+
+ public abstract Object evaluate(Filterable message) throws FilterException;
+
+ public boolean matches(Filterable message) throws FilterException {
+ Object object = evaluate(message);
+ return object != null && object == Boolean.TRUE;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/PropertyExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/PropertyExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/PropertyExpression.java
new file mode 100755
index 0000000..767aed4
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/PropertyExpression.java
@@ -0,0 +1,67 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+
+/**
+ * Represents a property expression
+ *
+ * @version $Revision: 1.5 $
+ */
+public class PropertyExpression implements Expression {
+
+ private final String name;
+
+ public PropertyExpression(String name) {
+ this.name = name;
+ }
+
+ public Object evaluate(Filterable message) throws FilterException {
+ return message.getProperty(name);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o) {
+ if (o == null || !this.getClass().equals(o.getClass())) {
+ return false;
+ }
+ return name.equals(((PropertyExpression)o).name);
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/UnaryExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/UnaryExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/UnaryExpression.java
new file mode 100755
index 0000000..75433f2
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/UnaryExpression.java
@@ -0,0 +1,256 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * An expression which performs an operation on two expression values
+ *
+ * @version $Revision: 1.3 $
+ */
+public abstract class UnaryExpression implements Expression {
+
+ private static final BigDecimal BD_LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE);
+ protected Expression right;
+
+ public UnaryExpression(Expression left) {
+ this.right = left;
+ }
+
+ public static Expression createNegate(Expression left) {
+ return new UnaryExpression(left) {
+ public Object evaluate(Filterable message) throws FilterException {
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null) {
+ return null;
+ }
+ if (rvalue instanceof Number) {
+ return negate((Number)rvalue);
+ }
+ return null;
+ }
+
+ public String getExpressionSymbol() {
+ return "-";
+ }
+ };
+ }
+
+ public static BooleanExpression createInExpression(PropertyExpression right, List<Object> elements, final boolean not) {
+
+ // Use a HashSet if there are many elements.
+ Collection<Object> t;
+ if (elements.size() == 0) {
+ t = null;
+ } else if (elements.size() < 5) {
+ t = elements;
+ } else {
+ t = new HashSet<Object>(elements);
+ }
+ final Collection<Object> inList = t;
+
+ return new BooleanUnaryExpression(right) {
+ public Object evaluate(Filterable message) throws FilterException {
+
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null) {
+ return null;
+ }
+ if (rvalue.getClass() != String.class) {
+ return null;
+ }
+
+ if ((inList != null && inList.contains(rvalue)) ^ not) {
+ return Boolean.TRUE;
+ } else {
+ return Boolean.FALSE;
+ }
+
+ }
+
+ public String toString() {
+ StringBuffer answer = new StringBuffer();
+ answer.append(right);
+ answer.append(" ");
+ answer.append(getExpressionSymbol());
+ answer.append(" ( ");
+
+ int count = 0;
+ for (Iterator<Object> i = inList.iterator(); i.hasNext();) {
+ Object o = (Object)i.next();
+ if (count != 0) {
+ answer.append(", ");
+ }
+ answer.append(o);
+ count++;
+ }
+
+ answer.append(" )");
+ return answer.toString();
+ }
+
+ public String getExpressionSymbol() {
+ if (not) {
+ return "NOT IN";
+ } else {
+ return "IN";
+ }
+ }
+ };
+ }
+
+ abstract static class BooleanUnaryExpression extends UnaryExpression implements BooleanExpression {
+ public BooleanUnaryExpression(Expression left) {
+ super(left);
+ }
+
+ public boolean matches(Filterable message) throws FilterException {
+ Object object = evaluate(message);
+ return object != null && object == Boolean.TRUE;
+ }
+ };
+
+ public static BooleanExpression createNOT(BooleanExpression left) {
+ return new BooleanUnaryExpression(left) {
+ public Object evaluate(Filterable message) throws FilterException {
+ Boolean lvalue = (Boolean)right.evaluate(message);
+ if (lvalue == null) {
+ return null;
+ }
+ return lvalue.booleanValue() ? Boolean.FALSE : Boolean.TRUE;
+ }
+
+ public String getExpressionSymbol() {
+ return "NOT";
+ }
+ };
+ }
+
+ public static BooleanExpression createXPath(final String xpath) {
+ return new XPathExpression(xpath);
+ }
+
+ public static BooleanExpression createXQuery(final String xpath) {
+ return new XQueryExpression(xpath);
+ }
+
+ public static BooleanExpression createBooleanCast(Expression left) {
+ return new BooleanUnaryExpression(left) {
+ public Object evaluate(Filterable message) throws FilterException {
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null) {
+ return null;
+ }
+ if (!rvalue.getClass().equals(Boolean.class)) {
+ return Boolean.FALSE;
+ }
+ return ((Boolean)rvalue).booleanValue() ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public String toString() {
+ return right.toString();
+ }
+
+ public String getExpressionSymbol() {
+ return "";
+ }
+ };
+ }
+
+ private static Number negate(Number left) {
+ Class clazz = left.getClass();
+ if (clazz == Integer.class) {
+ return new Integer(-left.intValue());
+ } else if (clazz == Long.class) {
+ return new Long(-left.longValue());
+ } else if (clazz == Float.class) {
+ return new Float(-left.floatValue());
+ } else if (clazz == Double.class) {
+ return new Double(-left.doubleValue());
+ } else if (clazz == BigDecimal.class) {
+ // We ussually get a big deciamal when we have Long.MIN_VALUE
+ // constant in the
+ // Selector. Long.MIN_VALUE is too big to store in a Long as a
+ // positive so we store it
+ // as a Big decimal. But it gets Negated right away.. to here we try
+ // to covert it back
+ // to a Long.
+ BigDecimal bd = (BigDecimal)left;
+ bd = bd.negate();
+
+ if (BD_LONG_MIN_VALUE.compareTo(bd) == 0) {
+ return Long.valueOf(Long.MIN_VALUE);
+ }
+ return bd;
+ } else {
+ throw new RuntimeException("Don't know how to negate: " + left);
+ }
+ }
+
+ public Expression getRight() {
+ return right;
+ }
+
+ public void setRight(Expression expression) {
+ right = expression;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "(" + getExpressionSymbol() + " " + right.toString() + ")";
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o) {
+
+ if (o == null || !this.getClass().equals(o.getClass())) {
+ return false;
+ }
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Returns the symbol that represents this binary expression. For example,
+ * addition is represented by "+"
+ *
+ * @return
+ */
+ public abstract String getExpressionSymbol();
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XPathExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XPathExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XPathExpression.java
new file mode 100755
index 0000000..20c7a6d
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XPathExpression.java
@@ -0,0 +1,75 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+/**
+ * Used to evaluate an XPath Expression in a JMS selector.
+ */
+public final class XPathExpression implements BooleanExpression {
+
+ public static XPathEvaluatorFactory XPATH_EVALUATOR_FACTORY = null;
+ static {
+ // Install the xalan xpath evaluator if it available.
+ new XalanXPathEvaluator("//root").evaluate("<root></root>");
+ try {
+ XPATH_EVALUATOR_FACTORY = new XPathExpression.XPathEvaluatorFactory() {
+ public XPathExpression.XPathEvaluator create(String xpath) {
+ return new XalanXPathEvaluator(xpath);
+ }
+ };
+ } catch(Throwable e) {
+ }
+ }
+
+ private final String xpath;
+ private final XPathEvaluator evaluator;
+
+ public static interface XPathEvaluatorFactory {
+ XPathEvaluator create(String xpath);
+ }
+
+ public static interface XPathEvaluator {
+ boolean evaluate(Filterable message) throws FilterException;
+ }
+
+ XPathExpression(String xpath) {
+ if( XPATH_EVALUATOR_FACTORY == null ) {
+ throw new IllegalArgumentException("XPATH support not enabled.");
+ }
+ this.xpath = xpath;
+ this.evaluator = XPATH_EVALUATOR_FACTORY.create(xpath);
+ }
+
+ public Object evaluate(Filterable message) throws FilterException {
+ return evaluator.evaluate(message) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public String toString() {
+ return "XPATH " + ConstantExpression.encodeString(xpath);
+ }
+
+ /**
+ * @param message
+ * @return true if the expression evaluates to Boolean.TRUE.
+ * @throws FilterException
+ */
+ public boolean matches(Filterable message) throws FilterException {
+ Object object = evaluate(message);
+ return object != null && object == Boolean.TRUE;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/a5fe3018/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XQueryExpression.java
----------------------------------------------------------------------
diff --git a/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XQueryExpression.java b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XQueryExpression.java
new file mode 100755
index 0000000..114ada2
--- /dev/null
+++ b/qpid-jms-selector/src/main/java/org/apache/qpid/jms/selector/filter/XQueryExpression.java
@@ -0,0 +1,49 @@
+/**
+ * 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.qpid.jms.selector.filter;
+
+
+/**
+ * Used to evaluate an XQuery Expression in a JMS selector.
+ */
+public final class XQueryExpression implements BooleanExpression {
+ private final String xpath;
+
+ XQueryExpression(String xpath) {
+ super();
+ this.xpath = xpath;
+ }
+
+ public Object evaluate(Filterable message) throws FilterException {
+ return Boolean.FALSE;
+ }
+
+ public String toString() {
+ return "XQUERY " + ConstantExpression.encodeString(xpath);
+ }
+
+ /**
+ * @param message
+ * @return true if the expression evaluates to Boolean.TRUE.
+ * @throws FilterException
+ */
+ public boolean matches(Filterable message) throws FilterException {
+ Object object = evaluate(message);
+ return object != null && object == Boolean.TRUE;
+ }
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org