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/02/11 15:56:49 UTC

[sling-whiteboard] branch master updated: 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-whiteboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 7f513b4  initial commit
7f513b4 is described below

commit 7f513b4b6f7d2f869deeda4ee3d613afbc879475
Author: Jason Bailey <je...@apache.org>
AuthorDate: Sun Feb 11 08:04:18 2018 -0500

    initial commit
---
 streams/.gitignore                                 |    6 +
 streams/README.md                                  |  114 ++
 streams/pom.xml                                    |  121 ++
 .../sling/resource/stream/ResourceFilter.java      |   39 +
 .../sling/resource/stream/ResourceStream.java      |  200 +++
 .../apache/sling/resource/stream/package-info.java |   15 +
 .../sling/resource/stream/parser/FilterParser.java |  456 ++++++
 .../stream/parser/FilterParserConstants.java       |  135 ++
 .../stream/parser/FilterParserTokenManager.java    | 1568 ++++++++++++++++++++
 .../resource/stream/parser/ParseException.java     |  187 +++
 .../apache/sling/resource/stream/parser/README.txt |    2 +
 .../resource/stream/parser/SimpleCharStream.java   |  471 ++++++
 .../apache/sling/resource/stream/parser/Token.java |  131 ++
 .../resource/stream/parser/TokenMgrError.java      |  147 ++
 .../stream/parser/api/ResourceFilterFunction.java  |   42 +
 .../sling/resource/stream/parser/api/Visitor.java  |   28 +
 .../stream/parser/impl/InstantProvider.java        |   66 +
 .../sling/resource/stream/parser/node/Node.java    |   94 ++
 .../predicates/ComparisonPredicateFactory.java     |   59 +
 .../parser/predicates/ComparisonPredicates.java    |  388 +++++
 .../resource/stream/parser/predicates/Null.java    |   69 +
 .../stream/parser/visitor/ComparisonVisitor.java   |  108 ++
 .../stream/parser/visitor/LogicVisitor.java        |   88 ++
 .../stream/predicates/ChildResourcePredicates.java |   81 +
 .../resource/stream/predicates/Conditions.java     |   67 +
 .../stream/predicates/PropertyPredicates.java      |  329 ++++
 .../stream/predicates/ResourcePredicates.java      |   47 +
 streams/src/main/javacc/resourceFilter.jj          |  314 ++++
 .../resource/stream/ResourceFilterDateTest.java    |  102 ++
 .../resource/stream/ResourceFilterLogicTest.java   |   68 +
 .../sling/resource/stream/ResourceFilterTest.java  |  266 ++++
 .../resource/stream/ResourcePredicateTest.java     |  100 ++
 streams/src/test/resources/data.json               |  105 ++
 33 files changed, 6013 insertions(+)

diff --git a/streams/.gitignore b/streams/.gitignore
new file mode 100644
index 0000000..d5a487d
--- /dev/null
+++ b/streams/.gitignore
@@ -0,0 +1,6 @@
+*.class
+.settings
+.classpath
+.project
+
+/target/
diff --git a/streams/README.md b/streams/README.md
new file mode 100644
index 0000000..11397d8
--- /dev/null
+++ b/streams/README.md
@@ -0,0 +1,114 @@
+# Resource Stream Support
+
+* `ResourceStream` utility to provide a `Stream<Resource>` which traverses the resource and it's children
+* Fluent interface for creation of a `Predicate<Resource>` for use with Collections and Streams
+* Predefined predicates for Resources and Properties
+* Script support for creation of a complex `Predicate<Resource>` for use with Collections and Streams
+
+Example of a stream.
+
+```java
+ResourceStream
+	.from(resource)
+	.stream(
+		where(property("jcr:primaryType").is("cq:Page")))
+   .filter(
+      aChildResource("jcr:content")
+          .has(property("sling:resourceType")
+		    .isNot("sling/components/page/folder")));
+```
+
+same results using the filter script
+
+```java
+ResourceStream
+    .from(resource)
+    .stream("[jcr:primaryType] == 'cq:Page'")
+    .filter(new ResourceFilter("[jcr:content/sling:resourceType] != 'apps/components/page/folder'"));
+```
+
+
+
+## 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 a 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        |
diff --git a/streams/pom.xml b/streams/pom.xml
new file mode 100644
index 0000000..3a8eee1
--- /dev/null
+++ b/streams/pom.xml
@@ -0,0 +1,121 @@
+<?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>29</version>
+	</parent>
+
+	<artifactId>org.apache.sling.resource.stream</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>bundle</packaging>
+
+	<name>Apache Sling Stream Support</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>
+				<artifactId>maven-compiler-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.sling</groupId>
+			<artifactId>org.apache.sling.api</artifactId>
+			<version>2.9.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<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.jackrabbit</groupId>
+			<artifactId>jackrabbit-jcr-commons</artifactId>
+			<version>2.15.5</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.findbugs</groupId>
+			<artifactId>jsr305</artifactId>
+			<scope>provided</scope>
+		</dependency>
+	</dependencies>
+	<profiles>
+		<profile>
+			<id>autoInstall</id>
+			<build>
+				<plugins>
+					<plugin>
+						<groupId>org.apache.sling</groupId>
+						<artifactId>maven-sling-plugin</artifactId>
+						<executions>
+							<execution>
+								<id>install-bundle</id>
+								<goals>
+									<goal>install</goal>
+								</goals>
+							</execution>
+						</executions>
+						<configuration>
+							<slingUrl>http://localhost:4502/system/console</slingUrl>
+							<user>admin</user>
+							<password>admin</password>
+							<failOnError>true</failOnError>
+						</configuration>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
+	</profiles>
+	<description>provides a set of utilities to create and handle streams</description>
+</project>
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/ResourceFilter.java b/streams/src/main/java/org/apache/sling/resource/stream/ResourceFilter.java
new file mode 100644
index 0000000..14f552c
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/ResourceFilter.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.stream;
+
+import java.io.ByteArrayInputStream;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.stream.parser.FilterParser;
+import org.apache.sling.resource.stream.parser.ParseException;
+import org.apache.sling.resource.stream.parser.node.Node;
+import org.apache.sling.resource.stream.parser.visitor.LogicVisitor;
+
+public class ResourceFilter implements Predicate<Resource> {
+
+	private Predicate<Resource> parsedPredicate;
+	
+	public ResourceFilter(String filter) throws ParseException {
+		Node rootNode =new FilterParser(new ByteArrayInputStream(filter.getBytes())).parse();
+		this.parsedPredicate = rootNode.accept(new LogicVisitor());
+	}
+
+	@Override
+	public boolean test(Resource resource) {
+		return parsedPredicate.test(resource);
+	}
+	
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/ResourceStream.java b/streams/src/main/java/org/apache/sling/resource/stream/ResourceStream.java
new file mode 100644
index 0000000..11beeed
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/ResourceStream.java
@@ -0,0 +1,200 @@
+/*
+ * 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.stream;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import javax.annotation.Nonnull;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.stream.parser.ParseException;
+
+/**
+ * Base class from which a fluent api can be created or which can be defined
+ * using the integrated query language.
+ * 
+ * Additionally provides the ability to stream child resources.
+ *
+ */
+public class ResourceStream {
+
+	// starting resource
+	private Resource resource;
+
+	private long limit = 0;
+
+	private long startOfRange;
+
+	/**
+	 * Starting point to locate resources. resources of the start resource.
+	 * 
+	 * @param resource
+	 *            starting point for the traversal
+	 * @return new instance of ResourceQuery;
+	 */
+	public static ResourceStream from(@Nonnull Resource resource) {
+		return new ResourceStream(resource);
+	}
+
+	/*
+	 * Constructor to establish the starting resource
+	 * 
+	 * @param resource
+	 */
+	private ResourceStream(Resource resource) {
+		this.resource = Objects.requireNonNull(resource);
+	}
+
+	/**
+	 * Rests the starting path for the query to be the provided path. This can be
+	 * used to limit the possible branching options beneath a resource tree, or to
+	 * use a parent the resource as the basis of permissions for another resource
+	 * structure
+	 * 
+	 * 
+	 * @param path
+	 *            set the internal resource to path
+	 * @return this locator
+	 */
+	public ResourceStream startingFrom(String path) {
+		this.resource = Objects.requireNonNull(resource.getResourceResolver().getResource(path));
+		return this;
+	}
+
+	/**
+	 * Sets the maximum number of items to be returned or processed. Starting from
+	 * the first matching resource. This method is mutually exclusive to the range
+	 * method
+	 * 
+	 * This performs the same form of limitation as a limit on a Stream
+	 * 
+	 * @param number
+	 *            maximum number of items to be returned
+	 * @return this locator
+	 */
+	public ResourceStream limit(long number) {
+		if (number < 0) {
+			throw new IllegalArgumentException("value may not be negative");
+		}
+		this.startOfRange = 0;
+		this.limit = number;
+		return this;
+	}
+
+	/**
+	 * Sets the maximum number of items to be returned or processed. Starting from
+	 * the nth identified resource as set by the startOfRange. This method is
+	 * mutually exclusive to the limit method
+	 * 
+	 * This can be achieved on a Stream by performing a a filter operation
+	 * 
+	 * @param startOfRange
+	 * @param limit
+	 * @return
+	 */
+	public ResourceStream range(long startOfRange, long limit) {
+		if (startOfRange < 0 || limit < 0) {
+			throw new IllegalArgumentException("value may not be negative");
+		}
+		this.startOfRange = startOfRange;
+		this.limit = limit;
+		return this;
+	}
+
+	/**
+	 * Provides a stream of resources starting from the initiator resource and
+	 * traversing through it's descendants The only fluent api check it performs is
+	 * of the traversal predicate.
+	 * 
+	 * @return self closing {@code Stream<Resource>} of unknown size.
+	 */
+	public Stream<Resource> stream() {
+		return stream(resource -> true);
+	}
+
+	/**
+	 * Provides a stream of resources starting from the initiator resource and
+	 * traversing through it's descendants The only fluent api check it performs is
+	 * of the traversal predicate.
+	 * 
+	 * @return self closing {@code Stream<Resource>} of unknown size.
+	 */
+	public Stream<Resource> stream(String branchSelector) throws ParseException {
+		return stream(new ResourceFilter(branchSelector));
+	}
+
+	/**
+	 * Provides a stream of resources starting from the initiator resource and
+	 * traversing through it's descendants The only fluent api check it performs is
+	 * of the traversal predicate.
+	 * 
+	 * @return self closing {@code Stream<Resource>} of unknown size.
+	 */
+	public Stream<Resource> stream(Predicate<Resource> branchSelector) {
+		return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<Resource>() {
+
+			final LinkedList<Resource> resourcesToCheck = new LinkedList<>();
+			final AtomicInteger index = new AtomicInteger(0);
+
+			{
+				resourcesToCheck.addFirst(resource);
+			}
+
+			Resource current;
+
+			@Override
+			public boolean hasNext() {
+				do {
+					if (resourcesToCheck.isEmpty()) {
+						return false;
+					}
+
+					current = resourcesToCheck.removeFirst();
+
+					current.listChildren().forEachRemaining(child -> {
+						if (branchSelector.test(child)) {
+							resourcesToCheck.add(index.getAndIncrement(), child);
+						}
+					});
+
+					index.set(0);
+
+					if (startOfRange > 0) {
+						--startOfRange;
+					}
+					if (limit > 0 && startOfRange == 0) {
+						if (--limit == 0) {
+							resourcesToCheck.clear();
+						}
+					}
+				} while (startOfRange > 0);
+				return true;
+			}
+
+			@Override
+			public Resource next() {
+				return current;
+			}
+		}, Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
+	}
+
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/package-info.java b/streams/src/main/java/org/apache/sling/resource/stream/package-info.java
new file mode 100644
index 0000000..8272e6e
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/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.stream;
\ No newline at end of file
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParser.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParser.java
new file mode 100644
index 0000000..9657f93
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParser.java
@@ -0,0 +1,456 @@
+/* Generated By:JavaCC: Do not edit this line. FilterParser.java */
+package org.apache.sling.resource.stream.parser;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.sling.resource.stream.parser.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 NUMBER:
+    case STRING:
+    case NULL:
+    case BOOLEAN:
+    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 NUMBER:
+    case STRING:
+    case NULL:
+    case BOOLEAN:
+    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 NUMBER:
+    case STRING:
+    case NULL:
+    case BOOLEAN:
+      selector = literal();
+      break;
+    case PROPERTY:
+      selector = property();
+      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 NUMBER:
+    case STRING:
+    case NULL:
+    case BOOLEAN:
+    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 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;
+    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[] {0x20000,0x10000,0x4c2100,0xff800000,0x442100,0x200000,0x442100,0x442100,0x442100,};
+   }
+   private static void jj_la1_init_1() {
+      jj_la1_1 = new int[] {0x0,0x0,0x60,0x1f,0x60,0x0,0x60,0x60,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[40];
+    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 < 40; 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/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParserConstants.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParserConstants.java
new file mode 100644
index 0000000..8bc03cb
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParserConstants.java
@@ -0,0 +1,135 @@
+/* Generated By:JavaCC: Do not edit this line. FilterParserConstants.java */
+package org.apache.sling.resource.stream.parser;
+
+
+/**
+ * Token literal values and constants.
+ * Generated by org.javacc.parser.OtherFilesGen#start()
+ */
+public interface FilterParserConstants {
+
+  /** End of File. */
+  int EOF = 0;
+  /** RegularExpression Id. */
+  int DOT = 3;
+  /** RegularExpression Id. */
+  int PLUS = 4;
+  /** RegularExpression Id. */
+  int MINUS = 5;
+  /** RegularExpression Id. */
+  int DIGIT = 6;
+  /** RegularExpression Id. */
+  int EXP = 7;
+  /** RegularExpression Id. */
+  int NUMBER = 8;
+  /** RegularExpression Id. */
+  int INTEGER = 9;
+  /** RegularExpression Id. */
+  int FRACTIONAL_DIGITS = 10;
+  /** RegularExpression Id. */
+  int EXPONENT = 11;
+  /** RegularExpression Id. */
+  int DIGITS = 12;
+  /** RegularExpression Id. */
+  int STRING = 13;
+  /** RegularExpression Id. */
+  int SQUOTE = 14;
+  /** RegularExpression Id. */
+  int DQUOTE = 15;
+  /** RegularExpression Id. */
+  int AND = 16;
+  /** RegularExpression Id. */
+  int OR = 17;
+  /** RegularExpression Id. */
+  int NULL = 18;
+  /** RegularExpression Id. */
+  int LPAREN = 19;
+  /** RegularExpression Id. */
+  int RPAREN = 20;
+  /** RegularExpression Id. */
+  int COMMA = 21;
+  /** RegularExpression Id. */
+  int BOOLEAN = 22;
+  /** RegularExpression Id. */
+  int EQUAL = 23;
+  /** RegularExpression Id. */
+  int NOT_EQUAL = 24;
+  /** RegularExpression Id. */
+  int GREATER_THAN = 25;
+  /** RegularExpression Id. */
+  int GREATER_THAN_OR_EQUAL = 26;
+  /** RegularExpression Id. */
+  int LESS_THAN = 27;
+  /** RegularExpression Id. */
+  int LESS_THAN_OR_EQUAL = 28;
+  /** RegularExpression Id. */
+  int LIKE = 29;
+  /** RegularExpression Id. */
+  int LIKE_NOT = 30;
+  /** RegularExpression Id. */
+  int CONTAINS = 31;
+  /** RegularExpression Id. */
+  int CONTAINS_NOT = 32;
+  /** RegularExpression Id. */
+  int CONTAINS_ANY = 33;
+  /** RegularExpression Id. */
+  int CONTAINS_NOT_ANY = 34;
+  /** RegularExpression Id. */
+  int IN = 35;
+  /** RegularExpression Id. */
+  int NOT_IN = 36;
+  /** RegularExpression Id. */
+  int FUNCTION_NAME = 37;
+  /** RegularExpression Id. */
+  int PROPERTY = 38;
+  /** RegularExpression Id. */
+  int UNKNOWN = 39;
+
+  /** Lexical state. */
+  int DEFAULT = 0;
+
+  /** Literal token values. */
+  String[] tokenImage = {
+    "<EOF>",
+    "\" \"",
+    "\"\\t\"",
+    "\".\"",
+    "\"+\"",
+    "\"-\"",
+    "<DIGIT>",
+    "<EXP>",
+    "<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/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParserTokenManager.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParserTokenManager.java
new file mode 100644
index 0000000..1cd42c4
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/FilterParserTokenManager.java
@@ -0,0 +1,1568 @@
+/* Generated By:JavaCC: Do not edit this line. FilterParserTokenManager.java */
+package org.apache.sling.resource.stream.parser;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.sling.resource.stream.parser.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 & 0x4L) != 0L)
+            return 58;
+         if ((active0 & 0x780000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            return 58;
+         }
+         if ((active0 & 0x20000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            return 97;
+         }
+         if ((active0 & 0x800000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            return 83;
+         }
+         if ((active0 & 0x10000000L) != 0L)
+         {
+            jjmatchedKind = 27;
+            return -1;
+         }
+         if ((active0 & 0x1000000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            return 56;
+         }
+         if ((active0 & 0x4000000L) != 0L)
+         {
+            jjmatchedKind = 25;
+            return -1;
+         }
+         return -1;
+      case 1:
+         if ((active0 & 0x800000000L) != 0L)
+            return 58;
+         if ((active0 & 0x20000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 1;
+            return 103;
+         }
+         if ((active0 & 0x1000000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 1;
+            return 55;
+         }
+         if ((active0 & 0x780000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 1;
+            return 58;
+         }
+         if ((active0 & 0x10000000L) != 0L)
+         {
+            if (jjmatchedPos == 0)
+            {
+               jjmatchedKind = 27;
+               jjmatchedPos = 0;
+            }
+            return -1;
+         }
+         if ((active0 & 0x4000000L) != 0L)
+         {
+            if (jjmatchedPos == 0)
+            {
+               jjmatchedKind = 25;
+               jjmatchedPos = 0;
+            }
+            return -1;
+         }
+         return -1;
+      case 2:
+         if ((active0 & 0x1000000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 2;
+            return 54;
+         }
+         if ((active0 & 0x20000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 2;
+            return 102;
+         }
+         if ((active0 & 0x780000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 2;
+            return 58;
+         }
+         return -1;
+      case 3:
+         if ((active0 & 0x20000000L) != 0L)
+            return 101;
+         if ((active0 & 0x1000000000L) != 0L)
+         {
+            if (jjmatchedPos < 2)
+            {
+               jjmatchedKind = 37;
+               jjmatchedPos = 2;
+            }
+            return 53;
+         }
+         if ((active0 & 0x780000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 3;
+            return 58;
+         }
+         return -1;
+      case 4:
+         if ((active0 & 0x780000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 4;
+            return 58;
+         }
+         if ((active0 & 0x1000000000L) != 0L)
+         {
+            if (jjmatchedPos < 2)
+            {
+               jjmatchedKind = 37;
+               jjmatchedPos = 2;
+            }
+            return -1;
+         }
+         return -1;
+      case 5:
+         if ((active0 & 0x780000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 5;
+            return 58;
+         }
+         if ((active0 & 0x1000000000L) != 0L)
+         {
+            if (jjmatchedPos < 2)
+            {
+               jjmatchedKind = 37;
+               jjmatchedPos = 2;
+            }
+            return -1;
+         }
+         return -1;
+      case 6:
+         if ((active0 & 0x780000000L) != 0L)
+         {
+            jjmatchedKind = 37;
+            jjmatchedPos = 6;
+            return 58;
+         }
+         return -1;
+      case 7:
+         if ((active0 & 0x780000000L) != 0L)
+            return 58;
+         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, 58);
+      case 32:
+         jjmatchedKind = 1;
+         return jjMoveStringLiteralDfa1_0(0x40000L);
+      case 40:
+         return jjStopAtPos(0, 19);
+      case 41:
+         return jjStopAtPos(0, 20);
+      case 44:
+         return jjStopAtPos(0, 21);
+      case 60:
+         return jjMoveStringLiteralDfa1_0(0x10000000L);
+      case 62:
+         return jjMoveStringLiteralDfa1_0(0x4000000L);
+      case 99:
+         return jjMoveStringLiteralDfa1_0(0x780000000L);
+      case 105:
+         return jjMoveStringLiteralDfa1_0(0x800000000L);
+      case 108:
+         return jjMoveStringLiteralDfa1_0(0x20000000L);
+      case 110:
+         return jjMoveStringLiteralDfa1_0(0x1000000000L);
+      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 & 0x4000000L) != 0L)
+            return jjStopAtPos(1, 26);
+         else if ((active0 & 0x10000000L) != 0L)
+            return jjStopAtPos(1, 28);
+         break;
+      case 105:
+         return jjMoveStringLiteralDfa2_0(active0, 0x20000000L);
+      case 110:
+         if ((active0 & 0x800000000L) != 0L)
+            return jjStartNfaWithStates_0(1, 35, 58);
+         return jjMoveStringLiteralDfa2_0(active0, 0x40000L);
+      case 111:
+         return jjMoveStringLiteralDfa2_0(active0, 0x1780000000L);
+      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 107:
+         return jjMoveStringLiteralDfa3_0(active0, 0x20000000L);
+      case 110:
+         return jjMoveStringLiteralDfa3_0(active0, 0x780000000L);
+      case 116:
+         return jjMoveStringLiteralDfa3_0(active0, 0x1000000000L);
+      case 117:
+         return jjMoveStringLiteralDfa3_0(active0, 0x40000L);
+      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, 0x1000000000L);
+      case 101:
+         if ((active0 & 0x20000000L) != 0L)
+            return jjStartNfaWithStates_0(3, 29, 101);
+         break;
+      case 108:
+         return jjMoveStringLiteralDfa4_0(active0, 0x40000L);
+      case 116:
+         return jjMoveStringLiteralDfa4_0(active0, 0x780000000L);
+      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, 0x780000000L);
+      case 105:
+         return jjMoveStringLiteralDfa5_0(active0, 0x1000000000L);
+      case 108:
+         if ((active0 & 0x40000L) != 0L)
+            return jjStopAtPos(4, 18);
+         break;
+      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, 0x780000000L);
+      case 110:
+         if ((active0 & 0x1000000000L) != 0L)
+            return jjStopAtPos(5, 36);
+         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, 0x780000000L);
+      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 & 0x80000000L) != 0L)
+         {
+            jjmatchedKind = 31;
+            jjmatchedPos = 7;
+         }
+         return jjMoveStringLiteralDfa8_0(active0, 0x700000000L);
+      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, 0x700000000L);
+      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, 0x200000000L);
+      case 110:
+         return jjMoveStringLiteralDfa10_0(active0, 0x500000000L);
+      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, 0x200000000L);
+      case 111:
+         return jjMoveStringLiteralDfa11_0(active0, 0x500000000L);
+      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 & 0x100000000L) != 0L)
+         {
+            jjmatchedKind = 32;
+            jjmatchedPos = 11;
+         }
+         return jjMoveStringLiteralDfa12_0(active0, 0x400000000L);
+      case 121:
+         if ((active0 & 0x200000000L) != 0L)
+            return jjStopAtPos(11, 33);
+         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, 0x400000000L);
+      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, 0x400000000L);
+      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, 0x400000000L);
+      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 & 0x400000000L) != 0L)
+            return jjStopAtPos(15, 34);
+         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 = 105;
+   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 101:
+                  if ((0x8c00847affffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  else if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 100;
+                  break;
+               case 55:
+               case 58:
+                  if ((0x8c00847affffffffL & l) == 0L)
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 97:
+                  if ((0x8c00847affffffffL & l) == 0L)
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 102:
+                  if ((0x8c00847affffffffL & l) == 0L)
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 56:
+                  if ((0x8c00847affffffffL & l) == 0L)
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 0:
+                  if ((0x8c00847affffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  else if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 8)
+                        kind = 8;
+                     jjCheckNAddStates(0, 8);
+                  }
+                  else if (curChar == 45)
+                     jjCheckNAddStates(9, 13);
+                  else if (curChar == 60)
+                  {
+                     if (kind > 27)
+                        kind = 27;
+                  }
+                  else if (curChar == 62)
+                  {
+                     if (kind > 25)
+                        kind = 25;
+                  }
+                  else if (curChar == 61)
+                     jjstateSet[jjnewStateCnt++] = 32;
+                  else if (curChar == 34)
+                     jjCheckNAddStates(14, 16);
+                  else if (curChar == 39)
+                     jjCheckNAddStates(17, 19);
+                  else if (curChar == 46)
+                     jjCheckNAdd(1);
+                  if (curChar == 33)
+                     jjstateSet[jjnewStateCnt++] = 34;
+                  else if (curChar == 38)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  break;
+               case 83:
+                  if ((0x8c00847affffffffL & l) == 0L)
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 103:
+                  if ((0x8c00847affffffffL & l) == 0L)
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 54:
+                  if ((0x8c00847affffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  else if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 53;
+                  break;
+               case 1:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 10)
+                     kind = 10;
+                  jjCheckNAdd(1);
+                  break;
+               case 3:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(4);
+                  break;
+               case 4:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 11)
+                     kind = 11;
+                  jjCheckNAdd(4);
+                  break;
+               case 5:
+                  if (curChar == 39)
+                     jjCheckNAddStates(17, 19);
+                  break;
+               case 7:
+                  jjCheckNAddStates(17, 19);
+                  break;
+               case 8:
+                  if ((0xffffff7fffffffffL & l) != 0L)
+                     jjCheckNAddStates(17, 19);
+                  break;
+               case 9:
+                  if (curChar == 39 && kind > 13)
+                     kind = 13;
+                  break;
+               case 10:
+                  if (curChar == 34)
+                     jjCheckNAddStates(14, 16);
+                  break;
+               case 12:
+                  jjCheckNAddStates(14, 16);
+                  break;
+               case 13:
+                  if ((0xfffffffbffffffffL & l) != 0L)
+                     jjCheckNAddStates(14, 16);
+                  break;
+               case 14:
+                  if (curChar == 34 && kind > 13)
+                     kind = 13;
+                  break;
+               case 15:
+                  if (curChar == 38 && kind > 16)
+                     kind = 16;
+                  break;
+               case 16:
+                  if (curChar == 38)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  break;
+               case 32:
+                  if (curChar == 61 && kind > 23)
+                     kind = 23;
+                  break;
+               case 33:
+                  if (curChar == 61)
+                     jjstateSet[jjnewStateCnt++] = 32;
+                  break;
+               case 34:
+                  if (curChar == 61 && kind > 24)
+                     kind = 24;
+                  break;
+               case 35:
+                  if (curChar == 33)
+                     jjstateSet[jjnewStateCnt++] = 34;
+                  break;
+               case 36:
+                  if (curChar == 62 && kind > 25)
+                     kind = 25;
+                  break;
+               case 41:
+                  if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 40;
+                  break;
+               case 49:
+                  if (curChar == 60 && kind > 27)
+                     kind = 27;
+                  break;
+               case 61:
+               case 62:
+                  jjCheckNAddStates(20, 22);
+                  break;
+               case 64:
+                  if (curChar == 45)
+                     jjCheckNAddStates(9, 13);
+                  break;
+               case 65:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 8)
+                     kind = 8;
+                  jjCheckNAdd(65);
+                  break;
+               case 66:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(66, 67);
+                  break;
+               case 67:
+                  if (curChar == 46)
+                     jjCheckNAdd(68);
+                  break;
+               case 68:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 8)
+                     kind = 8;
+                  jjCheckNAdd(68);
+                  break;
+               case 69:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(69, 70);
+                  break;
+               case 71:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(72);
+                  break;
+               case 72:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 8)
+                     kind = 8;
+                  jjCheckNAdd(72);
+                  break;
+               case 73:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(73, 74);
+                  break;
+               case 74:
+                  if (curChar == 46)
+                     jjCheckNAdd(75);
+                  break;
+               case 75:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(75, 76);
+                  break;
+               case 77:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(78);
+                  break;
+               case 78:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 8)
+                     kind = 8;
+                  jjCheckNAdd(78);
+                  break;
+               case 79:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAdd(79);
+                  break;
+               case 80:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 8)
+                     kind = 8;
+                  jjCheckNAddStates(0, 8);
+                  break;
+               case 81:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 12)
+                     kind = 12;
+                  jjCheckNAdd(81);
+                  break;
+               case 87:
+                  if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 86;
+                  break;
+               case 94:
+                  if (curChar == 32)
+                     jjstateSet[jjnewStateCnt++] = 93;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 101:
+               case 58:
+                  if ((0xffffffffd7ffffffL & l) == 0L)
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 55:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 54;
+                  break;
+               case 97:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 103;
+                  else if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 96;
+                  break;
+               case 102:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 101;
+                  break;
+               case 56:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 55;
+                  break;
+               case 0:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  else if (curChar == 91)
+                     jjCheckNAddStates(20, 22);
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(23, 24);
+                  else if (curChar == 108)
+                     jjAddStates(25, 26);
+                  else if (curChar == 105)
+                     jjAddStates(27, 28);
+                  else if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 56;
+                  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 83:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 87;
+                  if (curChar == 115)
+                  {
+                     if (kind > 23)
+                        kind = 23;
+                  }
+                  break;
+               case 103:
+                  if ((0xffffffffd7ffffffL & l) != 0L)
+                  {
+                     if (kind > 37)
+                        kind = 37;
+                     jjCheckNAdd(58);
+                  }
+                  if (curChar == 107)
+                     jjstateSet[jjnewStateCnt++] = 102;
+                  break;
+               case 54:
+                  if ((0xffffffffd7ffffffL & l) == 0L)
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 2:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(23, 24);
+                  break;
+               case 6:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 7;
+                  break;
+               case 7:
+                  jjCheckNAddStates(17, 19);
+                  break;
+               case 8:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(17, 19);
+                  break;
+               case 11:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 12;
+                  break;
+               case 12:
+                  jjCheckNAddStates(14, 16);
+                  break;
+               case 13:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(14, 16);
+                  break;
+               case 17:
+                  if (curChar == 100 && kind > 16)
+                     kind = 16;
+                  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 > 17)
+                     kind = 17;
+                  break;
+               case 21:
+                  if (curChar == 124)
+                     jjstateSet[jjnewStateCnt++] = 20;
+                  break;
+               case 22:
+                  if (curChar == 114 && kind > 17)
+                     kind = 17;
+                  break;
+               case 23:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 22;
+                  break;
+               case 24:
+                  if (curChar == 101 && kind > 22)
+                     kind = 22;
+                  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 > 25)
+                     kind = 25;
+                  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 50:
+                  if (curChar == 101 && kind > 30)
+                     kind = 30;
+                  break;
+               case 51:
+                  if (curChar == 107)
+                     jjstateSet[jjnewStateCnt++] = 50;
+                  break;
+               case 52:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 51;
+                  break;
+               case 53:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 52;
+                  break;
+               case 57:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 56;
+                  break;
+               case 59:
+                  if (curChar == 91)
+                     jjCheckNAddStates(20, 22);
+                  break;
+               case 60:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 61;
+                  break;
+               case 61:
+                  jjCheckNAddStates(20, 22);
+                  break;
+               case 62:
+                  if ((0xffffffffcfffffffL & l) != 0L)
+                     jjCheckNAddStates(20, 22);
+                  break;
+               case 63:
+                  if (curChar == 93 && kind > 38)
+                     kind = 38;
+                  break;
+               case 70:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(29, 30);
+                  break;
+               case 76:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(31, 32);
+                  break;
+               case 82:
+                  if (curChar == 105)
+                     jjAddStates(27, 28);
+                  break;
+               case 84:
+                  if (curChar == 116 && kind > 24)
+                     kind = 24;
+                  break;
+               case 85:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 84;
+                  break;
+               case 86:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 85;
+                  break;
+               case 88:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 87;
+                  break;
+               case 89:
+                  if (curChar == 108)
+                     jjAddStates(25, 26);
+                  break;
+               case 90:
+                  if (curChar == 110 && kind > 27)
+                     kind = 27;
+                  break;
+               case 91:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 90;
+                  break;
+               case 92:
+                  if (curChar == 104)
+                     jjstateSet[jjnewStateCnt++] = 91;
+                  break;
+               case 93:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 92;
+                  break;
+               case 95:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 94;
+                  break;
+               case 96:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 95;
+                  break;
+               case 98:
+                  if (curChar == 116 && kind > 30)
+                     kind = 30;
+                  break;
+               case 99:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 98;
+                  break;
+               case 100:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 99;
+                  break;
+               case 104:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 103;
+                  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 101:
+               case 58:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 55:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 97:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 102:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 56:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 0:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 83:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 103:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 54:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 37)
+                     kind = 37;
+                  jjCheckNAdd(58);
+                  break;
+               case 7:
+               case 8:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddStates(17, 19);
+                  break;
+               case 12:
+               case 13:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddStates(14, 16);
+                  break;
+               case 61:
+               case 62:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddStates(20, 22);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 105 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   65, 66, 67, 69, 70, 73, 74, 79, 81, 65, 66, 69, 73, 79, 11, 13, 
+   14, 6, 8, 9, 60, 62, 63, 3, 4, 97, 104, 83, 88, 71, 72, 77, 
+   78, 
+};
+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, "\40\156\165\154\154", "\50", "\51", "\54", null, null, 
+null, null, "\76\75", null, "\74\75", "\154\151\153\145", 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", null, null, null, };
+
+/** Lexer state names. */
+public static final String[] lexStateNames = {
+   "DEFAULT",
+};
+static final long[] jjtoToken = {
+   0xffffff3f01L, 
+};
+static final long[] jjtoSkip = {
+   0x6L, 
+};
+protected SimpleCharStream input_stream;
+private final int[] jjrounds = new int[105];
+private final int[] jjstateSet = new int[210];
+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 = 105; 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;
+
+   jjmatchedKind = 0x7fffffff;
+   jjmatchedPos = 0;
+   curPos = jjMoveStringLiteralDfa0_0();
+   if (jjmatchedPos == 0 && jjmatchedKind > 39)
+   {
+      jjmatchedKind = 39;
+   }
+   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 13 :
+        image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+                                       matchedToken.image = image.substring(1, lengthOfMatch - 1);
+         break;
+      case 38 :
+        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/streams/src/main/java/org/apache/sling/resource/stream/parser/ParseException.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/ParseException.java
new file mode 100644
index 0000000..4ea6e2e
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/ParseException.java
@@ -0,0 +1,187 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 5.0 */
+/* JavaCCOptions:KEEP_LINE_COL=null */
+package org.apache.sling.resource.stream.parser;
+
+/**
+ * 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=84f506fca4df66f6d0df1c90850f2234 (do not edit this line) */
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/README.txt b/streams/src/main/java/org/apache/sling/resource/stream/parser/README.txt
new file mode 100644
index 0000000..b46dba8
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/README.txt
@@ -0,0 +1,2 @@
+# DO NOT EDIT
+This folder contains files autogenerated by JavaCC do not edit files in this folder
\ No newline at end of file
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/SimpleCharStream.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/SimpleCharStream.java
new file mode 100644
index 0000000..9d6ab9d
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/SimpleCharStream.java
@@ -0,0 +1,471 @@
+/* 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.stream.parser;
+
+/**
+ * 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=1cacff966e43d00cdb95b69a53874762 (do not edit this line) */
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/Token.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/Token.java
new file mode 100644
index 0000000..778a7d4
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/Token.java
@@ -0,0 +1,131 @@
+/* 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.stream.parser;
+
+/**
+ * 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=ed3fbbf53658adc92e1e017840b5a209 (do not edit this line) */
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/TokenMgrError.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/TokenMgrError.java
new file mode 100644
index 0000000..50eddd5
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/TokenMgrError.java
@@ -0,0 +1,147 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */
+/* JavaCCOptions: */
+package org.apache.sling.resource.stream.parser;
+
+/** 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=4d60cfab556123e5280cd4c24fcb8fdf (do not edit this line) */
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/api/ResourceFilterFunction.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/api/ResourceFilterFunction.java
new file mode 100644
index 0000000..d614cbc
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/api/ResourceFilterFunction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.stream.parser.api;
+
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * A ResourceFilterFunction implementation is used to translate a String from a script or
+ * an Object as a result of a custom function into a value that is used for
+ * Comparison
+ * 
+ */
+public interface ResourceFilterFunction {
+
+	/**
+	 * This method returns a {@code Function} which accepts the resource being
+	 * tested and returns an 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 Function} which will provide a String, Instant, or Number to
+	 *         be used as part of a comparison or Function
+	 */
+	Function<Resource, Object> provision(List<Function<Resource, Object>> arguments);
+
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/api/Visitor.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/api/Visitor.java
new file mode 100644
index 0000000..4a9c5d3
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/api/Visitor.java
@@ -0,0 +1,28 @@
+/*
+ * 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.stream.parser.api;
+
+import org.apache.sling.resource.stream.parser.node.Node;
+
+/**
+ * An interface for visiting AST nodes of the RSQL.
+ *
+ * @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/streams/src/main/java/org/apache/sling/resource/stream/parser/impl/InstantProvider.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/impl/InstantProvider.java
new file mode 100644
index 0000000..4d97a43
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/impl/InstantProvider.java
@@ -0,0 +1,66 @@
+/*
+ * 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.stream.parser.impl;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.stream.parser.api.ResourceFilterFunction;
+
+/**
+ * Implementation of {@link ResourceFilterFunction} for the 'date' function.
+ * 
+ * The following combination of arguments are supported
+ * <pre>
+ * arguments      results
+ * ======================================================
+ * none     | current system time
+ * one      | ISO88601 String with offset
+ * two      | Date String followed by Date Format String
+ * 
+ * </pre>
+ *
+ */
+public class InstantProvider implements ResourceFilterFunction {
+
+	@Override
+	public Function<Resource, Object> provision(List<Function<Resource, Object>> arguments) {
+
+		return resource -> {
+			if (arguments.isEmpty()) {
+				return Instant.now();
+			}
+			String dateString = arguments.get(0).apply(resource).toString();
+			String formatString = null;
+			if (arguments.size() > 1) {
+				formatString = arguments.get(1).apply(resource).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/streams/src/main/java/org/apache/sling/resource/stream/parser/node/Node.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/node/Node.java
new file mode 100644
index 0000000..7d24e45
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/node/Node.java
@@ -0,0 +1,94 @@
+/*
+ * 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.stream.parser.node;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.resource.stream.parser.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(item -> item.toString())
+				.collect(Collectors.joining(text.toString(), "(", ")"));
+	}
+
+	public <R, A> List<R> visitChildren(Visitor<R> visitor) {
+		return children.stream().map(child -> child.accept(visitor)).collect(Collectors.toList());
+	}
+
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/predicates/ComparisonPredicateFactory.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/predicates/ComparisonPredicateFactory.java
new file mode 100644
index 0000000..482b392
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/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.stream.parser.predicates;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.stream.parser.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/streams/src/main/java/org/apache/sling/resource/stream/parser/predicates/ComparisonPredicates.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/predicates/ComparisonPredicates.java
new file mode 100644
index 0000000..a060a44
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/predicates/ComparisonPredicates.java
@@ -0,0 +1,388 @@
+/*
+ * 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.stream.parser.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/streams/src/main/java/org/apache/sling/resource/stream/parser/predicates/Null.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/predicates/Null.java
new file mode 100644
index 0000000..c85bcc8
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/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.stream.parser.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 0;
+	}
+
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/visitor/ComparisonVisitor.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/visitor/ComparisonVisitor.java
new file mode 100644
index 0000000..25896bd
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/visitor/ComparisonVisitor.java
@@ -0,0 +1,108 @@
+/*
+ * 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.stream.parser.visitor;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+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.stream.parser.FilterParserConstants;
+import org.apache.sling.resource.stream.parser.api.ResourceFilterFunction;
+import org.apache.sling.resource.stream.parser.api.Visitor;
+import org.apache.sling.resource.stream.parser.impl.InstantProvider;
+import org.apache.sling.resource.stream.parser.node.Node;
+import org.apache.sling.resource.stream.parser.predicates.Null;
+
+public class ComparisonVisitor implements Visitor<Function<Resource, Object>> {
+
+	private Map<String, ResourceFilterFunction> functions = new HashMap<>();
+	
+	private ResourceFilterFunction instant = new InstantProvider();
+
+	@Override
+	public Function<Resource, Object> visit(Node node) {
+		switch (node.kind) {
+		case FilterParserConstants.FUNCTION_NAME:
+			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.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;
+			};
+		default:
+			return resource -> node.text;
+		}
+		// will only get here in the case of the 'FUNCTION' switch case
+		switch (node.text) {
+		case "name":
+			return resource -> resource.getName();
+		case "date":
+			return instant.provision(node.visitChildren(this));
+		case "path":
+			return resource -> resource.getPath();
+		default:
+			ResourceFilterFunction temp = functions.get(node.text);
+			if (temp !=  null){
+				return temp.provision(node.visitChildren(this));
+			}
+			
+		}
+		return null;
+	}
+
+	public ResourceFilterFunction registerFunction(String functionName, ResourceFilterFunction function) {
+		return this.functions.put(functionName, function);
+	}
+
+	public ResourceFilterFunction removeFunction(String functionName) {
+		return this.functions.remove(functionName);
+	}
+	
+	private ValueMap valueMapOf(Resource resource){
+		if (resource == null || ResourceUtil.isNonExistingResource(resource)){
+			return ValueMap.EMPTY;
+		}
+		 return resource.adaptTo(ValueMap.class);
+	}
+
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/parser/visitor/LogicVisitor.java b/streams/src/main/java/org/apache/sling/resource/stream/parser/visitor/LogicVisitor.java
new file mode 100644
index 0000000..d8f4829
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/parser/visitor/LogicVisitor.java
@@ -0,0 +1,88 @@
+/*
+ * 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.stream.parser.visitor;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.stream.parser.FilterParserConstants;
+import org.apache.sling.resource.stream.parser.api.Visitor;
+import org.apache.sling.resource.stream.parser.node.Node;
+import org.apache.sling.resource.stream.parser.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 ComparisonVisitor valueVisitor = new ComparisonVisitor();
+
+	@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(child -> {
+			return visit(child);
+		}).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(child -> {
+			return visit(child);
+		}).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(valueVisitor);
+		Function<Resource, Object> rightValue = comparisonNode.rightNode.accept(valueVisitor);
+		return ComparisonPredicateFactory.toPredicate(comparisonNode.kind, leftValue, rightValue);
+	}
+
+	public ComparisonVisitor getValueVisitor() {
+		return valueVisitor;
+	}
+
+}
\ No newline at end of file
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/predicates/ChildResourcePredicates.java b/streams/src/main/java/org/apache/sling/resource/stream/predicates/ChildResourcePredicates.java
new file mode 100644
index 0000000..0fbcedc
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/predicates/ChildResourcePredicates.java
@@ -0,0 +1,81 @@
+/*
+ * 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.stream.predicates;
+
+import java.util.Objects;
+/*
+ * Copyright 2016 Jason E Bailey
+ *
+ * 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.
+ */
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Predicates that are to be used against a child resource
+ * 
+ *
+ */
+public class ChildResourcePredicates {
+
+	private final String name;
+
+	private ChildResourcePredicates(String name) {
+		this.name = Objects.requireNonNull(name, "name value may not be null");
+		;
+	}
+
+	/**
+	 * Instantiates a ChildResourcePredicate object to provide the application of
+	 * Predicates against the named child
+	 * 
+	 * @param name
+	 *            of the expected child resource
+	 * @return Object providing helper predicates for a child resource
+	 */
+	static public ChildResourcePredicates child(String name) {
+		return new ChildResourcePredicates(name);
+	}
+
+	/**
+	 * Applies a predicate against the named child resource. The returned predicate
+	 * will always return 'false' for a child that doesn't exist
+	 * 
+	 * @param predicate
+	 *            to be used against the child resource
+	 * @return Predicate which will apply the given predicate to the child resource
+	 */
+	public Predicate<Resource> has(Predicate<Resource> predicate) {
+		Objects.requireNonNull(predicate, "predicate may not be null");
+		return resource -> {
+			Resource child = resource.getChild(name);
+			if (child != null) {
+				return predicate.test(child);
+			}
+			return false;
+		};
+	}
+
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/predicates/Conditions.java b/streams/src/main/java/org/apache/sling/resource/stream/predicates/Conditions.java
new file mode 100644
index 0000000..a6d07bf
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/predicates/Conditions.java
@@ -0,0 +1,67 @@
+/*
+ * 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.stream.predicates;
+
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * A collection of terms to assist in building a fluent api. These terms may not
+ * have functionality other than providing syntax sugar over existing features,
+ * or merely providing context for other terms
+ *
+ */
+public class Conditions {
+
+	/**
+	 * Syntactic sugar to provide context for the builder
+	 * 
+	 * @param predicate
+	 *            to be wrapped
+	 * @return the predicate that was passed in
+	 */
+	public static <T> Predicate<T> where(Predicate<T> predicate) {
+		return predicate;
+	}
+
+	/**
+	 * Syntactic sugar to provide context for the builder
+	 * 
+	 * @param predicate
+	 *            to be wrapped
+	 * @return the predicate that was passed in
+	 */
+	public static <T> Predicate<T> when(Predicate<T> predicate) {
+		return predicate;
+	}
+
+	/**
+	 * 'or' equivalent. only evaluates the second predicate if the first one
+	 * fails
+	 * 
+	 * @param firstPredicate
+	 *            always evaluated
+	 * @param secondPredicate
+	 *            evaluated if firstPredicate returns false
+	 * @return a new predicate which wraps the or method on the firstPredicate
+	 */
+	public static <T> Predicate<T> either(
+			final Predicate<T> firstPredicate,
+			final Predicate<T> secondPredicate) {
+		Objects.requireNonNull(firstPredicate, "predicate may not be null");
+		Objects.requireNonNull(secondPredicate, "predicate may not be null");
+		return firstPredicate.or(secondPredicate);
+	}
+
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/predicates/PropertyPredicates.java b/streams/src/main/java/org/apache/sling/resource/stream/predicates/PropertyPredicates.java
new file mode 100644
index 0000000..b6037a4
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/predicates/PropertyPredicates.java
@@ -0,0 +1,329 @@
+/*
+ * 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.stream.predicates;
+
+import java.lang.reflect.Array;
+import java.util.Date;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+
+/**
+ * Provides property based predicates. The method follows the following
+ * conventions
+ * 
+ * <br>
+ * is = equal to argument<br>
+ * isNot = not equal to argument<br>
+ * isIn = property is a single value which matches one of the arguments passed in
+ * for comparison<br>
+ * greaterThan = greater than the property<br>
+ * lessThan = less than the property<br>
+ * exists = is the property of the child <br> 
+ * contains = property is an array which contains all of the arguments passed in<br>
+ * 
+ *
+ */
+public class PropertyPredicates {
+
+	// key value to be used against the provided resource object
+	private final String key;
+
+	private PropertyPredicates(String name) {
+		this.key = Objects.requireNonNull(name, "value may not be null");;
+	}
+
+	/**
+	 * Used to define which value in the underlying map will we be used.
+	 * 
+	 * @param name key value of the property
+	 * @return PropertyPredicate instance
+	 */
+	static public PropertyPredicates property(String name) {
+		return new PropertyPredicates(name);
+	}
+	
+	/**
+	 * Assumes that the referenced property value is a date and that this date
+	 * is earlier in the epoch than the one being tested
+	 * 
+	 * @param when latest acceptable time
+	 * @return predicate which will perform the matching
+	 */
+	public Predicate<Resource> isBefore(Date when) {
+		Objects.requireNonNull(when, "value may not be null");
+		return value -> {
+			Date then = value.adaptTo(ValueMap.class).get(key, Date.class);
+			if (then != null) {
+				return then.before(when);
+			}
+			return false;
+		};
+	}
+
+	/**
+	 * Assumes that the referenced property value is a date and that this date
+	 * is later in the epoch than the one being tested
+	 * 
+	 * @param when earliest acceptable value
+	 * @return predicate
+	 */
+	public Predicate<Resource> isAfter(Date when) {
+		Objects.requireNonNull(when, "value may not be null");
+		return value -> {
+			Date then = value.adaptTo(ValueMap.class).get(key, Date.class);
+			if (then != null) {
+				return then.after(when);
+			}
+			return false;
+		};
+	}
+
+	/*
+	 * Generic equalities method that is accessed via public methods that have
+	 * specific types
+	 */
+	public <T> Predicate<Resource> is(T type) {
+		Objects.requireNonNull(type, "type value may not be null");
+		return resource -> {
+			@SuppressWarnings("unchecked")
+			T propValue = (T) valueMapOf(resource).get(key,
+					type.getClass());
+			return type.equals(propValue);
+		};
+
+	}
+	
+	/*
+	 * Generic greater then method that is accessed via public methods that have
+	 * specific types
+	 */
+	public <T extends Comparable<T>> Predicate<Resource> greaterThan(T type) {
+		Objects.requireNonNull(type, "type value may not be null");
+		return resource -> {
+			@SuppressWarnings("unchecked")
+			T propValue = (T) valueMapOf(resource).get(key,
+					type.getClass());
+			if (propValue instanceof Comparable<?>){
+				return ((Comparable<T>)propValue).compareTo(type) > 0;
+			}
+			return type.equals(propValue);
+		};
+
+	}
+	
+	/*
+	 * Generic greater then method that is accessed via public methods that have
+	 * specific types
+	 */
+	public <T extends Comparable<T>> Predicate<Resource> greaterThanOrEqual(T type) {
+		Objects.requireNonNull(type, "type value may not be null");
+		return resource -> {
+			@SuppressWarnings("unchecked")
+			T propValue = (T) valueMapOf(resource).get(key,
+					type.getClass());
+			if (propValue instanceof Comparable<?>){
+				return ((Comparable<T>)propValue).compareTo(type) >= 0;
+			}
+			return type.equals(propValue);
+		};
+
+	}
+	
+	/*
+	 * Generic greater then method that is accessed via public methods that have
+	 * specific types
+	 */
+	public <T extends Comparable<T>> Predicate<Resource> lessThan(T type) {
+		Objects.requireNonNull(type, "type value may not be null");
+		return resource -> {
+			@SuppressWarnings("unchecked")
+			T propValue = (T) valueMapOf(resource).get(key,
+					type.getClass());
+			if (propValue instanceof Comparable<?>){
+				return ((Comparable<T>)propValue).compareTo(type) < 0;
+			}
+			return type.equals(propValue);
+		};
+
+	}
+	
+	/*
+	 * Generic greater then method that is accessed via public methods that have
+	 * specific types
+	 */
+	public <T extends Comparable<T>> Predicate<Resource> lessThanOrEqual(T type) {
+		Objects.requireNonNull(type, "type value may not be null");
+		return resource -> {
+			@SuppressWarnings("unchecked")
+			T propValue = (T) valueMapOf(resource).get(key,
+					type.getClass());
+			if (propValue instanceof Comparable<?>){
+				return ((Comparable<T>)propValue).compareTo(type) >= 0;
+			}
+			return type.equals(propValue);
+		};
+
+	}
+
+	public <T> Predicate<Resource> isNot(final T type) {
+		return is(type).negate();
+	}
+
+	@SuppressWarnings("unchecked")
+	public <T> Predicate<Resource> contains(final T[] values) {
+		Objects.requireNonNull(values, "value may not be null");
+		return resource -> {
+			T[] propValues = (T[]) valueMapOf(resource).get(key,
+					values.getClass());
+			if (propValues == null) {
+				if (values.length > 1) {
+					// no point converting if the number of values to test
+					// exceeds the possible values in the repository
+					return false;
+				}
+				Class<?> componentType = values.getClass().getComponentType();
+				T tempValue = (T) valueMapOf(resource).get(key,
+						componentType);
+				if (tempValue != null) {
+					propValues = (T[]) Array.newInstance(componentType, 1);
+					propValues[0] = tempValue;
+				}
+			}
+			// property identified by resource is either not present or is
+			// of a type that is not the type being requested
+			if (propValues == null || propValues.length < values.length) {
+				return false;
+			}
+			// validate that all items in values have matches in properties
+			for (T item : values) {
+				innerloop: {
+					for (T propItem : propValues) {
+						if (item.equals(propItem)) {
+							break innerloop;
+						}
+					}
+					return false;
+				}
+			}
+			return true;
+		};
+
+	}
+	
+	/**
+	 * Contains any take one or move values as it's input and returns true if any of
+	 * the property values matches any of the provides values.
+	 * 
+	 * @param values
+	 *            One or more objects to be compared with
+	 * @return
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> Predicate<Resource> containsAny(final T... values) {
+		Objects.requireNonNull(values, "value may not be null");
+		return resource -> {
+			T[] propValues = (T[]) valueMapOf(resource).get(key, values.getClass());
+			if (propValues == null) {
+				if (values.length > 1) {
+					// no point converting if the number of values to test
+					// exceeds the possible values in the repository
+					return false;
+				}
+				Class<?> componentType = values.getClass().getComponentType();
+				T tempValue = (T) valueMapOf(resource).get(key, componentType);
+				if (tempValue != null) {
+					propValues = (T[]) Array.newInstance(componentType, 1);
+					propValues[0] = tempValue;
+				}
+			}
+			// property identified by resource is not present
+			if (propValues == null) {
+				return false;
+			}
+			// validate that all items in values have matches in properties
+			for (T item : values) {
+				for (T propItem : propValues) {
+					if (item.equals(propItem)) {
+						return true;
+					}
+				}
+			}
+			return false;
+		};
+
+	}
+
+
+	public <T> Predicate<Resource> isIn(final T[] values) {
+		Objects.requireNonNull(values, "values may not be null");
+		return resource -> {
+			Object propValue = valueMapOf(resource).get(key,
+					values.getClass().getComponentType());
+			if (propValue == null) {
+				return false;
+			}
+			for (T value : values) {
+				if (value.equals(propValue)) {
+					return true;
+				}
+			}
+			return false;
+		};
+	}
+
+	/**
+	 * property value is not null
+	 * 
+	 * @return a predicate that determines existence of the value
+	 */
+	public Predicate<Resource> exists() {
+		return resource -> valueMapOf(resource).get(key) != null;
+	}
+
+	/**
+	 * property value is null
+	 * 
+	 * @return a predicate that determines non existence of the value
+	 */
+	public Predicate<Resource> doesNotExist() {
+		return exists().negate();
+	}
+
+	
+	public Predicate<Resource> isNotIn(final Object... objects) {
+		Objects.requireNonNull(objects, "objects may not be null");
+		return resource -> {
+			Object value = valueMapOf(resource).get(key);
+
+			for (Object object : objects) {
+				if (object.equals(value)) {
+					return false;
+				}
+			}
+			return true;
+		};
+	}
+	
+	private ValueMap valueMapOf(Resource resource){
+		if (resource == null || ResourceUtil.isNonExistingResource(resource)){
+			return ValueMap.EMPTY;
+		}
+		 return resource.adaptTo(ValueMap.class);
+	}
+	
+}
diff --git a/streams/src/main/java/org/apache/sling/resource/stream/predicates/ResourcePredicates.java b/streams/src/main/java/org/apache/sling/resource/stream/predicates/ResourcePredicates.java
new file mode 100644
index 0000000..cb707f0
--- /dev/null
+++ b/streams/src/main/java/org/apache/sling/resource/stream/predicates/ResourcePredicates.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.stream.predicates;
+
+import java.util.function.Predicate;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Collection of predicates specific to obtaining information from the resource
+ * object which isn't a property value
+ *
+ */
+public class ResourcePredicates {
+
+	/**
+	 * Convenience method to wrap the resources method 'isResourceType'
+	 * 
+	 * @param resourceType
+	 * @return predicate which evaluates
+	 */
+	public static Predicate<Resource> isResourceType(final String resourceType) {
+		return resource -> resource.isResourceType(resourceType);
+	}
+
+	/**
+	 * Convenience method to determine depth of resource via the name pattern
+	 * 
+	 * @param resourceType
+	 * @return predicate which evaluates
+	 */
+	public static Predicate<Resource> depthIsLessThan(final int depth) {
+		return resource -> resource.getPath().split("/").length < depth;
+	}
+
+}
diff --git a/streams/src/main/javacc/resourceFilter.jj b/streams/src/main/javacc/resourceFilter.jj
new file mode 100644
index 0000000..c83c65e
--- /dev/null
+++ b/streams/src/main/javacc/resourceFilter.jj
@@ -0,0 +1,314 @@
+/*
+ *
+ * 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.stream.parser;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.sling.resource.stream.parser.node.*;
+
+public final class FilterParser
+{
+}
+
+PARSER_END(FilterParser)
+
+SKIP :
+{
+  " "
+| "\t"
+}
+
+TOKEN :
+{
+  < #DOT: "." >
+| < #PLUS: "+" >
+| < #MINUS: "-" >
+| < #DIGIT: [ "0"-"9" ] >
+| < #EXP: ["e","E"] ( < PLUS > | < MINUS > )? >
+| < NUMBER:  <INTEGER> | <INTEGER> <FRACTIONAL_DIGITS> | <INTEGER> <EXPONENT> | <INTEGER> <FRACTIONAL_DIGITS> <EXPONENT> >
+| < INTEGER: (<MINUS>)? ( <DIGITS> ) >
+| < FRACTIONAL_DIGITS: <DOT> <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" >
+| < 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 = 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 literal() :
+{
+}
+{
+  (
+    < STRING >
+  | < NUMBER >
+  | < NULL >
+  | < BOOLEAN >
+  )
+  {
+    return new Node(token.kind, token.image);
+  }
+}
+
+Node property() :
+{
+}
+{
+  < PROPERTY > 
+  {
+    return new Node(token.kind, token.image);
+  }
+}
diff --git a/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterDateTest.java b/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterDateTest.java
new file mode 100644
index 0000000..c8150ba
--- /dev/null
+++ b/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterDateTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.stream;
+
+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.stream.ResourceFilter;
+import org.apache.sling.resource.stream.ResourceStream;
+import org.apache.sling.resource.stream.parser.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 ResourceStream.from(resource).stream().filter(new ResourceFilter(filter)).collect(Collectors.toList());
+	}
+}
diff --git a/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterLogicTest.java b/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterLogicTest.java
new file mode 100644
index 0000000..e34bf5a
--- /dev/null
+++ b/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterLogicTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.stream;
+
+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.stream.ResourceFilter;
+import org.apache.sling.resource.stream.ResourceStream;
+import org.apache.sling.resource.stream.parser.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 ResourceStream.from(resource).stream().filter(new ResourceFilter(filter)).collect(Collectors.toList());
+	}
+}
diff --git a/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterTest.java b/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterTest.java
new file mode 100644
index 0000000..06deb05
--- /dev/null
+++ b/streams/src/test/java/org/apache/sling/resource/stream/ResourceFilterTest.java
@@ -0,0 +1,266 @@
+/*
+ * 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.stream;
+
+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.stream.ResourceFilter;
+import org.apache.sling.resource.stream.ResourceStream;
+import org.apache.sling.resource.stream.parser.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 = ResourceStream.from(resource).limit(3).stream().filter(new ResourceFilter(query))
+				.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 ResourceStream.from(resource).stream().filter(f).collect(Collectors.toList());
+	}
+}
diff --git a/streams/src/test/java/org/apache/sling/resource/stream/ResourcePredicateTest.java b/streams/src/test/java/org/apache/sling/resource/stream/ResourcePredicateTest.java
new file mode 100644
index 0000000..2330deb
--- /dev/null
+++ b/streams/src/test/java/org/apache/sling/resource/stream/ResourcePredicateTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.stream;
+
+import static org.apache.sling.resource.stream.predicates.ChildResourcePredicates.child;
+import static org.apache.sling.resource.stream.predicates.PropertyPredicates.property;
+import static org.junit.Assert.assertEquals;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.resource.stream.ResourceStream;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ResourcePredicateTest {
+
+	@Rule
+	public final SlingContext context = new SlingContext();
+	
+	private Date midPoint;
+	
+	private static String DATE_STRING = "Thu Aug 07 2013 16:32:59 GMT+0200";
+	
+	private static String DATE_FORMAT = "EEE MMM dd yyyy HH:mm:ss 'GMT'Z";
+
+	@Before
+	public void setUp() throws ParseException {
+		context.load().json("/data.json", "/content/sample/en");
+		midPoint = new SimpleDateFormat(DATE_FORMAT).parse(DATE_STRING);
+	}
+
+	@Test
+	public void testObtainResourceFromContext() {
+		Resource resource = context.resourceResolver().getResource("/content/sample/en");
+		assertEquals("en", resource.getName());
+	}
+
+	@Test
+	public void testMatchingName() {
+		Resource resource = context.resourceResolver().getResource("/content/sample/en");
+		List<Resource> found = ResourceStream.from(resource).stream().filter(item -> {
+			return item.getName().equals("testpage1");
+		}).collect(Collectors.toList());
+		assertEquals(1, found.size());
+	}
+
+	@Test 
+	public void testBeforeThenDate() {
+		Resource resource = context.resourceResolver().getResource("/content/sample/en");
+		List<Resource> found = ResourceStream.from(resource).stream().filter(
+				child("jcr:content").has(property("created").isBefore(Calendar.getInstance().getTime()))).collect(Collectors.toList());
+		assertEquals(5, found.size());
+	}
+	
+	
+	@Test
+	public void testAfterThenDate() {
+		Resource resource = context.resourceResolver().getResource("/content/sample/en");
+		List<Resource> found = ResourceStream.from(resource).stream().filter(
+				child("jcr:content").has(property("created").isAfter(new Date(0)))).collect(Collectors.toList());
+		assertEquals(5, found.size());
+	}
+	
+	@Test
+	public void testAfterMidDate() {
+		Resource resource = context.resourceResolver().getResource("/content/sample/en");
+		List<Resource> found = ResourceStream.from(resource).stream().filter(
+				child("jcr:content").has(property("created").isAfter(midPoint))).collect(Collectors.toList());
+		assertEquals(2, found.size());
+	}
+	
+	@Test 
+	public void testBeforeMidDate() {
+		Resource resource = context.resourceResolver().getResource("/content/sample/en");
+		List<Resource> found = ResourceStream.from(resource).stream().filter(
+				child("jcr:content").has(property("created").isBefore(midPoint))).collect(Collectors.toList());
+		assertEquals(2, found.size());
+	}
+	
+	
+}
diff --git a/streams/src/test/resources/data.json b/streams/src/test/resources/data.json
new file mode 100644
index 0000000..8778ad5
--- /dev/null
+++ b/streams/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

-- 
To stop receiving notification emails like this one, please contact
jeb@apache.org.