You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by je...@apache.org on 2018/06/28 15:45:15 UTC

[sling-org-apache-sling-resource-filter] 01/01: initial commit

This is an automated email from the ASF dual-hosted git repository.

jeb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resource-filter.git

commit 6c437e3f1f94ba3a02b445f71f8523bbdcd3e71d
Author: JE Bailey <je...@apache.org>
AuthorDate: Thu Jun 28 11:42:04 2018 -0400

    initial commit
---
 .gitignore                                         |    8 +
 README.md                                          |  118 ++
 pom.xml                                            |   81 +
 .../sling/resource/filter/ResourceFilter.java      |   63 +
 .../resource/filter/ResourceFilterStream.java      |   39 +
 .../sling/resource/filter/ResourceStream.java      |  106 ++
 .../apache/sling/resource/filter/api/Context.java  |   43 +
 .../resource/filter/api/ResourceFilterFactory.java |   22 +
 .../filter/api/ResourceFilterFunction.java         |   47 +
 .../apache/sling/resource/filter/api/Visitor.java  |   30 +
 .../filter/api/impl/ComparisonVisitor.java         |  147 ++
 .../resource/filter/api/impl/DefaultContext.java   |   87 +
 .../resource/filter/api/impl/LogicVisitor.java     |   86 +
 .../filter/api/impl/ResourceFactoryImpl.java       |   51 +
 .../sling/resource/filter/impl/FilterParser.java   |  493 ++++++
 .../filter/impl/FilterParserConstants.java         |  153 ++
 .../filter/impl/FilterParserTokenManager.java      | 1825 ++++++++++++++++++++
 .../sling/resource/filter/impl/ParseException.java |  178 ++
 .../resource/filter/impl/SimpleCharStream.java     |  416 +++++
 .../apache/sling/resource/filter/impl/Token.java   |  126 ++
 .../sling/resource/filter/impl/TokenMgrError.java  |  145 ++
 .../sling/resource/filter/impl/node/Node.java      |   97 ++
 .../predicates/ComparisonPredicateFactory.java     |   59 +
 .../impl/predicates/ComparisonPredicates.java      |  387 +++++
 .../resource/filter/impl/predicates/Null.java      |   69 +
 .../apache/sling/resource/filter/package-info.java |   15 +
 src/main/javacc/resourceFilter.jj                  |  338 ++++
 .../resource/filter/ResourceFilterArgTest.java     |   69 +
 .../resource/filter/ResourceFilterDateTest.java    |  101 ++
 .../resource/filter/ResourceFilterLogicTest.java   |   65 +
 .../sling/resource/filter/ResourceFilterTest.java  |  267 +++
 src/test/resources/data.json                       |  105 ++
 32 files changed, 5836 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b018844
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.project
+.classpath
+.settings
+target
+bin
+*.iml
+.idea
+.DS_Store
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8c09685
--- /dev/null
+++ b/README.md
@@ -0,0 +1,118 @@
+# Resource Stream Support
+
+* `ResourceStream` utility to provide a `Stream<Resource>` which traverses the subtree of a resource
+* Script support for creation of a complex `Predicate<Resource>` for use with Collections and Streams
+
+Example of a stream using the filter script
+
+```java
+new ResourceStream(resource)
+    .setBranchSelector("[jcr:primaryType] == 'cq:Page'")
+    .stream()
+    .filter(new ResourceFilter("[jcr:content/sling:resourceType] != 'apps/components/page/folder'"))
+    .collect(Collections.toList());
+```
+
+
+
+## ResourceFilter Script
+
+### Operators
+
+| Name       | Comparison Type | Description                                |
+| ---------  | --------------- | --------------------------------           |
+| and        | NA              | Logical AND                                |
+| &&         | NA              | Logical AND                                |
+| or         | NA              | Logical OR                                 |
+|&#124;&#124;| NA              | Logical OR                                 |
+| ==         | String          | Equal operator for Strings                 |
+| <          | Number         | Less than operator for Numbers             |
+| <=         | Number         | Less than or equal operator for Numbers    |
+| >          | Number         | Greater than operator for Numbers          |
+| >=         | Number         | Greater than or equal operator for Numbers |
+| !=         | String          | Is not equal to for Strings                |
+| less than  | Number         | less than operator for Numbers             |
+| greater than| Number        | greater than operator for Numbers          |
+| is          | String         | Equal operator for Strings                 |
+| is not      | String         | Is not equal operator for Strings          |
+| like        | String - Regex  | Regex match against String                |
+| like not    | String - Regex  | Regex match against String                |
+| contains         | String[] &#124; String[] | String[] contains all of items |
+| contains not     | String[] | String[] does not contain all of the items |
+| contains any     | String[] | String[] contains at least one of items |
+| contains not any | String[] | String[] does not contain any of the items |
+### Logical Operators
+The 'and' and 'or' operators are logical operators that string together conditions. 'And' operators take precedence. 'Or' operators evaluate from left to right
+
+
+### Values
+
+Values for comparison are obtained through multiple methods
+
+| Method       | Description                               |
+| ----------   | ----------------------------------------  |
+| Literal      | Single(') or double (") quoted text in the query will be interpreted as a String. Boolean values of *true* and *false* will be translated to a String. |
+| Property     | A String between square brackets '[',']'s will be interpreted as a property value and will be retrieved from the Resource using the get method |
+| Function     | A string followed by parens containing an optional comma separated list of values. |
+
+### Types
+All types are converted to either a String or a Number. For direct equivalence the comparison is done as a String. For relational comparisons the object will be adapted to a number.
+
+### Dates/Instants
+Dates are special, there are multiple ways to enter a date.
+
+In line, as part of the query, a date can be identified as a string that conforms to a standard ISO-8601 date time.
+
+> '2013-08-08T16:32:59.000'
+>
+> '2013-08-08T16:32:59'
+>
+> '2013-08-08T16:32'
+
+Are all valid date representations that are defaulting to the UTC timezone.
+
+For a ISO8601 date with timezone offset use the date function.
+
+> date('2013-08-08T16:32:59.000+02:00')
+
+If you need a different date format then the date function can accommodate that
+
+> date('2013-08-08','yyyy-MM-dd')
+
+Or you can add your own custom Function 
+
+Dates are transitionally represented as a java.util.Instant which is then converted to a String in ISO-8601 format or as a Long number based on the type of comparison. The number representing the time in milliseconds since the EPOCH UTC region
+
+### Functions
+
+Functions provide the ability to add additional functionality to the Filter language. A Function is written in the format
+
+> string '(' comma, separated, list() ')'
+
+All functions MUST return either a String, a Number, or an Instant. Strings are assumed to be using the default UTF encoding.
+
+OOTB Functions are:
+
+| Name  | Arguments | Returns | Description                                                    |
+| ----  | --------- | ------- | -----------------------------------                            |
+| name  | none      | String  | Provides the name of the resource                              |
+| date  | 0 - 2     | Instant | First argument is string representation of the date, second argument is a standard Java DateFormat representation of the value. No argument returns the current time. |
+| path  | none		| String  | path of the tested resource        |
+
+### Arguments
+The ResourceFilter can have key value pairs added so that the values may be used as part of the script resolution. Arguments are accessed by using the dollar sign '$'
+
+```java
+new ResourceFilter("[jcr:content/sling:resourceType] != $type").addArgument("type","apps/components/page/folder");
+```
+
+
+## Optimizing Traversals
+Similar to indexing in a query there are strategies that you can do within a tree traversal so that traversals can be done in an efficient manner across a large number of resources. The following strategies will assist in traversal optimization.
+
+### Limit traversal paths
+In a naive implementation of a tree traversal the traversal occurs across all nodes in the tree regardless of the ability of the tree structure to support the nodes that are being looked for. An example of this is a tree of Page resources that have have a child node of jcr:content which contains a subtree of data to define the page structure. If the jcr:content node is not capable of having a child resource of type Page and the goal of the traversal is to identify Page resources that mat [...]
+  
+### Limit memory consumption
+The instantiation of a Resource object from the underlying ResourceResolver is a non trivial consumption of memory. When the focus of a tree traversal is obtaining information from thousands of Resources, an effective method is to extract the information as part of the stream processing or utilizing the forEach method of the ResourceStream object which allows the resource to be garbage collected in an efficient manner. 
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d72fcdf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--Licensed 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/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.sling</groupId>
+		<artifactId>sling</artifactId>
+		<version>32</version>
+	</parent>
+
+	<artifactId>org.apache.sling.resource.filter</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>bundle</packaging>
+
+	<name>Apache Sling Resource Filter</name>
+
+	<properties>
+		<sling.java.version>8</sling.java.version>
+	</properties>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.sling</groupId>
+				<artifactId>maven-sling-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>javacc-maven-plugin</artifactId>
+				<version>2.6</version>
+				<executions>
+					<execution>
+						<id>javacc</id>
+						<goals>
+							<goal>javacc</goal>
+						</goals>
+						<configuration>
+							<jdkVersion>1.8</jdkVersion>
+							<javadocFriendlyComments>true</javadocFriendlyComments>
+							<outputDirectory>${basedir}/src/main/java</outputDirectory>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>3.4</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.testing.sling-mock</artifactId>
+			<version>2.2.10</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.api</artifactId>
+			<version>2.16.4</version>
+			<scope>provided</scope>
+		</dependency>
+	</dependencies>
+	<description>provides a set of utilities to create and handle streams</description>
+</project>
diff --git a/src/main/java/org/apache/sling/resource/filter/ResourceFilter.java b/src/main/java/org/apache/sling/resource/filter/ResourceFilter.java
new file mode 100644
index 0000000..a752032
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/ResourceFilter.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed 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.sling.resource.filter;
+
+import java.io.ByteArrayInputStream;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.api.Context;
+import org.apache.sling.resource.filter.api.impl.ComparisonVisitor;
+import org.apache.sling.resource.filter.api.impl.DefaultContext;
+import org.apache.sling.resource.filter.api.impl.LogicVisitor;
+import org.apache.sling.resource.filter.impl.FilterParser;
+import org.apache.sling.resource.filter.impl.ParseException;
+import org.apache.sling.resource.filter.impl.node.Node;
+
+public class ResourceFilter implements Predicate<Resource> {
+
+    private Predicate<Resource> parsedPredicate;
+
+    private Context context;
+
+    public ResourceFilter(String filter) throws ParseException {
+        Node rootNode = new FilterParser(new ByteArrayInputStream(filter.getBytes())).parse();
+        this.parsedPredicate = rootNode.accept(getContext().getLogicVisitor());
+    }
+
+    @Override
+    public boolean test(Resource resource) {
+        return parsedPredicate.test(resource);
+    }
+
+    public Context getContext() {
+        if (context == null) {
+            context = new DefaultContext();
+            new LogicVisitor(context);
+            new ComparisonVisitor(context);
+        }
+        return context;
+    }
+
+    public ResourceFilter setContext(Context context) {
+        this.context = context;
+        return this;
+    }
+
+    public ResourceFilter addArgument(String key, Object value) {
+        getContext().addArgument(key, value);
+        return this;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/ResourceFilterStream.java b/src/main/java/org/apache/sling/resource/filter/ResourceFilterStream.java
new file mode 100644
index 0000000..6f9ca80
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/ResourceFilterStream.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed 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.sling.resource.filter;
+
+import java.util.stream.Stream;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.impl.ParseException;
+
+public class ResourceFilterStream extends ResourceStream {
+
+    protected ResourceFilterStream(Resource resource) {
+        super(resource);
+    }
+
+    /**
+     * Predicate used to select child resources for traversal
+     * 
+     * @param branchSelector
+     *            resourceFilter script for traversal control
+     * @return ResourceStream
+     * @throws ParseException
+     */
+    public Stream<Resource> stream(String branchSelector) throws ParseException {
+        return stream(new ResourceFilter(branchSelector));
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/resource/filter/ResourceStream.java b/src/main/java/org/apache/sling/resource/filter/ResourceStream.java
new file mode 100644
index 0000000..db4d78a
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/ResourceStream.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed 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.sling.resource.filter;
+
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Utility to create a Stream<Resource> of Resource objects from a managed
+ * traversal of a Resource tree
+ *
+ */
+public class ResourceStream {
+
+    private Resource resource;
+
+    public ResourceStream(Resource resource) {
+        this.resource = resource;
+    }
+
+    /**
+     * Provides a depth first {@code Stream<Resource>} traversal of the resource
+     * tree starting with the current resource. The traversal is controlled by the
+     * provided predicate which determines if a given child is traversed. If no
+     * children matches the predicate, the traversal for that branch ends
+     * 
+     * @param branchSelector
+     *            used to determine whether a given child resource is traversed
+     * @return {@code Stream<Resource>} of unknown size.
+     */
+    public Stream<Resource> stream(Predicate<Resource> branchSelector) {
+
+        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<Resource>() {
+
+            private final Deque<Iterator<Resource>> resources = new LinkedList<>();
+            private Resource current = resource;
+
+            {
+                resources.push(resource.getChildren().iterator());
+            }
+
+            @Override
+            public boolean hasNext() {
+                if (current == null) {
+                    return seek();
+                }
+                return true;
+            }
+
+            @Override
+            public Resource next() {
+                if (current == null) {
+                    seek();
+                }
+                if (current == null) {
+                    throw new NoSuchElementException();
+                }
+                Resource next = current;
+                current = null;
+                return next;
+            }
+
+            private boolean seek() {
+                while (!resources.isEmpty()) {
+                    Iterator<Resource> iterator = resources.peek();
+                    if (iterator.hasNext()) {
+                        current = iterator.next();
+                        if (branchSelector.test(current)) {
+                            resources.push(current.getChildren().iterator());
+                        }
+                        return true;
+                    }
+                    resources.pop();
+                }
+                return false;
+            }
+
+        }, Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
+    }
+
+    public Stream<Resource> listChildren(Predicate<Resource> childSelector) {
+        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(resource.listChildren(),
+                Spliterator.ORDERED | Spliterator.IMMUTABLE), false).filter(childSelector);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/api/Context.java b/src/main/java/org/apache/sling/resource/filter/api/Context.java
new file mode 100644
index 0000000..4e90f2a
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/api/Context.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed 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.sling.resource.filter.api;
+
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+
+public interface Context {
+
+    Context addFunction(String name, BiFunction<Object[], Resource, Object> functionImpl);
+
+    Context removeFunction(String name);
+
+    Context addArgument(String name, Object object);
+
+    Visitor<Predicate<Resource>> getLogicVisitor();
+
+    Visitor<Function<Resource, Object>> getComparisonVisitor();
+
+    void setLogicVisitor(Visitor<Predicate<Resource>> logicVisitor);
+
+    void setComparionVisitor(Visitor<Function<Resource, Object>> comparisonVisitor);
+
+    Optional<BiFunction<Object[], Resource, Object>> getFunction(String text);
+
+    Optional<Object> getArgument(String text);
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/api/ResourceFilterFactory.java b/src/main/java/org/apache/sling/resource/filter/api/ResourceFilterFactory.java
new file mode 100644
index 0000000..c9e2fb0
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/api/ResourceFilterFactory.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed 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.sling.resource.filter.api;
+
+import org.apache.sling.resource.filter.ResourceFilter;
+
+public interface ResourceFilterFactory {
+
+    ResourceFilter getResourceFilter(String script);
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/api/ResourceFilterFunction.java b/src/main/java/org/apache/sling/resource/filter/api/ResourceFilterFunction.java
new file mode 100644
index 0000000..1633bc1
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/api/ResourceFilterFunction.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed 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.sling.resource.filter.api;
+
+import java.util.function.BiFunction;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * A CustomFilterFunction implementation is used to translate a command in a
+ * script or an Object that is a result of a custom function. Into a value that
+ * is used for Comparison
+ * 
+ */
+public interface ResourceFilterFunction extends BiFunction<Object[], Resource, Object> {
+
+    /**
+     * This method returns a {@code Object} to be used as part of a comparison.
+     * 
+     * @param arguments
+     *            A list of {@code Function}'s which provides the arguments defined
+     *            in the script, to obtain the arguments, each argument must be
+     *            called
+     * @return A {@code Object} which should be a String, Instant, or Number to be
+     *         used as part of a comparison or Function
+     */
+    Object apply(Object[] arguments, Resource resource);
+
+    /**
+     * Allows the name of the function to be defined
+     * 
+     * @return name to be used in the script
+     */
+    String getName();
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/api/Visitor.java b/src/main/java/org/apache/sling/resource/filter/api/Visitor.java
new file mode 100644
index 0000000..2fd5126
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/api/Visitor.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed 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.sling.resource.filter.api;
+
+import org.apache.sling.resource.filter.impl.node.Node;
+
+/**
+ * An interface for visiting AST nodes
+ *
+ * @param <R>
+ *            Return type of the visitor's method.
+ * @param <A>
+ *            Type of an optional parameter passed to the visitor's method.
+ */
+public interface Visitor<R> {
+
+    R visit(Node abstractNode);
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/api/impl/ComparisonVisitor.java b/src/main/java/org/apache/sling/resource/filter/api/impl/ComparisonVisitor.java
new file mode 100644
index 0000000..43f42a8
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/api/impl/ComparisonVisitor.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed 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.sling.resource.filter.api.impl;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.resource.filter.api.Context;
+import org.apache.sling.resource.filter.api.Visitor;
+import org.apache.sling.resource.filter.impl.FilterParserConstants;
+import org.apache.sling.resource.filter.impl.node.Node;
+import org.apache.sling.resource.filter.impl.predicates.Null;
+
+public class ComparisonVisitor implements Visitor<Function<Resource, Object>> {
+
+    private Context context;
+
+    public ComparisonVisitor(Context context) {
+        this.context = context;
+        context.setComparionVisitor(this);
+    }
+
+    @Override
+    public Function<Resource, Object> visit(Node node) {
+
+        switch (node.kind) {
+        case FilterParserConstants.FUNCTION_NAME:
+            // will only get here in the case of the 'FUNCTION' switch case
+            switch (node.text) {
+            case "name":
+                return Resource::getName;
+            case "path":
+                return Resource::getPath;
+            case "date":
+                return resource -> {
+                    Object[] arguments = node.visitChildren(this).stream().map(funct -> funct.apply(resource))
+                            .toArray();
+                    return dateHandler(arguments);
+                };
+            default:
+                Optional<BiFunction<Object[], Resource, Object>> temp = context.getFunction(node.text);
+                if (temp.isPresent()) {
+                    final List<Function<Resource, Object>> children2 = node.visitChildren(this);
+                    return resource -> {
+                        Object[] arguments = children2.stream().map(funct -> funct.apply(resource)).toArray();
+                        return temp.get().apply(arguments, resource);
+                    };
+                }
+            }
+            break;
+        case FilterParserConstants.NULL:
+            return resource -> new Null();
+        case FilterParserConstants.NUMBER:
+            Number numericValue = null;
+            String numberText = node.text;
+            try {
+                numericValue = Integer.valueOf(numberText);
+            } catch (NumberFormatException nfe1) {
+                try {
+                    numericValue = new BigDecimal(numberText);
+                } catch (NumberFormatException nfe2) {
+                    // swallow
+                }
+            }
+            final Number numericReply = numericValue;
+            return resource -> numericReply;
+        case FilterParserConstants.OFFSETDATETIME:
+            return resource -> OffsetDateTime.parse(node.text).toInstant();
+        case FilterParserConstants.DATETIME:
+            return resource -> LocalDateTime.parse(node.text).atOffset(ZoneOffset.UTC).toInstant();
+        case FilterParserConstants.DATE:
+            return resource -> LocalDate.parse(node.text).atStartOfDay(ZoneOffset.UTC).toInstant();
+        case FilterParserConstants.PROPERTY:
+            return resource -> {
+                Object value = valueMapOf(resource).get(node.text);
+                if (value instanceof Boolean) {
+                    return value.toString();
+                }
+                if (value instanceof Calendar) {
+                    return ((Calendar) value).toInstant();
+                }
+                return value;
+            };
+        case FilterParserConstants.DYNAMIC_ARG:
+            return resource -> {
+                String argument = node.text;
+                return context.getArgument(argument).orElse(new Null());
+            };
+        default:
+            return resource -> node.text;
+        }
+        return null;
+    }
+
+    private ValueMap valueMapOf(Resource resource) {
+        if (resource == null || ResourceUtil.isNonExistingResource(resource)) {
+            return ValueMap.EMPTY;
+        }
+        return resource.adaptTo(ValueMap.class);
+    }
+
+    private static Object dateHandler(Object[] arguments) {
+        if (arguments.length == 0) {
+            return Instant.now();
+        }
+        String dateString = arguments[0].toString();
+        String formatString = null;
+        if (arguments.length > 1) {
+            formatString = arguments[1].toString();
+            SimpleDateFormat dateFormat = new SimpleDateFormat(formatString);
+            try {
+                return Instant.ofEpochMilli(dateFormat.parse(dateString).getTime());
+            } catch (ParseException e) {
+                return null;
+            }
+        } else {
+            return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(dateString, OffsetDateTime::from).toInstant();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/api/impl/DefaultContext.java b/src/main/java/org/apache/sling/resource/filter/api/impl/DefaultContext.java
new file mode 100644
index 0000000..a263025
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/api/impl/DefaultContext.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed 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.sling.resource.filter.api.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.api.Context;
+import org.apache.sling.resource.filter.api.Visitor;
+
+public class DefaultContext implements Context {
+
+    private Map<String, BiFunction<Object[], Resource, Object>> functions = new HashMap<>();
+
+    private Map<String, Object> arguments = new HashMap<>();
+
+    private Visitor<Predicate<Resource>> logicVisitor;
+
+    private Visitor<Function<Resource, Object>> comparisonVisitor;
+
+    @Override
+    public Context addFunction(String name, BiFunction<Object[], Resource, Object> functionImpl) {
+        functions.put(name, functionImpl);
+        return this;
+    }
+
+    @Override
+    public Context removeFunction(String name) {
+        functions.remove(name);
+        return this;
+    }
+
+    @Override
+    public Context addArgument(String name, Object object) {
+        arguments.put(name, object);
+        return this;
+    }
+
+    @Override
+    public Visitor<Predicate<Resource>> getLogicVisitor() {
+        return logicVisitor;
+    }
+
+    @Override
+    public Visitor<Function<Resource, Object>> getComparisonVisitor() {
+        return comparisonVisitor;
+    }
+
+    @Override
+    public void setLogicVisitor(Visitor<Predicate<Resource>> logicVisitor) {
+        this.logicVisitor = logicVisitor;
+
+    }
+
+    @Override
+    public void setComparionVisitor(Visitor<Function<Resource, Object>> comparisonVisitor) {
+        this.comparisonVisitor = comparisonVisitor;
+
+    }
+
+    @Override
+    public Optional<BiFunction<Object[], Resource, Object>> getFunction(String text) {
+        return Optional.ofNullable(functions.get(text));
+    }
+
+    @Override
+    public Optional<Object> getArgument(String text) {
+        return Optional.ofNullable(arguments.get(text));
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/api/impl/LogicVisitor.java b/src/main/java/org/apache/sling/resource/filter/api/impl/LogicVisitor.java
new file mode 100644
index 0000000..319d94d
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/api/impl/LogicVisitor.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed 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.sling.resource.filter.api.impl;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.api.Context;
+import org.apache.sling.resource.filter.api.Visitor;
+import org.apache.sling.resource.filter.impl.FilterParserConstants;
+import org.apache.sling.resource.filter.impl.node.Node;
+import org.apache.sling.resource.filter.impl.predicates.ComparisonPredicateFactory;
+
+/**
+ * Visitor implementation that handles the high level handling of logic between
+ * statements that define the comparisons that would be performed.
+ * 
+ * In practical terms this handles the "and" and "or" predicates, if it
+ * encounters a COMPARISON node, it then hands off the internal ValueVisitor
+ * 
+ */
+public class LogicVisitor implements Visitor<Predicate<Resource>> {
+
+    private Context context;
+
+    public LogicVisitor(Context context) {
+        this.context = context;
+        context.setLogicVisitor(this);
+    }
+
+    @Override
+    public Predicate<Resource> visit(Node node) {
+        switch (node.kind) {
+        case FilterParserConstants.AND:
+            return createAndPredicate(node);
+        case FilterParserConstants.OR:
+            return createOrPredicate(node);
+        default:
+            return createComparisonPredicate(node);
+        }
+    }
+
+    private Predicate<Resource> createAndPredicate(Node node) {
+        return node.children.stream().map(this::visit).reduce(null, (predicate, accumulator) -> {
+            if (predicate == null) {
+                return accumulator;
+            }
+            return accumulator.and(predicate);
+        });
+    }
+
+    /**
+     * Returns a predicate which consists of a series of Or statements
+     * 
+     * @param node
+     * @param param
+     * @return
+     */
+    private Predicate<Resource> createOrPredicate(Node node) {
+        return node.children.stream().map(this::visit).reduce(null, (predicate, accumulator) -> {
+            if (predicate == null) {
+                return accumulator;
+            }
+            return accumulator.or(predicate);
+        });
+    }
+
+    private Predicate<Resource> createComparisonPredicate(Node comparisonNode) {
+        Function<Resource, Object> leftValue = comparisonNode.leftNode.accept(context.getComparisonVisitor());
+        Function<Resource, Object> rightValue = comparisonNode.rightNode.accept(context.getComparisonVisitor());
+        return ComparisonPredicateFactory.toPredicate(comparisonNode.kind, leftValue, rightValue);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/resource/filter/api/impl/ResourceFactoryImpl.java b/src/main/java/org/apache/sling/resource/filter/api/impl/ResourceFactoryImpl.java
new file mode 100644
index 0000000..5915b67
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/api/impl/ResourceFactoryImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed 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.sling.resource.filter.api.impl;
+
+import java.util.List;
+
+import org.apache.sling.resource.filter.ResourceFilter;
+import org.apache.sling.resource.filter.api.Context;
+import org.apache.sling.resource.filter.api.ResourceFilterFactory;
+import org.apache.sling.resource.filter.api.ResourceFilterFunction;
+import org.apache.sling.resource.filter.impl.ParseException;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(property = { "service.description=ResourceFilter Factory", "service.vendor=The Apache Software Foundation" })
+public class ResourceFactoryImpl implements ResourceFilterFactory {
+
+    @Reference(policyOption=ReferencePolicyOption.GREEDY)
+    List<ResourceFilterFunction> functions;
+
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public ResourceFilter getResourceFilter(String script) {
+        try {
+            ResourceFilter filter = new ResourceFilter(script);
+            Context context = filter.getContext();
+            for (ResourceFilterFunction func : functions) {
+                context.addArgument(func.getName(), func);
+            }
+        } catch (ParseException e) {
+            log.error(e.getLocalizedMessage());
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/FilterParser.java b/src/main/java/org/apache/sling/resource/filter/impl/FilterParser.java
new file mode 100644
index 0000000..86f8656
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/FilterParser.java
@@ -0,0 +1,493 @@
+/* Generated By:JavaCC: Do not edit this line. FilterParser.java */
+package org.apache.sling.resource.filter.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.sling.resource.filter.impl.node.*;
+
+public final class FilterParser implements FilterParserConstants {
+
+  final public Node parse() throws ParseException {
+  final Node node;
+    node = or();
+    jj_consume_token(0);
+    {if (true) return node;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node or() throws ParseException {
+  final List < Node > nodes = new ArrayList < Node > (3);
+  Node node;
+    node = and();
+    nodes.add(node);
+    label_1:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case OR:
+        ;
+        break;
+      default:
+        jj_la1[0] = jj_gen;
+        break label_1;
+      }
+      jj_consume_token(OR);
+      node = and();
+      nodes.add(node);
+    }
+    {if (true) return nodes.size() != 1 ? new Node(FilterParserConstants.OR, nodes) : nodes.get(0);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node and() throws ParseException {
+  final List < Node > nodes = new ArrayList < Node > (3);
+  Node node;
+    node = constraint();
+    nodes.add(node);
+    label_2:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case AND:
+        ;
+        break;
+      default:
+        jj_la1[1] = jj_gen;
+        break label_2;
+      }
+      jj_consume_token(AND);
+      node = constraint();
+      nodes.add(node);
+    }
+    {if (true) return nodes.size() != 1 ? new Node(FilterParserConstants.AND, nodes) : nodes.get(0);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node constraint() throws ParseException {
+  final Node node;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case LPAREN:
+      node = group();
+      break;
+    case OFFSETDATETIME:
+    case DATETIME:
+    case DATE:
+    case NUMBER:
+    case STRING:
+    case NULL:
+    case BOOLEAN:
+    case DYNAMIC_ARG:
+    case FUNCTION_NAME:
+    case PROPERTY:
+      node = comparison();
+      break;
+    default:
+      jj_la1[2] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    {if (true) return node;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node group() throws ParseException {
+  final Node node;
+    jj_consume_token(LPAREN);
+    node = or();
+    jj_consume_token(RPAREN);
+    {if (true) return node;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node comparison() throws ParseException {
+  Node leftValue;
+  Token op;
+  Node rightValue;
+    leftValue = argument();
+    op = comparisonValue();
+    rightValue = argument();
+    {if (true) return new Node(op.kind, op.image, leftValue, rightValue);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Token comparisonValue() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case EQUAL:
+      jj_consume_token(EQUAL);
+      break;
+    case NOT_EQUAL:
+      jj_consume_token(NOT_EQUAL);
+      break;
+    case GREATER_THAN:
+      jj_consume_token(GREATER_THAN);
+      break;
+    case GREATER_THAN_OR_EQUAL:
+      jj_consume_token(GREATER_THAN_OR_EQUAL);
+      break;
+    case LESS_THAN:
+      jj_consume_token(LESS_THAN);
+      break;
+    case LESS_THAN_OR_EQUAL:
+      jj_consume_token(LESS_THAN_OR_EQUAL);
+      break;
+    case LIKE:
+      jj_consume_token(LIKE);
+      break;
+    case LIKE_NOT:
+      jj_consume_token(LIKE_NOT);
+      break;
+    case CONTAINS:
+      jj_consume_token(CONTAINS);
+      break;
+    case CONTAINS_NOT:
+      jj_consume_token(CONTAINS_NOT);
+      break;
+    case CONTAINS_ANY:
+      jj_consume_token(CONTAINS_ANY);
+      break;
+    case CONTAINS_NOT_ANY:
+      jj_consume_token(CONTAINS_NOT_ANY);
+      break;
+    case IN:
+      jj_consume_token(IN);
+      break;
+    case NOT_IN:
+      jj_consume_token(NOT_IN);
+      break;
+    default:
+      jj_la1[3] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    {if (true) return token;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public List < Node > Arguments() throws ParseException {
+  Object value = new ArrayList();
+    jj_consume_token(LPAREN);
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case OFFSETDATETIME:
+    case DATETIME:
+    case DATE:
+    case NUMBER:
+    case STRING:
+    case NULL:
+    case BOOLEAN:
+    case DYNAMIC_ARG:
+    case FUNCTION_NAME:
+    case PROPERTY:
+      value = commaSepArguments();
+      break;
+    default:
+      jj_la1[4] = jj_gen;
+      ;
+    }
+    jj_consume_token(RPAREN);
+    {if (true) return (List) value;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public List < Node > commaSepArguments() throws ParseException {
+  final List < Node > list = new ArrayList < Node > (3);
+  Node arg;
+    arg = argument();
+    list.add(arg);
+    label_3:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case COMMA:
+        ;
+        break;
+      default:
+        jj_la1[5] = jj_gen;
+        break label_3;
+      }
+      jj_consume_token(COMMA);
+      arg = argument();
+      list.add(arg);
+    }
+    {if (true) return list;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node argument() throws ParseException {
+  Node selector = null;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case OFFSETDATETIME:
+    case DATETIME:
+    case DATE:
+    case NUMBER:
+    case STRING:
+    case NULL:
+    case BOOLEAN:
+      selector = literal();
+      break;
+    case PROPERTY:
+      selector = property();
+      break;
+    case DYNAMIC_ARG:
+      selector = dynamicArg();
+      break;
+    case FUNCTION_NAME:
+      selector = function();
+      break;
+    default:
+      jj_la1[6] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    {if (true) return selector;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node function() throws ParseException {
+  String functionName = null;
+  List < Node > children = null;
+    jj_consume_token(FUNCTION_NAME);
+    functionName = token.image;
+    jj_consume_token(LPAREN);
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case OFFSETDATETIME:
+    case DATETIME:
+    case DATE:
+    case NUMBER:
+    case STRING:
+    case NULL:
+    case BOOLEAN:
+    case DYNAMIC_ARG:
+    case FUNCTION_NAME:
+    case PROPERTY:
+      children = commaSepArguments();
+      break;
+    default:
+      jj_la1[7] = jj_gen;
+      ;
+    }
+    jj_consume_token(RPAREN);
+    {if (true) return new Node(FilterParserConstants.FUNCTION_NAME, functionName, children);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node dynamicArg() throws ParseException {
+  String functionName = null;
+    jj_consume_token(DYNAMIC_ARG);
+    jj_consume_token(FUNCTION_NAME);
+    functionName = token.image;
+    {if (true) return new Node(FilterParserConstants.DYNAMIC_ARG, functionName);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node literal() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case STRING:
+      jj_consume_token(STRING);
+      break;
+    case NUMBER:
+      jj_consume_token(NUMBER);
+      break;
+    case NULL:
+      jj_consume_token(NULL);
+      break;
+    case BOOLEAN:
+      jj_consume_token(BOOLEAN);
+      break;
+    case DATE:
+      jj_consume_token(DATE);
+      break;
+    case DATETIME:
+      jj_consume_token(DATETIME);
+      break;
+    case OFFSETDATETIME:
+      jj_consume_token(OFFSETDATETIME);
+      break;
+    default:
+      jj_la1[8] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    {if (true) return new Node(token.kind, token.image);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Node property() throws ParseException {
+    jj_consume_token(PROPERTY);
+    {if (true) return new Node(token.kind, token.image);}
+    throw new Error("Missing return statement in function");
+  }
+
+  /** Generated Token Manager. */
+  public FilterParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  /** Current token. */
+  public Token token;
+  /** Next token. */
+  public Token jj_nt;
+  private int jj_ntk;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[9];
+  static private int[] jj_la1_0;
+  static private int[] jj_la1_1;
+  static {
+      jj_la1_init_0();
+      jj_la1_init_1();
+   }
+   private static void jj_la1_init_0() {
+      jj_la1_0 = new int[] {0x400000,0x200000,0x9843c00,0xf0000000,0x8843c00,0x4000000,0x8843c00,0x8843c00,0x8843c00,};
+   }
+   private static void jj_la1_init_1() {
+      jj_la1_1 = new int[] {0x0,0x0,0x1c00,0x3ff,0x1c00,0x0,0x1c00,0x1c00,0x0,};
+   }
+
+  /** Constructor with InputStream. */
+  public FilterParser(java.io.InputStream stream) {
+     this(stream, null);
+  }
+  /** Constructor with InputStream and supplied encoding */
+  public FilterParser(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source = new FilterParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 9; i++) jj_la1[i] = -1;
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream stream) {
+     ReInit(stream, null);
+  }
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 9; i++) jj_la1[i] = -1;
+  }
+
+  /** Constructor. */
+  public FilterParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new FilterParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 9; i++) jj_la1[i] = -1;
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 9; i++) jj_la1[i] = -1;
+  }
+
+  /** Constructor with generated Token Manager. */
+  public FilterParser(FilterParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 9; i++) jj_la1[i] = -1;
+  }
+
+  /** Reinitialise. */
+  public void ReInit(FilterParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 9; i++) jj_la1[i] = -1;
+  }
+
+  private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+
+/** Get the next Token. */
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+/** Get the specific Token. */
+  final public Token getToken(int index) {
+    Token t = token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private java.util.List<int[]> jj_expentries = new java.util.ArrayList<int[]>();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+
+  /** Generate ParseException. */
+  public ParseException generateParseException() {
+    jj_expentries.clear();
+    boolean[] la1tokens = new boolean[46];
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 9; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+          if ((jj_la1_1[i] & (1<<j)) != 0) {
+            la1tokens[32+j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 46; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.add(jj_expentry);
+      }
+    }
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = jj_expentries.get(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  /** Enable tracing. */
+  final public void enable_tracing() {
+  }
+
+  /** Disable tracing. */
+  final public void disable_tracing() {
+  }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/FilterParserConstants.java b/src/main/java/org/apache/sling/resource/filter/impl/FilterParserConstants.java
new file mode 100644
index 0000000..731912e
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/FilterParserConstants.java
@@ -0,0 +1,153 @@
+/* Generated By:JavaCC: Do not edit this line. FilterParserConstants.java */
+package org.apache.sling.resource.filter.impl;
+
+
+/**
+ * Token literal values and constants.
+ * Generated by org.javacc.parser.OtherFilesGen#start()
+ */
+public interface FilterParserConstants {
+
+  /** End of File. */
+  int EOF = 0;
+  /** RegularExpression Id. */
+  int PLUS = 3;
+  /** RegularExpression Id. */
+  int MINUS = 4;
+  /** RegularExpression Id. */
+  int DIGIT = 5;
+  /** RegularExpression Id. */
+  int EXP = 6;
+  /** RegularExpression Id. */
+  int OFFSET = 7;
+  /** RegularExpression Id. */
+  int YYYYMMDD = 8;
+  /** RegularExpression Id. */
+  int TIME = 9;
+  /** RegularExpression Id. */
+  int OFFSETDATETIME = 10;
+  /** RegularExpression Id. */
+  int DATETIME = 11;
+  /** RegularExpression Id. */
+  int DATE = 12;
+  /** RegularExpression Id. */
+  int NUMBER = 13;
+  /** RegularExpression Id. */
+  int INTEGER = 14;
+  /** RegularExpression Id. */
+  int FRACTIONAL_DIGITS = 15;
+  /** RegularExpression Id. */
+  int EXPONENT = 16;
+  /** RegularExpression Id. */
+  int DIGITS = 17;
+  /** RegularExpression Id. */
+  int STRING = 18;
+  /** RegularExpression Id. */
+  int SQUOTE = 19;
+  /** RegularExpression Id. */
+  int DQUOTE = 20;
+  /** RegularExpression Id. */
+  int AND = 21;
+  /** RegularExpression Id. */
+  int OR = 22;
+  /** RegularExpression Id. */
+  int NULL = 23;
+  /** RegularExpression Id. */
+  int LPAREN = 24;
+  /** RegularExpression Id. */
+  int RPAREN = 25;
+  /** RegularExpression Id. */
+  int COMMA = 26;
+  /** RegularExpression Id. */
+  int BOOLEAN = 27;
+  /** RegularExpression Id. */
+  int EQUAL = 28;
+  /** RegularExpression Id. */
+  int NOT_EQUAL = 29;
+  /** RegularExpression Id. */
+  int GREATER_THAN = 30;
+  /** RegularExpression Id. */
+  int GREATER_THAN_OR_EQUAL = 31;
+  /** RegularExpression Id. */
+  int LESS_THAN = 32;
+  /** RegularExpression Id. */
+  int LESS_THAN_OR_EQUAL = 33;
+  /** RegularExpression Id. */
+  int LIKE = 34;
+  /** RegularExpression Id. */
+  int LIKE_NOT = 35;
+  /** RegularExpression Id. */
+  int CONTAINS = 36;
+  /** RegularExpression Id. */
+  int CONTAINS_NOT = 37;
+  /** RegularExpression Id. */
+  int CONTAINS_ANY = 38;
+  /** RegularExpression Id. */
+  int CONTAINS_NOT_ANY = 39;
+  /** RegularExpression Id. */
+  int IN = 40;
+  /** RegularExpression Id. */
+  int NOT_IN = 41;
+  /** RegularExpression Id. */
+  int DYNAMIC_ARG = 42;
+  /** RegularExpression Id. */
+  int FUNCTION_NAME = 43;
+  /** RegularExpression Id. */
+  int PROPERTY = 44;
+  /** RegularExpression Id. */
+  int UNKNOWN = 45;
+
+  /** Lexical state. */
+  int DEFAULT = 0;
+
+  /** Literal token values. */
+  String[] tokenImage = {
+    "<EOF>",
+    "\" \"",
+    "\"\\t\"",
+    "\"+\"",
+    "\"-\"",
+    "<DIGIT>",
+    "<EXP>",
+    "<OFFSET>",
+    "<YYYYMMDD>",
+    "<TIME>",
+    "<OFFSETDATETIME>",
+    "<DATETIME>",
+    "<DATE>",
+    "<NUMBER>",
+    "<INTEGER>",
+    "<FRACTIONAL_DIGITS>",
+    "<EXPONENT>",
+    "<DIGITS>",
+    "<STRING>",
+    "<SQUOTE>",
+    "<DQUOTE>",
+    "<AND>",
+    "<OR>",
+    "\"null\"",
+    "\"(\"",
+    "\")\"",
+    "\",\"",
+    "<BOOLEAN>",
+    "<EQUAL>",
+    "<NOT_EQUAL>",
+    "<GREATER_THAN>",
+    "\">=\"",
+    "<LESS_THAN>",
+    "\"<=\"",
+    "<LIKE>",
+    "<LIKE_NOT>",
+    "\"contains\"",
+    "\"contains not\"",
+    "\"contains any\"",
+    "\"contains not any\"",
+    "\"in\"",
+    "\"not in\"",
+    "\"$\"",
+    "<FUNCTION_NAME>",
+    "<PROPERTY>",
+    "<UNKNOWN>",
+  };
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/FilterParserTokenManager.java b/src/main/java/org/apache/sling/resource/filter/impl/FilterParserTokenManager.java
new file mode 100644
index 0000000..f01222b
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/FilterParserTokenManager.java
@@ -0,0 +1,1825 @@
+/* Generated By:JavaCC: Do not edit this line. FilterParserTokenManager.java */
+package org.apache.sling.resource.filter.impl;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.sling.resource.filter.impl.node.*;
+
+/** Token Manager. */
+public class FilterParserTokenManager implements FilterParserConstants
+{
+
+  /** Debug output. */
+  public  java.io.PrintStream debugStream = System.out;
+  /** Set debug output. */
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      case 0:
+         if ((active0 & 0x200000000L) != 0L)
+         {
+            jjmatchedKind = 32;
+            return -1;
+         }
+         if ((active0 & 0x10000000000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            return 168;
+         }
+         if ((active0 & 0xf000000000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            return 60;
+         }
+         if ((active0 & 0x4L) != 0L)
+            return 60;
+         if ((active0 & 0x80000000L) != 0L)
+         {
+            jjmatchedKind = 30;
+            return -1;
+         }
+         if ((active0 & 0x20000800000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            return 58;
+         }
+         return -1;
+      case 1:
+         if ((active0 & 0x200000000L) != 0L)
+         {
+            if (jjmatchedPos == 0)
+            {
+               jjmatchedKind = 32;
+               jjmatchedPos = 0;
+            }
+            return -1;
+         }
+         if ((active0 & 0x20000000000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            jjmatchedPos = 1;
+            return 57;
+         }
+         if ((active0 & 0x10000000000L) != 0L)
+            return 60;
+         if ((active0 & 0x80000000L) != 0L)
+         {
+            if (jjmatchedPos == 0)
+            {
+               jjmatchedKind = 30;
+               jjmatchedPos = 0;
+            }
+            return -1;
+         }
+         if ((active0 & 0xf000800000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            jjmatchedPos = 1;
+            return 60;
+         }
+         return -1;
+      case 2:
+         if ((active0 & 0x20000000000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            jjmatchedPos = 2;
+            return 56;
+         }
+         if ((active0 & 0xf000800000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            jjmatchedPos = 2;
+            return 60;
+         }
+         return -1;
+      case 3:
+         if ((active0 & 0xf000000000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            jjmatchedPos = 3;
+            return 60;
+         }
+         if ((active0 & 0x800000L) != 0L)
+            return 60;
+         if ((active0 & 0x20000000000L) != 0L)
+         {
+            if (jjmatchedPos < 2)
+            {
+               jjmatchedKind = 43;
+               jjmatchedPos = 2;
+            }
+            return 55;
+         }
+         return -1;
+      case 4:
+         if ((active0 & 0xf000000000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            jjmatchedPos = 4;
+            return 60;
+         }
+         if ((active0 & 0x20000000000L) != 0L)
+         {
+            if (jjmatchedPos < 2)
+            {
+               jjmatchedKind = 43;
+               jjmatchedPos = 2;
+            }
+            return -1;
+         }
+         return -1;
+      case 5:
+         if ((active0 & 0x20000000000L) != 0L)
+         {
+            if (jjmatchedPos < 2)
+            {
+               jjmatchedKind = 43;
+               jjmatchedPos = 2;
+            }
+            return -1;
+         }
+         if ((active0 & 0xf000000000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            jjmatchedPos = 5;
+            return 60;
+         }
+         return -1;
+      case 6:
+         if ((active0 & 0xf000000000L) != 0L)
+         {
+            jjmatchedKind = 43;
+            jjmatchedPos = 6;
+            return 60;
+         }
+         return -1;
+      case 7:
+         if ((active0 & 0xf000000000L) != 0L)
+            return 60;
+         return -1;
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 9:
+         return jjStartNfaWithStates_0(0, 2, 60);
+      case 36:
+         return jjStopAtPos(0, 42);
+      case 40:
+         return jjStopAtPos(0, 24);
+      case 41:
+         return jjStopAtPos(0, 25);
+      case 44:
+         return jjStopAtPos(0, 26);
+      case 60:
+         return jjMoveStringLiteralDfa1_0(0x200000000L);
+      case 62:
+         return jjMoveStringLiteralDfa1_0(0x80000000L);
+      case 99:
+         return jjMoveStringLiteralDfa1_0(0xf000000000L);
+      case 105:
+         return jjMoveStringLiteralDfa1_0(0x10000000000L);
+      case 110:
+         return jjMoveStringLiteralDfa1_0(0x20000800000L);
+      default :
+         return jjMoveNfa_0(0, 0);
+   }
+}
+private int jjMoveStringLiteralDfa1_0(long active0)
+{
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(0, active0);
+      return 1;
+   }
+   switch(curChar)
+   {
+      case 61:
+         if ((active0 & 0x80000000L) != 0L)
+            return jjStopAtPos(1, 31);
+         else if ((active0 & 0x200000000L) != 0L)
+            return jjStopAtPos(1, 33);
+         break;
+      case 110:
+         if ((active0 & 0x10000000000L) != 0L)
+            return jjStartNfaWithStates_0(1, 40, 60);
+         break;
+      case 111:
+         return jjMoveStringLiteralDfa2_0(active0, 0x2f000000000L);
+      case 117:
+         return jjMoveStringLiteralDfa2_0(active0, 0x800000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(0, active0);
+}
+private int jjMoveStringLiteralDfa2_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(0, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(1, active0);
+      return 2;
+   }
+   switch(curChar)
+   {
+      case 108:
+         return jjMoveStringLiteralDfa3_0(active0, 0x800000L);
+      case 110:
+         return jjMoveStringLiteralDfa3_0(active0, 0xf000000000L);
+      case 116:
+         return jjMoveStringLiteralDfa3_0(active0, 0x20000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(1, active0);
+}
+private int jjMoveStringLiteralDfa3_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(1, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(2, active0);
+      return 3;
+   }
+   switch(curChar)
+   {
+      case 32:
+         return jjMoveStringLiteralDfa4_0(active0, 0x20000000000L);
+      case 108:
+         if ((active0 & 0x800000L) != 0L)
+            return jjStartNfaWithStates_0(3, 23, 60);
+         break;
+      case 116:
+         return jjMoveStringLiteralDfa4_0(active0, 0xf000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(2, active0);
+}
+private int jjMoveStringLiteralDfa4_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(2, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(3, active0);
+      return 4;
+   }
+   switch(curChar)
+   {
+      case 97:
+         return jjMoveStringLiteralDfa5_0(active0, 0xf000000000L);
+      case 105:
+         return jjMoveStringLiteralDfa5_0(active0, 0x20000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(3, active0);
+}
+private int jjMoveStringLiteralDfa5_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(3, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(4, active0);
+      return 5;
+   }
+   switch(curChar)
+   {
+      case 105:
+         return jjMoveStringLiteralDfa6_0(active0, 0xf000000000L);
+      case 110:
+         if ((active0 & 0x20000000000L) != 0L)
+            return jjStopAtPos(5, 41);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_0(4, active0);
+}
+private int jjMoveStringLiteralDfa6_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(4, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(5, active0);
+      return 6;
+   }
+   switch(curChar)
+   {
+      case 110:
+         return jjMoveStringLiteralDfa7_0(active0, 0xf000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(5, active0);
+}
+private int jjMoveStringLiteralDfa7_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(5, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(6, active0);
+      return 7;
+   }
+   switch(curChar)
+   {
+      case 115:
+         if ((active0 & 0x1000000000L) != 0L)
+         {
+            jjmatchedKind = 36;
+            jjmatchedPos = 7;
+         }
+         return jjMoveStringLiteralDfa8_0(active0, 0xe000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(6, active0);
+}
+private int jjMoveStringLiteralDfa8_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(6, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(7, active0);
+      return 8;
+   }
+   switch(curChar)
+   {
+      case 32:
+         return jjMoveStringLiteralDfa9_0(active0, 0xe000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(7, active0);
+}
+private int jjMoveStringLiteralDfa9_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(7, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(8, active0);
+      return 9;
+   }
+   switch(curChar)
+   {
+      case 97:
+         return jjMoveStringLiteralDfa10_0(active0, 0x4000000000L);
+      case 110:
+         return jjMoveStringLiteralDfa10_0(active0, 0xa000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(8, active0);
+}
+private int jjMoveStringLiteralDfa10_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(8, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(9, active0);
+      return 10;
+   }
+   switch(curChar)
+   {
+      case 110:
+         return jjMoveStringLiteralDfa11_0(active0, 0x4000000000L);
+      case 111:
+         return jjMoveStringLiteralDfa11_0(active0, 0xa000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(9, active0);
+}
+private int jjMoveStringLiteralDfa11_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(9, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(10, active0);
+      return 11;
+   }
+   switch(curChar)
+   {
+      case 116:
+         if ((active0 & 0x2000000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 11;
+         }
+         return jjMoveStringLiteralDfa12_0(active0, 0x8000000000L);
+      case 121:
+         if ((active0 & 0x4000000000L) != 0L)
+            return jjStopAtPos(11, 38);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_0(10, active0);
+}
+private int jjMoveStringLiteralDfa12_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(10, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(11, active0);
+      return 12;
+   }
+   switch(curChar)
+   {
+      case 32:
+         return jjMoveStringLiteralDfa13_0(active0, 0x8000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(11, active0);
+}
+private int jjMoveStringLiteralDfa13_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(11, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(12, active0);
+      return 13;
+   }
+   switch(curChar)
+   {
+      case 97:
+         return jjMoveStringLiteralDfa14_0(active0, 0x8000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(12, active0);
+}
+private int jjMoveStringLiteralDfa14_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(12, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(13, active0);
+      return 14;
+   }
+   switch(curChar)
+   {
+      case 110:
+         return jjMoveStringLiteralDfa15_0(active0, 0x8000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(13, active0);
+}
+private int jjMoveStringLiteralDfa15_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(13, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(14, active0);
+      return 15;
+   }
+   switch(curChar)
+   {
+      case 121:
+         if ((active0 & 0x8000000000L) != 0L)
+            return jjStopAtPos(15, 39);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_0(14, active0);
+}
+private int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+static final long[] jjbitVec0 = {
+   0xfffffffffffffffeL, 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec2 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private int jjMoveNfa_0(int startState, int curPos)
+{
+   int startsAt = 0;
+   jjnewStateCnt = 193;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 57:
+               case 60:
+                  if ((0x8c00846affffffffL & l) == 0L)
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 0:
+                  if ((0x8c00846affffffffL & l) != 0L)
+                  {
+                     if (kind > 43)
+                        kind = 43;
+                     jjCheckNAdd(60);
+                  }
+                  else if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 13)
+                        kind = 13;
+                     jjCheckNAddStates(0, 14);
+                  }
+                  else if (curChar == 45)
+                     jjCheckNAddStates(15, 22);
+                  else if (curChar == 60)
+                  {
+                     if (kind > 32)
+                        kind = 32;
+                  }
+                  else if (curChar == 62)
+                  {
+                     if (kind > 30)
+                        kind = 30;
+                  }
+                  else if (curChar == 61)
+                     jjstateSet[jjnewStateCnt++] = 32;
+                  else if (curChar == 34)
+                     jjCheckNAddStates(23, 25);
+                  else if (curChar == 39)
+                     jjCheckNAddStates(26, 28);
+                  else if (curChar == 46)
+                     jjCheckNAdd(1);
+                  if (curChar == 33)
+                     jjstateSet[jjnewStateCnt++] = 34;
+                  else if (curChar == 38)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  break;
+               case 168:
+                  if ((0x8c00846affffffffL & l) == 0L)
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 56:
+                  if ((0x8c00846affffffffL & l) != 0L)
+                  {
+                     if (kind > 43)
+                        kind = 43;
+                     jjCheckNAdd(60);
+                  }
+                  else if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 55;
+                  break;
+               case 58:
+                  if ((0x8c00846affffffffL & l) == 0L)
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 1:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 15)
+                     kind = 15;
+                  jjCheckNAdd(1);
+                  break;
+               case 3:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(4);
+                  break;
+               case 4:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 16)
+                     kind = 16;
+                  jjCheckNAdd(4);
+                  break;
+               case 5:
+                  if (curChar == 39)
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 7:
+                  jjCheckNAddStates(26, 28);
+                  break;
+               case 8:
+                  if ((0xffffff7fffffffffL & l) != 0L)
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 9:
+                  if (curChar == 39 && kind > 18)
+                     kind = 18;
+                  break;
+               case 10:
+                  if (curChar == 34)
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 12:
+                  jjCheckNAddStates(23, 25);
+                  break;
+               case 13:
+                  if ((0xfffffffbffffffffL & l) != 0L)
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 14:
+                  if (curChar == 34 && kind > 18)
+                     kind = 18;
+                  break;
+               case 15:
+                  if (curChar == 38 && kind > 21)
+                     kind = 21;
+                  break;
+               case 16:
+                  if (curChar == 38)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  break;
+               case 32:
+                  if (curChar == 61 && kind > 28)
+                     kind = 28;
+                  break;
+               case 33:
+                  if (curChar == 61)
+                     jjstateSet[jjnewStateCnt++] = 32;
+                  break;
+               case 34:
+                  if (curChar == 61 && kind > 29)
+                     kind = 29;
+                  break;
+               case 35:
+                  if (curChar == 33)
+                     jjstateSet[jjnewStateCnt++] = 34;
+                  break;
+               case 36:
+                  if (curChar == 62 && kind > 30)
+                     kind = 30;
+                  break;
+               case 41:
+                  if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 40;
+                  break;
+               case 49:
+                  if (curChar == 60 && kind > 32)
+                     kind = 32;
+                  break;
+               case 50:
+                  if (curChar == 61 && kind > 34)
+                     kind = 34;
+                  break;
+               case 63:
+               case 64:
+                  jjCheckNAddStates(29, 31);
+                  break;
+               case 66:
+                  if (curChar == 45)
+                     jjCheckNAddStates(15, 22);
+                  break;
+               case 67:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(68, 102);
+                  break;
+               case 68:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 69;
+                  break;
+               case 69:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(70, 101);
+                  break;
+               case 70:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 71;
+                  break;
+               case 71:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(72, 100);
+                  break;
+               case 72:
+                  if (curChar == 45)
+                     jjCheckNAdd(73);
+                  break;
+               case 73:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(73, 74);
+                  break;
+               case 74:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 75;
+                  break;
+               case 75:
+                  if ((0x3000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(76, 77);
+                  break;
+               case 76:
+                  if (curChar == 45)
+                     jjCheckNAdd(77);
+                  break;
+               case 77:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(77, 78);
+                  break;
+               case 78:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 79;
+                  break;
+               case 79:
+                  if ((0xf000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 80;
+                  break;
+               case 80:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 81;
+                  break;
+               case 82:
+                  if ((0x7000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 83;
+                  break;
+               case 83:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 84;
+                  break;
+               case 84:
+                  if (curChar == 58)
+                     jjstateSet[jjnewStateCnt++] = 85;
+                  break;
+               case 85:
+                  if ((0x3f000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 86;
+                  break;
+               case 86:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(87, 94);
+                  break;
+               case 87:
+                  if (curChar == 58)
+                     jjstateSet[jjnewStateCnt++] = 88;
+                  break;
+               case 88:
+                  if ((0x3f000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 89;
+                  break;
+               case 89:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(90, 94);
+                  break;
+               case 90:
+                  if (curChar == 46)
+                     jjstateSet[jjnewStateCnt++] = 91;
+                  break;
+               case 91:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 92;
+                  break;
+               case 92:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 93;
+                  break;
+               case 93:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAdd(94);
+                  break;
+               case 94:
+                  if ((0x280000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 95;
+                  break;
+               case 95:
+                  if ((0x7000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 96;
+                  break;
+               case 96:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 97;
+                  break;
+               case 97:
+                  if (curChar == 58)
+                     jjstateSet[jjnewStateCnt++] = 98;
+                  break;
+               case 98:
+                  if ((0x3f000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 99;
+                  break;
+               case 99:
+                  if ((0x3ff000000000000L & l) != 0L && kind > 10)
+                     kind = 10;
+                  break;
+               case 100:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(32, 35);
+                  break;
+               case 101:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(36, 39);
+                  break;
+               case 102:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(40, 43);
+                  break;
+               case 103:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(104, 132);
+                  break;
+               case 104:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 105;
+                  break;
+               case 105:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(106, 131);
+                  break;
+               case 106:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 107;
+                  break;
+               case 107:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(108, 130);
+                  break;
+               case 108:
+                  if (curChar == 45)
+                     jjCheckNAdd(109);
+                  break;
+               case 109:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(109, 110);
+                  break;
+               case 110:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 111;
+                  break;
+               case 111:
+                  if ((0x3000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(112, 113);
+                  break;
+               case 112:
+                  if (curChar == 45)
+                     jjCheckNAdd(113);
+                  break;
+               case 113:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(113, 114);
+                  break;
+               case 114:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 115;
+                  break;
+               case 115:
+                  if ((0xf000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 116;
+                  break;
+               case 116:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 117;
+                  break;
+               case 118:
+                  if ((0x7000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 119;
+                  break;
+               case 119:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 120;
+                  break;
+               case 120:
+                  if (curChar == 58)
+                     jjstateSet[jjnewStateCnt++] = 121;
+                  break;
+               case 121:
+                  if ((0x3f000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 122;
+                  break;
+               case 122:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 11)
+                     kind = 11;
+                  jjstateSet[jjnewStateCnt++] = 123;
+                  break;
+               case 123:
+                  if (curChar == 58)
+                     jjstateSet[jjnewStateCnt++] = 124;
+                  break;
+               case 124:
+                  if ((0x3f000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 125;
+                  break;
+               case 125:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 11)
+                     kind = 11;
+                  jjstateSet[jjnewStateCnt++] = 126;
+                  break;
+               case 126:
+                  if (curChar == 46)
+                     jjstateSet[jjnewStateCnt++] = 127;
+                  break;
+               case 127:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 128;
+                  break;
+               case 128:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 129;
+                  break;
+               case 129:
+                  if ((0x3ff000000000000L & l) != 0L && kind > 11)
+                     kind = 11;
+                  break;
+               case 130:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(44, 47);
+                  break;
+               case 131:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(48, 51);
+                  break;
+               case 132:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(52, 55);
+                  break;
+               case 133:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(134, 149);
+                  break;
+               case 134:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 135;
+                  break;
+               case 135:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(136, 148);
+                  break;
+               case 136:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 137;
+                  break;
+               case 137:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(138, 147);
+                  break;
+               case 138:
+                  if (curChar == 45)
+                     jjCheckNAdd(139);
+                  break;
+               case 139:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(139, 140);
+                  break;
+               case 140:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 141;
+                  break;
+               case 141:
+                  if ((0x3000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(142, 143);
+                  break;
+               case 142:
+                  if (curChar == 45)
+                     jjCheckNAdd(143);
+                  break;
+               case 143:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(143, 144);
+                  break;
+               case 144:
+                  if (curChar == 45)
+                     jjstateSet[jjnewStateCnt++] = 145;
+                  break;
+               case 145:
+                  if ((0xf000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 146;
+                  break;
+               case 146:
+                  if ((0x3ff000000000000L & l) != 0L && kind > 12)
+                     kind = 12;
+                  break;
+               case 147:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(56, 59);
+                  break;
+               case 148:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(60, 63);
+                  break;
+               case 149:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddStates(64, 67);
+                  break;
+               case 150:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 13)
+                     kind = 13;
+                  jjCheckNAdd(150);
+                  break;
+               case 151:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(151, 152);
+                  break;
+               case 152:
+                  if (curChar == 46)
+                     jjCheckNAdd(153);
+                  break;
+               case 153:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 13)
+                     kind = 13;
+                  jjCheckNAdd(153);
+                  break;
+               case 154:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(154, 155);
+                  break;
+               case 156:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(157);
+                  break;
+               case 157:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 13)
+                     kind = 13;
+                  jjCheckNAdd(157);
+                  break;
+               case 158:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(158, 159);
+                  break;
+               case 159:
+                  if (curChar == 46)
+                     jjCheckNAdd(160);
+                  break;
+               case 160:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(160, 161);
+                  break;
+               case 162:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(163);
+                  break;
+               case 163:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 13)
+                     kind = 13;
+                  jjCheckNAdd(163);
+                  break;
+               case 164:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 14)
+                     kind = 14;
+                  jjCheckNAdd(164);
+                  break;
+               case 165:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 13)
+                     kind = 13;
+                  jjCheckNAddStates(0, 14);
+                  break;
+               case 166:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 17)
+                     kind = 17;
+                  jjCheckNAdd(166);
+                  break;
+               case 172:
+                  if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 171;
+                  break;
+               case 179:
+                  if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 178;
+                  break;
+               case 189:
+                  if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 188;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 57:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 43)
+                        kind = 43;
+                     jjCheckNAdd(60);
+                  }
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 56;
+                  break;
+               case 0:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 43)
+                        kind = 43;
+                     jjCheckNAdd(60);
+                  }
+                  else if (curChar == 91)
+                     jjCheckNAddStates(29, 31);
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(68, 69);
+                  else if (curChar == 108)
+                     jjAddStates(70, 72);
+                  else if (curChar == 105)
+                     jjAddStates(73, 74);
+                  else if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 58;
+                  else if (curChar == 126)
+                     jjstateSet[jjnewStateCnt++] = 50;
+                  else if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 47;
+                  else if (curChar == 102)
+                     jjstateSet[jjnewStateCnt++] = 30;
+                  else if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 26;
+                  else if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 22;
+                  else if (curChar == 124)
+                     jjstateSet[jjnewStateCnt++] = 20;
+                  else if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 18;
+                  break;
+               case 168:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 43)
+                        kind = 43;
+                     jjCheckNAdd(60);
+                  }
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 172;
+                  if (curChar == 115)
+                  {
+                     if (kind > 28)
+                        kind = 28;
+                  }
+                  break;
+               case 56:
+               case 60:
+                  if ((0xffffffffd7ffffffL & l) == 0L)
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 58:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 43)
+                        kind = 43;
+                     jjCheckNAdd(60);
+                  }
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 57;
+                  break;
+               case 2:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(68, 69);
+                  break;
+               case 6:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 7;
+                  break;
+               case 7:
+                  jjCheckNAddStates(26, 28);
+                  break;
+               case 8:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 11:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 12;
+                  break;
+               case 12:
+                  jjCheckNAddStates(23, 25);
+                  break;
+               case 13:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 17:
+                  if (curChar == 100 && kind > 21)
+                     kind = 21;
+                  break;
+               case 18:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 17;
+                  break;
+               case 19:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 18;
+                  break;
+               case 20:
+                  if (curChar == 124 && kind > 22)
+                     kind = 22;
+                  break;
+               case 21:
+                  if (curChar == 124)
+                     jjstateSet[jjnewStateCnt++] = 20;
+                  break;
+               case 22:
+                  if (curChar == 114 && kind > 22)
+                     kind = 22;
+                  break;
+               case 23:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 22;
+                  break;
+               case 24:
+                  if (curChar == 101 && kind > 27)
+                     kind = 27;
+                  break;
+               case 25:
+                  if (curChar == 117)
+                     jjCheckNAdd(24);
+                  break;
+               case 26:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 25;
+                  break;
+               case 27:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 26;
+                  break;
+               case 28:
+                  if (curChar == 115)
+                     jjCheckNAdd(24);
+                  break;
+               case 29:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 28;
+                  break;
+               case 30:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 29;
+                  break;
+               case 31:
+                  if (curChar == 102)
+                     jjstateSet[jjnewStateCnt++] = 30;
+                  break;
+               case 37:
+                  if (curChar == 110 && kind > 30)
+                     kind = 30;
+                  break;
+               case 38:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 37;
+                  break;
+               case 39:
+                  if (curChar == 104)
+                     jjstateSet[jjnewStateCnt++] = 38;
+                  break;
+               case 40:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 39;
+                  break;
+               case 42:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 41;
+                  break;
+               case 43:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 42;
+                  break;
+               case 44:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 43;
+                  break;
+               case 45:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 44;
+                  break;
+               case 46:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 45;
+                  break;
+               case 47:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 46;
+                  break;
+               case 48:
+                  if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 47;
+                  break;
+               case 51:
+                  if (curChar == 126)
+                     jjstateSet[jjnewStateCnt++] = 50;
+                  break;
+               case 52:
+                  if (curChar == 101 && kind > 35)
+                     kind = 35;
+                  break;
+               case 53:
+                  if (curChar == 107)
+                     jjstateSet[jjnewStateCnt++] = 52;
+                  break;
+               case 54:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 53;
+                  break;
+               case 55:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 54;
+                  break;
+               case 59:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 58;
+                  break;
+               case 61:
+                  if (curChar == 91)
+                     jjCheckNAddStates(29, 31);
+                  break;
+               case 62:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 63;
+                  break;
+               case 63:
+                  jjCheckNAddStates(29, 31);
+                  break;
+               case 64:
+                  if ((0xffffffffcfffffffL & l) != 0L)
+                     jjCheckNAddStates(29, 31);
+                  break;
+               case 65:
+                  if (curChar == 93 && kind > 44)
+                     kind = 44;
+                  break;
+               case 81:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 82;
+                  break;
+               case 117:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 118;
+                  break;
+               case 155:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(75, 76);
+                  break;
+               case 161:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(77, 78);
+                  break;
+               case 167:
+                  if (curChar == 105)
+                     jjAddStates(73, 74);
+                  break;
+               case 169:
+                  if (curChar == 116 && kind > 29)
+                     kind = 29;
+                  break;
+               case 170:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 169;
+                  break;
+               case 171:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 170;
+                  break;
+               case 173:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 172;
+                  break;
+               case 174:
+                  if (curChar == 108)
+                     jjAddStates(70, 72);
+                  break;
+               case 175:
+                  if (curChar == 110 && kind > 32)
+                     kind = 32;
+                  break;
+               case 176:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 175;
+                  break;
+               case 177:
+                  if (curChar == 104)
+                     jjstateSet[jjnewStateCnt++] = 176;
+                  break;
+               case 178:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 177;
+                  break;
+               case 180:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 179;
+                  break;
+               case 181:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 180;
+                  break;
+               case 182:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 181;
+                  break;
+               case 183:
+                  if (curChar == 101 && kind > 34)
+                     kind = 34;
+                  break;
+               case 184:
+                  if (curChar == 107)
+                     jjstateSet[jjnewStateCnt++] = 183;
+                  break;
+               case 185:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 184;
+                  break;
+               case 186:
+                  if (curChar == 116 && kind > 35)
+                     kind = 35;
+                  break;
+               case 187:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 186;
+                  break;
+               case 188:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 187;
+                  break;
+               case 190:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 189;
+                  break;
+               case 191:
+                  if (curChar == 107)
+                     jjstateSet[jjnewStateCnt++] = 190;
+                  break;
+               case 192:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 191;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int hiByte = (int)(curChar >> 8);
+         int i1 = hiByte >> 6;
+         long l1 = 1L << (hiByte & 077);
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 57:
+               case 60:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 0:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 168:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 56:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 58:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 43)
+                     kind = 43;
+                  jjCheckNAdd(60);
+                  break;
+               case 7:
+               case 8:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 12:
+               case 13:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 63:
+               case 64:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddStates(29, 31);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 193 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   68, 104, 134, 150, 151, 152, 154, 155, 158, 159, 164, 166, 149, 132, 102, 67, 
+   103, 133, 150, 151, 154, 158, 164, 11, 13, 14, 6, 8, 9, 62, 64, 65, 
+   72, 73, 74, 100, 70, 72, 100, 101, 68, 70, 101, 102, 108, 109, 110, 130, 
+   106, 108, 130, 131, 104, 106, 131, 132, 138, 139, 140, 147, 136, 138, 147, 148, 
+   134, 136, 148, 149, 3, 4, 182, 185, 192, 168, 173, 156, 157, 162, 163, 
+};
+private static final boolean jjCanMove_0(int hiByte, int i1, int i2, long l1, long l2)
+{
+   switch(hiByte)
+   {
+      case 0:
+         return ((jjbitVec2[i2] & l2) != 0L);
+      default :
+         if ((jjbitVec0[i1] & l1) != 0L)
+            return true;
+         return false;
+   }
+}
+
+/** Token literal values. */
+public static final String[] jjstrLiteralImages = {
+"", null, null, null, null, null, null, null, null, null, null, null, null, 
+null, null, null, null, null, null, null, null, null, null, "\156\165\154\154", 
+"\50", "\51", "\54", null, null, null, null, "\76\75", null, "\74\75", null, null, 
+"\143\157\156\164\141\151\156\163", "\143\157\156\164\141\151\156\163\40\156\157\164", 
+"\143\157\156\164\141\151\156\163\40\141\156\171", "\143\157\156\164\141\151\156\163\40\156\157\164\40\141\156\171", "\151\156", 
+"\156\157\164\40\151\156", "\44", null, null, null, };
+
+/** Lexer state names. */
+public static final String[] lexStateNames = {
+   "DEFAULT",
+};
+static final long[] jjtoToken = {
+   0x3fffffe7fc01L, 
+};
+static final long[] jjtoSkip = {
+   0x6L, 
+};
+protected SimpleCharStream input_stream;
+private final int[] jjrounds = new int[193];
+private final int[] jjstateSet = new int[386];
+private final StringBuilder jjimage = new StringBuilder();
+private StringBuilder image = jjimage;
+private int jjimageLen;
+private int lengthOfMatch;
+protected char curChar;
+/** Constructor. */
+public FilterParserTokenManager(SimpleCharStream stream){
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+
+/** Constructor. */
+public FilterParserTokenManager(SimpleCharStream stream, int lexState){
+   this(stream);
+   SwitchTo(lexState);
+}
+
+/** Reinitialise parser. */
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 193; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+
+/** Reinitialise parser. */
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+
+/** Switch to specified lex state. */
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 1 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+   final Token t;
+   final String curTokenImage;
+   final int beginLine;
+   final int endLine;
+   final int beginColumn;
+   final int endColumn;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   curTokenImage = (im == null) ? input_stream.GetImage() : im;
+   beginLine = input_stream.getBeginLine();
+   beginColumn = input_stream.getBeginColumn();
+   endLine = input_stream.getEndLine();
+   endColumn = input_stream.getEndColumn();
+   t = Token.newToken(jjmatchedKind, curTokenImage);
+
+   t.beginLine = beginLine;
+   t.endLine = endLine;
+   t.beginColumn = beginColumn;
+   t.endColumn = endColumn;
+
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+/** Get the next Token. */
+public Token getNextToken() 
+{
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {
+   try
+   {
+      curChar = input_stream.BeginToken();
+   }
+   catch(java.io.IOException e)
+   {
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      return matchedToken;
+   }
+   image = jjimage;
+   image.setLength(0);
+   jjimageLen = 0;
+
+   try { input_stream.backup(0);
+      while (curChar <= 32 && (0x100000000L & (1L << curChar)) != 0L)
+         curChar = input_stream.BeginToken();
+   }
+   catch (java.io.IOException e1) { continue EOFLoop; }
+   jjmatchedKind = 0x7fffffff;
+   jjmatchedPos = 0;
+   curPos = jjMoveStringLiteralDfa0_0();
+   if (jjmatchedPos == 0 && jjmatchedKind > 45)
+   {
+      jjmatchedKind = 45;
+   }
+   if (jjmatchedKind != 0x7fffffff)
+   {
+      if (jjmatchedPos + 1 < curPos)
+         input_stream.backup(curPos - jjmatchedPos - 1);
+      if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+      {
+         matchedToken = jjFillToken();
+         TokenLexicalActions(matchedToken);
+         return matchedToken;
+      }
+      else
+      {
+         continue EOFLoop;
+      }
+   }
+   int error_line = input_stream.getEndLine();
+   int error_column = input_stream.getEndColumn();
+   String error_after = null;
+   boolean EOFSeen = false;
+   try { input_stream.readChar(); input_stream.backup(1); }
+   catch (java.io.IOException e1) {
+      EOFSeen = true;
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+      if (curChar == '\n' || curChar == '\r') {
+         error_line++;
+         error_column = 0;
+      }
+      else
+         error_column++;
+   }
+   if (!EOFSeen) {
+      input_stream.backup(1);
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+   }
+   throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+  }
+}
+
+void TokenLexicalActions(Token matchedToken)
+{
+   switch(jjmatchedKind)
+   {
+      case 18 :
+        image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+                                       matchedToken.image = image.substring(1, lengthOfMatch - 1);
+         break;
+      case 44 :
+        image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+                                                        matchedToken.image = image.substring(1, lengthOfMatch - 1);
+         break;
+      default :
+         break;
+   }
+}
+private void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+
+private void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/ParseException.java b/src/main/java/org/apache/sling/resource/filter/impl/ParseException.java
new file mode 100644
index 0000000..193925c
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/ParseException.java
@@ -0,0 +1,178 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 5.0 */
+/* JavaCCOptions:KEEP_LINE_COL=null */
+package org.apache.sling.resource.filter.impl;
+
+/**
+ * This exception is thrown when parse errors are encountered. You can
+ * explicitly create objects of this exception type by calling the method
+ * generateParseException in the generated parser.
+ *
+ * You can modify this class to customize your error reporting mechanisms so
+ * long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+    /**
+     * The version identifier for this Serializable class. Increment only if the
+     * <i>serialized</i> form of the class changes.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * This constructor is used by the method "generateParseException" in the
+     * generated parser. Calling this constructor generates a new object of this
+     * type with the fields "currentToken", "expectedTokenSequences", and
+     * "tokenImage" set.
+     */
+    public ParseException(Token currentTokenVal, int[][] expectedTokenSequencesVal, String[] tokenImageVal) {
+        super(initialise(currentTokenVal, expectedTokenSequencesVal, tokenImageVal));
+        currentToken = currentTokenVal;
+        expectedTokenSequences = expectedTokenSequencesVal;
+        tokenImage = tokenImageVal;
+    }
+
+    /**
+     * The following constructors are for use by you for whatever purpose you can
+     * think of. Constructing the exception in this manner makes the exception
+     * behave in the normal way - i.e., as documented in the class "Throwable". The
+     * fields "errorToken", "expectedTokenSequences", and "tokenImage" do not
+     * contain relevant information. The JavaCC generated code does not use these
+     * constructors.
+     */
+
+    public ParseException() {
+        super();
+    }
+
+    /** Constructor with message. */
+    public ParseException(String message) {
+        super(message);
+    }
+
+    /**
+     * This is the last token that has been consumed successfully. If this object
+     * has been created due to a parse error, the token followng this token will
+     * (therefore) be the first error token.
+     */
+    public Token currentToken;
+
+    /**
+     * Each entry in this array is an array of integers. Each array of integers
+     * represents a sequence of tokens (by their ordinal values) that is expected at
+     * this point of the parse.
+     */
+    public int[][] expectedTokenSequences;
+
+    /**
+     * This is a reference to the "tokenImage" array of the generated parser within
+     * which the parse error occurred. This array is defined in the generated
+     * ...Constants interface.
+     */
+    public String[] tokenImage;
+
+    /**
+     * It uses "currentToken" and "expectedTokenSequences" to generate a parse error
+     * message and returns it. If this object has been created due to a parse error,
+     * and you do not catch it (it gets thrown from the parser) the correct error
+     * message gets displayed.
+     */
+    private static String initialise(Token currentToken, int[][] expectedTokenSequences, String[] tokenImage) {
+        String eol = System.getProperty("line.separator", "\n");
+        StringBuffer expected = new StringBuffer();
+        int maxSize = 0;
+        for (int i = 0; i < expectedTokenSequences.length; i++) {
+            if (maxSize < expectedTokenSequences[i].length) {
+                maxSize = expectedTokenSequences[i].length;
+            }
+            for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+                expected.append(tokenImage[expectedTokenSequences[i][j]]).append(' ');
+            }
+            if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+                expected.append("...");
+            }
+            expected.append(eol).append("    ");
+        }
+        String retval = "Encountered \"";
+        Token tok = currentToken.next;
+        for (int i = 0; i < maxSize; i++) {
+            if (i != 0)
+                retval += " ";
+            if (tok.kind == 0) {
+                retval += tokenImage[0];
+                break;
+            }
+            retval += " " + tokenImage[tok.kind];
+            retval += " \"";
+            retval += add_escapes(tok.image);
+            retval += " \"";
+            tok = tok.next;
+        }
+        retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+        retval += "." + eol;
+        if (expectedTokenSequences.length == 1) {
+            retval += "Was expecting:" + eol + "    ";
+        } else {
+            retval += "Was expecting one of:" + eol + "    ";
+        }
+        retval += expected.toString();
+        return retval;
+    }
+
+    /**
+     * The end of line string for this machine.
+     */
+    protected String eol = System.getProperty("line.separator", "\n");
+
+    /**
+     * Used to convert raw characters to their escaped version when these raw
+     * version cannot be used as part of an ASCII string literal.
+     */
+    static String add_escapes(String str) {
+        StringBuffer retval = new StringBuffer();
+        char ch;
+        for (int i = 0; i < str.length(); i++) {
+            switch (str.charAt(i)) {
+            case 0:
+                continue;
+            case '\b':
+                retval.append("\\b");
+                continue;
+            case '\t':
+                retval.append("\\t");
+                continue;
+            case '\n':
+                retval.append("\\n");
+                continue;
+            case '\f':
+                retval.append("\\f");
+                continue;
+            case '\r':
+                retval.append("\\r");
+                continue;
+            case '\"':
+                retval.append("\\\"");
+                continue;
+            case '\'':
+                retval.append("\\\'");
+                continue;
+            case '\\':
+                retval.append("\\\\");
+                continue;
+            default:
+                if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                    String s = "0000" + Integer.toString(ch, 16);
+                    retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+                } else {
+                    retval.append(ch);
+                }
+                continue;
+            }
+        }
+        return retval.toString();
+    }
+
+}
+/*
+ * JavaCC - OriginalChecksum=83a1ca447d862df57c34d5cbcbc80371 (do not edit this
+ * line)
+ */
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/SimpleCharStream.java b/src/main/java/org/apache/sling/resource/filter/impl/SimpleCharStream.java
new file mode 100644
index 0000000..ebbdd2e
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/SimpleCharStream.java
@@ -0,0 +1,416 @@
+/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */
+/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
+package org.apache.sling.resource.filter.impl;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).
+ */
+
+public class SimpleCharStream {
+    /** Whether parser is static. */
+    public static final boolean staticFlag = false;
+    int bufsize;
+    int available;
+    int tokenBegin;
+    /** Position in buffer. */
+    public int bufpos = -1;
+    protected int bufline[];
+    protected int bufcolumn[];
+
+    protected int column = 0;
+    protected int line = 1;
+
+    protected boolean prevCharIsCR = false;
+    protected boolean prevCharIsLF = false;
+
+    protected java.io.Reader inputStream;
+
+    protected char[] buffer;
+    protected int maxNextCharInd = 0;
+    protected int inBuf = 0;
+    protected int tabSize = 8;
+
+    protected void setTabSize(int i) {
+        tabSize = i;
+    }
+
+    protected int getTabSize(int i) {
+        return tabSize;
+    }
+
+    protected void ExpandBuff(boolean wrapAround) {
+        char[] newbuffer = new char[bufsize + 2048];
+        int newbufline[] = new int[bufsize + 2048];
+        int newbufcolumn[] = new int[bufsize + 2048];
+
+        try {
+            if (wrapAround) {
+                System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+                System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos);
+                buffer = newbuffer;
+
+                System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+                System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+                bufline = newbufline;
+
+                System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+                System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+                bufcolumn = newbufcolumn;
+
+                maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+            } else {
+                System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+                buffer = newbuffer;
+
+                System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+                bufline = newbufline;
+
+                System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+                bufcolumn = newbufcolumn;
+
+                maxNextCharInd = (bufpos -= tokenBegin);
+            }
+        } catch (Throwable t) {
+            throw new Error(t.getMessage());
+        }
+
+        bufsize += 2048;
+        available = bufsize;
+        tokenBegin = 0;
+    }
+
+    protected void FillBuff() throws java.io.IOException {
+        if (maxNextCharInd == available) {
+            if (available == bufsize) {
+                if (tokenBegin > 2048) {
+                    bufpos = maxNextCharInd = 0;
+                    available = tokenBegin;
+                } else if (tokenBegin < 0)
+                    bufpos = maxNextCharInd = 0;
+                else
+                    ExpandBuff(false);
+            } else if (available > tokenBegin)
+                available = bufsize;
+            else if ((tokenBegin - available) < 2048)
+                ExpandBuff(true);
+            else
+                available = tokenBegin;
+        }
+
+        int i;
+        try {
+            if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) {
+                inputStream.close();
+                throw new java.io.IOException();
+            } else
+                maxNextCharInd += i;
+            return;
+        } catch (java.io.IOException e) {
+            --bufpos;
+            backup(0);
+            if (tokenBegin == -1)
+                tokenBegin = bufpos;
+            throw e;
+        }
+    }
+
+    /** Start. */
+    public char BeginToken() throws java.io.IOException {
+        tokenBegin = -1;
+        char c = readChar();
+        tokenBegin = bufpos;
+
+        return c;
+    }
+
+    protected void UpdateLineColumn(char c) {
+        column++;
+
+        if (prevCharIsLF) {
+            prevCharIsLF = false;
+            line += (column = 1);
+        } else if (prevCharIsCR) {
+            prevCharIsCR = false;
+            if (c == '\n') {
+                prevCharIsLF = true;
+            } else
+                line += (column = 1);
+        }
+
+        switch (c) {
+        case '\r':
+            prevCharIsCR = true;
+            break;
+        case '\n':
+            prevCharIsLF = true;
+            break;
+        case '\t':
+            column--;
+            column += (tabSize - (column % tabSize));
+            break;
+        default:
+            break;
+        }
+
+        bufline[bufpos] = line;
+        bufcolumn[bufpos] = column;
+    }
+
+    /** Read a character. */
+    public char readChar() throws java.io.IOException {
+        if (inBuf > 0) {
+            --inBuf;
+
+            if (++bufpos == bufsize)
+                bufpos = 0;
+
+            return buffer[bufpos];
+        }
+
+        if (++bufpos >= maxNextCharInd)
+            FillBuff();
+
+        char c = buffer[bufpos];
+
+        UpdateLineColumn(c);
+        return c;
+    }
+
+    @Deprecated
+    /**
+     * @deprecated
+     * @see #getEndColumn
+     */
+
+    public int getColumn() {
+        return bufcolumn[bufpos];
+    }
+
+    @Deprecated
+    /**
+     * @deprecated
+     * @see #getEndLine
+     */
+
+    public int getLine() {
+        return bufline[bufpos];
+    }
+
+    /** Get token end column number. */
+    public int getEndColumn() {
+        return bufcolumn[bufpos];
+    }
+
+    /** Get token end line number. */
+    public int getEndLine() {
+        return bufline[bufpos];
+    }
+
+    /** Get token beginning column number. */
+    public int getBeginColumn() {
+        return bufcolumn[tokenBegin];
+    }
+
+    /** Get token beginning line number. */
+    public int getBeginLine() {
+        return bufline[tokenBegin];
+    }
+
+    /** Backup a number of characters. */
+    public void backup(int amount) {
+
+        inBuf += amount;
+        if ((bufpos -= amount) < 0)
+            bufpos += bufsize;
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn, int buffersize) {
+        inputStream = dstream;
+        line = startline;
+        column = startcolumn - 1;
+
+        available = bufsize = buffersize;
+        buffer = new char[buffersize];
+        bufline = new int[buffersize];
+        bufcolumn = new int[buffersize];
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.Reader dstream, int startline, int startcolumn) {
+        this(dstream, startline, startcolumn, 4096);
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.Reader dstream) {
+        this(dstream, 1, 1, 4096);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.Reader dstream, int startline, int startcolumn, int buffersize) {
+        inputStream = dstream;
+        line = startline;
+        column = startcolumn - 1;
+
+        if (buffer == null || buffersize != buffer.length) {
+            available = bufsize = buffersize;
+            buffer = new char[buffersize];
+            bufline = new int[buffersize];
+            bufcolumn = new int[buffersize];
+        }
+        prevCharIsLF = prevCharIsCR = false;
+        tokenBegin = inBuf = maxNextCharInd = 0;
+        bufpos = -1;
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.Reader dstream, int startline, int startcolumn) {
+        ReInit(dstream, startline, startcolumn, 4096);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.Reader dstream) {
+        ReInit(dstream, 1, 1, 4096);
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn,
+            int buffersize) throws java.io.UnsupportedEncodingException {
+        this(encoding == null ? new java.io.InputStreamReader(dstream)
+                : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) {
+        this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline, int startcolumn)
+            throws java.io.UnsupportedEncodingException {
+        this(dstream, encoding, startline, startcolumn, 4096);
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.InputStream dstream, int startline, int startcolumn) {
+        this(dstream, startline, startcolumn, 4096);
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException {
+        this(dstream, encoding, 1, 1, 4096);
+    }
+
+    /** Constructor. */
+    public SimpleCharStream(java.io.InputStream dstream) {
+        this(dstream, 1, 1, 4096);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn, int buffersize)
+            throws java.io.UnsupportedEncodingException {
+        ReInit(encoding == null ? new java.io.InputStreamReader(dstream)
+                : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.InputStream dstream, int startline, int startcolumn, int buffersize) {
+        ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException {
+        ReInit(dstream, encoding, 1, 1, 4096);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.InputStream dstream) {
+        ReInit(dstream, 1, 1, 4096);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.InputStream dstream, String encoding, int startline, int startcolumn)
+            throws java.io.UnsupportedEncodingException {
+        ReInit(dstream, encoding, startline, startcolumn, 4096);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(java.io.InputStream dstream, int startline, int startcolumn) {
+        ReInit(dstream, startline, startcolumn, 4096);
+    }
+
+    /** Get token literal value. */
+    public String GetImage() {
+        if (bufpos >= tokenBegin)
+            return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+        else
+            return new String(buffer, tokenBegin, bufsize - tokenBegin) + new String(buffer, 0, bufpos + 1);
+    }
+
+    /** Get the suffix. */
+    public char[] GetSuffix(int len) {
+        char[] ret = new char[len];
+
+        if ((bufpos + 1) >= len)
+            System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+        else {
+            System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, len - bufpos - 1);
+            System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+        }
+
+        return ret;
+    }
+
+    /** Reset buffer when finished. */
+    public void Done() {
+        buffer = null;
+        bufline = null;
+        bufcolumn = null;
+    }
+
+    /**
+     * Method to adjust line and column numbers for the start of a token.
+     */
+    public void adjustBeginLineColumn(int newLine, int newCol) {
+        int start = tokenBegin;
+        int len;
+
+        if (bufpos >= tokenBegin) {
+            len = bufpos - tokenBegin + inBuf + 1;
+        } else {
+            len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+        }
+
+        int i = 0, j = 0, k = 0;
+        int nextColDiff = 0, columnDiff = 0;
+
+        while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) {
+            bufline[j] = newLine;
+            nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+            bufcolumn[j] = newCol + columnDiff;
+            columnDiff = nextColDiff;
+            i++;
+        }
+
+        if (i < len) {
+            bufline[j] = newLine++;
+            bufcolumn[j] = newCol + columnDiff;
+
+            while (i++ < len) {
+                if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+                    bufline[j] = newLine++;
+                else
+                    bufline[j] = newLine;
+            }
+        }
+
+        line = bufline[j];
+        column = bufcolumn[j];
+    }
+
+}
+/*
+ * JavaCC - OriginalChecksum=6135efa69418e21b667355763fbdf75c (do not edit this
+ * line)
+ */
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/Token.java b/src/main/java/org/apache/sling/resource/filter/impl/Token.java
new file mode 100644
index 0000000..cbe4fe5
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/Token.java
@@ -0,0 +1,126 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */
+/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
+package org.apache.sling.resource.filter.impl;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token implements java.io.Serializable {
+
+    /**
+     * The version identifier for this Serializable class. Increment only if the
+     * <i>serialized</i> form of the class changes.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * An integer that describes the kind of this token. This numbering system is
+     * determined by JavaCCParser, and a table of these numbers is stored in the
+     * file ...Constants.java.
+     */
+    public int kind;
+
+    /** The line number of the first character of this Token. */
+    public int beginLine;
+    /** The column number of the first character of this Token. */
+    public int beginColumn;
+    /** The line number of the last character of this Token. */
+    public int endLine;
+    /** The column number of the last character of this Token. */
+    public int endColumn;
+
+    /**
+     * The string image of the token.
+     */
+    public String image;
+
+    /**
+     * A reference to the next regular (non-special) token from the input stream. If
+     * this is the last token from the input stream, or if the token manager has not
+     * read tokens beyond this one, this field is set to null. This is true only if
+     * this token is also a regular token. Otherwise, see below for a description of
+     * the contents of this field.
+     */
+    public Token next;
+
+    /**
+     * This field is used to access special tokens that occur prior to this token,
+     * but after the immediately preceding regular (non-special) token. If there are
+     * no such special tokens, this field is set to null. When there are more than
+     * one such special token, this field refers to the last of these special
+     * tokens, which in turn refers to the next previous special token through its
+     * specialToken field, and so on until the first special token (whose
+     * specialToken field is null). The next fields of special tokens refer to other
+     * special tokens that immediately follow it (without an intervening regular
+     * token). If there is no such token, this field is null.
+     */
+    public Token specialToken;
+
+    /**
+     * An optional attribute value of the Token. Tokens which are not used as
+     * syntactic sugar will often contain meaningful values that will be used later
+     * on by the compiler or interpreter. This attribute value is often different
+     * from the image. Any subclass of Token that actually wants to return a
+     * non-null value can override this method as appropriate.
+     */
+    public Object getValue() {
+        return null;
+    }
+
+    /**
+     * No-argument constructor
+     */
+    public Token() {
+    }
+
+    /**
+     * Constructs a new token for the specified Image.
+     */
+    public Token(int kind) {
+        this(kind, null);
+    }
+
+    /**
+     * Constructs a new token for the specified Image and Kind.
+     */
+    public Token(int kind, String image) {
+        this.kind = kind;
+        this.image = image;
+    }
+
+    /**
+     * Returns the image.
+     */
+    public String toString() {
+        return image;
+    }
+
+    /**
+     * Returns a new Token object, by default. However, if you want, you can create
+     * and return subclass objects based on the value of ofKind. Simply add the
+     * cases to the switch for all those special cases. For example, if you have a
+     * subclass of Token called IDToken that you want to create if ofKind is ID,
+     * simply add something like :
+     *
+     * case MyParserConstants.ID : return new IDToken(ofKind, image);
+     *
+     * to the following switch statement. Then you can cast matchedToken variable to
+     * the appropriate type and use sit in your lexical actions.
+     */
+    public static Token newToken(int ofKind, String image) {
+        switch (ofKind) {
+        default:
+            return new Token(ofKind, image);
+        }
+    }
+
+    public static Token newToken(int ofKind) {
+        return newToken(ofKind, null);
+    }
+
+}
+/*
+ * JavaCC - OriginalChecksum=a81505762739ec99dc6610e2843bdc32 (do not edit this
+ * line)
+ */
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/TokenMgrError.java b/src/main/java/org/apache/sling/resource/filter/impl/TokenMgrError.java
new file mode 100644
index 0000000..9f5ef60
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/TokenMgrError.java
@@ -0,0 +1,145 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */
+/* JavaCCOptions: */
+package org.apache.sling.resource.filter.impl;
+
+/** Token Manager Error. */
+public class TokenMgrError extends Error {
+
+    /**
+     * The version identifier for this Serializable class. Increment only if the
+     * <i>serialized</i> form of the class changes.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /*
+     * Ordinals for various reasons why an Error of this type can be thrown.
+     */
+
+    /**
+     * Lexical error occurred.
+     */
+    static final int LEXICAL_ERROR = 0;
+
+    /**
+     * An attempt was made to create a second instance of a static token manager.
+     */
+    static final int STATIC_LEXER_ERROR = 1;
+
+    /**
+     * Tried to change to an invalid lexical state.
+     */
+    static final int INVALID_LEXICAL_STATE = 2;
+
+    /**
+     * Detected (and bailed out of) an infinite loop in the token manager.
+     */
+    static final int LOOP_DETECTED = 3;
+
+    /**
+     * Indicates the reason why the exception is thrown. It will have one of the
+     * above 4 values.
+     */
+    int errorCode;
+
+    /**
+     * Replaces unprintable characters by their escaped (or unicode escaped)
+     * equivalents in the given string
+     */
+    protected static final String addEscapes(String str) {
+        StringBuffer retval = new StringBuffer();
+        char ch;
+        for (int i = 0; i < str.length(); i++) {
+            switch (str.charAt(i)) {
+            case 0:
+                continue;
+            case '\b':
+                retval.append("\\b");
+                continue;
+            case '\t':
+                retval.append("\\t");
+                continue;
+            case '\n':
+                retval.append("\\n");
+                continue;
+            case '\f':
+                retval.append("\\f");
+                continue;
+            case '\r':
+                retval.append("\\r");
+                continue;
+            case '\"':
+                retval.append("\\\"");
+                continue;
+            case '\'':
+                retval.append("\\\'");
+                continue;
+            case '\\':
+                retval.append("\\\\");
+                continue;
+            default:
+                if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                    String s = "0000" + Integer.toString(ch, 16);
+                    retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+                } else {
+                    retval.append(ch);
+                }
+                continue;
+            }
+        }
+        return retval.toString();
+    }
+
+    /**
+     * Returns a detailed message for the Error when it is thrown by the token
+     * manager to indicate a lexical error. Parameters : EOFSeen : indicates if EOF
+     * caused the lexical error curLexState : lexical state in which this error
+     * occurred errorLine : line number when the error occurred errorColumn : column
+     * number when the error occurred errorAfter : prefix that was seen before this
+     * error occurred curchar : the offending character Note: You can customize the
+     * lexical error message by modifying this method.
+     */
+    protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn,
+            String errorAfter, char curChar) {
+        return ("Lexical error at line " + errorLine + ", column " + errorColumn + ".  Encountered: "
+                + (EOFSeen ? "<EOF> "
+                        : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int) curChar + "), ")
+                + "after : \"" + addEscapes(errorAfter) + "\"");
+    }
+
+    /**
+     * You can also modify the body of this method to customize your error messages.
+     * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not of
+     * end-users concern, so you can return something like :
+     *
+     * "Internal Error : Please file a bug report .... "
+     *
+     * from this method for such cases in the release version of your parser.
+     */
+    public String getMessage() {
+        return super.getMessage();
+    }
+
+    /*
+     * Constructors of various flavors follow.
+     */
+
+    /** No arg constructor. */
+    public TokenMgrError() {
+    }
+
+    /** Constructor with message and reason. */
+    public TokenMgrError(String message, int reason) {
+        super(message);
+        errorCode = reason;
+    }
+
+    /** Full Constructor. */
+    public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar,
+            int reason) {
+        this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+    }
+}
+/*
+ * JavaCC - OriginalChecksum=cb6190a073e75e5f61dac8e989eb2b78 (do not edit this
+ * line)
+ */
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/node/Node.java b/src/main/java/org/apache/sling/resource/filter/impl/node/Node.java
new file mode 100644
index 0000000..a9933f6
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/node/Node.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed 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.sling.resource.filter.impl.node;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.resource.filter.api.Visitor;
+
+/**
+ * 
+ */
+public class Node {
+
+    public String text;
+    public Node leftNode;
+    public Node rightNode;
+    public List<Node> children = Collections.emptyList();
+    public int kind;
+
+    /**
+     * creates a node which represents a literal
+     * 
+     * @param value
+     */
+    public Node(int kind, String text) {
+        this.kind = kind;
+        this.text = text;
+    }
+
+    /**
+     * creates a logical node
+     * 
+     * @param value
+     */
+    public Node(int kind, List<Node> children) {
+        this.kind = kind;
+        this.children = children;
+    }
+
+    /**
+     * Node with children
+     * 
+     * @param value
+     */
+    public Node(int kind, String text, List<Node> children) {
+        this.kind = kind;
+        this.text = text;
+        this.children = children;
+    }
+
+    /**
+     * Node used for comparison
+     * 
+     * @param kind
+     *            nominally of type comparison
+     * @param operator
+     *            defines the type of comparison
+     * @param leftValue
+     *            basis of comparison
+     * @param rightValue
+     *            to be compared to
+     */
+    public Node(int kind, String operator, Node leftValue, Node rightValue) {
+        this.kind = kind;
+        this.text = operator;
+        this.leftNode = leftValue;
+        this.rightNode = rightValue;
+    }
+
+    public <R> R accept(Visitor<R> visitor) {
+        return visitor.visit(this);
+    }
+
+    @Override
+    public String toString() {
+        return text
+                + children.stream().map(Node::toString).collect(Collectors.joining(text, "(", ")"));
+    }
+
+    public <R> List<R> visitChildren(Visitor<R> visitor) {
+        return children.stream().map(child -> child.accept(visitor)).collect(Collectors.toList());
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/predicates/ComparisonPredicateFactory.java b/src/main/java/org/apache/sling/resource/filter/impl/predicates/ComparisonPredicateFactory.java
new file mode 100644
index 0000000..222f86f
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/predicates/ComparisonPredicateFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed 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.sling.resource.filter.impl.predicates;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.impl.FilterParserConstants;
+
+public class ComparisonPredicateFactory {
+
+    public static Predicate<Resource> toPredicate(int kind, Function<Resource, Object> leftValue,
+            Function<Resource, Object> rightValue) {
+        switch (kind) {
+        case FilterParserConstants.EQUAL:
+            return ComparisonPredicates.is(leftValue, rightValue);
+        case FilterParserConstants.NOT_EQUAL:
+            return ComparisonPredicates.isNot(leftValue, rightValue);
+        case FilterParserConstants.GREATER_THAN:
+            return ComparisonPredicates.gt(leftValue, rightValue);
+        case FilterParserConstants.GREATER_THAN_OR_EQUAL:
+            return ComparisonPredicates.gte(leftValue, rightValue);
+        case FilterParserConstants.LESS_THAN:
+            return ComparisonPredicates.lt(leftValue, rightValue);
+        case FilterParserConstants.LESS_THAN_OR_EQUAL:
+            return ComparisonPredicates.lte(leftValue, rightValue);
+        case FilterParserConstants.LIKE:
+            return ComparisonPredicates.like(leftValue, rightValue);
+        case FilterParserConstants.LIKE_NOT:
+            return ComparisonPredicates.like(leftValue, rightValue).negate();
+        case FilterParserConstants.CONTAINS:
+            return ComparisonPredicates.contains(leftValue, rightValue);
+        case FilterParserConstants.CONTAINS_NOT:
+            return ComparisonPredicates.contains(leftValue, rightValue).negate();
+        case FilterParserConstants.CONTAINS_ANY:
+            return ComparisonPredicates.containsAny(leftValue, rightValue);
+        case FilterParserConstants.CONTAINS_NOT_ANY:
+            return ComparisonPredicates.containsAny(leftValue, rightValue).negate();
+        case FilterParserConstants.IN:
+            return ComparisonPredicates.in(leftValue, rightValue);
+        case FilterParserConstants.NOT_IN:
+            return ComparisonPredicates.in(leftValue, rightValue).negate();
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/predicates/ComparisonPredicates.java b/src/main/java/org/apache/sling/resource/filter/impl/predicates/ComparisonPredicates.java
new file mode 100644
index 0000000..afbb88e
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/predicates/ComparisonPredicates.java
@@ -0,0 +1,387 @@
+/*
+ * Licensed 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.sling.resource.filter.impl.predicates;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Predicates to handle comparisons that are defined in the filter language
+ *
+ */
+public class ComparisonPredicates {
+
+    /**
+     * Values are converted to Strings.
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if right hand String is equal to left hand String
+     */
+    public static Predicate<Resource> is(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(rhs, "statement may not be null");
+        return resource -> {
+            CharSequence lhValue = ComparisonPredicates.getString(lhs.apply(resource));
+            CharSequence rhValue = ComparisonPredicates.getString(rhs.apply(resource));
+            if (lhValue == null || rhValue == null) {
+                return (rhValue instanceof Null || lhValue instanceof Null);
+            }
+            return lhValue.equals(rhValue);
+        };
+
+    }
+
+    /**
+     * Values are converted to Strings.
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if right hand String is equal to left hand String
+     */
+    public static Predicate<Resource> isNot(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(rhs, "statement may not be null");
+        return resource -> {
+            CharSequence lhValue = ComparisonPredicates.getString(lhs.apply(resource));
+            CharSequence rhValue = ComparisonPredicates.getString(rhs.apply(resource));
+            if (lhValue == null || rhValue == null) {
+                return false;
+            }
+            return !lhValue.equals(rhValue);
+        };
+
+    }
+
+    /**
+     * Values are converted to Strings. Right hand value is treated as a Regular
+     * expression.
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if right hand value pattern matches the left hand value
+     */
+    public static Predicate<Resource> like(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(rhs, "value may not be null");
+        return resource -> {
+            CharSequence lhValue = ComparisonPredicates.getString(lhs.apply(resource));
+            CharSequence rhValue = ComparisonPredicates.getString(rhs.apply(resource));
+            if (lhValue == null || rhValue == null) {
+                return false;
+            }
+            return Pattern.matches(rhValue.toString(), lhValue);
+        };
+
+    }
+
+    /**
+     * Values are converted to a Number, and then additionally converted to a common
+     * type as the basis of comparison
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if left hand value is greater than right hand value
+     */
+    @SuppressWarnings("unchecked")
+    public static Predicate<Resource> gt(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(rhs, "statement may not be null");
+        return resource -> {
+            Number lhValue = ComparisonPredicates.getNumber(lhs.apply(resource));
+            Number rhValue = ComparisonPredicates.getNumber(rhs.apply(resource));
+            if (lhValue == null || rhValue == null) {
+                return false;
+            }
+            lhValue = standardizeNumbers(lhValue, rhValue.getClass());
+            rhValue = standardizeNumbers(rhValue, lhValue.getClass());
+            if (lhValue instanceof Comparable) {
+                return ((Comparable<Number>) lhValue).compareTo(rhValue) > 0;
+            }
+            return false;
+        };
+
+    }
+
+    /**
+     * Values are converted to a Number, and then additionally converted to a common
+     * type as the basis of comparison
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if left hand value is greater than or equal to right hand value
+     */
+    @SuppressWarnings("unchecked")
+    public static Predicate<Resource> gte(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(rhs, "statement may not be null");
+        return resource -> {
+            Number lhValue = ComparisonPredicates.getNumber(lhs.apply(resource));
+            Number rhValue = ComparisonPredicates.getNumber(rhs.apply(resource));
+            if (lhValue == null || rhValue == null) {
+                return false;
+            }
+            lhValue = standardizeNumbers(lhValue, rhValue.getClass());
+            rhValue = standardizeNumbers(rhValue, lhValue.getClass());
+            if (lhValue instanceof Comparable) {
+                return ((Comparable<Number>) lhValue).compareTo(rhValue) >= 0;
+            }
+            return false;
+        };
+    }
+
+    /**
+     * Values are converted to a Number, and then additionally converted to a common
+     * type as the basis of comparison
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if left hand value is less than right hand value
+     */
+    @SuppressWarnings("unchecked")
+    public static Predicate<Resource> lt(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(rhs, "type value may not be null");
+        return resource -> {
+            Number lhValue = ComparisonPredicates.getNumber(lhs.apply(resource));
+            Number rhValue = ComparisonPredicates.getNumber(rhs.apply(resource));
+            if (lhValue == null || rhValue == null) {
+                return false;
+            }
+            lhValue = standardizeNumbers(lhValue, rhValue.getClass());
+            rhValue = standardizeNumbers(rhValue, lhValue.getClass());
+            if (lhValue instanceof Comparable) {
+                return ((Comparable<Number>) lhValue).compareTo(rhValue) < 0;
+            }
+            return false;
+        };
+
+    }
+
+    /**
+     * Values are converted to a Number, and then additionally converted to a common
+     * type as the basis of comparison
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if left hand value is less than or equal to right hand value
+     */
+    @SuppressWarnings("unchecked")
+    public static Predicate<Resource> lte(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(rhs, "statement may not be null");
+        return resource -> {
+            Number lhValue = ComparisonPredicates.getNumber(lhs.apply(resource));
+            Number rhValue = ComparisonPredicates.getNumber(rhs.apply(resource));
+            if (lhValue == null || rhValue == null) {
+                return false;
+            }
+            lhValue = standardizeNumbers(lhValue, rhValue.getClass());
+            rhValue = standardizeNumbers(rhValue, lhValue.getClass());
+            if (lhValue instanceof Comparable) {
+                return ((Comparable<Number>) lhValue).compareTo(rhValue) <= 0;
+            }
+            return false;
+        };
+    }
+
+    /**
+     * Right and Left values are converted to String arrays
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if left hand values are a subset of right hand values
+     */
+    public static Predicate<Resource> contains(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(rhs, "statement may not be null");
+        return resource -> {
+            String[] lhValues = adaptToArray(lhs.apply(resource));
+            String[] rhValues = adaptToArray(rhs.apply(resource));
+            if (lhValues == null || rhValues == null) {
+                return false;
+            }
+            if (lhValues.length < rhValues.length) {
+                return false;
+            }
+            for (String rhValue : rhValues) {
+                innerLoop: {
+                    for (String lhValue : lhValues) {
+                        if (lhValue.equals(rhValue)) {
+                            break innerLoop;
+                        }
+                    }
+                    return false;
+                }
+            }
+            // reaches here only if every rhValue was successfully found in
+            // lhValues
+            return true;
+        };
+    }
+
+    /**
+     * Right and Left values are converted to String arrays
+     * 
+     * @param lhs
+     *            Function which provides comparison value
+     * @param rhs
+     *            Function which provides comparison value
+     * @return true if the left hand values matches any of the right hand values
+     */
+    public static Predicate<Resource> containsAny(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        return resource -> {
+            String[] lhValues = adaptToArray(lhs.apply(resource));
+            String[] rhValues = adaptToArray(rhs.apply(resource));
+            if (lhValues == null || rhValues == null) {
+                return false;
+            }
+            for (String rhValue : rhValues) {
+                for (String lhValue : lhValues) {
+                    if (lhValue.equals(rhValue)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        };
+    }
+
+    /**
+     * Right and Left values are converted to String arrays
+     * 
+     * @param lhs
+     *            Function which provides value for comparison
+     * @param rhs
+     *            Function which provides value for comparison
+     * @return true if left hand values are a subset of right hand values
+     */
+    public static Predicate<Resource> in(Function<Resource, Object> lhs, Function<Resource, Object> rhs) {
+        Objects.requireNonNull(lhs, "left hand statement may not be null");
+        Objects.requireNonNull(rhs, "right hand statement may not be null");
+        return resource -> {
+            String[] lhValues = adaptToArray(lhs.apply(resource));
+            String[] rhValues = adaptToArray(rhs.apply(resource));
+            if (lhValues == null || rhValues == null) {
+                return false;
+            }
+            for (String lhValue : lhValues) {
+                innerLoop: {
+                    for (String rhValue : rhValues) {
+                        if (rhValue.equals(lhValue)) {
+                            break innerLoop;
+                        }
+                    }
+                    return false;
+                }
+            }
+            // reaches here only if every lhValue was successfully found in
+            // rhValues
+            return true;
+        };
+    }
+
+    private static Number standardizeNumbers(Number value, Class<? extends Number> klass) {
+        if (value.getClass() == klass || value instanceof BigDecimal) {
+            return value;
+        }
+        if (value instanceof Double) {
+            return BigDecimal.valueOf(value.doubleValue());
+        }
+        if (value instanceof Null) {
+            return Double.NaN;
+        }
+        return BigDecimal.valueOf(value.longValue());
+    }
+
+    private static String[] adaptToArray(Object arr) {
+        if (arr instanceof String[] || arr == null) {
+            return (String[]) arr;
+        }
+        ArrayList<CharSequence> response = new ArrayList<>();
+        if (arr.getClass().isArray()) {
+            for (Object thing : (Object[]) arr) {
+                response.add(ComparisonPredicates.getString(thing));
+            }
+        } else {
+            response.add(ComparisonPredicates.getString(arr));
+        }
+        return response.toArray(new String[] {});
+    }
+
+    private static CharSequence getString(final Object initialValue) {
+        if (initialValue == null) {
+            return null;
+        }
+        if (initialValue instanceof CharSequence) {
+            return (CharSequence) initialValue;
+        } else if (initialValue instanceof Instant) {
+            return ((Instant) initialValue).atOffset(ZoneOffset.UTC).toString();
+        } else {
+            return initialValue.toString();
+        }
+
+    }
+
+    private static Number getNumber(final Object initialValue) {
+        if (initialValue == null) {
+            return null;
+        }
+
+        if (initialValue instanceof Number) {
+            return (Number) initialValue;
+        }
+
+        if (initialValue instanceof Instant) {
+            return ((Instant) initialValue).toEpochMilli();
+        } else {
+            String value = initialValue.toString();
+            try {
+                return Integer.valueOf(value);
+            } catch (NumberFormatException nfe) {
+                try {
+                    return new BigDecimal(value);
+                } catch (NumberFormatException nfe2) {
+                    try {
+                        return LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME).toInstant(ZoneOffset.UTC)
+                                .toEpochMilli();
+                    } catch (DateTimeParseException dtpe) {
+                        // swallow
+                        return null;
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/impl/predicates/Null.java b/src/main/java/org/apache/sling/resource/filter/impl/predicates/Null.java
new file mode 100644
index 0000000..066501c
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/impl/predicates/Null.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed 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.sling.resource.filter.impl.predicates;
+
+/**
+ * 
+ * @author jabail
+ *
+ */
+public class Null extends Number implements CharSequence {
+
+    /**
+     * The version identifier for this Serializable class. Increment only if the
+     * <i>serialized</i> form of the class changes.
+     */
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object obj) {
+        return false;
+    }
+
+    @Override
+    public int length() {
+        return 0;
+    }
+
+    @Override
+    public char charAt(int index) {
+        return 0;
+    }
+
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        return null;
+    }
+
+    @Override
+    public int intValue() {
+        return 0;
+    }
+
+    @Override
+    public long longValue() {
+        return 0;
+    }
+
+    @Override
+    public float floatValue() {
+        return 0;
+    }
+
+    @Override
+    public double doubleValue() {
+        return Double.NaN;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resource/filter/package-info.java b/src/main/java/org/apache/sling/resource/filter/package-info.java
new file mode 100644
index 0000000..404537e
--- /dev/null
+++ b/src/main/java/org/apache/sling/resource/filter/package-info.java
@@ -0,0 +1,15 @@
+/*
+ * Licensed 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.
+ */
+@org.osgi.annotation.versioning.Version("0.0.1")
+package org.apache.sling.resource.filter;
\ No newline at end of file
diff --git a/src/main/javacc/resourceFilter.jj b/src/main/javacc/resourceFilter.jj
new file mode 100644
index 0000000..4c73a2c
--- /dev/null
+++ b/src/main/javacc/resourceFilter.jj
@@ -0,0 +1,338 @@
+/*
+ *
+ * Licensed 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
+{
+  STATIC = false;
+  UNICODE_INPUT = true;
+}
+
+PARSER_BEGIN(FilterParser)
+package org.apache.sling.resource.filter.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.sling.resource.filter.impl.node.*;
+
+public final class FilterParser
+{
+}
+
+PARSER_END(FilterParser)
+
+SKIP :
+{
+  " "
+| "\t"
+}
+
+TOKEN :
+{
+  < #PLUS: "+" >
+| < #MINUS: "-" >
+| < #DIGIT: [ "0"-"9" ] >
+| < #EXP: ["e","E"] ( < PLUS > | < MINUS > )? >
+| < #OFFSET: ["+","-"]["0"-"2"] <DIGIT> ":" ["0"-"5"] <DIGIT> >
+| < #YYYYMMDD: <INTEGER> <INTEGER> <INTEGER> <INTEGER> <MINUS> ["0"-"1"] <INTEGER> <MINUS> ["0"-"3"] <DIGIT> >
+| < #TIME: ["0"-"2"] <DIGIT> ":" ["0"-"5"] <DIGIT> ( ":" ["0"-"5"] <DIGIT> ("." <DIGIT>  <DIGIT>  <DIGIT>)? )? >
+| < OFFSETDATETIME: <YYYYMMDD> "T" <TIME> <OFFSET> >
+| < DATETIME: <YYYYMMDD> "T" <TIME> >
+| < DATE: <YYYYMMDD> >
+| < NUMBER:  <INTEGER> | <INTEGER> <FRACTIONAL_DIGITS> | <INTEGER> <EXPONENT> | <INTEGER> <FRACTIONAL_DIGITS> <EXPONENT> >
+| < INTEGER: (<MINUS>)? ( <DIGITS> ) >
+| < FRACTIONAL_DIGITS: "." <DIGITS> >
+| < EXPONENT: <EXP> <DIGITS> >
+| < DIGITS: ( <DIGIT> )+ >
+}
+
+TOKEN :
+{
+  < STRING : < SQUOTE > | < DQUOTE > >{matchedToken.image = image.substring(1, lengthOfMatch - 1);}
+| < #SQUOTE :
+    (
+      "'"
+      ( "\\" ~[ ] | ~[ "'", "\\" ] )*
+      "'"
+    ) >
+| < #DQUOTE :
+    (
+      "\""
+      ( "\\" ~[ ] | ~[ "\"", "\\" ] )*
+      "\""
+    ) >
+}
+
+TOKEN :
+{
+  < AND :
+    (
+      "&&"
+    | "and"
+    ) >
+| < OR :
+    (
+      "||"
+    | "or"
+    ) >
+| < NULL : "null" >
+| < LPAREN : "(" >
+| < RPAREN : ")" >
+| < COMMA : "," >
+| < BOOLEAN: "true" | "false" >
+| < EQUAL : "==" | "is" >
+| < NOT_EQUAL : "!=" | "is not" >
+| < GREATER_THAN : ">" | "greater than" >
+| < GREATER_THAN_OR_EQUAL : ">=" >
+| < LESS_THAN : "<" | "less than" >
+| < LESS_THAN_OR_EQUAL : "<=" >
+| < LIKE : "~=" | "like" >
+| < LIKE_NOT : "like not" | "not like" >
+| < CONTAINS : "contains" >
+| < CONTAINS_NOT : "contains not" >
+| < CONTAINS_ANY : "contains any" >
+| < CONTAINS_NOT_ANY : "contains not any" >
+| < IN : "in" >
+| < NOT_IN : "not in" >
+| < DYNAMIC_ARG : "$" >
+| < FUNCTION_NAME : (~[ "\"", "'", "$", "(", ")", ",", "=", "<", ">", " ", "[", "]","+","-",".","0"-"9" ])+ >
+| < PROPERTY: "[" ( "\\" ~[ ] | ~[ "]", "\\" ] )* "]" >{matchedToken.image = image.substring(1, lengthOfMatch - 1);}
+}
+
+<*> TOKEN : { <UNKNOWN: ~[]> }
+
+Node parse() :
+{
+  final Node node;
+}
+{
+  node = or() < EOF >
+  {
+    return node;
+  }
+}
+
+Node or() :
+{
+  final List < Node > nodes = new ArrayList < Node > (3);
+  Node node;
+}
+{
+  node = and()
+  {
+    nodes.add(node);
+  }
+  (
+    < OR > node = and()
+    {
+      nodes.add(node);
+    }
+  )*
+  {
+    return nodes.size() != 1 ? new Node(FilterParserConstants.OR, nodes) : nodes.get(0);
+  }
+}
+
+Node and() :
+{
+  final List < Node > nodes = new ArrayList < Node > (3);
+  Node node;
+}
+{
+  node = constraint()
+  {
+    nodes.add(node);
+  }
+  (
+    < AND > node = constraint()
+    {
+      nodes.add(node);
+    }
+  )*
+  {
+    return nodes.size() != 1 ? new Node(FilterParserConstants.AND, nodes) : nodes.get(0);
+  }
+}
+
+Node constraint() :
+{
+  final Node node;
+}
+{
+  (
+    node = group()
+  | node = comparison()
+  )
+  {
+    return node;
+  }
+}
+
+Node group() :
+{
+  final Node node;
+}
+{
+  < LPAREN > node = or() < RPAREN >
+  {
+    return node;
+  }
+}
+
+Node comparison() :
+{
+  Node leftValue;
+  Token op;
+  Node rightValue;
+}
+{
+  leftValue = argument()
+  op = comparisonValue()
+  rightValue = argument()
+  {
+    return new Node(op.kind, op.image, leftValue, rightValue);
+  }
+}
+
+Token comparisonValue() :
+{
+}
+{
+  (
+     < EQUAL >
+  |  < NOT_EQUAL >
+  |  < GREATER_THAN >
+  |  < GREATER_THAN_OR_EQUAL >
+  |  < LESS_THAN >
+  |  < LESS_THAN_OR_EQUAL >
+  |  < LIKE >
+  |  < LIKE_NOT >
+  |  < CONTAINS >
+  |  < CONTAINS_NOT >
+  |  < CONTAINS_ANY >
+  |  < CONTAINS_NOT_ANY >
+  |  < IN >
+  |  < NOT_IN >
+  )
+  {
+    return token;
+  }
+}
+
+
+List < Node > Arguments() :
+{
+  Object value = new ArrayList();
+}
+{
+  
+  < LPAREN > [ value = commaSepArguments() ] < RPAREN >
+  {
+    return (List) value;
+  }
+}
+
+List < Node > commaSepArguments() :
+{
+  final List < Node > list = new ArrayList < Node > (3);
+  Node arg;
+}
+{
+  arg = argument()
+  {
+    list.add(arg);
+  }
+  (
+    < COMMA > 
+    arg = argument()
+    {
+      list.add(arg);
+    }
+  )*
+  {
+    return list;
+  }
+}
+
+Node argument() :
+{
+  Node selector = null;
+}
+{
+  (
+    selector = literal()
+  | selector = property()
+  | selector = dynamicArg()
+  | selector = function()
+
+  )
+  {
+    return selector;
+  }
+}
+
+Node function() :
+{
+  String functionName = null;
+  List < Node > children = null;
+}
+{
+  < FUNCTION_NAME >
+  {
+    functionName = token.image;
+  }
+  < LPAREN > [ children = commaSepArguments() ] < RPAREN >
+  {
+    return new Node(FilterParserConstants.FUNCTION_NAME, functionName, children);
+  }
+}
+
+Node dynamicArg() :
+{
+  String functionName = null;
+}
+{
+  < DYNAMIC_ARG >< FUNCTION_NAME >
+  {
+    functionName = token.image;
+    return new Node(FilterParserConstants.DYNAMIC_ARG, functionName);
+  }
+}
+
+Node literal() :
+{
+}
+{
+  (
+    < STRING >
+  | < NUMBER >
+  | < NULL >
+  | < BOOLEAN >
+  | < DATE >
+  | < DATETIME >
+  | < OFFSETDATETIME >
+  )
+  {
+    return new Node(token.kind, token.image);
+  }
+}
+
+Node property() :
+{
+}
+{
+  < PROPERTY > 
+  {
+    return new Node(token.kind, token.image);
+  }
+}
diff --git a/src/test/java/org/apache/sling/resource/filter/ResourceFilterArgTest.java b/src/test/java/org/apache/sling/resource/filter/ResourceFilterArgTest.java
new file mode 100644
index 0000000..cfb0017
--- /dev/null
+++ b/src/test/java/org/apache/sling/resource/filter/ResourceFilterArgTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed 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.sling.resource.filter;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.ResourceFilter;
+import org.apache.sling.resource.filter.ResourceStream;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ResourceFilterArgTest {
+
+    private static final String START_PATH = "/content/sample/en";
+    @Rule
+    public final SlingContext context = new SlingContext();
+
+    @Before
+    public void setUp() throws ParseException {
+        context.load().json("/data.json", "/content/sample/en");
+    }
+
+    @Test
+    public void testMatchingNameInArg() throws Exception {
+        ResourceFilter filter = new ResourceFilter("[jcr:content/jcr:title] == $lang")
+                .addArgument("date", "2013-08-08T16:32:59").addArgument("lang", "Mongolian");
+        List<Resource> found = handle(START_PATH, filter);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testMatchingNameAndMultipleArgs() throws Exception {
+        ResourceFilter filter = new ResourceFilter("[jcr:content/created] > $date and [jcr:content/jcr:title] == $lang")
+                .addArgument("date", "2013-08-08T16:32:59").addArgument("lang", "Mongolian");
+        List<Resource> found = handle(START_PATH, filter);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testNameFunctionAgainstRegex() throws ParseException, Exception {
+        ResourceFilter query = new ResourceFilter("name() like $regex").addArgument("regex", "testpage[1-2]");
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(2, found.size());
+    }
+
+    private List<Resource> handle(String startPath, ResourceFilter filter) {
+        Resource resource = context.resourceResolver().getResource(startPath);
+        return new ResourceStream(resource).stream(r -> true).filter(filter).collect(Collectors.toList());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/resource/filter/ResourceFilterDateTest.java b/src/test/java/org/apache/sling/resource/filter/ResourceFilterDateTest.java
new file mode 100644
index 0000000..69eb00c
--- /dev/null
+++ b/src/test/java/org/apache/sling/resource/filter/ResourceFilterDateTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed 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.sling.resource.filter;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.ResourceFilterStream;
+import org.apache.sling.resource.filter.impl.ParseException;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ResourceFilterDateTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+
+    private static String START_PATH = "/content/sample/en";
+
+    @Before
+    public void setUp() throws ParseException, java.text.ParseException {
+        context.load().json("/data.json", "/content/sample/en");
+    }
+
+    @Test
+    public void testPropLessThanDateFunction() throws ParseException {
+        String query = "[jcr:content/created] < date('2013-08-08T16:32:59.000+02:00')";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] < 2013-08-08T16:32:59.000";
+        found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] < 2013-08-08T16:32";
+        found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] < date('2013-08-08','yyyy-MM-dd')";
+        found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] less than 2013-08-07T14:32:59";
+        found = handle(START_PATH, query);
+        assertEquals(2, found.size());
+
+        query = "[jcr:content/created] <= 2013-08-07T14:32:59";
+        found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] <= 2013-08-07T14:32";
+        found = handle(START_PATH, query);
+        assertEquals(2, found.size());
+
+        query = "[jcr:content/created] < 2013-08-07T14:32:59.010";
+        found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] > 2013-08-07T14:32";
+        found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] greater than 2013-08-07T14:32:59";
+        found = handle(START_PATH, query);
+        assertEquals(2, found.size());
+
+        query = "[jcr:content/created] >= 2013-08-07T14:32:59";
+        found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] like '2013-08-07.*'";
+        found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+
+        query = "[jcr:content/created] like '201[2-5].*'";
+        found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    private List<Resource> handle(String path, String filter) throws ParseException {
+        Resource resource = context.resourceResolver().getResource(path);
+        return new ResourceFilterStream(resource).stream(r -> true).filter(new ResourceFilter(filter))
+                .collect(Collectors.toList());
+    }
+}
diff --git a/src/test/java/org/apache/sling/resource/filter/ResourceFilterLogicTest.java b/src/test/java/org/apache/sling/resource/filter/ResourceFilterLogicTest.java
new file mode 100644
index 0000000..99f6591
--- /dev/null
+++ b/src/test/java/org/apache/sling/resource/filter/ResourceFilterLogicTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed 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.sling.resource.filter;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.ResourceFilterStream;
+import org.apache.sling.resource.filter.impl.ParseException;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ResourceFilterLogicTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+
+    private static String START_PATH = "/content/sample/en";
+
+    @Before
+    public void setUp() throws ParseException, java.text.ParseException {
+        context.load().json("/data.json", START_PATH);
+    }
+
+    @Test
+    public void testDateAndProperty() throws ParseException {
+        String query = "[jcr:content/created] > '2013-08-08T16:32:59' and [jcr:content/jcr:title] == 'Mongolian'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+
+        query = "([jcr:content/created] < '2013-08-08T16:32' and [jcr:content/jcr:title] == 'English') or [jcr:content/jcr:title] == 'Mongolian'";
+        found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+
+        query = "[jcr:content/created] < '2013-08-08T16:32' and ([jcr:content/jcr:title] == 'English' or [jcr:content/jcr:title] == 'Mongolian')";
+        found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+
+        query = "[jcr:content/created] < date('2013-08-08T16:32:59.000+02:00') or [jcr:content/jcr:title] == 'Mongolian'";
+        found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    private List<Resource> handle(String path, String filter) throws ParseException {
+        Resource resource = context.resourceResolver().getResource(path);
+        return new ResourceFilterStream(resource).stream(r -> true).filter(new ResourceFilter(filter))
+                .collect(Collectors.toList());
+    }
+}
diff --git a/src/test/java/org/apache/sling/resource/filter/ResourceFilterTest.java b/src/test/java/org/apache/sling/resource/filter/ResourceFilterTest.java
new file mode 100644
index 0000000..1fc6cc8
--- /dev/null
+++ b/src/test/java/org/apache/sling/resource/filter/ResourceFilterTest.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed 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.sling.resource.filter;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.filter.ResourceFilter;
+import org.apache.sling.resource.filter.ResourceStream;
+import org.apache.sling.resource.filter.impl.ParseException;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ResourceFilterTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+
+    private static String START_PATH = "/content/sample/en";
+    private Date midPoint;
+
+    private static String DATE_STRING = "Thu Aug 07 2013 16:32:59 GMT+0200";
+    private static String NEW_DATE = "2013-08-08T16:32:59.000+02:00";
+    private static String DATE_FORMAT = "EEE MMM dd yyyy HH:mm:ss 'GMT'Z";
+
+    @Before
+    public void setUp() throws ParseException, java.text.ParseException {
+        context.load().json("/data.json", "/content/sample/en");
+        midPoint = new SimpleDateFormat(DATE_FORMAT).parse(DATE_STRING);
+    }
+
+    @Test
+    public void testPropertyEquals() throws ParseException {
+        String query = "[jcr:content/jcr:title] == 'English'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    @Test
+    public void testPropertyIs() throws ParseException {
+        String query = "[jcr:content/jcr:title] is 'English'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    @Test
+    public void testDateBeforeValue() throws ParseException {
+        String query = "[jcr:content/created] < date('2013-08-08T16:32:59.000+02:00')";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+    }
+
+    @Test
+    public void testDateBeforeValue2() throws ParseException {
+        String query = "[jcr:content/created] less than date('2013-08-08T16:32:59.000+02:00')";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+    }
+
+    @Test
+    public void testDateBeforeValue3() throws ParseException {
+        String query = "[jcr:content/created] < date('2013-08-08','yyyy-MM-dd')";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+    }
+
+    @Test
+    public void testDateAndProperty() throws ParseException {
+        String query = "[jcr:content/created] < date('2013-08-08T16:32:59.000+02:00') and [jcr:content/jcr:title] == 'English'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+    }
+
+    @Test
+    public void testDateAndPropertyTwice() throws ParseException {
+        String query = "([jcr:content/created] < date('2013-08-08T16:32:59.000+02:00') and [jcr:content/jcr:title] == 'English') or [jcr:content/jcr:title] == 'Mongolian'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    @Test
+    public void testDateOrProperty() throws ParseException {
+        String query = "[jcr:content/created] < date('2013-08-08T16:32:59.000+02:00') or [jcr:content/jcr:title] == 'Mongolian'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    @Test
+    public void testDateAsString() throws ParseException {
+        String query = "[jcr:content/created] < '2013-08-08T16:32'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(3, found.size());
+    }
+
+    @Test
+    public void testNullPropertyAndLimit() throws ParseException {
+        String query = "[jcr:content/foo] == null ";
+        Resource resource = context.resourceResolver().getResource(START_PATH);
+        List<Resource> found = new ResourceStream(resource).stream(r -> true).filter(new ResourceFilter(query)).limit(3)
+                .collect(Collectors.toList());
+        assertEquals(3, found.size());
+    }
+
+    @Test
+    public void testNullProperty() throws ParseException {
+        String query = "[jcr:content/foo] == null ";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(20, found.size());
+    }
+
+    @Test
+    public void testNumberLiteral() throws ParseException {
+        String query = "[count] < 2 ";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testNumberLiteral2() throws ParseException {
+        String query = "[count] < 2 or [count] > 1";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testNumberLiteral3() throws ParseException {
+        String query = "[views] < 7 ";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testNotNullProperty() throws ParseException {
+        String query = "[layout] != null ";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(5, found.size());
+    }
+
+    @Test
+    public void testNotProperty() throws ParseException {
+        String query = "[layout] != 'foo' ";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    @Test
+    public void testNameFunctionIs() throws ParseException {
+        String query = "name() == 'testpage1'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testNameFunctionAgainstRegex() throws ParseException {
+        String query = "name() like 'testpage.*'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    @Test
+    public void testNameFunctionAgainstRegex2() throws ParseException {
+        String query = "name() like 'testpage[1-2]'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(2, found.size());
+    }
+
+    @Test
+    public void testChildExistence() throws ParseException {
+        String query = "name() == 'testpage3' ";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testBoolean() throws ParseException {
+        String query = "[published] == true";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testContains() throws ParseException {
+        String query = "[jcr:content/monkey] contains 'fish'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testContainsNot() throws ParseException {
+        String query = "[jcr:content/monkey] contains not 'fish'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(19, found.size());
+    }
+
+    @Test
+    public void testIn() throws ParseException {
+        String query = "'fish' in [jcr:content/monkey]";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testPathLike() throws ParseException {
+        String query = "path() like '/content/sample/en/testpage1.*'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(4, found.size());
+    }
+
+    @Test
+    public void testPathLike2() throws ParseException {
+        String query = "path() like '/content/sample/en/testpage1'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testPathLike3() throws ParseException {
+        String query = "path() is '/content/sample/en/testpage1'";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(1, found.size());
+    }
+
+    @Test
+    public void testNotIn() throws ParseException {
+        String query = "'fish' not in [jcr:content/monkey]";
+        List<Resource> found = handle(START_PATH, query);
+        assertEquals(19, found.size());
+    }
+
+    @Test
+    public void testInNotException() throws ParseException {
+        ParseException error = null;
+        try {
+            String query = "'fish' in not [jcr:content/monkey]";
+            handle(START_PATH, query);
+        } catch (ParseException e) {
+            error = e;
+        }
+        assert (error.getMessage()
+                .startsWith("Encountered \" <PROPERTY> \"jcr:content/monkey \"\" at line 1, column 15."));
+    }
+
+    private List<Resource> handle(String path, String filter) throws ParseException {
+        Resource resource = context.resourceResolver().getResource(path);
+        Predicate<Resource> f = new ResourceFilter(filter);
+        return new ResourceStream(resource).stream(r -> true).filter(f).collect(Collectors.toList());
+    }
+}
diff --git a/src/test/resources/data.json b/src/test/resources/data.json
new file mode 100644
index 0000000..8778ad5
--- /dev/null
+++ b/src/test/resources/data.json
@@ -0,0 +1,105 @@
+{
+	"jcr:primaryType": "app:Page",
+	"jcr:content": {
+		"jcr:primaryType": "app:PageContent",
+		"jcr:title": "English",
+		"app:template": "/apps/sample/templates/homepage",
+		"sling:resourceType": "sample/components/homepage",
+		"jcr:createdBy": "admin",
+		"created": "Thu Aug 07 2011 16:32:59 GMT+0200",
+		"par": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "foundation/components/parsys",
+			"colctrl": {
+				"jcr:primaryType": "nt:unstructured",
+				"layout": "2;colctrl-lt0",
+				"sling:resourceType": "foundation/components/parsys/colctrl"
+			}
+		}
+	},
+	"testpage1": {
+		"jcr:primaryType": "app:Page",
+		"jcr:content": {
+			"jcr:primaryType": "app:PageContent",
+			"jcr:title": "English",
+			"app:template": "/apps/sample/templates/homepage",
+			"sling:resourceType": "sample/components/homepage",
+			"jcr:createdBy": "admin",
+			"created": "Thu Aug 07 2012 16:32:59 GMT+0200",
+			"published": true,
+			"views" : 10,
+			"monkey": ["foo", "fish"],
+			"par": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "foundation/components/parsys",
+				"colctrl": {
+					"jcr:primaryType": "nt:unstructured",
+					"layout": "2;colctrl-lt0",
+					"sling:resourceType": "foundation/components/parsys/colctrl"
+				}
+			}
+		}
+	},
+	"testpage2": {
+		"jcr:primaryType": "app:Page",
+		"jcr:content": {
+			"jcr:primaryType": "app:PageContent",
+			"jcr:title": "English",
+			"app:template": "/apps/sample/templates/homepage",
+			"sling:resourceType": "sample/components/homepage",
+			"jcr:createdBy": "admin",
+			"created": "Thu Aug 07 2013 16:32:59 GMT+0200",
+			"par": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "foundation/components/parsys",
+				"colctrl": {
+					"jcr:primaryType": "nt:unstructured",
+					"layout": "2;colctrl-lt0",
+					"sling:resourceType": "foundation/components/parsys/colctrl"
+				}
+			}
+		}
+	},
+	"testpage3": {
+		"jcr:primaryType": "app:Page",
+		"jcr:content": {
+			"jcr:primaryType": "app:PageContent",
+			"jcr:title": "English",
+			"app:template": "/apps/sample/templates/homepage",
+			"sling:resourceType": "sample/components/homepage",
+			"jcr:createdBy": "admin",
+			"created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+			"count" : 1.345666,
+			"par": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "foundation/components/parsys",
+				"colctrl": {
+					"jcr:primaryType": "nt:unstructured",
+					"layout": "2;colctrl-lt0",
+					"sling:resourceType": "foundation/components/parsys/colctrl"
+				}
+			}
+		}
+	},
+	"testpage4": {
+		"jcr:primaryType": "app:Page",
+		"jcr:content": {
+			"jcr:primaryType": "app:PageContent",
+			"jcr:title": "Mongolian",
+			"app:template": "/apps/sample/templates/homepage",
+			"sling:resourceType": "sample/components/homepage",
+			"views" : 3,
+			"jcr:createdBy": "admin",
+			"created": "Thu Aug 07 2015 16:30:59 GMT+0200",
+			"par": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "foundation/components/parsys",
+				"colctrl": {
+					"jcr:primaryType": "nt:unstructured",
+					"layout": "foo",
+					"sling:resourceType": "foundation/components/parsys/colctrl"
+				}
+			}
+		}
+	}
+}
\ No newline at end of file