You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:57:42 UTC

[sling-org-apache-sling-query] annotated tag org.apache.sling.query-2.0.0 created (now 6df46f2)

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

rombert pushed a change to annotated tag org.apache.sling.query-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-query.git.


      at 6df46f2  (tag)
 tagging 265963f7eab4b30601b6e891b7dbee5f2497265c (commit)
      by Robert Munteanu
      on Fri Aug 8 09:09:57 2014 +0000

- Log -----------------------------------------------------------------
org.apache.sling.query-2.0.0
-----------------------------------------------------------------------

This annotated tag includes the following new commits:

     new c7beeec  SLING-3667 Add SlingQuery library, donated by Tomek Rękawek, thanks!
     new dc76001  Use latest parent
     new e1ed08b  sling-query - remove CHANGELOG.md and bump version to 2.0.0-SNAPSHOT
     new 5099de6  [maven-release-plugin] prepare release org.apache.sling.query-2.0.0
     new 265963f  [maven-release-plugin]  copy for tag org.apache.sling.query-2.0.0

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


-- 
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].

[sling-org-apache-sling-query] 04/05: [maven-release-plugin] prepare release org.apache.sling.query-2.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.query-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-query.git

commit 5099de6c098824dd181f176f0cf59a134ba1d701
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Aug 8 09:09:29 2014 +0000

    [maven-release-plugin] prepare release org.apache.sling.query-2.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/sling-query@1616698 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/pom.xml b/pom.xml
index 477a2c3..ad29cbf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,10 +18,7 @@
     under the License.
 -->
 
-<project
-    xmlns="http://maven.apache.org/POM/4.0.0"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
     <parent>
         <groupId>org.apache.sling</groupId>
@@ -31,7 +28,7 @@
 
     <modelVersion>4.0.0</modelVersion>
     <artifactId>org.apache.sling.query</artifactId>
-    <version>2.0.0-SNAPSHOT</version>
+    <version>2.0.0</version>
     <packaging>bundle</packaging>
 
     <name>sling-query</name>
@@ -42,9 +39,9 @@
     </properties>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/sling-query</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/sling-query</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-query</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.query-2.0.0</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.query-2.0.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.query-2.0.0</url>
     </scm>
 
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-query] 05/05: [maven-release-plugin] copy for tag org.apache.sling.query-2.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.query-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-query.git

commit 265963f7eab4b30601b6e891b7dbee5f2497265c
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Aug 8 09:09:57 2014 +0000

    [maven-release-plugin]  copy for tag org.apache.sling.query-2.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.query-2.0.0@1616699 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-query] 03/05: sling-query - remove CHANGELOG.md and bump version to 2.0.0-SNAPSHOT

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.query-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-query.git

commit e1ed08bca1c57160edf83fc0e2693160d3830fea
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Aug 8 09:03:45 2014 +0000

    sling-query - remove CHANGELOG.md and bump version to 2.0.0-SNAPSHOT
    
    See discussion at http://sling-dev.markmail.org/thread/xiruzia372scxqzb#query:+page:1+mid:mzrdqgooezpz43cz+state:results
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/sling-query@1616697 13f79535-47bb-0310-9956-ffa450edef68
---
 CHANGELOG.md | 37 -------------------------------------
 pom.xml      |  2 +-
 2 files changed, 1 insertion(+), 38 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 0cdb453..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,37 +0,0 @@
-## SlingQuery 1.4.2
-
-* Added new add(...) method
-
-## SlingQuery 1.4.1
-
-* Fixed bug in the QUERY search strategy related
-
-## SlingQuery 1.4.0
-
-* `not()` function is now lazy (eg. `$(resourceResolver).not('cq:InvalidType').first()` return results immediately)
-* multiple selectors can be joined with comma: `$('[jcr:title=Foo], [jcr:title=Bar])`
-* JCR query becomes the default SearchStrategy
-
-## SlingQuery 1.3.1
-
-* fixed `javax.jcr.RepositoryException: invalid node type name` bug (reported by Dawid Jędraszek)
-
-## SlingQuery 1.3.0
-
-* lazy `asList()` method
-
-## SlingQuery 1.2.0
-
-* new strategy for the `find()`: `JCR`
-
-## SlingQuery 1.1.0
-
-* new selector features:
-	* [hierarchy operators](https://github.com/Cognifide/Sling-Query/wiki/Hierarchy-operator-list),
-	* [resource name](https://github.com/Cognifide/Sling-Query/wiki/Selector-syntax#wiki-resource-name),
-	* [new attribute operators](https://github.com/Cognifide/Sling-Query/wiki/Operator%20list)
-* two strategies for the `find()` method: `DFS` and `BFS`
-
-## SlingQuery 1.0.0
-
-* first released version
diff --git a/pom.xml b/pom.xml
index b728002..477a2c3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,7 @@
 
     <modelVersion>4.0.0</modelVersion>
     <artifactId>org.apache.sling.query</artifactId>
-    <version>1.4.5-SNAPSHOT</version>
+    <version>2.0.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>sling-query</name>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-query] 02/05: Use latest parent

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.query-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-query.git

commit dc76001d2deff82bf98665961a2a06ca37e2e745
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Aug 8 08:36:44 2014 +0000

    Use latest parent
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/sling-query@1616689 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index a6c191f..b728002 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>19</version>
+        <version>20</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-query] 01/05: SLING-3667 Add SlingQuery library, donated by Tomek Rękawek, thanks!

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.query-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-query.git

commit c7beeec3078b8236302b949307ae163aaa64c240
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Fri Jul 25 12:45:33 2014 +0000

    SLING-3667 Add SlingQuery library, donated by Tomek Rękawek, thanks!
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/sling-query@1613419 13f79535-47bb-0310-9956-ffa450edef68
---
 CHANGELOG.md                                       |  37 +
 README.md                                          |  66 ++
 pom.xml                                            | 109 +++
 .../java/org/apache/sling/query/AbstractQuery.java | 802 +++++++++++++++++++++
 .../java/org/apache/sling/query/IteratorUtils.java |  43 ++
 src/main/java/org/apache/sling/query/LazyList.java | 278 +++++++
 .../java/org/apache/sling/query/SlingQuery.java    |  95 +++
 .../java/org/apache/sling/query/api/Function.java  |  38 +
 .../java/org/apache/sling/query/api/Predicate.java |  36 +
 .../org/apache/sling/query/api/SearchStrategy.java |  24 +
 .../org/apache/sling/query/api/TreeProvider.java   |  42 ++
 .../api/function/ElementToIteratorFunction.java    |  27 +
 .../api/function/IteratorToIteratorFunction.java   |  27 +
 .../apache/sling/query/api/function/Option.java    |  82 +++
 .../apache/sling/query/function/AddFunction.java   |  43 ++
 .../sling/query/function/ChildrenFunction.java     |  39 +
 .../sling/query/function/ClosestFunction.java      |  51 ++
 .../sling/query/function/CompositeFunction.java    |  45 ++
 .../sling/query/function/DescendantFunction.java   |  45 ++
 .../apache/sling/query/function/EvenFunction.java  |  56 ++
 .../sling/query/function/FilterFunction.java       |  42 ++
 .../apache/sling/query/function/FindFunction.java  |  75 ++
 .../apache/sling/query/function/HasFunction.java   |  70 ++
 .../sling/query/function/IdentityFunction.java     |  34 +
 .../IteratorToIteratorFunctionWrapper.java         |  54 ++
 .../apache/sling/query/function/LastFunction.java  |  35 +
 .../apache/sling/query/function/NextFunction.java  |  49 ++
 .../apache/sling/query/function/NotFunction.java   |  41 ++
 .../sling/query/function/ParentFunction.java       |  46 ++
 .../sling/query/function/ParentsFunction.java      |  45 ++
 .../apache/sling/query/function/PrevFunction.java  |  49 ++
 .../sling/query/function/SiblingsFunction.java     |  45 ++
 .../apache/sling/query/function/SliceFunction.java |  52 ++
 .../sling/query/function/UniqueFunction.java       |  42 ++
 .../sling/query/iterator/AbstractIterator.java     |  53 ++
 .../sling/query/iterator/AdaptToIterator.java      |  75 ++
 .../sling/query/iterator/AlternativeIterator.java  |  48 ++
 .../query/iterator/ArgumentResettingIterator.java  |  52 ++
 .../sling/query/iterator/DescendantsIterator.java  |  70 ++
 .../sling/query/iterator/EmptyElementFilter.java   |  45 ++
 .../sling/query/iterator/ExpandingIterator.java    |  65 ++
 .../sling/query/iterator/FilteringIterator.java    |  50 ++
 .../apache/sling/query/iterator/LastIterator.java  |  79 ++
 .../sling/query/iterator/MergingIterator.java      |  46 ++
 .../query/iterator/OptionDecoratingIterator.java   |  52 ++
 .../query/iterator/OptionStrippingIterator.java    |  48 ++
 .../sling/query/iterator/ParentsIterator.java      |  57 ++
 .../sling/query/iterator/ReverseIterator.java      |  56 ++
 .../sling/query/iterator/SiblingsIterator.java     | 119 +++
 .../apache/sling/query/iterator/SliceIterator.java |  66 ++
 .../apache/sling/query/iterator/SuppIterator.java  |  74 ++
 .../sling/query/iterator/UniqueIterator.java       |  56 ++
 .../sling/query/iterator/WarningIterator.java      |  61 ++
 .../sling/query/iterator/tree/BfsTreeIterator.java |  57 ++
 .../sling/query/iterator/tree/DfsTreeIterator.java |  54 ++
 .../query/predicate/IterableContainsPredicate.java |  46 ++
 .../sling/query/predicate/ParentPredicate.java     |  38 +
 .../sling/query/predicate/RejectingPredicate.java  |  45 ++
 .../sling/query/predicate/SelectorOperator.java    |  82 +++
 .../sling/query/resource/ResourcePredicate.java    |  99 +++
 .../query/resource/ResourcePropertyPredicate.java  |  51 ++
 .../sling/query/resource/ResourceTreeProvider.java |  87 +++
 .../sling/query/resource/jcr/JcrOperator.java      |  76 ++
 .../sling/query/resource/jcr/JcrQueryIterator.java |  56 ++
 .../sling/query/resource/jcr/JcrTypeResolver.java  |  26 +
 .../query/resource/jcr/SessionJcrTypeResolver.java |  85 +++
 .../sling/query/resource/jcr/query/Atomic.java     |  34 +
 .../sling/query/resource/jcr/query/Formula.java    |  61 ++
 .../query/resource/jcr/query/JcrQueryBuilder.java  | 141 ++++
 .../sling/query/resource/jcr/query/Term.java       |  24 +
 .../apache/sling/query/selector/FunctionType.java  | 116 +++
 .../sling/query/selector/HierarchyOperator.java    |  83 +++
 .../sling/query/selector/SelectorFunction.java     | 101 +++
 .../sling/query/selector/parser/Attribute.java     |  71 ++
 .../sling/query/selector/parser/Modifier.java      |  69 ++
 .../sling/query/selector/parser/ParserContext.java | 164 +++++
 .../sling/query/selector/parser/Selector.java      |  40 +
 .../query/selector/parser/SelectorParser.java      |  55 ++
 .../query/selector/parser/SelectorSegment.java     | 107 +++
 .../apache/sling/query/selector/parser/State.java  | 238 ++++++
 src/test/java/org/apache/sling/query/AddTest.java  |  58 ++
 .../apache/sling/query/AttributeOperatorTest.java  | 129 ++++
 .../java/org/apache/sling/query/ChildrenTest.java  |  62 ++
 .../java/org/apache/sling/query/ClosestTest.java   |  52 ++
 .../java/org/apache/sling/query/FilterTest.java    |  57 ++
 src/test/java/org/apache/sling/query/FindTest.java |  87 +++
 src/test/java/org/apache/sling/query/HasTest.java  |  49 ++
 .../apache/sling/query/HierarchySelectorTest.java  |  57 ++
 src/test/java/org/apache/sling/query/LastTest.java |  46 ++
 src/test/java/org/apache/sling/query/ListTest.java |  94 +++
 src/test/java/org/apache/sling/query/MapTest.java  |  44 ++
 .../apache/sling/query/MultipleSelectorTest.java   |  46 ++
 .../org/apache/sling/query/NextPrevAllTest.java    |  96 +++
 .../java/org/apache/sling/query/NextPrevTest.java  |  96 +++
 .../org/apache/sling/query/NextPrevUntilTest.java  |  97 +++
 src/test/java/org/apache/sling/query/NotTest.java  |  65 ++
 .../java/org/apache/sling/query/ParentTest.java    |  44 ++
 .../java/org/apache/sling/query/ParentsTest.java   |  60 ++
 .../org/apache/sling/query/ParentsUntilTest.java   |  54 ++
 .../apache/sling/query/SelectorFunctionTest.java   | 121 ++++
 .../java/org/apache/sling/query/SiblingsTest.java  |  56 ++
 .../java/org/apache/sling/query/SliceTest.java     | 105 +++
 .../java/org/apache/sling/query/TestUtils.java     |  96 +++
 .../java/org/apache/sling/query/UniqueTest.java    |  41 ++
 .../apache/sling/query/iterator/ReverseTest.java   |  53 ++
 .../org/apache/sling/query/iterator/SuppTest.java  | 136 ++++
 .../apache/sling/query/mock/MockTypeResolver.java  |  49 ++
 .../org/apache/sling/query/mock/ResourceMock.java  | 144 ++++
 .../sling/query/mock/StringResourceMock.java       | 107 +++
 .../sling/query/mock/json/JsonToResource.java      |  65 ++
 .../selector/parser/JcrMultiSelectorTest.java      |  58 ++
 .../query/selector/parser/JcrSelectorTest.java     | 136 ++++
 .../sling/query/selector/parser/SelectorTest.java  | 265 +++++++
 src/test/resources/sample_tree.json                | 439 +++++++++++
 114 files changed, 9106 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..0cdb453
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,37 @@
+## SlingQuery 1.4.2
+
+* Added new add(...) method
+
+## SlingQuery 1.4.1
+
+* Fixed bug in the QUERY search strategy related
+
+## SlingQuery 1.4.0
+
+* `not()` function is now lazy (eg. `$(resourceResolver).not('cq:InvalidType').first()` return results immediately)
+* multiple selectors can be joined with comma: `$('[jcr:title=Foo], [jcr:title=Bar])`
+* JCR query becomes the default SearchStrategy
+
+## SlingQuery 1.3.1
+
+* fixed `javax.jcr.RepositoryException: invalid node type name` bug (reported by Dawid Jędraszek)
+
+## SlingQuery 1.3.0
+
+* lazy `asList()` method
+
+## SlingQuery 1.2.0
+
+* new strategy for the `find()`: `JCR`
+
+## SlingQuery 1.1.0
+
+* new selector features:
+	* [hierarchy operators](https://github.com/Cognifide/Sling-Query/wiki/Hierarchy-operator-list),
+	* [resource name](https://github.com/Cognifide/Sling-Query/wiki/Selector-syntax#wiki-resource-name),
+	* [new attribute operators](https://github.com/Cognifide/Sling-Query/wiki/Operator%20list)
+* two strategies for the `find()` method: `DFS` and `BFS`
+
+## SlingQuery 1.0.0
+
+* first released version
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8005459
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# SlingQuery
+[![Build Status](https://travis-ci.org/Cognifide/Sling-Query.png?branch=master)](https://travis-ci.org/Cognifide/Sling-Query)
+
+SlingQuery is a Sling resource tree traversal tool inspired by the [jQuery](http://api.jquery.com/category/traversing/tree-traversal/).
+
+## Introduction
+
+Recommended way to find resources in the Sling repository is using tree-traversal methods, like `listChildren()` and `getParent()` rather than JCR queries. The latter are great for listing resources with given properties, but we can't leverage the repository tree structure with such queries. On the other hand, using tree-traversal method is quite verbose. Consider following code that takes an resource and returns its first ancestor, being `cq:Page`, with given `jcr:content/cq:template` a [...]
+
+    Resource resource = ...;
+    while ((resource = resource.getParent()) != null) {
+        if (!resource.isResourceType("cq:Page")) {
+            continue;
+        }
+        Resource template = resource.getChild("jcr:content/cq:template");
+        if (template != null && "my/template".equals(template.adaptTo(String.class))) {
+            break;
+        }
+    }
+    if (resource != null) {
+        // we've found appropriate ancestor
+    }
+
+SlingQuery is a tool that helps creating such queries in a more concise way. Above code could be written as:
+
+    import static com.cognifide.sling.query.api.SlingQuery.$;
+    // ...
+    $(resource).closest("cq:Page[jcr:content/cq:template=my/template]")
+
+Dollar sign is a static method that takes the resource array and creates SlingQuery object. The `closest()` method returns the first ancestor matching the selector string passed as the argument.
+
+SlingQuery is inspired by the jQuery framework. jQuery is the source of method names, selector string syntax and the dollar sign method used as a collection constructor.
+
+## Features
+
+* useful [operations](https://github.com/Cognifide/Sling-Query/wiki/Method-list) to traverse the resource tree,
+* flexible [filtering syntax](https://github.com/Cognifide/Sling-Query/wiki/Selector-syntax),
+* lazy evaluation of the query result,
+* `SlingQuery` object is immutable (thread-safe),
+* fluent, friendly, jQuery-like API.
+
+## Installation
+
+Add following Maven dependency to your `pom.xml`:
+
+	<dependency>
+		<groupId>org.apache.sling</groupId>
+		<artifactId>org.apache.sling.query</artifactId>
+		<version>1.4.4</version>
+	</dependency>
+
+## Documentation
+
+* [CIRCUIT 2014 presentation](http://cognifide.github.io/Sling-Query/circuit2014/)
+* [Basic ideas](https://github.com/Cognifide/Sling-Query/wiki/Basic-ideas)
+* [Method list](https://github.com/Cognifide/Sling-Query/wiki/Method-list)
+* [Selector syntax](https://github.com/Cognifide/Sling-Query/wiki/Selector-syntax)
+	* [Operator list](https://github.com/Cognifide/Sling-Query/wiki/Operator-list)
+	* [Modifier list](https://github.com/Cognifide/Sling-Query/wiki/Modifier-list)
+	* [Hierarchy operator list](https://github.com/Cognifide/Sling-Query/wiki/Hierarchy-operator-list)
+* [Examples](https://github.com/Cognifide/Sling-Query/wiki/Examples)
+
+## External resources
+
+* See the [Apache Sling website](http://sling.apache.org/) for the Sling reference documentation. Apache Sling, Apache and Sling are trademarks of the [Apache Software Foundation](http://apache.org).
+* Method names, selector syntax and some parts of documentation are inspired by the [jQuery](http://jquery.com/) library.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a6c191f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+
+<project
+    xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>19</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>org.apache.sling.query</artifactId>
+    <version>1.4.5-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>sling-query</name>
+    <description>jQuery-inspired repository traversal tool for Sling</description>
+
+    <properties>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/sling-query</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/sling-query</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-query</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <!-- commons -->
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <scope>provided</scope>
+            <version>2.5</version>
+        </dependency>
+
+        <!-- sling -->
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <scope>provided</scope>
+            <version>2.2.4</version>
+        </dependency>
+
+        <!-- jcr -->
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- org.slf4j -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- test dependencies -->
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.2.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/query/AbstractQuery.java b/src/main/java/org/apache/sling/query/AbstractQuery.java
new file mode 100644
index 0000000..3145668
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/AbstractQuery.java
@@ -0,0 +1,802 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.api.Function;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.SearchStrategy;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.function.AddFunction;
+import org.apache.sling.query.function.ChildrenFunction;
+import org.apache.sling.query.function.ClosestFunction;
+import org.apache.sling.query.function.CompositeFunction;
+import org.apache.sling.query.function.DescendantFunction;
+import org.apache.sling.query.function.FilterFunction;
+import org.apache.sling.query.function.FindFunction;
+import org.apache.sling.query.function.HasFunction;
+import org.apache.sling.query.function.IdentityFunction;
+import org.apache.sling.query.function.LastFunction;
+import org.apache.sling.query.function.NextFunction;
+import org.apache.sling.query.function.NotFunction;
+import org.apache.sling.query.function.ParentFunction;
+import org.apache.sling.query.function.ParentsFunction;
+import org.apache.sling.query.function.PrevFunction;
+import org.apache.sling.query.function.SiblingsFunction;
+import org.apache.sling.query.function.SliceFunction;
+import org.apache.sling.query.function.UniqueFunction;
+import org.apache.sling.query.iterator.EmptyElementFilter;
+import org.apache.sling.query.iterator.OptionDecoratingIterator;
+import org.apache.sling.query.iterator.OptionStrippingIterator;
+import org.apache.sling.query.predicate.IterableContainsPredicate;
+import org.apache.sling.query.predicate.RejectingPredicate;
+import org.apache.sling.query.selector.SelectorFunction;
+
+public abstract class AbstractQuery<T, Q extends AbstractQuery<T, Q>> implements Iterable<T> {
+
+	protected final List<Function<?, ?>> functions = new ArrayList<Function<?, ?>>();
+
+	private final List<T> initialCollection;
+
+	private final SearchStrategy searchStrategy;
+
+	private final TreeProvider<T> provider;
+
+	protected AbstractQuery(TreeProvider<T> provider, T[] initialCollection, SearchStrategy strategy) {
+		this.provider = provider;
+		this.initialCollection = new ArrayList<T>(Arrays.asList(initialCollection));
+		this.searchStrategy = strategy;
+	}
+
+	protected AbstractQuery(AbstractQuery<T, Q> original, SearchStrategy searchStrategy) {
+		this.functions.addAll(original.functions);
+		this.initialCollection = new ArrayList<T>(original.initialCollection);
+		this.searchStrategy = searchStrategy;
+		this.provider = original.provider;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public Iterator<T> iterator() {
+		IteratorToIteratorFunction<T> f = new CompositeFunction<T>(functions);
+		Iterator<Option<T>> iterator = f.apply(new OptionDecoratingIterator<T>(initialCollection.iterator()));
+		iterator = new EmptyElementFilter<T>(iterator);
+		return new OptionStrippingIterator<T>(iterator);
+	}
+
+	/**
+	 * Include resources to the collection.
+	 * 
+	 * @param iterable Resources to include
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q add(T... resources) {
+		return function(new AddFunction<T>(Arrays.asList(resources)));
+	}
+
+	/**
+	 * Include resources to the collection.
+	 * 
+	 * @param iterable Resources to include
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q add(Iterable<T> iterable) {
+		return function(new AddFunction<T>(iterable));
+	}
+
+	/**
+	 * Transform SlingQuery collection into a lazy list.
+	 * 
+	 * @return List containing all elements from the collection.
+	 */
+	public List<T> asList() {
+		return new LazyList<T>(iterator());
+	}
+
+	/**
+	 * Get list of the children for each Resource in the collection.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q children() {
+		return function(new ChildrenFunction<T>(provider));
+	}
+
+	/**
+	 * Get list of the children for each Resource in the collection.
+	 * 
+	 * @param filter Children filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q children(String filter) {
+		return function(new ChildrenFunction<T>(provider), filter);
+	}
+
+	/**
+	 * Get list of the children for each Resource in the collection.
+	 * 
+	 * @param filter Children filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q children(Predicate<T> filter) {
+		return function(new ChildrenFunction<T>(provider), filter);
+	}
+
+	/**
+	 * Get list of the children for each Resource in the collection.
+	 * 
+	 * @param filter Children filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q children(Iterable<T> filter) {
+		return function(new ChildrenFunction<T>(provider), filter);
+	}
+
+	/**
+	 * For each Resource in the collection, return the first element matching the selector testing the
+	 * Resource itself and traversing up its ancestors.
+	 * 
+	 * @param selector Ancestor filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q closest(String selector) {
+		return closest(parse(selector));
+	}
+
+	/**
+	 * For each Resource in the collection, return the first element matching the selector testing the
+	 * Resource itself and traversing up its ancestors.
+	 * 
+	 * @param iterable Ancestor filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q closest(Iterable<T> iterable) {
+		return closest(new IterableContainsPredicate<T>(iterable, provider));
+	}
+
+	/**
+	 * For each Resource in the collection, return the first element matching the selector testing the
+	 * Resource itself and traversing up its ancestors.
+	 * 
+	 * @param selector Ancestor filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q closest(Predicate<T> predicate) {
+		return function(new ClosestFunction<T>(predicate, provider));
+	}
+
+	/**
+	 * Reduce Resource collection to the one Resource at the given 0-based index.
+	 * 
+	 * @param index 0-based index
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q eq(int index) {
+		return slice(index, index);
+	}
+
+	/**
+	 * Filter Resource collection using given selector.
+	 * 
+	 * @param selector Selector
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q filter(String selector) {
+		return function(new IdentityFunction<T>(), selector);
+	}
+
+	/**
+	 * Filter Resource collection using given predicate object.
+	 * 
+	 * @param predicate Collection filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q filter(Predicate<T> predicate) {
+		return function(new FilterFunction<T>(predicate));
+	}
+
+	/**
+	 * Filter Resource collection using given iterable.
+	 * 
+	 * @param iterable Collection filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q filter(Iterable<T> iterable) {
+		return function(new FilterFunction<T>(new IterableContainsPredicate<T>(iterable, provider)));
+	}
+
+	/**
+	 * For each Resource in collection use depth-first search to return all its descendants. Please notice
+	 * that invoking this method on a Resource being a root of a large subtree may and will cause performance
+	 * problems.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q find() {
+		return function(new FindFunction<T>(searchStrategy, provider, ""));
+	}
+
+	/**
+	 * For each Resource in collection use breadth-first search to return all its descendants. Please notice
+	 * that invoking this method on a Resource being a root of a large subtree may and will cause performance
+	 * problems.
+	 * 
+	 * @param selector descendants filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q find(String selector) {
+		return function(new FindFunction<T>(searchStrategy, provider, selector), selector);
+	}
+
+	/**
+	 * For each Resource in collection use breadth-first search to return all its descendants. Please notice
+	 * that invoking this method on a Resource being a root of a large subtree may and will cause performance
+	 * problems.
+	 * 
+	 * @param predicate descendants filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q find(Predicate<T> predicate) {
+		return function(new FindFunction<T>(searchStrategy, provider, ""), predicate);
+	}
+
+	/**
+	 * For each Resource in collection use breadth-first search to return all its descendants. Please notice
+	 * that invoking this method on a Resource being a root of a large subtree may and will cause performance
+	 * problems.
+	 * 
+	 * @param iterable descendants filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q find(Iterable<T> iterable) {
+		return function(new DescendantFunction<T>(new LazyList<T>(iterable.iterator()), provider));
+	}
+
+	/**
+	 * Filter Resource collection to the first element. Equivalent to {@code eq(0)} or {@code slice(0, 0)}.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q first() {
+		return eq(0);
+	}
+
+	/**
+	 * Pick such Resources from the collection that have descendant matching the selector.
+	 * 
+	 * @param selector Descendant selector
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q has(String selector) {
+		return function(new HasFunction<T>(selector, searchStrategy, provider));
+	}
+
+	/**
+	 * Pick such Resources from the collection that have descendant matching the selector.
+	 * 
+	 * @param predicate Descendant selector
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q has(Predicate<T> predicate) {
+		return function(new HasFunction<T>(predicate, searchStrategy, provider));
+	}
+
+	/**
+	 * Pick such Resources from the collection that have descendant matching the selector.
+	 * 
+	 * @param iterable Descendant selector
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q has(Iterable<T> iterable) {
+		return function(new HasFunction<T>(iterable, provider));
+	}
+
+	/**
+	 * Filter Resource collection to the last element.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q last() {
+		return function(new LastFunction<T>());
+	}
+
+	/**
+	 * Return the next sibling for each Resource in the collection.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q next() {
+		return function(new NextFunction<T>(provider));
+	}
+
+	/**
+	 * Return the next sibling for each Resource in the collection and filter it by a selector. If the next
+	 * sibling doesn't match it, empty collection will be returned.
+	 * 
+	 * @param selector Next sibling filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q next(String selector) {
+		return function(new NextFunction<T>(provider), selector);
+	}
+
+	/**
+	 * Return the next sibling for each Resource in the collection and filter it by a selector. If the next
+	 * sibling doesn't match it, empty collection will be returned.
+	 * 
+	 * @param predicate Next sibling filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q next(Predicate<T> predicate) {
+		return function(new NextFunction<T>(provider), predicate);
+	}
+
+	/**
+	 * Return the next sibling for each Resource in the collection and filter it by a selector. If the next
+	 * sibling doesn't match it, empty collection will be returned.
+	 * 
+	 * @param iterable Next sibling filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q next(Iterable<T> iterable) {
+		return function(new NextFunction<T>(provider), iterable);
+	}
+
+	/**
+	 * Return all following siblings for each Resource in the collection.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q nextAll() {
+		return function(new NextFunction<T>(new RejectingPredicate<T>(), provider));
+	}
+
+	/**
+	 * Return all following siblings for each Resource in the collection, filtering them by a selector.
+	 * 
+	 * @param selector Following siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q nextAll(String selector) {
+		return function(new NextFunction<T>(new RejectingPredicate<T>(), provider), selector);
+	}
+
+	/**
+	 * Return all following siblings for each Resource in the collection, filtering them by a selector.
+	 * 
+	 * @param predicate Following siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q nextAll(Predicate<T> predicate) {
+		return function(new NextFunction<T>(new RejectingPredicate<T>(), provider), predicate);
+	}
+
+	/**
+	 * Return all following siblings for each Resource in the collection, filtering them by a selector.
+	 * 
+	 * @param iterable Following siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q nextAll(Iterable<T> iterable) {
+		return function(new NextFunction<T>(new RejectingPredicate<T>(), provider), iterable);
+	}
+
+	/**
+	 * Return all following siblings for each Resource in the collection up to, but not including, Resource
+	 * matched by a selector.
+	 * 
+	 * @param until Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q nextUntil(String until) {
+		return function(new NextFunction<T>(parse(until), provider));
+	}
+
+	/**
+	 * Return all following siblings for each Resource in the collection up to, but not including, Resource
+	 * matched by a selector.
+	 * 
+	 * @param predicate Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q nextUntil(Predicate<T> predicate) {
+		return function(new NextFunction<T>(predicate, provider));
+	}
+
+	/**
+	 * Return all following siblings for each Resource in the collection up to, but not including, Resource
+	 * matched by a selector.
+	 * 
+	 * @param iterable Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q nextUntil(Iterable<T> iterable) {
+		return nextUntil(new IterableContainsPredicate<T>(iterable, provider));
+	}
+
+	/**
+	 * Remove elements from the collection.
+	 * 
+	 * @param selector Selector used to remove Resources
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q not(String selector) {
+		return function(new NotFunction<T>(parse(selector)));
+	}
+
+	/**
+	 * Remove elements from the collection.
+	 * 
+	 * @param predicate Selector used to remove Resources
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q not(Predicate<T> predicate) {
+		return function(new FilterFunction<T>(new RejectingPredicate<T>(predicate)));
+	}
+
+	/**
+	 * Remove elements from the collection.
+	 * 
+	 * @param iterable Selector used to remove Resources
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q not(Iterable<T> iterable) {
+		return not(new IterableContainsPredicate<T>(iterable, provider));
+	}
+
+	/**
+	 * Replace each element in the collection with its parent.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q parent() {
+		return function(new ParentFunction<T>(provider));
+	}
+
+	/**
+	 * For each element in the collection find its all ancestor.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q parents() {
+		return function(new ParentsFunction<T>(new RejectingPredicate<T>(), provider));
+	}
+
+	/**
+	 * For each element in the collection find its all ancestor, filtered by a selector.
+	 * 
+	 * @param selector Parents filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q parents(String selector) {
+		return function(new ParentsFunction<T>(new RejectingPredicate<T>(), provider), selector);
+	}
+
+	/**
+	 * For each element in the collection find its all ancestor, filtered by a selector.
+	 * 
+	 * @param predicate Parents filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q parents(Predicate<T> predicate) {
+		return function(new ParentsFunction<T>(new RejectingPredicate<T>(), provider), predicate);
+	}
+
+	/**
+	 * For each element in the collection find its all ancestor, filtered by a selector.
+	 * 
+	 * @param predicate Parents filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q parents(Iterable<T> iterable) {
+		return function(new ParentsFunction<T>(new RejectingPredicate<T>(), provider), iterable);
+	}
+
+	/**
+	 * For each element in the collection find all of its ancestors until the predicate is met.
+	 * 
+	 * @param until Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q parentsUntil(String until) {
+		return function(new ParentsFunction<T>(parse(until), provider));
+	}
+
+	/**
+	 * For each element in the collection find all of its ancestors until the predicate is met.
+	 * 
+	 * @param predicate Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q parentsUntil(Predicate<T> predicate) {
+		return function(new ParentsFunction<T>(predicate, provider));
+	}
+
+	/**
+	 * For each element in the collection find all of its ancestors until the predicate is met.
+	 * 
+	 * @param iterable Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q parentsUntil(Iterable<T> iterable) {
+		return parentsUntil(new IterableContainsPredicate<T>(iterable, provider));
+	}
+
+	/**
+	 * Return the previous sibling for each Resource in the collection.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prev() {
+		return function(new PrevFunction<T>(provider));
+	}
+
+	/**
+	 * Return the previous sibling for each Resource in the collection and filter it by a selector. If the
+	 * previous sibling doesn't match it, empty collection will be returned.
+	 * 
+	 * @param selector Previous sibling filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prev(String selector) {
+		return function(new PrevFunction<T>(null, provider), selector);
+	}
+
+	/**
+	 * Return the previous sibling for each Resource in the collection and filter it by a selector. If the
+	 * previous sibling doesn't match it, empty collection will be returned.
+	 * 
+	 * @param predicate Previous sibling filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prev(Predicate<T> predicate) {
+		return function(new PrevFunction<T>(null, provider), predicate);
+	}
+
+	/**
+	 * Return the previous sibling for each Resource in the collection and filter it by a selector. If the
+	 * previous sibling doesn't match it, empty collection will be returned.
+	 * 
+	 * @param iterable Previous sibling filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prev(Iterable<T> iterable) {
+		return function(new PrevFunction<T>(null, provider), iterable);
+	}
+
+	/**
+	 * Return all previous siblings for each Resource in the collection.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prevAll() {
+		return function(new PrevFunction<T>(new RejectingPredicate<T>(), provider));
+	}
+
+	/**
+	 * Return all previous siblings for each Resource in the collection, filtering them by a selector.
+	 * 
+	 * @param selector Previous siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prevAll(String selector) {
+		return function(new PrevFunction<T>(new RejectingPredicate<T>(), provider), selector);
+	}
+
+	/**
+	 * Return all previous siblings for each Resource in the collection, filtering them by a selector.
+	 * 
+	 * @param predicate Previous siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prevAll(Predicate<T> predicate) {
+		return function(new PrevFunction<T>(new RejectingPredicate<T>(), provider), predicate);
+	}
+
+	/**
+	 * Return all previous siblings for each Resource in the collection, filtering them by a selector.
+	 * 
+	 * @param iterable Previous siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prevAll(Iterable<T> iterable) {
+		return function(new PrevFunction<T>(new RejectingPredicate<T>(), provider), iterable);
+	}
+
+	/**
+	 * Return all previous siblings for each Resource in the collection up to, but not including, Resource
+	 * matched by a selector.
+	 * 
+	 * @param until Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prevUntil(String until) {
+		return function(new PrevFunction<T>(parse(until), provider));
+	}
+
+	/**
+	 * Return all previous siblings for each Resource in the collection up to, but not including, Resource
+	 * matched by a selector.
+	 * 
+	 * @param predicate Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prevUntil(Predicate<T> predicate) {
+		return function(new PrevFunction<T>(predicate, provider));
+	}
+
+	/**
+	 * Return all previous siblings for each Resource in the collection up to, but not including, Resource
+	 * matched by a selector.
+	 * 
+	 * @param iterable Selector marking when the operation should stop
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q prevUntil(Iterable<T> iterable) {
+		return prevUntil(new IterableContainsPredicate<T>(iterable, provider));
+	}
+
+	/**
+	 * Set new search strategy, which will be used in {@link Q#find()} and {@link Q#has(String)} functions.
+	 * 
+	 * @param strategy Search strategy type
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q searchStrategy(SearchStrategy strategy) {
+		return clone(this, strategy);
+	}
+
+	/**
+	 * Return siblings for the given Ts.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q siblings() {
+		return siblings("");
+	}
+
+	/**
+	 * Return siblings for the given Resources filtered by a selector.
+	 * 
+	 * @param selector Siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q siblings(String selector) {
+		return function(new SiblingsFunction<T>(provider), selector);
+	}
+
+	/**
+	 * Return siblings for the given Resources filtered by a selector.
+	 * 
+	 * @param predicate Siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q siblings(Predicate<T> predicate) {
+		return function(new SiblingsFunction<T>(provider), predicate);
+	}
+
+	/**
+	 * Return siblings for the given Resources filtered by a selector.
+	 * 
+	 * @param iterable Siblings filter
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q siblings(Iterable<T> iterable) {
+		return function(new SiblingsFunction<T>(provider), iterable);
+	}
+
+	/**
+	 * Filter out first {@code from} Resources from the collection.
+	 * 
+	 * @param from How many Resources to cut out
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q slice(int from) {
+		if (from < 0) {
+			throw new IndexOutOfBoundsException();
+		}
+		return function(new SliceFunction<T>(from));
+	}
+
+	/**
+	 * Reduce the collection to a subcollection specified by a given range. Both from and to are inclusive,
+	 * 0-based indices.
+	 * 
+	 * @param from Low endpoint (inclusive) of the subcollection
+	 * @param to High endpoint (inclusive) of the subcollection
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q slice(int from, int to) {
+		if (from < 0) {
+			throw new IndexOutOfBoundsException();
+		}
+		if (from > to) {
+			throw new IllegalArgumentException();
+		}
+		return function(new SliceFunction<T>(from, to));
+	}
+
+	/**
+	 * Filter out repeated adjacent resources.
+	 * 
+	 * @return new SlingQuery object transformed by this operation
+	 */
+	public Q unique() {
+		return function(new UniqueFunction<T>(provider));
+	}
+
+	private Q function(Function<?, ?> function, Iterable<T> iterable) {
+		Q newQuery = clone(this, this.searchStrategy);
+		newQuery.functions.add(function);
+		newQuery.functions.add(new FilterFunction<T>(new IterableContainsPredicate<T>(iterable, provider)));
+		return newQuery;
+	}
+
+	private Q function(Function<?, ?> function, Predicate<T> predicate) {
+		Q newQuery = clone(this, this.searchStrategy);
+		newQuery.functions.add(function);
+		newQuery.functions.add(new FilterFunction<T>(predicate));
+		return newQuery;
+	}
+
+	private Q function(Function<?, ?> function, String selector) {
+		Q newQuery = clone(this, this.searchStrategy);
+		newQuery.functions.add(function);
+		newQuery.functions.add(new SelectorFunction<T>(selector, provider, searchStrategy));
+		return newQuery;
+	}
+
+	private Q function(Function<?, ?> function) {
+		Q newQuery = clone(this, this.searchStrategy);
+		newQuery.functions.add(function);
+		return newQuery;
+	}
+
+	private SelectorFunction<T> parse(String selector) {
+		return new SelectorFunction<T>(selector, provider, searchStrategy);
+	}
+
+	protected abstract Q clone(AbstractQuery<T, Q> original, SearchStrategy strategy);
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder();
+		builder.append("$(");
+		Iterator<T> iterator = this.iterator();
+		while (iterator.hasNext()) {
+			builder.append('[');
+			builder.append(iterator.next());
+			builder.append(']');
+			if (iterator.hasNext()) {
+				builder.append(", ");
+			}
+		}
+		builder.append(")");
+		return builder.toString();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/IteratorUtils.java b/src/main/java/org/apache/sling/query/IteratorUtils.java
new file mode 100644
index 0000000..4975be6
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/IteratorUtils.java
@@ -0,0 +1,43 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+public final class IteratorUtils {
+
+	private IteratorUtils() {
+	}
+
+	public static <T> Iterator<T> arrayIterator(T... elements) {
+		return Arrays.asList(elements).iterator();
+	}
+
+	@SuppressWarnings("unchecked")
+	public static <T> Iterator<T> singleElementIterator(T element) {
+		return Arrays.asList(element).iterator();
+	}
+
+	@SuppressWarnings("unchecked")
+	public static <T> Iterator<T> emptyIterator() {
+		return Arrays.<T> asList().iterator();
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/LazyList.java b/src/main/java/org/apache/sling/query/LazyList.java
new file mode 100644
index 0000000..ce6a630
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/LazyList.java
@@ -0,0 +1,278 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+public class LazyList<E> implements List<E> {
+
+	private final class LazyListIterator implements ListIterator<E> {
+		private int index = 0;
+
+		private LazyListIterator() {
+			this.index = 0;
+		}
+
+		private LazyListIterator(int index) {
+			this.index = index;
+		}
+
+		@Override
+		public boolean hasNext() {
+			fillToSize(index + 1);
+			return arrayList.size() > index;
+		}
+
+		@Override
+		public E next() {
+			if (!hasNext()) {
+				throw new NoSuchElementException();
+			}
+			return arrayList.get(index++);
+		}
+
+		@Override
+		public boolean hasPrevious() {
+			return index > 0;
+		}
+
+		@Override
+		public E previous() {
+			if (!hasPrevious()) {
+				throw new NoSuchElementException();
+			}
+			fillToSize(index);
+			return arrayList.get(--index);
+		}
+
+		@Override
+		public int nextIndex() {
+			return index;
+		}
+
+		@Override
+		public int previousIndex() {
+			return index - 1;
+		}
+
+		@Override
+		public void remove() {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void set(E e) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void add(E e) {
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	private final List<E> arrayList;
+
+	private final Iterator<E> iterator;
+
+	public LazyList(Iterator<E> iterator) {
+		this.arrayList = new ArrayList<E>();
+		this.iterator = iterator;
+	}
+
+	private void fillAll() {
+		while (fillNext() != -1)
+			;
+	}
+
+	private void fillToSize(int size) {
+		for (int s = arrayList.size(); s < size; s++) {
+			if (fillNext() == -1) {
+				break;
+			}
+		}
+	}
+
+	private int fillNext() {
+		if (iterator.hasNext()) {
+			E element = iterator.next();
+			arrayList.add(element);
+			return arrayList.size() - 1;
+		}
+		return -1;
+	}
+
+	@Override
+	public int size() {
+		fillAll();
+		return arrayList.size();
+	}
+
+	@Override
+	public boolean isEmpty() {
+		return arrayList.isEmpty() && !iterator.hasNext();
+	}
+
+	@Override
+	public boolean contains(Object o) {
+		return indexOf(o) != -1;
+	}
+
+	@Override
+	public Iterator<E> iterator() {
+		return new LazyListIterator();
+	}
+
+	@Override
+	public Object[] toArray() {
+		fillAll();
+		return arrayList.toArray();
+	}
+
+	@Override
+	public <T> T[] toArray(T[] a) {
+		fillAll();
+		return arrayList.toArray(a);
+	}
+
+	@Override
+	public boolean containsAll(Collection<?> c) {
+		for (Object o : c) {
+			if (!contains(o)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	@Override
+	public E get(int index) {
+		fillToSize(index + 1);
+		return arrayList.get(index);
+	}
+
+	@Override
+	public int indexOf(Object o) {
+		int index = arrayList.indexOf(o);
+		if (index > -1) {
+			return index;
+		}
+		int addedIndex;
+		while ((addedIndex = fillNext()) != -1) {
+			E element = arrayList.get(addedIndex);
+			if (element == null && o == null) {
+				return addedIndex;
+			} else if (element != null && element.equals(o)) {
+				return addedIndex;
+			}
+		}
+		return -1;
+	}
+
+	@Override
+	public int lastIndexOf(Object o) {
+		fillAll();
+		return arrayList.lastIndexOf(o);
+	}
+
+	@Override
+	public ListIterator<E> listIterator() {
+		return new LazyListIterator();
+	}
+
+	@Override
+	public ListIterator<E> listIterator(int index) {
+		return new LazyListIterator(index);
+	}
+
+	@Override
+	public List<E> subList(int fromIndex, int toIndex) {
+		fillToSize(toIndex);
+		return arrayList.subList(fromIndex, toIndex);
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		fillAll();
+		return arrayList.equals(o);
+	}
+
+	@Override
+	public int hashCode() {
+		fillAll();
+		return arrayList.hashCode();
+	}
+
+	@Override
+	public boolean add(E e) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public boolean remove(Object o) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public boolean addAll(Collection<? extends E> c) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public boolean addAll(int index, Collection<? extends E> c) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public boolean removeAll(Collection<?> c) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public boolean retainAll(Collection<?> c) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void clear() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public E set(int index, E element) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void add(int index, E element) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public E remove(int index) {
+		throw new UnsupportedOperationException();
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/SlingQuery.java b/src/main/java/org/apache/sling/query/SlingQuery.java
new file mode 100644
index 0000000..a28cdfa
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/SlingQuery.java
@@ -0,0 +1,95 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import java.util.Iterator;
+
+import org.apache.sling.api.adapter.Adaptable;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.query.api.SearchStrategy;
+import org.apache.sling.query.iterator.AdaptToIterator;
+import org.apache.sling.query.resource.ResourceTreeProvider;
+
+/**
+ * SlingQuery is a Sling resource tree traversal tool inspired by the jQuery.
+ * 
+ * @author Tomasz Rękawek
+ */
+public class SlingQuery extends AbstractQuery<Resource, SlingQuery> {
+
+	private SlingQuery(AbstractQuery<Resource, SlingQuery> original, SearchStrategy strategy) {
+		super(original, strategy);
+	}
+
+	private SlingQuery(Resource[] resources, SearchStrategy strategy) {
+		super(new ResourceTreeProvider(resources[0].getResourceResolver()), resources, strategy);
+	}
+
+	public static SlingQuery $(Resource... resources) {
+		if (resources.length == 0) {
+			throw new IllegalArgumentException("Initial collection can't be empty");
+		} else {
+			return new SlingQuery(resources, SearchStrategy.QUERY);
+		}
+	}
+
+	public static SlingQuery $(ResourceResolver resolver) {
+		return $(resolver.getResource("/"));
+	}
+
+	/**
+	 * Transform the whole collection to a new {@link Iterable} object, invoking
+	 * {@link Adaptable#adaptTo(Class)} method on each Resource. If some Resource can't be adapted to the
+	 * class (eg. {@code adaptTo()} returns {@code null}), it will be skipped.
+	 * 
+	 * @param clazz Class used to adapt the Resources
+	 * @return new iterable containing succesfully adapted Resources
+	 */
+	public <E> Iterable<E> map(final Class<? extends E> clazz) {
+		return new Iterable<E>() {
+			@Override
+			public Iterator<E> iterator() {
+				return new AdaptToIterator<Resource, E>(SlingQuery.this.iterator(), clazz);
+			}
+		};
+	}
+
+	@Override
+	protected SlingQuery clone(AbstractQuery<Resource, SlingQuery> original, SearchStrategy strategy) {
+		return new SlingQuery(original, strategy);
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder();
+		builder.append("$(");
+		Iterator<Resource> iterator = this.iterator();
+		while (iterator.hasNext()) {
+			builder.append(iterator.next().getPath());
+			if (iterator.hasNext()) {
+				builder.append(", ");
+			}
+		}
+		builder.append(")");
+		return builder.toString();
+	}
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/api/Function.java b/src/main/java/org/apache/sling/query/api/Function.java
new file mode 100644
index 0000000..627875c
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/api/Function.java
@@ -0,0 +1,38 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.api;
+
+/**
+ * Function can transform one value into another.
+ * 
+ * @author Tomasz Rękawek
+ * 
+ * @param <F> Input type
+ * @param <T> Output type
+ */
+public interface Function<F, T> {
+	/**
+	 * Take input F and transform it into output T.
+	 * 
+	 * @param input Input value
+	 * @return Output value
+	 */
+	T apply(F input);
+}
diff --git a/src/main/java/org/apache/sling/query/api/Predicate.java b/src/main/java/org/apache/sling/query/api/Predicate.java
new file mode 100644
index 0000000..0354a51
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/api/Predicate.java
@@ -0,0 +1,36 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.api;
+
+/**
+ * ResourcePredicate determine true or false value for a given T.
+ * 
+ * @author Tomasz Rękawek
+ * 
+ */
+public interface Predicate<T> {
+	/**
+	 * Accept or reject given resource.
+	 * 
+	 * @param element Object to test
+	 * @return {@code true} or {@code false}
+	 */
+	boolean accepts(T element);
+}
diff --git a/src/main/java/org/apache/sling/query/api/SearchStrategy.java b/src/main/java/org/apache/sling/query/api/SearchStrategy.java
new file mode 100644
index 0000000..781dd0d
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/api/SearchStrategy.java
@@ -0,0 +1,24 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.api;
+
+public enum SearchStrategy {
+	BFS, DFS, QUERY;
+}
diff --git a/src/main/java/org/apache/sling/query/api/TreeProvider.java b/src/main/java/org/apache/sling/query/api/TreeProvider.java
new file mode 100644
index 0000000..959b2ea
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/api/TreeProvider.java
@@ -0,0 +1,42 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.api;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.selector.parser.Attribute;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+
+public interface TreeProvider<T> {
+	Iterator<T> listChildren(T parent);
+
+	T getParent(T element);
+
+	String getName(T element);
+
+	Predicate<T> getPredicate(String type, String name, List<Attribute> attributes);
+
+	Iterator<T> query(List<SelectorSegment> segment, T resource);
+
+	boolean sameElement(T o1, T o2);
+
+	boolean isDescendant(T root, T testedElement);
+}
diff --git a/src/main/java/org/apache/sling/query/api/function/ElementToIteratorFunction.java b/src/main/java/org/apache/sling/query/api/function/ElementToIteratorFunction.java
new file mode 100644
index 0000000..4c98a58
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/api/function/ElementToIteratorFunction.java
@@ -0,0 +1,27 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.api.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Function;
+
+public interface ElementToIteratorFunction<T> extends Function<T, Iterator<T>> {
+}
diff --git a/src/main/java/org/apache/sling/query/api/function/IteratorToIteratorFunction.java b/src/main/java/org/apache/sling/query/api/function/IteratorToIteratorFunction.java
new file mode 100644
index 0000000..cb14cd2
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/api/function/IteratorToIteratorFunction.java
@@ -0,0 +1,27 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.api.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Function;
+
+public interface IteratorToIteratorFunction<T> extends Function<Iterator<Option<T>>, Iterator<Option<T>>> {
+}
diff --git a/src/main/java/org/apache/sling/query/api/function/Option.java b/src/main/java/org/apache/sling/query/api/function/Option.java
new file mode 100644
index 0000000..dbc0249
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/api/function/Option.java
@@ -0,0 +1,82 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.api.function;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+public class Option<T> {
+	private final T element;
+
+	private final int argumentId;
+
+	private Option(T element, int argumentId) {
+		this.element = element;
+		this.argumentId = argumentId;
+	}
+
+	public static <T> Option<T> of(T element, int argumentId) {
+		return new Option<T>(element, argumentId);
+	}
+
+	public static <T> Option<T> empty(int argumentId) {
+		return new Option<T>(null, argumentId);
+	}
+
+	public int getArgumentId() {
+		return argumentId;
+	}
+
+	public T getElement() {
+		return element;
+	}
+
+	public boolean isEmpty() {
+		return element == null;
+	}
+
+	public String toString() {
+		if (isEmpty()) {
+			return "Option[-]";
+		} else {
+			return String.format("Option[%s]", element.toString());
+		}
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		if (obj == this) {
+			return true;
+		}
+		if (obj.getClass() != getClass()) {
+			return false;
+		}
+		Option<?> rhs = (Option<?>) obj;
+		return new EqualsBuilder().append(element, rhs.element).isEquals();
+	}
+
+	@Override
+	public int hashCode() {
+		return new HashCodeBuilder().append(element).toHashCode();
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/function/AddFunction.java b/src/main/java/org/apache/sling/query/function/AddFunction.java
new file mode 100644
index 0000000..4d4f364
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/AddFunction.java
@@ -0,0 +1,43 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.MergingIterator;
+import org.apache.sling.query.iterator.OptionDecoratingIterator;
+
+public class AddFunction<T> implements IteratorToIteratorFunction<T> {
+
+	private final Iterable<T> iterable;
+
+	public AddFunction(Iterable<T> iterable) {
+		this.iterable = iterable;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		return new MergingIterator<Option<T>>(input, new OptionDecoratingIterator<T>(iterable.iterator()));
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/function/ChildrenFunction.java b/src/main/java/org/apache/sling/query/function/ChildrenFunction.java
new file mode 100644
index 0000000..17fce29
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/ChildrenFunction.java
@@ -0,0 +1,39 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+
+public class ChildrenFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final TreeProvider<T> provider;
+
+	public ChildrenFunction(TreeProvider<T> provider) {
+		this.provider = provider;
+	}
+
+	@Override
+	public Iterator<T> apply(T parent) {
+		return provider.listChildren(parent);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/function/ClosestFunction.java b/src/main/java/org/apache/sling/query/function/ClosestFunction.java
new file mode 100644
index 0000000..92eb1a8
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/ClosestFunction.java
@@ -0,0 +1,51 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.IteratorUtils;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+
+public class ClosestFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final Predicate<T> predicate;
+
+	private final TreeProvider<T> provider;
+
+	public ClosestFunction(Predicate<T> predicate, TreeProvider<T> provider) {
+		this.predicate = predicate;
+		this.provider = provider;
+	}
+
+	@Override
+	public Iterator<T> apply(T resource) {
+		T current = resource;
+		while (current != null) {
+			if (predicate.accepts(current)) {
+				return IteratorUtils.singleElementIterator(current);
+			}
+			current = provider.getParent(current);
+		}
+		return IteratorUtils.emptyIterator();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/function/CompositeFunction.java b/src/main/java/org/apache/sling/query/function/CompositeFunction.java
new file mode 100644
index 0000000..0041600
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/CompositeFunction.java
@@ -0,0 +1,45 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.api.Function;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+
+public class CompositeFunction<T> implements IteratorToIteratorFunction<T> {
+
+	private final List<Function<?, ?>> functions;
+
+	public CompositeFunction(List<Function<?, ?>> functions) {
+		this.functions = functions;
+	}
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		Iterator<Option<T>> iterator = input;
+		for (Function<?, ?> f : functions) {
+			iterator = new IteratorToIteratorFunctionWrapper<T>(f).apply(iterator);
+		}
+		return iterator;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/function/DescendantFunction.java b/src/main/java/org/apache/sling/query/function/DescendantFunction.java
new file mode 100644
index 0000000..3510f40
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/DescendantFunction.java
@@ -0,0 +1,45 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.DescendantsIterator;
+
+public class DescendantFunction<T> implements IteratorToIteratorFunction<T> {
+
+	private final Iterable<T> descendants;
+
+	private final TreeProvider<T> provider;
+
+	public DescendantFunction(Iterable<T> descendants, TreeProvider<T> provider) {
+		this.descendants = descendants;
+		this.provider = provider;
+	}
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		return new DescendantsIterator<T>(input, descendants.iterator(), provider);
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/function/EvenFunction.java b/src/main/java/org/apache/sling/query/function/EvenFunction.java
new file mode 100644
index 0000000..a127b74
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/EvenFunction.java
@@ -0,0 +1,56 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.FilteringIterator;
+
+public class EvenFunction<T> implements IteratorToIteratorFunction<T> {
+
+	private final boolean even;
+
+	public EvenFunction(boolean even) {
+		this.even = even;
+	}
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> resources) {
+		return new FilteringIterator<T>(resources, new EvenPredicate<T>(even));
+	}
+
+	private static class EvenPredicate<T> implements Predicate<T> {
+		private boolean accept;
+
+		public EvenPredicate(boolean firstState) {
+			accept = firstState;
+		}
+
+		@Override
+		public boolean accepts(T element) {
+			boolean oldAccept = accept;
+			accept = !accept;
+			return oldAccept;
+		}
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/function/FilterFunction.java b/src/main/java/org/apache/sling/query/function/FilterFunction.java
new file mode 100644
index 0000000..d45c49f
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/FilterFunction.java
@@ -0,0 +1,42 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.FilteringIterator;
+
+public class FilterFunction<T> implements IteratorToIteratorFunction<T> {
+
+	private final Predicate<T> predicate;
+
+	public FilterFunction(Predicate<T> predicate) {
+		this.predicate = predicate;
+	}
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		return new FilteringIterator<T>(input, predicate);
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/function/FindFunction.java b/src/main/java/org/apache/sling/query/function/FindFunction.java
new file mode 100644
index 0000000..e38205c
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/FindFunction.java
@@ -0,0 +1,75 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.api.SearchStrategy;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+import org.apache.sling.query.iterator.WarningIterator;
+import org.apache.sling.query.iterator.tree.BfsTreeIterator;
+import org.apache.sling.query.iterator.tree.DfsTreeIterator;
+import org.apache.sling.query.selector.parser.Selector;
+import org.apache.sling.query.selector.parser.SelectorParser;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+
+public class FindFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final List<SelectorSegment> preFilteringSelector;
+
+	private final TreeProvider<T> provider;
+
+	private final SearchStrategy strategy;
+
+	public FindFunction(SearchStrategy searchStrategy, TreeProvider<T> provider,
+			SelectorSegment preFilteringSelector) {
+		this.strategy = searchStrategy;
+		this.provider = provider;
+		this.preFilteringSelector = Arrays.asList(preFilteringSelector);
+	}
+
+	public FindFunction(SearchStrategy searchStrategy, TreeProvider<T> provider, String preFilteringSelector) {
+		this.strategy = searchStrategy;
+		this.provider = provider;
+		List<Selector> selectors = SelectorParser.parse(preFilteringSelector);
+		this.preFilteringSelector = SelectorParser.getFirstSegmentFromEachSelector(selectors);
+	}
+
+	@Override
+	public Iterator<T> apply(T input) {
+		Iterator<T> iterator;
+		switch (strategy) {
+			case BFS:
+				iterator = new BfsTreeIterator<T>(input, provider);
+				break;
+			case QUERY:
+				iterator = provider.query(preFilteringSelector, input);
+				break;
+			case DFS:
+			default:
+				iterator = new DfsTreeIterator<T>(input, provider);
+				break;
+		}
+		return new WarningIterator<T>(iterator);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/function/HasFunction.java b/src/main/java/org/apache/sling/query/function/HasFunction.java
new file mode 100644
index 0000000..7222e5d
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/HasFunction.java
@@ -0,0 +1,70 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.IteratorUtils;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.SearchStrategy;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.EmptyElementFilter;
+import org.apache.sling.query.selector.SelectorFunction;
+
+public class HasFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final IteratorToIteratorFunction<T> findFunction;
+
+	private final IteratorToIteratorFunction<T> filter;
+
+	private HasFunction(FindFunction<T> findFunction, IteratorToIteratorFunction<T> filter) {
+		this.findFunction = new IteratorToIteratorFunctionWrapper<T>(findFunction);
+		this.filter = filter;
+	}
+
+	public HasFunction(String selectorString, SearchStrategy searchStrategy, TreeProvider<T> provider) {
+		this(new FindFunction<T>(searchStrategy, provider, selectorString), new SelectorFunction<T>(
+				selectorString, provider, searchStrategy));
+	}
+
+	public HasFunction(Predicate<T> predicate, SearchStrategy searchStrategy, TreeProvider<T> provider) {
+		this(new FindFunction<T>(searchStrategy, provider, ""), new FilterFunction<T>(predicate));
+	}
+
+	public HasFunction(Iterable<T> iterable, TreeProvider<T> provider) {
+		this.findFunction = new DescendantFunction<T>(iterable, provider);
+		this.filter = new IdentityFunction<T>();
+	}
+
+	@Override
+	public Iterator<T> apply(T input) {
+		Iterator<Option<T>> iterator = IteratorUtils.singleElementIterator(Option.of(input, 0));
+		iterator = findFunction.apply(iterator);
+		iterator = filter.apply(iterator);
+		if (new EmptyElementFilter<T>(iterator).hasNext()) {
+			return IteratorUtils.singleElementIterator(input);
+		} else {
+			return IteratorUtils.emptyIterator();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/function/IdentityFunction.java b/src/main/java/org/apache/sling/query/function/IdentityFunction.java
new file mode 100644
index 0000000..71b3d4f
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/IdentityFunction.java
@@ -0,0 +1,34 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+
+public class IdentityFunction<T> implements IteratorToIteratorFunction<T> {
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		return input;
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/function/IteratorToIteratorFunctionWrapper.java b/src/main/java/org/apache/sling/query/function/IteratorToIteratorFunctionWrapper.java
new file mode 100644
index 0000000..ec207cb
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/IteratorToIteratorFunctionWrapper.java
@@ -0,0 +1,54 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Function;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.ExpandingIterator;
+
+public class IteratorToIteratorFunctionWrapper<T> implements IteratorToIteratorFunction<T> {
+
+	private final Function<?, ?> function;
+
+	public IteratorToIteratorFunctionWrapper(Function<?, ?> function) {
+		this.function = function;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> parentIterator) {
+		if (function instanceof ElementToIteratorFunction) {
+			return getOptionIterator((ElementToIteratorFunction<T>) function, parentIterator);
+		} else if (function instanceof IteratorToIteratorFunction) {
+			return ((IteratorToIteratorFunction<T>) function).apply(parentIterator);
+		} else {
+			throw new IllegalArgumentException("Don't know how to handle " + function.toString());
+		}
+	}
+
+	private static <T> Iterator<Option<T>> getOptionIterator(ElementToIteratorFunction<T> function,
+			Iterator<Option<T>> parentIterator) {
+		return new ExpandingIterator<T>((ElementToIteratorFunction<T>) function, parentIterator);
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/function/LastFunction.java b/src/main/java/org/apache/sling/query/function/LastFunction.java
new file mode 100644
index 0000000..e808eaa
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/LastFunction.java
@@ -0,0 +1,35 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.LastIterator;
+
+public class LastFunction<T> implements IteratorToIteratorFunction<T> {
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		return new LastIterator<T>(input);
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/function/NextFunction.java b/src/main/java/org/apache/sling/query/function/NextFunction.java
new file mode 100644
index 0000000..b27bc2e
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/NextFunction.java
@@ -0,0 +1,49 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+import org.apache.sling.query.iterator.SiblingsIterator;
+import org.apache.sling.query.iterator.SiblingsIterator.Type;
+
+public class NextFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final Predicate<T> until;
+
+	private final TreeProvider<T> provider;
+
+	public NextFunction(TreeProvider<T> provider) {
+		this(null, provider);
+	}
+
+	public NextFunction(Predicate<T> until, TreeProvider<T> provider) {
+		this.until = until;
+		this.provider = provider;
+	}
+
+	@Override
+	public Iterator<T> apply(T resource) {
+		return new SiblingsIterator<T>(until, resource, Type.NEXT, provider);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/function/NotFunction.java b/src/main/java/org/apache/sling/query/function/NotFunction.java
new file mode 100644
index 0000000..b244a2b
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/NotFunction.java
@@ -0,0 +1,41 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.ReverseIterator;
+
+public class NotFunction<T> implements IteratorToIteratorFunction<T> {
+
+	private IteratorToIteratorFunction<T> function;
+
+	public NotFunction(IteratorToIteratorFunction<T> function) {
+		this.function = function;
+	}
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		return new ReverseIterator<T>(function, input);
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/function/ParentFunction.java b/src/main/java/org/apache/sling/query/function/ParentFunction.java
new file mode 100644
index 0000000..6f0c19e
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/ParentFunction.java
@@ -0,0 +1,46 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.IteratorUtils;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+
+public class ParentFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final TreeProvider<T> provider;
+
+	public ParentFunction(TreeProvider<T> provider) {
+		this.provider = provider;
+	}
+
+	@Override
+	public Iterator<T> apply(T element) {
+		T parent = provider.getParent(element);
+		if (parent == null) {
+			return IteratorUtils.emptyIterator();
+		} else {
+			return IteratorUtils.singleElementIterator(parent);
+		}
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/function/ParentsFunction.java b/src/main/java/org/apache/sling/query/function/ParentsFunction.java
new file mode 100644
index 0000000..4b8a7ce
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/ParentsFunction.java
@@ -0,0 +1,45 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+import org.apache.sling.query.iterator.ParentsIterator;
+
+public class ParentsFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final Predicate<T> until;
+
+	private final TreeProvider<T> provider;
+
+	public ParentsFunction(Predicate<T> until, TreeProvider<T> provider) {
+		this.until = until;
+		this.provider = provider;
+	}
+
+	@Override
+	public Iterator<T> apply(T resource) {
+		return new ParentsIterator<T>(until, resource, provider);
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/function/PrevFunction.java b/src/main/java/org/apache/sling/query/function/PrevFunction.java
new file mode 100644
index 0000000..1354295
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/PrevFunction.java
@@ -0,0 +1,49 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+import org.apache.sling.query.iterator.SiblingsIterator;
+import org.apache.sling.query.iterator.SiblingsIterator.Type;
+
+public class PrevFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final Predicate<T> until;
+
+	private final TreeProvider<T> provider;
+
+	public PrevFunction(TreeProvider<T> provider) {
+		this(null, provider);
+	}
+
+	public PrevFunction(Predicate<T> until, TreeProvider<T> provider) {
+		this.until = until;
+		this.provider = provider;
+	}
+
+	@Override
+	public Iterator<T> apply(T resource) {
+		return new SiblingsIterator<T>(until, resource, Type.PREV, provider);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/function/SiblingsFunction.java b/src/main/java/org/apache/sling/query/function/SiblingsFunction.java
new file mode 100644
index 0000000..94bf6f0
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/SiblingsFunction.java
@@ -0,0 +1,45 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.IteratorUtils;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+
+public class SiblingsFunction<T> implements ElementToIteratorFunction<T> {
+
+	private final TreeProvider<T> provider;
+
+	public SiblingsFunction(TreeProvider<T> provider) {
+		this.provider = provider;
+	}
+
+	@Override
+	public Iterator<T> apply(T resource) {
+		T parent = provider.getParent(resource);
+		if (parent == null) {
+			return IteratorUtils.singleElementIterator(resource);
+		} else {
+			return provider.listChildren(parent);
+		}
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/function/SliceFunction.java b/src/main/java/org/apache/sling/query/function/SliceFunction.java
new file mode 100644
index 0000000..2e58e0e
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/SliceFunction.java
@@ -0,0 +1,52 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.SliceIterator;
+
+public class SliceFunction<T> implements IteratorToIteratorFunction<T> {
+
+	private final int from;
+
+	private final Integer to;
+
+	public SliceFunction(int from, int to) {
+		this.from = from;
+		this.to = to;
+	}
+
+	public SliceFunction(int from) {
+		this.from = from;
+		this.to = null;
+	}
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> resources) {
+		if (to == null) {
+			return new SliceIterator<T>(resources, from);
+		} else {
+			return new SliceIterator<T>(resources, from, to);
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/function/UniqueFunction.java b/src/main/java/org/apache/sling/query/function/UniqueFunction.java
new file mode 100644
index 0000000..c18880c
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/function/UniqueFunction.java
@@ -0,0 +1,42 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.function;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.iterator.UniqueIterator;
+
+public class UniqueFunction<T> implements IteratorToIteratorFunction<T> {
+
+	private final TreeProvider<T> treeProvider;
+
+	public UniqueFunction(TreeProvider<T> treeProvider) {
+		this.treeProvider = treeProvider;
+	}
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		return new UniqueIterator<T>(input, treeProvider);
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/AbstractIterator.java b/src/main/java/org/apache/sling/query/iterator/AbstractIterator.java
new file mode 100644
index 0000000..944e297
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/AbstractIterator.java
@@ -0,0 +1,53 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public abstract class AbstractIterator<T> implements Iterator<T> {
+
+	private T currentElement;
+
+	@Override
+	public boolean hasNext() {
+		if (currentElement == null) {
+			currentElement = getElement();
+		}
+		return currentElement != null;
+	}
+
+	@Override
+	public T next() {
+		if (!hasNext()) {
+			throw new NoSuchElementException();
+		}
+		T result = currentElement;
+		currentElement = null;
+		return result;
+	}
+
+	@Override
+	public void remove() {
+		throw new UnsupportedOperationException();
+	}
+
+	protected abstract T getElement();
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/AdaptToIterator.java b/src/main/java/org/apache/sling/query/iterator/AdaptToIterator.java
new file mode 100644
index 0000000..0e0715a
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/AdaptToIterator.java
@@ -0,0 +1,75 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.sling.api.adapter.Adaptable;
+
+public class AdaptToIterator<F, T> implements Iterator<T> {
+
+	private final Iterator<F> iterator;
+
+	private final Class<? extends T> clazz;
+
+	private T currentModel;
+
+	public AdaptToIterator(Iterator<F> iterator, Class<? extends T> clazz) {
+		this.clazz = clazz;
+		this.iterator = iterator;
+	}
+
+	@Override
+	public boolean hasNext() {
+		if (currentModel == null) {
+			getCurrentModel();
+		}
+		return currentModel != null;
+	}
+
+	@Override
+	public T next() {
+		if (!hasNext()) {
+			throw new NoSuchElementException();
+		}
+		T model = currentModel;
+		currentModel = null;
+		return model;
+	}
+
+	public void getCurrentModel() {
+		while (iterator.hasNext()) {
+			F element = iterator.next();
+			if (element instanceof Adaptable) {
+				currentModel = ((Adaptable) element).adaptTo(clazz);
+			}
+			if (currentModel != null) {
+				break;
+			}
+		}
+	}
+
+	@Override
+	public void remove() {
+		throw new UnsupportedOperationException();
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/AlternativeIterator.java b/src/main/java/org/apache/sling/query/iterator/AlternativeIterator.java
new file mode 100644
index 0000000..d9badcf
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/AlternativeIterator.java
@@ -0,0 +1,48 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.api.function.Option;
+
+public class AlternativeIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final List<Iterator<Option<T>>> iterators;
+
+	public AlternativeIterator(List<Iterator<Option<T>>> iterators) {
+		this.iterators = iterators;
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		Option<T> element = null;
+		for (Iterator<Option<T>> i : iterators) {
+			if (i.hasNext()) {
+				Option<T> option = i.next();
+				if (element == null || !option.isEmpty()) {
+					element = option;
+				}
+			}
+		}
+		return element;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/iterator/ArgumentResettingIterator.java b/src/main/java/org/apache/sling/query/iterator/ArgumentResettingIterator.java
new file mode 100644
index 0000000..14a9778
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/ArgumentResettingIterator.java
@@ -0,0 +1,52 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.Option;
+
+public class ArgumentResettingIterator<T> implements Iterator<Option<T>> {
+
+	private final Iterator<Option<T>> iterator;
+
+	private int index;
+
+	public ArgumentResettingIterator(Iterator<Option<T>> iterator) {
+		this.iterator = iterator;
+		this.index = 0;
+	}
+
+	@Override
+	public boolean hasNext() {
+		return iterator.hasNext();
+	}
+
+	@Override
+	public Option<T> next() {
+		return Option.of(iterator.next().getElement(), index++);
+	}
+
+	@Override
+	public void remove() {
+		iterator.remove();
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/DescendantsIterator.java b/src/main/java/org/apache/sling/query/iterator/DescendantsIterator.java
new file mode 100644
index 0000000..e40fa60
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/DescendantsIterator.java
@@ -0,0 +1,70 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.sling.query.LazyList;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.Option;
+
+public class DescendantsIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final Iterator<Option<T>> input;
+
+	private Option<T> current;
+
+	private Iterator<T> descendants;
+
+	private final TreeProvider<T> provider;
+
+	public DescendantsIterator(Iterator<Option<T>> input, Iterator<T> descendants, TreeProvider<T> provider) {
+		this.input = input;
+		this.current = null;
+		this.descendants = new ArrayList<T>(new LazyList<T>(descendants)).iterator();
+		this.provider = provider;
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		if (current == null) {
+			if (input.hasNext()) {
+				current = input.next();
+			} else {
+				return null;
+			}
+		}
+		return getDescendant();
+	}
+
+	private Option<T> getDescendant() {
+		while (descendants.hasNext()) {
+			T descendantCandidate = descendants.next();
+			if (provider.isDescendant(current.getElement(), descendantCandidate)) {
+				descendants.remove();
+				return Option.of(descendantCandidate, current.getArgumentId());
+			}
+		}
+		Option<T> result = Option.empty(current.getArgumentId());
+		current = null;
+		return result;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/EmptyElementFilter.java b/src/main/java/org/apache/sling/query/iterator/EmptyElementFilter.java
new file mode 100644
index 0000000..3f292f5
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/EmptyElementFilter.java
@@ -0,0 +1,45 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.Option;
+
+public class EmptyElementFilter<T> extends AbstractIterator<Option<T>> {
+
+	private final Iterator<Option<T>> input;
+
+	public EmptyElementFilter(Iterator<Option<T>> input) {
+		this.input = input;
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		while (input.hasNext()) {
+			Option<T> element = input.next();
+			if (!element.isEmpty()) {
+				return element;
+			}
+		}
+		return null;
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/ExpandingIterator.java b/src/main/java/org/apache/sling/query/iterator/ExpandingIterator.java
new file mode 100644
index 0000000..96d4be7
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/ExpandingIterator.java
@@ -0,0 +1,65 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+
+/**
+ * This iterator evaluates each element from the source iterator, expanding it using given function.
+ */
+public class ExpandingIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final ElementToIteratorFunction<T> function;
+
+	private final Iterator<Option<T>> parentIterator;
+
+	private Option<T> parentElement;
+
+	private Iterator<T> currentIterator;
+
+	public ExpandingIterator(ElementToIteratorFunction<T> expandingFunction,
+			Iterator<Option<T>> sourceIterator) {
+		this.function = expandingFunction;
+		this.parentIterator = sourceIterator;
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		if (currentIterator != null && currentIterator.hasNext()) {
+			return Option.of(currentIterator.next(), parentElement.getArgumentId());
+		}
+		while (parentIterator.hasNext()) {
+			parentElement = parentIterator.next();
+			if (parentElement.isEmpty()) {
+				return parentElement;
+			}
+			currentIterator = function.apply(parentElement.getElement());
+			if (currentIterator.hasNext()) {
+				return getElement();
+			} else {
+				return Option.empty(parentElement.getArgumentId());
+			}
+		}
+		return null;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/FilteringIterator.java b/src/main/java/org/apache/sling/query/iterator/FilteringIterator.java
new file mode 100644
index 0000000..fea6774
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/FilteringIterator.java
@@ -0,0 +1,50 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.function.Option;
+
+public class FilteringIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final Iterator<Option<T>> iterator;
+
+	private final Predicate<T> predicate;
+
+	public FilteringIterator(Iterator<Option<T>> iterator, Predicate<T> predicate) {
+		this.iterator = iterator;
+		this.predicate = predicate;
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		while (iterator.hasNext()) {
+			Option<T> element = iterator.next();
+			if (element.isEmpty() || predicate.accepts(element.getElement())) {
+				return element;
+			} else {
+				return Option.empty(element.getArgumentId());
+			}
+		}
+		return null;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/LastIterator.java b/src/main/java/org/apache/sling/query/iterator/LastIterator.java
new file mode 100644
index 0000000..10cc676
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/LastIterator.java
@@ -0,0 +1,79 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+import java.util.ListIterator;
+
+import org.apache.sling.query.LazyList;
+import org.apache.sling.query.api.function.Option;
+
+public class LastIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final LazyList<Option<T>> lazyList;
+
+	private final ListIterator<Option<T>> iterator;
+
+	private boolean finished;
+
+	private boolean initialized;
+
+	private int lastIndex = -1;
+
+	public LastIterator(Iterator<Option<T>> iterator) {
+		this.lazyList = new LazyList<Option<T>>(iterator);
+		this.iterator = lazyList.listIterator();
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		if (finished) {
+			return null;
+		}
+
+		initializeLastIndex();
+
+		Option<T> candidate;
+		if (iterator.hasNext()) {
+			candidate = iterator.next();
+		} else {
+			finished = true;
+			return null;
+		}
+		if (iterator.previousIndex() == lastIndex) {
+			finished = true;
+			return candidate;
+		} else {
+			return Option.empty(candidate.getArgumentId());
+		}
+	}
+
+	private void initializeLastIndex() {
+		ListIterator<Option<T>> i = lazyList.listIterator();
+		if (!initialized) {
+			while (i.hasNext()) {
+				if (!i.next().isEmpty()) {
+					lastIndex = i.previousIndex();
+				}
+			}
+		}
+		initialized = true;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/MergingIterator.java b/src/main/java/org/apache/sling/query/iterator/MergingIterator.java
new file mode 100644
index 0000000..3ff5fab
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/MergingIterator.java
@@ -0,0 +1,46 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+public class MergingIterator<T> extends AbstractIterator<T> {
+
+	private final Iterator<T>[] iterators;
+
+	private int index = 0;
+
+	public MergingIterator(Iterator<T>... iterators) {
+		this.iterators = iterators;
+	}
+
+	@Override
+	protected T getElement() {
+		while (index < iterators.length) {
+			if (iterators[index].hasNext()) {
+				return iterators[index].next();
+			} else {
+				index++;
+			}
+		}
+		return null;
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/OptionDecoratingIterator.java b/src/main/java/org/apache/sling/query/iterator/OptionDecoratingIterator.java
new file mode 100644
index 0000000..e88fd60
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/OptionDecoratingIterator.java
@@ -0,0 +1,52 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.Option;
+
+public class OptionDecoratingIterator<T> implements Iterator<Option<T>> {
+
+	private final Iterator<T> iterator;
+
+	private int index;
+
+	public OptionDecoratingIterator(Iterator<T> iterator) {
+		this.iterator = iterator;
+		this.index = 0;
+	}
+
+	@Override
+	public boolean hasNext() {
+		return iterator.hasNext();
+	}
+
+	@Override
+	public Option<T> next() {
+		return Option.of(iterator.next(), index++);
+	}
+
+	@Override
+	public void remove() {
+		iterator.remove();
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/OptionStrippingIterator.java b/src/main/java/org/apache/sling/query/iterator/OptionStrippingIterator.java
new file mode 100644
index 0000000..878f728
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/OptionStrippingIterator.java
@@ -0,0 +1,48 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.Option;
+
+public class OptionStrippingIterator<T> implements Iterator<T> {
+
+	private final Iterator<Option<T>> iterator;
+
+	public OptionStrippingIterator(Iterator<Option<T>> iterator) {
+		this.iterator = iterator;
+	}
+
+	@Override
+	public boolean hasNext() {
+		return iterator.hasNext();
+	}
+
+	@Override
+	public T next() {
+		return iterator.next().getElement();
+	}
+
+	@Override
+	public void remove() {
+		iterator.remove();
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/ParentsIterator.java b/src/main/java/org/apache/sling/query/iterator/ParentsIterator.java
new file mode 100644
index 0000000..78faf76
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/ParentsIterator.java
@@ -0,0 +1,57 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+
+public class ParentsIterator<T> extends AbstractIterator<T> {
+
+	private final Predicate<T> until;
+
+	private final TreeProvider<T> provider;
+	
+	private T currentResource;
+
+	public ParentsIterator(Predicate<T> until, T currentResource, TreeProvider<T> provider) {
+		this.currentResource = currentResource;
+		this.until = until;
+		this.provider = provider;
+	}
+
+	@Override
+	protected T getElement() {
+		if (currentResource == null) {
+			return null;
+		}
+		currentResource = provider.getParent(currentResource);
+
+		if (currentResource == null) {
+			return null;
+		}
+
+		if (until != null && until.accepts(currentResource)) {
+			return null;
+		}
+
+		return currentResource;
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/ReverseIterator.java b/src/main/java/org/apache/sling/query/iterator/ReverseIterator.java
new file mode 100644
index 0000000..828abfa
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/ReverseIterator.java
@@ -0,0 +1,56 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.LazyList;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+
+public class ReverseIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final Iterator<Option<T>> filtered;
+
+	private final Iterator<Option<T>> original;
+
+	public ReverseIterator(IteratorToIteratorFunction<T> function, Iterator<Option<T>> input) {
+		LazyList<Option<T>> lazyList = new LazyList<Option<T>>(input);
+		filtered = function.apply(lazyList.listIterator());
+		original = lazyList.listIterator();
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		if (original.hasNext()) {
+			Option<T> originalElement = original.next();
+			Option<T> filteredElement = null;
+			if (filtered.hasNext()) {
+				filteredElement = filtered.next();
+			}
+			if (filteredElement == null || filteredElement.isEmpty()) {
+				return originalElement;
+			} else {
+				return Option.empty(originalElement.getArgumentId());
+			}
+		}
+		return null;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/SiblingsIterator.java b/src/main/java/org/apache/sling/query/iterator/SiblingsIterator.java
new file mode 100644
index 0000000..d2b3a84
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/SiblingsIterator.java
@@ -0,0 +1,119 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+import java.util.ListIterator;
+
+import org.apache.sling.query.IteratorUtils;
+import org.apache.sling.query.LazyList;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+
+public class SiblingsIterator<T> extends AbstractIterator<T> {
+
+	private final Predicate<T> until;
+
+	private final ListIterator<T> siblings;
+
+	private final Type type;
+
+	private final TreeProvider<T> provider;
+
+	private boolean finished;
+
+	public SiblingsIterator(Predicate<T> until, T resource, Type type, TreeProvider<T> provider) {
+		this.provider = provider;
+		this.until = until;
+		this.siblings = getRewindedIterator(resource, type);
+		this.finished = false;
+		this.type = type;
+	}
+
+	@Override
+	protected T getElement() {
+		if (finished) {
+			return null;
+		}
+		while (type.canAdvance(siblings)) {
+			T resource = type.advance(siblings);
+			if (until != null && until.accepts(resource)) {
+				finished = true;
+				return null;
+			}
+			if (until == null) {
+				finished = true;
+			}
+			return resource;
+		}
+		return null;
+	}
+
+	private ListIterator<T> getRewindedIterator(T resource, Type type) {
+		String resourceName = provider.getName(resource);
+		T parent = provider.getParent(resource);
+		Iterator<T> iterator;
+		if (parent == null) {
+			iterator = IteratorUtils.singleElementIterator(resource);
+		} else {
+			iterator = provider.listChildren(parent);
+		}
+		ListIterator<T> listIterator = new LazyList<T>(iterator).listIterator();
+		while (listIterator.hasNext()) {
+			T sibling = listIterator.next();
+			if (provider.getName(sibling).equals(resourceName)) {
+				break;
+			}
+		}
+		if (type == Type.PREV) {
+			listIterator.previous();
+		}
+		return listIterator;
+	}
+
+	public enum Type {
+		NEXT {
+			@Override
+			public boolean canAdvance(ListIterator<?> iterator) {
+				return iterator.hasNext();
+			}
+
+			@Override
+			public <T> T advance(ListIterator<T> iterator) {
+				return iterator.next();
+			}
+		},
+		PREV {
+			@Override
+			public boolean canAdvance(ListIterator<?> iterator) {
+				return iterator.hasPrevious();
+			}
+
+			@Override
+			public <T> T advance(ListIterator<T> iterator) {
+				return iterator.previous();
+			}
+		};
+
+		public abstract boolean canAdvance(ListIterator<?> iterator);
+
+		public abstract <T> T advance(ListIterator<T> iterator);
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/SliceIterator.java b/src/main/java/org/apache/sling/query/iterator/SliceIterator.java
new file mode 100644
index 0000000..b51094c
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/SliceIterator.java
@@ -0,0 +1,66 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.function.Option;
+
+public class SliceIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final Iterator<Option<T>> iterator;
+
+	private final int from;
+
+	private final int to;
+
+	private int current;
+
+	public SliceIterator(Iterator<Option<T>> iterator, int from, int to) {
+		this.iterator = iterator;
+		this.current = -1;
+		this.from = from;
+		this.to = to;
+	}
+
+	public SliceIterator(Iterator<Option<T>> iterator, int from) {
+		this(iterator, from, Integer.MAX_VALUE);
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		if (current > to) {
+			return null;
+		}
+
+		if (iterator.hasNext()) {
+			Option<T> element = iterator.next();
+			if (element.isEmpty()) {
+				return element;
+			}
+			if (++current >= from && current <= to) {
+				return element;
+			} else {
+				return Option.empty(element.getArgumentId());
+			}
+		}
+		return null;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/iterator/SuppIterator.java b/src/main/java/org/apache/sling/query/iterator/SuppIterator.java
new file mode 100644
index 0000000..78aca5b
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/SuppIterator.java
@@ -0,0 +1,74 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+
+/**
+ * This iterator returns all elements of the input list which are mapped to non-empty values by the input
+ * function. Name is inspired by the <a href="http://en.wikipedia.org/wiki/Support_(mathematics)">support of
+ * the function</a>.
+ */
+public class SuppIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final List<Option<T>> input;
+
+	private final Iterator<Option<T>> output;
+
+	private Option<T> outputElement;
+
+	private int currentIndex = 0;
+
+	public SuppIterator(List<Option<T>> input, IteratorToIteratorFunction<T> function) {
+		this.input = input;
+		this.output = function.apply(new ArgumentResettingIterator<T>(input.iterator()));
+	}
+
+	/**
+	 * The idea behind this method is that index of each element in the input iterator is passed to the
+	 * function. Elements returned by the output iterator contains the same index, which can be used to assign
+	 * input to output elements. We check which indices are present in the output iterator and return only
+	 * related input elements.
+	 */
+	@Override
+	protected Option<T> getElement() {
+		if (outputElement != null) {
+			final int outputIndex = outputElement.getArgumentId();
+			if (currentIndex < outputIndex) {
+				return Option.empty(input.get(currentIndex++).getArgumentId());
+			} else if (currentIndex == outputIndex && !outputElement.isEmpty()) {
+				return input.get(currentIndex++);
+			}
+		}
+
+		while (output.hasNext()) {
+			outputElement = output.next();
+			final int outputIndex = outputElement.getArgumentId();
+			if ((outputIndex == currentIndex && !outputElement.isEmpty()) || outputIndex > currentIndex) {
+				return getElement();
+			}
+		}
+		return null;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/UniqueIterator.java b/src/main/java/org/apache/sling/query/iterator/UniqueIterator.java
new file mode 100644
index 0000000..2b05c84
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/UniqueIterator.java
@@ -0,0 +1,56 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.Option;
+
+public class UniqueIterator<T> extends AbstractIterator<Option<T>> {
+
+	private final Iterator<Option<T>> iterator;
+
+	private final TreeProvider<T> treeProvider;
+
+	private T lastElement;
+
+	public UniqueIterator(Iterator<Option<T>> input, TreeProvider<T> treeProvider) {
+		this.iterator = input;
+		this.treeProvider = treeProvider;
+	}
+
+	@Override
+	protected Option<T> getElement() {
+		if (!iterator.hasNext()) {
+			return null;
+		}
+		Option<T> candidate = iterator.next();
+		Option<T> result;
+		if (treeProvider.sameElement(lastElement, candidate.getElement())) {
+			result = Option.empty(candidate.getArgumentId());
+		} else {
+			result = candidate;
+		}
+		lastElement = candidate.getElement();
+		return result;
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/WarningIterator.java b/src/main/java/org/apache/sling/query/iterator/WarningIterator.java
new file mode 100644
index 0000000..47817da
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/WarningIterator.java
@@ -0,0 +1,61 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WarningIterator<T> extends AbstractIterator<T> {
+
+	private static final Logger LOG = LoggerFactory.getLogger("SlingQuery");
+
+	private static final int DEFAULT_LIMIT = 100;
+
+	private final Iterator<T> iterator;
+
+	private final int limit;
+
+	private int count = 0;
+
+	public WarningIterator(Iterator<T> iterator) {
+		this(iterator, DEFAULT_LIMIT);
+	}
+
+	public WarningIterator(Iterator<T> iterator, int limit) {
+		this.iterator = iterator;
+		this.limit = limit;
+	}
+
+	@Override
+	protected T getElement() {
+		if (!iterator.hasNext()) {
+			return null;
+		}
+		if (count++ == limit) {
+			LOG.warn(
+					"Number of processed resources exceeded {}. Consider using a JCR query instead of SlingQuery. More info here: http://git.io/h2HeUQ",
+					new Object[] { limit });
+		}
+		return iterator.next();
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/tree/BfsTreeIterator.java b/src/main/java/org/apache/sling/query/iterator/tree/BfsTreeIterator.java
new file mode 100644
index 0000000..25fbfc6
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/tree/BfsTreeIterator.java
@@ -0,0 +1,57 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator.tree;
+
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.iterator.AbstractIterator;
+
+public class BfsTreeIterator<T> extends AbstractIterator<T> {
+
+	private final Deque<T> queue = new LinkedList<T>();
+
+	private final TreeProvider<T> provider;
+
+	private Iterator<T> currentIterator;
+
+	public BfsTreeIterator(T root, TreeProvider<T> provider) {
+		this.currentIterator = provider.listChildren(root);
+		this.provider = provider;
+	}
+
+	@Override
+	protected T getElement() {
+		if (currentIterator.hasNext()) {
+			T resource = currentIterator.next();
+			queue.add(resource);
+			return resource;
+		}
+
+		if (!queue.isEmpty()) {
+			currentIterator = provider.listChildren(queue.pop());
+			return getElement();
+		}
+
+		return null;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/iterator/tree/DfsTreeIterator.java b/src/main/java/org/apache/sling/query/iterator/tree/DfsTreeIterator.java
new file mode 100644
index 0000000..27ae29c
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/iterator/tree/DfsTreeIterator.java
@@ -0,0 +1,54 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator.tree;
+
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.iterator.AbstractIterator;
+
+public class DfsTreeIterator<T> extends AbstractIterator<T> {
+
+	private final Deque<Iterator<T>> queue = new LinkedList<Iterator<T>>();
+
+	private final TreeProvider<T> provider;
+
+	public DfsTreeIterator(T root, TreeProvider<T> provider) {
+		this.provider = provider;
+		queue.add(provider.listChildren(root));
+	}
+
+	@Override
+	protected T getElement() {
+		if (queue.isEmpty()) {
+			return null;
+		}
+		if (queue.peekLast().hasNext()) {
+			T next = queue.peekLast().next();
+			queue.add(provider.listChildren(next));
+			return next;
+		} else {
+			queue.pollLast();
+			return getElement();
+		}
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/predicate/IterableContainsPredicate.java b/src/main/java/org/apache/sling/query/predicate/IterableContainsPredicate.java
new file mode 100644
index 0000000..90ca9db
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/predicate/IterableContainsPredicate.java
@@ -0,0 +1,46 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.predicate;
+
+import org.apache.sling.query.LazyList;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+
+public class IterableContainsPredicate<T> implements Predicate<T> {
+
+	private final Iterable<T> iterable;
+
+	private final TreeProvider<T> provider;
+
+	public IterableContainsPredicate(Iterable<T> iterable, TreeProvider<T> provider) {
+		this.iterable = new LazyList<T>(iterable.iterator());
+		this.provider = provider;
+	}
+
+	@Override
+	public boolean accepts(T element) {
+		for (T t : iterable) {
+			if (provider.sameElement(t, element)) {
+				return true;
+			}
+		}
+		return false;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/predicate/ParentPredicate.java b/src/main/java/org/apache/sling/query/predicate/ParentPredicate.java
new file mode 100644
index 0000000..c734a78
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/predicate/ParentPredicate.java
@@ -0,0 +1,38 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.predicate;
+
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+
+public class ParentPredicate<T> implements Predicate<T> {
+
+	private final TreeProvider<T> provider;
+
+	public ParentPredicate(TreeProvider<T> provider) {
+		this.provider = provider;
+	}
+
+	@Override
+	public boolean accepts(T resource) {
+		return provider.listChildren(resource).hasNext();
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/predicate/RejectingPredicate.java b/src/main/java/org/apache/sling/query/predicate/RejectingPredicate.java
new file mode 100644
index 0000000..67f4f5d
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/predicate/RejectingPredicate.java
@@ -0,0 +1,45 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.predicate;
+
+import org.apache.sling.query.api.Predicate;
+
+public class RejectingPredicate<T> implements Predicate<T> {
+
+	private final Predicate<T> predicate;
+
+	public RejectingPredicate() {
+		this(new Predicate<T>() {
+			@Override
+			public boolean accepts(T resource) {
+				return true;
+			}
+		});
+	}
+
+	public RejectingPredicate(Predicate<T> predicate) {
+		this.predicate = predicate;
+	}
+
+	@Override
+	public boolean accepts(T value) {
+		return !predicate.accepts(value);
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/predicate/SelectorOperator.java b/src/main/java/org/apache/sling/query/predicate/SelectorOperator.java
new file mode 100644
index 0000000..52a452e
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/predicate/SelectorOperator.java
@@ -0,0 +1,82 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.predicate;
+
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+
+public enum SelectorOperator {
+	CONTAINS("*=") {
+		@Override
+		public boolean accepts(String property, String value) {
+			return StringUtils.contains(property, value);
+		}
+	},
+	CONTAINS_WORD("~=") {
+		@Override
+		public boolean accepts(String property, String value) {
+			String quoted = Pattern.quote(value);
+			String regex = String.format("(^| )%s( |$)", quoted);
+			return property != null && Pattern.compile(regex).matcher(property).find();
+		}
+	},
+	ENDS_WITH("$=") {
+		@Override
+		public boolean accepts(String property, String value) {
+			return StringUtils.endsWith(property, value);
+		}
+	},
+	EQUALS("=") {
+		@Override
+		public boolean accepts(String property, String value) {
+			return StringUtils.equals(property, value);
+		}
+	},
+	NOT_EQUAL("!=") {
+		@Override
+		public boolean accepts(String property, String value) {
+			return !StringUtils.equals(property, value);
+		}
+	},
+	STARTS_WITH("^=") {
+		@Override
+		public boolean accepts(String property, String value) {
+			return StringUtils.startsWith(property, value);
+		}
+	};
+
+	private final String operator;
+
+	private SelectorOperator(String operator) {
+		this.operator = operator;
+	}
+
+	public abstract boolean accepts(String key, String value);
+
+	public static SelectorOperator getSelectorOperator(String operator) {
+		for (SelectorOperator o : values()) {
+			if (o.operator.equals(operator)) {
+				return o;
+			}
+		}
+		return EQUALS;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/resource/ResourcePredicate.java b/src/main/java/org/apache/sling/query/resource/ResourcePredicate.java
new file mode 100644
index 0000000..d766478
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/ResourcePredicate.java
@@ -0,0 +1,99 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.resource.jcr.JcrTypeResolver;
+import org.apache.sling.query.selector.parser.Attribute;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ResourcePredicate implements Predicate<Resource> {
+
+	private static final Logger LOG = LoggerFactory.getLogger(ResourcePredicate.class);
+
+	private final String resourceType;
+
+	private final String resourceName;
+
+	private final List<Predicate<Resource>> subPredicates;
+
+	private JcrTypeResolver typeResolver;
+
+	public ResourcePredicate(String resourceType, String resourceName, List<Attribute> attributes,
+			JcrTypeResolver typeResolver) {
+		this.resourceType = resourceType;
+		this.resourceName = resourceName;
+		this.subPredicates = new ArrayList<Predicate<Resource>>();
+		for (Attribute a : attributes) {
+			subPredicates.add(new ResourcePropertyPredicate(a));
+		}
+		this.typeResolver = typeResolver;
+	}
+
+	@Override
+	public boolean accepts(Resource resource) {
+		if (StringUtils.isNotBlank(resourceName) && !resource.getName().equals(resourceName)) {
+			return false;
+		}
+		if (!isResourceType(resource, resourceType)) {
+			return false;
+		}
+		for (Predicate<Resource> predicate : subPredicates) {
+			if (!predicate.accepts(resource)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	private boolean isResourceType(Resource resource, String resourceType) {
+		if (StringUtils.isBlank(resourceType)) {
+			return true;
+		}
+		if (resource.isResourceType(resourceType)) {
+			return true;
+		}
+		if (!isValidType(resourceType)) {
+			return false;
+		}
+		Node node = resource.adaptTo(Node.class);
+		try {
+			if (node != null) {
+				return node.isNodeType(resourceType);
+			}
+		} catch (RepositoryException e) {
+			LOG.error("Can't check node type", e);
+		}
+		return false;
+	}
+
+	private boolean isValidType(String type) {
+		return typeResolver.isJcrType(type);
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/resource/ResourcePropertyPredicate.java b/src/main/java/org/apache/sling/query/resource/ResourcePropertyPredicate.java
new file mode 100644
index 0000000..25ba0ea
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/ResourcePropertyPredicate.java
@@ -0,0 +1,51 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.predicate.SelectorOperator;
+import org.apache.sling.query.selector.parser.Attribute;
+
+public class ResourcePropertyPredicate implements Predicate<Resource> {
+	private final String key;
+
+	private final String value;
+
+	private final SelectorOperator operator;
+
+	public ResourcePropertyPredicate(Attribute attribute) {
+		this.key = attribute.getKey();
+		this.value = attribute.getValue();
+		this.operator = SelectorOperator.getSelectorOperator(attribute.getOperator());
+	}
+
+	@Override
+	public boolean accepts(Resource resource) {
+		Resource property = resource.getChild(key);
+		if (property == null) {
+			return false;
+		} else if (value == null) {
+			return true;
+		} else {
+			return operator.accepts(property.adaptTo(String.class), value);
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/resource/ResourceTreeProvider.java b/src/main/java/org/apache/sling/query/resource/ResourceTreeProvider.java
new file mode 100644
index 0000000..e1bb0a6
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/ResourceTreeProvider.java
@@ -0,0 +1,87 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.resource.jcr.JcrQueryIterator;
+import org.apache.sling.query.resource.jcr.JcrTypeResolver;
+import org.apache.sling.query.resource.jcr.SessionJcrTypeResolver;
+import org.apache.sling.query.selector.parser.Attribute;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+
+public class ResourceTreeProvider implements TreeProvider<Resource> {
+
+	private final JcrTypeResolver typeResolver;
+
+	public ResourceTreeProvider(ResourceResolver resolver) {
+		this.typeResolver = new SessionJcrTypeResolver(resolver);
+	}
+
+	@Override
+	public Iterator<Resource> listChildren(Resource parent) {
+		return parent.listChildren();
+	}
+
+	@Override
+	public Resource getParent(Resource element) {
+		return element.getParent();
+	}
+
+	@Override
+	public String getName(Resource element) {
+		return element.getName();
+	}
+
+	@Override
+	public Predicate<Resource> getPredicate(String type, String id, List<Attribute> attributes) {
+		return new ResourcePredicate(type, id, attributes, typeResolver);
+	}
+
+	@Override
+	public Iterator<Resource> query(List<SelectorSegment> segments, Resource resource) {
+		return new JcrQueryIterator(segments, resource, typeResolver);
+	}
+
+	@Override
+	public boolean sameElement(Resource o1, Resource o2) {
+		if (o1 == null && o2 == null) {
+			return true;
+		}
+		if (o1 == null || o2 == null) {
+			return false;
+		}
+		return o1.getPath().equals(o2.getPath());
+	}
+
+	@Override
+	public boolean isDescendant(Resource root, Resource testedElement) {
+		if (root == null || testedElement == null) {
+			return false;
+		}
+		return testedElement.getPath().startsWith(root.getPath());
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/resource/jcr/JcrOperator.java b/src/main/java/org/apache/sling/query/resource/jcr/JcrOperator.java
new file mode 100644
index 0000000..a585b21
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/jcr/JcrOperator.java
@@ -0,0 +1,76 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource.jcr;
+
+public enum JcrOperator {
+	CONTAINS("*=") {
+		@Override
+		public String getJcrQueryFragment(String key, String value) {
+			return String.format("s.[%s] LIKE '%%%s%%'", key, value);
+		}
+	},
+	CONTAINS_WORD("~=") {
+		@Override
+		public String getJcrQueryFragment(String key, String value) {
+			return CONTAINS.getJcrQueryFragment(key, value);
+		}
+	},
+	ENDS_WITH("$=") {
+		@Override
+		public String getJcrQueryFragment(String key, String value) {
+			return String.format("s.[%s] LIKE '%%%s'", key, value);
+		}
+	},
+	EQUALS("=") {
+		@Override
+		public String getJcrQueryFragment(String key, String value) {
+			return String.format("s.[%s] = '%s'", key, value);
+		}
+	},
+	NOT_EQUAL("!=") {
+		@Override
+		public String getJcrQueryFragment(String key, String value) {
+			return String.format("s.[%s] != '%s'", key, value);
+		}
+	},
+	STARTS_WITH("^=") {
+		@Override
+		public String getJcrQueryFragment(String key, String value) {
+			return String.format("s.[%s] LIKE '%s%%'", key, value);
+		}
+	};
+
+	private final String operator;
+
+	private JcrOperator(String operator) {
+		this.operator = operator;
+	}
+
+	public abstract String getJcrQueryFragment(String key, String value);
+
+	public static JcrOperator getSelectorOperator(String operator) {
+		for (JcrOperator o : values()) {
+			if (o.operator.equals(operator)) {
+				return o;
+			}
+		}
+		return EQUALS;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/resource/jcr/JcrQueryIterator.java b/src/main/java/org/apache/sling/query/resource/jcr/JcrQueryIterator.java
new file mode 100644
index 0000000..308fbac
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/jcr/JcrQueryIterator.java
@@ -0,0 +1,56 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource.jcr;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.query.iterator.AbstractIterator;
+import org.apache.sling.query.resource.jcr.query.JcrQueryBuilder;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+
+public class JcrQueryIterator extends AbstractIterator<Resource> {
+
+	private final ResourceResolver resolver;
+
+	private final String query;
+
+	private Iterator<Resource> currentIterator;
+
+	public JcrQueryIterator(List<SelectorSegment> segments, Resource root, JcrTypeResolver typeResolver) {
+		JcrQueryBuilder builder = new JcrQueryBuilder(typeResolver);
+		query = builder.buildQuery(segments, root.getPath());
+		resolver = root.getResourceResolver();
+	}
+
+	@Override
+	protected Resource getElement() {
+		if (currentIterator == null) {
+			currentIterator = resolver.findResources(query, "JCR-SQL2");
+		}
+		if (currentIterator.hasNext()) {
+			return currentIterator.next();
+		} else {
+			return null;
+		}
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/resource/jcr/JcrTypeResolver.java b/src/main/java/org/apache/sling/query/resource/jcr/JcrTypeResolver.java
new file mode 100644
index 0000000..d087d4e
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/jcr/JcrTypeResolver.java
@@ -0,0 +1,26 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource.jcr;
+
+public interface JcrTypeResolver {
+	boolean isJcrType(String name);
+
+	boolean isSubtype(String supertype, String subtype);
+}
diff --git a/src/main/java/org/apache/sling/query/resource/jcr/SessionJcrTypeResolver.java b/src/main/java/org/apache/sling/query/resource/jcr/SessionJcrTypeResolver.java
new file mode 100644
index 0000000..a07422b
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/jcr/SessionJcrTypeResolver.java
@@ -0,0 +1,85 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource.jcr;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeTypeManager;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SessionJcrTypeResolver implements JcrTypeResolver {
+
+	private static final Logger LOG = LoggerFactory.getLogger(SessionJcrTypeResolver.class);
+
+	private final NodeTypeManager nodeTypeManager;
+
+	public SessionJcrTypeResolver(ResourceResolver resolver) {
+		NodeTypeManager m = null;
+		try {
+			if (resolver != null) {
+				m = resolver.adaptTo(Session.class).getWorkspace().getNodeTypeManager();
+			}
+		} catch (RepositoryException e) {
+			LOG.error("Can't get node type manager", e);
+			m = null;
+		}
+		nodeTypeManager = m;
+	}
+
+	@Override
+	public boolean isJcrType(String name) {
+		if (nodeTypeManager == null) {
+			return false;
+		}
+		if (name == null || name.contains("/")) {
+			return false;
+		}
+		try {
+			nodeTypeManager.getNodeType(name);
+			return true;
+		} catch (NoSuchNodeTypeException e) {
+			return false;
+		} catch (RepositoryException e) {
+			LOG.error("Can't check node type " + name, e);
+			return false;
+		}
+	}
+
+	@Override
+	public boolean isSubtype(String supertype, String subtype) {
+		if (nodeTypeManager == null) {
+			return false;
+		}
+		if (!isJcrType(subtype) || !isJcrType(supertype)) {
+			return false;
+		}
+		try {
+			return nodeTypeManager.getNodeType(subtype).isNodeType(supertype);
+		} catch (RepositoryException e) {
+			LOG.error("Can't compare two node types: " + subtype + " and " + supertype, e);
+			return false;
+		}
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/resource/jcr/query/Atomic.java b/src/main/java/org/apache/sling/query/resource/jcr/query/Atomic.java
new file mode 100644
index 0000000..2ad70e3
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/jcr/query/Atomic.java
@@ -0,0 +1,34 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource.jcr.query;
+
+public class Atomic implements Term {
+
+	private final String condition;
+
+	public Atomic(String condition) {
+		this.condition = condition;
+	}
+
+	@Override
+	public String buildString() {
+		return condition;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/resource/jcr/query/Formula.java b/src/main/java/org/apache/sling/query/resource/jcr/query/Formula.java
new file mode 100644
index 0000000..6eab82a
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/jcr/query/Formula.java
@@ -0,0 +1,61 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource.jcr.query;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class Formula implements Term {
+	public enum Operator {
+		AND, OR
+	}
+
+	private final Operator operator;
+
+	private final List<Term> conditions;
+
+	public Formula(Operator operator, List<Term> conditions) {
+		this.operator = operator;
+		this.conditions = conditions;
+	}
+
+	public String buildString() {
+		if (conditions.isEmpty()) {
+			return "";
+		}
+
+		StringBuilder builder = new StringBuilder();
+		Iterator<Term> iterator = conditions.iterator();
+		if (conditions.size() > 1) {
+			builder.append("(");
+		}
+		while (iterator.hasNext()) {
+			Term term = iterator.next();
+			builder.append(term.buildString());
+			if (iterator.hasNext()) {
+				builder.append(' ').append(operator.toString()).append(' ');
+			}
+		}
+		if (conditions.size() > 1) {
+			builder.append(")");
+		}
+		return builder.toString();
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/resource/jcr/query/JcrQueryBuilder.java b/src/main/java/org/apache/sling/query/resource/jcr/query/JcrQueryBuilder.java
new file mode 100644
index 0000000..4797b15
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/jcr/query/JcrQueryBuilder.java
@@ -0,0 +1,141 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource.jcr.query;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.query.resource.jcr.JcrOperator;
+import org.apache.sling.query.resource.jcr.JcrTypeResolver;
+import org.apache.sling.query.resource.jcr.query.Formula.Operator;
+import org.apache.sling.query.selector.parser.Attribute;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+
+public class JcrQueryBuilder {
+
+	private final JcrTypeResolver typeResolver;
+
+	public JcrQueryBuilder(JcrTypeResolver typeResolver) {
+		this.typeResolver = typeResolver;
+	}
+
+	public String buildQuery(List<SelectorSegment> segments, String rootPath) {
+		StringBuilder query = new StringBuilder();
+		query.append("SELECT * FROM [");
+		query.append(findPrimaryType(segments));
+		query.append("]");
+		query.append(" AS s");
+
+		String conditionString = getConditionString(segments, rootPath);
+		if (StringUtils.isNotBlank(conditionString)) {
+			query.append(" WHERE ").append(conditionString);
+		}
+		return query.toString();
+	}
+
+	private String getConditionString(List<SelectorSegment> segments, String rootPath) {
+		Formula formula = prepareAlternativeConditions(segments);
+		if (StringUtils.isNotBlank(rootPath) && !"/".equals(rootPath)) {
+			List<Term> conditions = new ArrayList<Term>();
+			conditions.add(new Atomic(String.format("ISDESCENDANTNODE([%s])", rootPath)));
+			if (formula != null) {
+				conditions.add(formula);
+			}
+			formula = new Formula(Operator.AND, conditions);
+		}
+		if (formula == null) {
+			return null;
+		} else {
+			return formula.buildString();
+		}
+	}
+
+	private String findPrimaryType(List<SelectorSegment> segments) {
+		String result = null;
+		for (SelectorSegment s : segments) {
+			String type = s.getType();
+			if (!typeResolver.isJcrType(type)) {
+				continue;
+			}
+			if (result == null) {
+				result = type;
+			} else if (typeResolver.isSubtype(type, result)) {
+				result = type;
+			} else if (!typeResolver.isSubtype(result, type)) {
+				result = "nt:base";
+			}
+		}
+		if (result == null) {
+			result = "nt:base";
+		}
+		return result;
+	}
+
+	private static Formula prepareAlternativeConditions(List<SelectorSegment> segments) {
+		List<Term> list = new ArrayList<Term>();
+		for (SelectorSegment segment : segments) {
+			Formula conditions = prepareSegmentConditions(segment.getType(), segment.getName(),
+					segment.getAttributes());
+			if (conditions != null) {
+				list.add(conditions);
+			}
+		}
+		if (list.isEmpty()) {
+			return null;
+		} else {
+			return new Formula(Operator.OR, list);
+		}
+	}
+
+	private static Formula prepareSegmentConditions(String resourceType, String resourceName,
+			List<Attribute> attributes) {
+		List<Term> conditions = new ArrayList<Term>();
+		if (StringUtils.isNotBlank(resourceType) && !StringUtils.contains(resourceType, ':')) {
+			conditions.add(new Atomic(String.format("s.[sling:resourceType] = '%s'", resourceType)));
+		}
+		if (StringUtils.isNotBlank(resourceName)) {
+			conditions.add(new Atomic(String.format("NAME(s) = '%s'", resourceName)));
+		}
+		if (attributes != null) {
+			for (Attribute a : attributes) {
+				String attributeCondition = getAttributeCondition(a);
+				if (StringUtils.isNotBlank(attributeCondition)) {
+					conditions.add(new Atomic(attributeCondition));
+				}
+			}
+		}
+		if (conditions.isEmpty()) {
+			return null;
+		} else {
+			return new Formula(Operator.AND, conditions);
+		}
+	}
+
+	private static String getAttributeCondition(Attribute attribute) {
+		if (attribute.getKey().contains("/")) {
+			return null;
+		}
+
+		JcrOperator operator = JcrOperator.getSelectorOperator(attribute.getOperator());
+		String value = StringUtils.replace(attribute.getValue(), "'", "''");
+		return operator.getJcrQueryFragment(attribute.getKey(), value);
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/resource/jcr/query/Term.java b/src/main/java/org/apache/sling/query/resource/jcr/query/Term.java
new file mode 100644
index 0000000..baee88e
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/resource/jcr/query/Term.java
@@ -0,0 +1,24 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.resource.jcr.query;
+
+public interface Term {
+	String buildString();
+}
diff --git a/src/main/java/org/apache/sling/query/selector/FunctionType.java b/src/main/java/org/apache/sling/query/selector/FunctionType.java
new file mode 100644
index 0000000..806a837
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/FunctionType.java
@@ -0,0 +1,116 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector;
+
+import org.apache.sling.query.api.Function;
+import org.apache.sling.query.api.SearchStrategy;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.function.EvenFunction;
+import org.apache.sling.query.function.FilterFunction;
+import org.apache.sling.query.function.HasFunction;
+import org.apache.sling.query.function.LastFunction;
+import org.apache.sling.query.function.NotFunction;
+import org.apache.sling.query.function.SliceFunction;
+import org.apache.sling.query.predicate.ParentPredicate;
+import org.apache.sling.query.predicate.RejectingPredicate;
+
+public enum FunctionType {
+	EQ {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				TreeProvider<T> providerw) {
+			int index = Integer.parseInt(argument);
+			return new SliceFunction<T>(index, index);
+		}
+	},
+	FIRST {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				TreeProvider<T> provider) {
+			return new SliceFunction<T>(0, 0);
+		}
+	},
+	LAST {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				TreeProvider<T> provider) {
+			return new LastFunction<T>();
+		}
+	},
+	GT {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				TreeProvider<T> provider) {
+			return new SliceFunction<T>(Integer.valueOf(argument) + 1);
+		}
+	},
+	LT {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				TreeProvider<T> provider) {
+			return new SliceFunction<T>(0, Integer.valueOf(argument) - 1);
+		}
+	},
+	HAS {
+		@Override
+		public <T> Function<?, ?> getFunction(String selector, SearchStrategy strategy,
+				TreeProvider<T> provider) {
+			return new HasFunction<T>(selector, strategy, provider);
+		}
+	},
+	PARENT {
+		@Override
+		public <T> Function<?, ?> getFunction(String selector, SearchStrategy strategy,
+				final TreeProvider<T> provider) {
+			return new FilterFunction<T>(new ParentPredicate<T>(provider));
+		}
+	},
+	EMPTY {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				final TreeProvider<T> provider) {
+			return new FilterFunction<T>(new RejectingPredicate<T>(new ParentPredicate<T>(provider)));
+		}
+	},
+	ODD {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				TreeProvider<T> provider) {
+			return new EvenFunction<T>(false);
+		}
+	},
+	EVEN {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				TreeProvider<T> provider) {
+			return new EvenFunction<T>(true);
+		}
+	},
+	NOT {
+		@Override
+		public <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+				TreeProvider<T> provider) {
+			return new NotFunction<T>(new SelectorFunction<T>(argument, provider, strategy));
+		}
+	};
+
+	public abstract <T> Function<?, ?> getFunction(String argument, SearchStrategy strategy,
+			TreeProvider<T> provider);
+}
diff --git a/src/main/java/org/apache/sling/query/selector/HierarchyOperator.java b/src/main/java/org/apache/sling/query/selector/HierarchyOperator.java
new file mode 100644
index 0000000..bcb0750
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/HierarchyOperator.java
@@ -0,0 +1,83 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector;
+
+import org.apache.sling.query.api.Function;
+import org.apache.sling.query.api.SearchStrategy;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.function.ChildrenFunction;
+import org.apache.sling.query.function.FindFunction;
+import org.apache.sling.query.function.IdentityFunction;
+import org.apache.sling.query.function.NextFunction;
+import org.apache.sling.query.predicate.RejectingPredicate;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+
+public enum HierarchyOperator {
+//@formatter:off
+	CHILD('>') {
+		@Override
+		public <T> Function<?, ?> getFunction(SelectorSegment segment, SearchStrategy strategy, TreeProvider<T> provider) {
+			return new ChildrenFunction<T>(provider);
+		}
+	},
+	DESCENDANT(' ') {
+		@Override
+		public <T> Function<?, ?> getFunction(SelectorSegment segment, SearchStrategy strategy, TreeProvider<T> provider) {
+			return new FindFunction<T>(strategy, provider, segment);
+		}
+	},
+	NEXT_ADJACENT('+') {
+		@Override
+		public <T> Function<?, ?> getFunction(SelectorSegment segment, SearchStrategy strategy, TreeProvider<T> provider) {
+			return new NextFunction<T>(null, provider);
+		}
+	},
+	NEXT_SIBLINGS('~') {
+		@Override
+		public <T> Function<?, ?> getFunction(SelectorSegment segment, SearchStrategy strategy, TreeProvider<T> provider) {
+			return new NextFunction<T>(new RejectingPredicate<T>(), provider);
+		}
+	},
+	NOOP((char)0) {
+		@Override
+		public <T> Function<?, ?> getFunction(SelectorSegment segment, SearchStrategy strategy, TreeProvider<T> provider) {
+			return new IdentityFunction<T>();
+		}
+	};
+//@formatter:on
+
+	private final char c;
+
+	private HierarchyOperator(char c) {
+		this.c = c;
+	}
+
+	public abstract <T> Function<?, ?> getFunction(SelectorSegment segment, SearchStrategy strategy,
+			TreeProvider<T> provider);
+
+	public static HierarchyOperator findByCharacter(char c) {
+		for (HierarchyOperator operator : values()) {
+			if (operator.c == c) {
+				return operator;
+			}
+		}
+		return NOOP;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/selector/SelectorFunction.java b/src/main/java/org/apache/sling/query/selector/SelectorFunction.java
new file mode 100644
index 0000000..e3a2e1f
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/SelectorFunction.java
@@ -0,0 +1,101 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.IteratorUtils;
+import org.apache.sling.query.LazyList;
+import org.apache.sling.query.api.Function;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.SearchStrategy;
+import org.apache.sling.query.api.TreeProvider;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.function.CompositeFunction;
+import org.apache.sling.query.function.FilterFunction;
+import org.apache.sling.query.iterator.AlternativeIterator;
+import org.apache.sling.query.iterator.EmptyElementFilter;
+import org.apache.sling.query.iterator.SuppIterator;
+import org.apache.sling.query.selector.parser.Modifier;
+import org.apache.sling.query.selector.parser.Selector;
+import org.apache.sling.query.selector.parser.SelectorParser;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+
+public class SelectorFunction<T> implements IteratorToIteratorFunction<T>, Predicate<T> {
+
+	private final List<IteratorToIteratorFunction<T>> selectorFunctions;
+
+	private final TreeProvider<T> provider;
+
+	private final SearchStrategy strategy;
+
+	public SelectorFunction(String selector, TreeProvider<T> provider, SearchStrategy strategy) {
+		this.provider = provider;
+		this.strategy = strategy;
+		List<Selector> selectors = SelectorParser.parse(selector);
+		selectorFunctions = new ArrayList<IteratorToIteratorFunction<T>>();
+		for (Selector s : selectors) {
+			selectorFunctions.add(createSelectorFunction(s.getSegments()));
+		}
+	}
+
+	@Override
+	public Iterator<Option<T>> apply(Iterator<Option<T>> input) {
+		LazyList<Option<T>> list = new LazyList<Option<T>>(input);
+		List<Iterator<Option<T>>> iterators = new ArrayList<Iterator<Option<T>>>();
+		for (IteratorToIteratorFunction<T> function : selectorFunctions) {
+			iterators.add(new SuppIterator<T>(list, function));
+		}
+		return new AlternativeIterator<T>(iterators);
+	}
+
+	@Override
+	public boolean accepts(T resource) {
+		Iterator<Option<T>> result = apply(IteratorUtils.singleElementIterator(Option.of(resource, 0)));
+		return new EmptyElementFilter<T>(result).hasNext();
+	}
+
+	private IteratorToIteratorFunction<T> createSelectorFunction(List<SelectorSegment> segments) {
+		List<Function<?, ?>> segmentFunctions = new ArrayList<Function<?, ?>>();
+		for (SelectorSegment segment : segments) {
+			segmentFunctions.addAll(createSegmentFunction(segment));
+		}
+		return new CompositeFunction<T>(segmentFunctions);
+	}
+
+	private List<Function<?, ?>> createSegmentFunction(SelectorSegment segment) {
+		List<Function<?, ?>> functions = new ArrayList<Function<?, ?>>();
+		HierarchyOperator operator = HierarchyOperator.findByCharacter(segment.getHierarchyOperator());
+		functions.add(operator.getFunction(segment, strategy, provider));
+		Predicate<T> predicate = provider.getPredicate(segment.getType(), segment.getName(),
+				segment.getAttributes());
+		functions.add(new FilterFunction<T>(predicate));
+		for (Modifier modifiers : segment.getModifiers()) {
+			FunctionType type = FunctionType.valueOf(modifiers.getName().toUpperCase());
+			Function<?, ?> f = type.getFunction(modifiers.getArgument(), strategy, provider);
+			functions.add(f);
+		}
+		return functions;
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/selector/parser/Attribute.java b/src/main/java/org/apache/sling/query/selector/parser/Attribute.java
new file mode 100644
index 0000000..e549c75
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/parser/Attribute.java
@@ -0,0 +1,71 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+public class Attribute {
+	private final String key;
+
+	private final String operator;
+
+	private final String value;
+
+	public Attribute(String key, String operator, String value) {
+		this.key = key;
+		this.operator = operator;
+		this.value = value;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public String getOperator() {
+		return operator;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public String toString() {
+		return String.format("Attribute[%s %s %s]", key, operator, value);
+	}
+
+	public boolean equals(Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		if (obj == this) {
+			return true;
+		}
+		if (obj.getClass() != getClass()) {
+			return false;
+		}
+		Attribute rhs = (Attribute) obj;
+		return new EqualsBuilder().append(key, rhs.key).append(value, rhs.value).isEquals();
+	}
+
+	public int hashCode() {
+		return new HashCodeBuilder().append(key).append(value).toHashCode();
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/selector/parser/Modifier.java b/src/main/java/org/apache/sling/query/selector/parser/Modifier.java
new file mode 100644
index 0000000..a3ba7c0
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/parser/Modifier.java
@@ -0,0 +1,69 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+public class Modifier {
+
+	private final String name;
+
+	private final String argument;
+
+	public Modifier(String name, String argument) {
+		this.name = name;
+		this.argument = argument;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public String getArgument() {
+		return argument;
+	}
+
+	@Override
+	public String toString() {
+		return String.format("Modifier[%s,%s]", name, argument);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		if (obj == this) {
+			return true;
+		}
+		if (obj.getClass() != getClass()) {
+			return false;
+		}
+		Modifier rhs = (Modifier) obj;
+		return new EqualsBuilder().append(name, rhs.name).append(argument, rhs.argument)
+				.isEquals();
+	}
+
+	@Override
+	public int hashCode() {
+		return new HashCodeBuilder().append(name).append(argument).toHashCode();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/query/selector/parser/ParserContext.java b/src/main/java/org/apache/sling/query/selector/parser/ParserContext.java
new file mode 100644
index 0000000..e121aac
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/parser/ParserContext.java
@@ -0,0 +1,164 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ParserContext {
+	private final List<Selector> selectors = new ArrayList<Selector>();
+
+	private final List<SelectorSegment> segments = new ArrayList<SelectorSegment>();
+
+	private final List<Attribute> attributes = new ArrayList<Attribute>();
+
+	private final List<Modifier> modifiers = new ArrayList<Modifier>();
+
+	private char hierarchyOperator;
+
+	private State state = State.START;
+
+	private StringBuilder builder = new StringBuilder();
+
+	private String type;
+
+	private String name;
+
+	private String attributeKey;
+
+	private String attributeOperator;
+
+	private String attributeValue;
+
+	private String currentModifierName;
+
+	private int parenthesesCount = 0;
+
+	List<Attribute> getAttributes() {
+		return attributes;
+	}
+
+	List<Modifier> getModifiers() {
+		return modifiers;
+	}
+
+	String getType() {
+		return type;
+	}
+
+	String getName() {
+		return name;
+	}
+
+	char getHierarchyOperator() {
+		return hierarchyOperator;
+	}
+
+	public State getState() {
+		return state;
+	}
+
+	void increaseParentheses() {
+		parenthesesCount++;
+	}
+
+	int decreaseParentheses() {
+		return --parenthesesCount;
+	}
+
+	void setType() {
+		type = builder.toString();
+		builder = new StringBuilder();
+	}
+
+	void setName() {
+		name = builder.toString();
+		builder = new StringBuilder();
+	}
+
+	void setAttributeKey() {
+		attributeKey = builder.toString();
+		builder = new StringBuilder();
+	}
+
+	void setAttributeOperator() {
+		attributeOperator = builder.toString();
+		builder = new StringBuilder();
+	}
+
+	void setAttributeValue() {
+		attributeValue = builder.toString();
+		builder = new StringBuilder();
+	}
+
+	void addAttribute() {
+		attributes.add(new Attribute(attributeKey, attributeOperator, attributeValue));
+		attributeKey = null;
+		attributeOperator = null;
+		attributeValue = null;
+	}
+
+	void setModifierName() {
+		currentModifierName = builder.toString();
+		builder = new StringBuilder();
+	}
+
+	void addModifier() {
+		Modifier modifier;
+		if (currentModifierName == null) {
+			modifier = new Modifier(builder.toString(), null);
+		} else {
+			modifier = new Modifier(currentModifierName, builder.toString());
+			currentModifierName = null;
+		}
+		modifiers.add(modifier);
+		builder = new StringBuilder();
+	}
+
+	void setState(State state) {
+		this.state = state;
+	}
+
+	void setHierarchyOperator(char hierarchyOperator) {
+		this.hierarchyOperator = hierarchyOperator;
+	}
+
+	void finishSelectorSegment() {
+		segments.add(new SelectorSegment(this, segments.isEmpty()));
+		attributes.clear();
+		modifiers.clear();
+		hierarchyOperator = ' ';
+		type = null;
+		name = null;
+	}
+
+	void finishSelector() {
+		selectors.add(new Selector(segments));
+		segments.clear();
+	}
+
+	void append(char c) {
+		builder.append(c);
+	}
+
+	public List<Selector> getSelectors() {
+		return selectors;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/selector/parser/Selector.java b/src/main/java/org/apache/sling/query/selector/parser/Selector.java
new file mode 100644
index 0000000..817c2b7
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/parser/Selector.java
@@ -0,0 +1,40 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class Selector {
+	private final List<SelectorSegment> segments;
+
+	public Selector() {
+		this.segments = Collections.emptyList();
+	}
+
+	public Selector(List<SelectorSegment> segments) {
+		this.segments = new ArrayList<SelectorSegment>(segments);
+	}
+
+	public List<SelectorSegment> getSegments() {
+		return segments;
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/selector/parser/SelectorParser.java b/src/main/java/org/apache/sling/query/selector/parser/SelectorParser.java
new file mode 100644
index 0000000..6e4aaba
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/parser/SelectorParser.java
@@ -0,0 +1,55 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+public final class SelectorParser {
+
+	private SelectorParser() {
+	}
+
+	public static List<Selector> parse(String selector) {
+		if (StringUtils.isEmpty(selector)) {
+			return Arrays.asList(new Selector());
+		}
+		ParserContext context = new ParserContext();
+		for (char c : selector.toCharArray()) {
+			context.getState().process(context, c);
+		}
+		context.getState().process(context, (char) 0);
+		return context.getSelectors();
+	}
+
+	public static List<SelectorSegment> getFirstSegmentFromEachSelector(List<Selector> selectors) {
+		List<SelectorSegment> segments = new ArrayList<SelectorSegment>();
+		for (Selector selector : selectors) {
+			if (!selector.getSegments().isEmpty()) {
+				segments.add(selector.getSegments().get(0));
+			}
+		}
+		return segments;
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/query/selector/parser/SelectorSegment.java b/src/main/java/org/apache/sling/query/selector/parser/SelectorSegment.java
new file mode 100644
index 0000000..0c649c8
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/parser/SelectorSegment.java
@@ -0,0 +1,107 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+public class SelectorSegment {
+	private final String type;
+
+	private final String name;
+
+	private final List<Attribute> attributes;
+
+	private final List<Modifier> modifiers;
+
+	private final char hierarchyOperator;
+
+	public SelectorSegment(ParserContext context, boolean firstSegment) {
+		this.type = context.getType();
+		this.name = context.getName();
+		this.attributes = new ArrayList<Attribute>(context.getAttributes());
+		this.modifiers = new ArrayList<Modifier>(context.getModifiers());
+		if (firstSegment) {
+			hierarchyOperator = 0;
+		} else {
+			hierarchyOperator = context.getHierarchyOperator();
+		}
+	}
+
+	SelectorSegment(String type, String name, List<Attribute> attributes,
+			List<Modifier> modifiers, char hierarchyOperator) {
+		this.type = type;
+		this.name = name;
+		this.attributes = attributes;
+		this.modifiers = modifiers;
+		this.hierarchyOperator = hierarchyOperator;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public List<Attribute> getAttributes() {
+		return attributes;
+	}
+
+	public char getHierarchyOperator() {
+		return hierarchyOperator;
+	}
+
+	public List<Modifier> getModifiers() {
+		return modifiers;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj == null) {
+			return false;
+		}
+		if (obj == this) {
+			return true;
+		}
+		if (obj.getClass() != getClass()) {
+			return false;
+		}
+		SelectorSegment rhs = (SelectorSegment) obj;
+		return new EqualsBuilder().append(type, rhs.type).append(attributes, rhs.attributes)
+				.append(modifiers, rhs.modifiers).append(hierarchyOperator, rhs.hierarchyOperator).isEquals();
+	}
+
+	@Override
+	public int hashCode() {
+		return new HashCodeBuilder().append(type).append(attributes).append(modifiers)
+				.append(hierarchyOperator).toHashCode();
+	}
+
+	@Override
+	public String toString() {
+		return String.format("SelectorSegment[%s,%s,%s,%s]", type, attributes, modifiers,
+				hierarchyOperator);
+	}
+}
diff --git a/src/main/java/org/apache/sling/query/selector/parser/State.java b/src/main/java/org/apache/sling/query/selector/parser/State.java
new file mode 100644
index 0000000..b0c8d20
--- /dev/null
+++ b/src/main/java/org/apache/sling/query/selector/parser/State.java
@@ -0,0 +1,238 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import org.apache.commons.lang.ArrayUtils;
+
+public enum State {
+	START {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == '/') {
+				context.setState(State.TYPE_WITH_SLASHES);
+				context.append(c);
+			} else if (c == '[') {
+				context.setState(State.ATTRIBUTE_KEY);
+			} else if (c == ':') {
+				context.setState(State.MODIFIER);
+			} else if (c == '>' || c == '+' || c == '~') {
+				context.setHierarchyOperator(c);
+			} else if (c == '#') {
+				context.setType();
+				context.setState(NAME);
+			} else if (c != ' ') {
+				context.setState(State.TYPE);
+				context.append(c);
+			}
+		}
+	},
+	IDLE {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == '[') {
+				context.setState(State.ATTRIBUTE_KEY);
+			} else if (c == ':') {
+				context.setState(State.MODIFIER);
+			} else if (c == ' ') {
+				context.finishSelectorSegment();
+				context.setState(START);
+			} else if (c == ',' || c == 0) {
+				context.finishSelectorSegment();
+				context.finishSelector();
+				context.setState(START);
+			}
+		}
+	},
+	TYPE {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == '/') {
+				context.setState(State.TYPE_WITH_SLASHES);
+				context.append(c);
+			} else if (c == '[') {
+				context.setState(State.ATTRIBUTE_KEY);
+				context.setType();
+			} else if (c == ':') {
+				context.setState(State.TYPE_WITH_SLASHES);
+				context.append(c);
+			} else if (c == '#') {
+				context.setType();
+				context.setState(NAME);
+			} else if (c == ' ') {
+				context.setType();
+				context.finishSelectorSegment();
+				context.setState(START);
+			} else if (c == ',' || c == 0) {
+				context.setType();
+				context.finishSelectorSegment();
+				context.finishSelector();
+				context.setState(START);
+			} else {
+				context.append(c);
+			}
+		}
+	},
+	TYPE_WITH_SLASHES {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == '[') {
+				context.setState(State.ATTRIBUTE_KEY);
+				context.setType();
+			} else if (c == ':') {
+				context.setState(State.MODIFIER);
+				context.setType();
+			} else if (c == '#') {
+				context.setType();
+				context.setState(NAME);
+			} else if (c == ' ') {
+				context.setType();
+				context.finishSelectorSegment();
+				context.setState(START);
+			} else if (c == ',' || c == 0) {
+				context.setType();
+				context.finishSelectorSegment();
+				context.finishSelector();
+				context.setState(START);
+			} else {
+				context.append(c);
+			}
+		}
+	},
+	NAME {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == '[') {
+				context.setName();
+				context.setState(State.ATTRIBUTE_KEY);
+			} else if (c == ':') {
+				context.setName();
+				context.setState(State.MODIFIER);
+			} else if (c == ' ') {
+				context.setName();
+				context.finishSelectorSegment();
+				context.setState(START);
+			} else if (c == ',' || c == 0) {
+				context.setName();
+				context.finishSelectorSegment();
+				context.finishSelector();
+				context.setState(START);
+			} else if (c == '\'') {
+				context.setState(State.ESCAPED_NAME);
+			} else {
+				context.append(c);
+			}
+		}
+	},
+	ESCAPED_NAME {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == '\'') {
+				context.setName();
+				context.setState(IDLE);
+			} else {
+				context.append(c);
+			}
+		}
+	},
+	ATTRIBUTE_KEY {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == ']') {
+				context.setAttributeKey();
+				context.addAttribute();
+				context.setState(State.IDLE);
+			} else if (ArrayUtils.contains(OPERATORS, c)) {
+				context.setAttributeKey();
+				context.setState(State.ATTRIBUTE_OPERATOR);
+				context.append(c);
+			} else {
+				context.append(c);
+			}
+		}
+	},
+	ATTRIBUTE_OPERATOR {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (!ArrayUtils.contains(OPERATORS, c)) {
+				context.setAttributeOperator();
+				context.append(c);
+				context.setState(ATTRIBUTE_VALUE);
+			} else {
+				context.append(c);
+			}
+		}
+	},
+	ATTRIBUTE_VALUE {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == ']') {
+				context.setState(State.IDLE);
+				context.setAttributeValue();
+				context.addAttribute();
+			} else {
+				context.append(c);
+			}
+		}
+	},
+	MODIFIER {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == ':') {
+				context.addModifier();
+			} else if (c == '(') {
+				context.setModifierName();
+				context.setState(State.MODIFIER_ARGUMENT);
+				context.increaseParentheses();
+			} else if (c == ' ') {
+				context.addModifier();
+				context.finishSelectorSegment();
+				context.setState(START);
+			} else if (c == ',' || c == 0) {
+				context.addModifier();
+				context.finishSelectorSegment();
+				context.finishSelector();
+				context.setState(START);
+			} else {
+				context.append(c);
+			}
+		}
+	},
+	MODIFIER_ARGUMENT {
+		@Override
+		public void process(ParserContext context, char c) {
+			if (c == ')') {
+				if (context.decreaseParentheses() == 0) {
+					context.addModifier();
+					context.setState(IDLE);
+				} else {
+					context.append(c);
+				}
+			} else if (c == '(') {
+				context.increaseParentheses();
+				context.append(c);
+			} else {
+				context.append(c);
+			}
+		}
+	};
+	public abstract void process(ParserContext context, char c);
+
+	private static final char[] OPERATORS = "*~$!^=".toCharArray();
+}
diff --git a/src/test/java/org/apache/sling/query/AddTest.java b/src/test/java/org/apache/sling/query/AddTest.java
new file mode 100644
index 0000000..be5bebe
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/AddTest.java
@@ -0,0 +1,58 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class AddTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testSimpleAdd() {
+		SlingQuery query = $(tree.getChild("application")).add(tree.getChild("home"));
+		assertResourceSetEquals(query.iterator(), "application", "home");
+	}
+
+	@Test
+	public void testAddToChildren() {
+		SlingQuery query = $(tree).children("cq:Page").add(tree.getChild("home/java"));
+		assertResourceSetEquals(query.iterator(), "application", "home", "java");
+	}
+
+	@Test
+	public void testAddedChildren() {
+		SlingQuery query = $(tree).add(tree.getChild("home/java")).children("cq:Page");
+		assertResourceSetEquals(query.iterator(), "application", "home", "email", "labels", "navigation");
+	}
+
+	@Test
+	public void testAddIterable() {
+		SlingQuery query1 = $(tree).children("cq:Page");
+		SlingQuery query2 = $(tree.getChild("home/java")).children("cq:Page");
+		assertResourceSetEquals(query1.add(query2).iterator(), "application", "home", "email", "labels",
+				"navigation");
+	}
+
+}
diff --git a/src/test/java/org/apache/sling/query/AttributeOperatorTest.java b/src/test/java/org/apache/sling/query/AttributeOperatorTest.java
new file mode 100644
index 0000000..e1f0424
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/AttributeOperatorTest.java
@@ -0,0 +1,129 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class AttributeOperatorTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testEquals() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title=CQ Commons demo]");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotEquals() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title=123]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testContains() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title*=mmons de]");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotContains() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title*=123]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testContainsWord() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title~=Commons]");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotContainsWord() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title~=mmons de]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testEndsWith() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title$=demo]");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotEndsWith() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title$=CQ]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testNotEquals2() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title!=123]");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotNotEquals() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title!=CQ Commons demo]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testStartsWith() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title^=CQ]");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotStartsWith() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title^=Commons]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testHas() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title]");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotHas() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title123]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testMultipleAttributes() {
+		SlingQuery query = $(tree).children("cq:PageContent[jcr:title=CQ Commons demo][jcr:createdBy=admin]");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotMultipleAttributes() {
+		SlingQuery query = $(tree).children(
+				"cq:PageContent[jcr:title=CQ Commons demo aaa][jcr:createdBy=admin]");
+		assertEmptyIterator(query.iterator());
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/ChildrenTest.java b/src/test/java/org/apache/sling/query/ChildrenTest.java
new file mode 100644
index 0000000..3a46970
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/ChildrenTest.java
@@ -0,0 +1,62 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class ChildrenTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testChildren() {
+		SlingQuery query = $(tree).children();
+		assertResourceSetEquals(query.iterator(), "jcr:content", "application", "home");
+	}
+
+	@Test
+	public void testNameChildren() {
+		SlingQuery query = $(tree).children("cq:Page#application");
+		assertResourceSetEquals(query.iterator(), "application");
+	}
+
+	@Test
+	public void testFilteredChildren() {
+		SlingQuery query = $(tree).children("cq:Page");
+		assertResourceSetEquals(query.iterator(), "application", "home");
+	}
+
+	@Test
+	public void testNoChildren() {
+		SlingQuery query = $(tree.getChild("jcr:content")).children();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testLeafChildren() {
+		SlingQuery query = $(tree.getChild("jcr:content")).children();
+		assertEmptyIterator(query.iterator());
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/ClosestTest.java b/src/test/java/org/apache/sling/query/ClosestTest.java
new file mode 100644
index 0000000..232bc6f
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/ClosestTest.java
@@ -0,0 +1,52 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class ClosestTest {
+
+	private static final String PATH = "application/configuration/labels/jcr:content/configParsys/tab_0/items/text";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testClosest() {
+		SlingQuery query = $(tree.getChild(PATH)).closest("cq:Page");
+		assertResourceSetEquals(query.iterator(), "labels");
+	}
+
+	@Test
+	public void testNoClosest() {
+		SlingQuery query = $(tree.getChild(PATH)).closest("cq:Undefined");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testClosestOnRoot() {
+		SlingQuery query = $(tree).closest("cq:Page");
+		assertResourceSetEquals(query.iterator(), "/");
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/FilterTest.java b/src/test/java/org/apache/sling/query/FilterTest.java
new file mode 100644
index 0000000..5002e0f
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/FilterTest.java
@@ -0,0 +1,57 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.api.Predicate;
+import org.apache.sling.query.api.SearchStrategy;
+import org.junit.Test;
+
+public class FilterTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testFilter() {
+		SlingQuery query = $(tree).searchStrategy(SearchStrategy.DFS).find()
+				.filter(new Predicate<Resource>() {
+					@Override
+					public boolean accepts(Resource resource) {
+						return "configParsys".equals(resource.getName());
+					}
+				});
+		assertResourceSetEquals(query.iterator(), "configParsys");
+	}
+
+	@Test
+	public void testFilterOnEmptyCollection() {
+		SlingQuery query = $(tree).children("cq:Undefined").filter(new Predicate<Resource>() {
+			@Override
+			public boolean accepts(Resource resource) {
+				return true;
+			}
+		});
+		assertEmptyIterator(query.iterator());
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/FindTest.java b/src/test/java/org/apache/sling/query/FindTest.java
new file mode 100644
index 0000000..7a2ed8c
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/FindTest.java
@@ -0,0 +1,87 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceListEquals;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.api.SearchStrategy;
+import org.junit.Test;
+
+public class FindTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testFind() {
+		SlingQuery query = $(tree.getChild("application/configuration/labels")).searchStrategy(
+				SearchStrategy.DFS).find();
+		assertResourceSetEquals(query.iterator(), "jcr:content", "configParsys", "tab", "tab_0", "items",
+				"items", "localizedtext", "text", "text_0", "text", "lang");
+	}
+
+	@Test
+	public void testFindWithFilter() {
+		SlingQuery query = $(tree.getChild("application/configuration/labels")).searchStrategy(
+				SearchStrategy.DFS).find("cq-commons/config/components/text");
+		assertResourceSetEquals(query.iterator(), "text", "text");
+	}
+
+	@Test
+	public void testFindWithResources() {
+		SlingQuery query = $(tree.getChild("home")).find(
+				$(tree.getChild("home/java"), tree.getChild("home/js"), tree.getChild("application")));
+		assertResourceSetEquals(query.iterator(), "java", "js");
+	}
+
+	@Test
+	public void testLeaveFind() {
+		SlingQuery query = $(
+				tree.getChild("application/configuration/labels/jcr:content/configParsys/tab/items/localizedtext/lang"))
+				.searchStrategy(SearchStrategy.DFS).find();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testEmptyFind() {
+		SlingQuery query = $(tree.getChild("application/configuration/labels")).searchStrategy(
+				SearchStrategy.DFS).find("cq:Undefined");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testBfsFind() {
+		SlingQuery query = $(tree.getChild("application/configuration/labels")).searchStrategy(
+				SearchStrategy.BFS).find("");
+		assertResourceListEquals(query.iterator(), "jcr:content", "configParsys", "tab", "tab_0", "items",
+				"items", "localizedtext", "text", "text_0", "text", "lang");
+	}
+
+	@Test
+	public void testDfsFind() {
+		SlingQuery query = $(tree.getChild("application/configuration/labels")).searchStrategy(
+				SearchStrategy.DFS).find("");
+		assertResourceListEquals(query.iterator(), "jcr:content", "configParsys", "tab", "items",
+				"localizedtext", "lang", "text", "tab_0", "items", "text_0", "text");
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/HasTest.java b/src/test/java/org/apache/sling/query/HasTest.java
new file mode 100644
index 0000000..95b23ea
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/HasTest.java
@@ -0,0 +1,49 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.api.SearchStrategy;
+import org.junit.Test;
+
+public class HasTest {
+
+	private static final String PAR_PATH = "home/java";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testHas() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).searchStrategy(SearchStrategy.DFS).children()
+				.has("demo/core/components/configValue");
+		assertResourceSetEquals(query.iterator(), "labels");
+	}
+
+	@Test
+	public void testHasResource() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children().has(
+				$(tree.getChild("home/java/labels/jcr:content/par")));
+		assertResourceSetEquals(query.iterator(), "labels");
+	}
+
+}
diff --git a/src/test/java/org/apache/sling/query/HierarchySelectorTest.java b/src/test/java/org/apache/sling/query/HierarchySelectorTest.java
new file mode 100644
index 0000000..b81ecc0
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/HierarchySelectorTest.java
@@ -0,0 +1,57 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.api.SearchStrategy;
+import org.junit.Test;
+
+public class HierarchySelectorTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testChildrenWithAttribute() {
+		SlingQuery query = $(tree.getChild("home")).children("cq:Page > cq:PageContent[jcr:title=Java]");
+		assertResourceSetEquals(query.iterator(), "java");
+	}
+
+	@Test
+	public void testDescendant() {
+		SlingQuery query = $(tree.getChild("home")).searchStrategy(SearchStrategy.DFS).children(
+				"cq:Page demo/core/components/configValue");
+		assertResourceSetEquals(query.iterator(), "java");
+	}
+
+	@Test
+	public void testNextAdjacent() {
+		SlingQuery query = $(tree).children().filter("cq:PageContent + cq:Page");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNextSiblings() {
+		SlingQuery query = $(tree).children("cq:PageContent ~ cq:Page");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/LastTest.java b/src/test/java/org/apache/sling/query/LastTest.java
new file mode 100644
index 0000000..8a197ff
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/LastTest.java
@@ -0,0 +1,46 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class LastTest {
+
+	private static final String PAR_PATH = "home/java/labels/jcr:content/par";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testLast() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children().last();
+		assertResourceSetEquals(query.iterator(), "configvalue_2");
+	}
+
+	@Test
+	public void testLastOnEmptyCollection() {
+		SlingQuery query = $(tree).children("cq:Undefined").last();
+		assertEmptyIterator(query.iterator());
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/ListTest.java b/src/test/java/org/apache/sling/query/ListTest.java
new file mode 100644
index 0000000..702fbd1
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/ListTest.java
@@ -0,0 +1,94 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ListTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testIterator() {
+		List<Resource> list = $(tree).children().asList();
+		assertResourceSetEquals(list.iterator(), "jcr:content", "application", "home");
+	}
+	
+	@Test
+	public void testListIterator() {
+		ListIterator<Resource> iterator = $(tree).children().asList().listIterator();
+		Assert.assertEquals("jcr:content", iterator.next().getName());
+		Assert.assertEquals("application", iterator.next().getName());
+		Assert.assertEquals("home", iterator.next().getName());
+		Assert.assertEquals("home", iterator.previous().getName());
+		Assert.assertEquals("application", iterator.previous().getName());
+		Assert.assertEquals("jcr:content", iterator.previous().getName());
+	}
+
+	@Test
+	public void testSize() {
+		List<Resource> list = $(tree).children().asList();
+		Assert.assertEquals(3, list.size());
+	}
+
+	@Test
+	public void testGet() {
+		List<Resource> list = $(tree).children().asList();
+		Assert.assertEquals("jcr:content", list.get(0).getName());
+		Assert.assertEquals("application", list.get(1).getName());
+		Assert.assertEquals("home", list.get(2).getName());
+	}
+
+	@Test
+	public void testIndexOf() {
+		Resource home = $(tree).children("#home").iterator().next();
+		List<Resource> list = $(tree).children().asList();
+		Assert.assertEquals(2, list.indexOf(home));
+		Assert.assertEquals(-1, list.indexOf(tree));
+	}
+
+	@Test
+	public void testContains() {
+		Resource home = $(tree).children("#home").iterator().next();
+		List<Resource> list = $(tree).children().asList();
+		Assert.assertEquals(true, list.contains(home));
+		Assert.assertEquals(false, list.contains(tree));
+	}
+
+	@Test
+	public void testEmpty() {
+		Assert.assertEquals(true, $(tree).children("#aaa").asList().isEmpty());
+		Assert.assertEquals(false, $(tree).children().asList().isEmpty());
+	}
+
+	@Test
+	public void testSublist() {
+		List<Resource> list = $(tree).children().asList().subList(1, 3);
+		assertResourceSetEquals(list.iterator(), "application", "home");
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/MapTest.java b/src/test/java/org/apache/sling/query/MapTest.java
new file mode 100644
index 0000000..6be2f5c
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/MapTest.java
@@ -0,0 +1,44 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MapTest {
+
+	private static final String PAR_PATH = "home/java/labels/jcr:content/par";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testMap() {
+		@SuppressWarnings("rawtypes")
+		Iterable<Map> iterable = $(tree.getChild(PAR_PATH)).children().map(Map.class);
+		for (Map<?, ?> m : iterable) {
+			Assert.assertEquals("nt:unstructured", m.get("jcr:primaryType"));
+		}
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/MultipleSelectorTest.java b/src/test/java/org/apache/sling/query/MultipleSelectorTest.java
new file mode 100644
index 0000000..c019b6d
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/MultipleSelectorTest.java
@@ -0,0 +1,46 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class MultipleSelectorTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testTwoNames() {
+		SlingQuery query = $(tree).children("#application, #home");
+		assertResourceSetEquals(query.iterator(), "application", "home");
+	}
+
+	@Test
+	public void testEverything() {
+		SlingQuery query = $(tree).children(":not(#application), #application");
+		assertResourceSetEquals(query.iterator(), "jcr:content", "application", "home");
+
+		query = $(tree).children("#application, :not(#application)");
+		assertResourceSetEquals(query.iterator(), "jcr:content", "application", "home");
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/NextPrevAllTest.java b/src/test/java/org/apache/sling/query/NextPrevAllTest.java
new file mode 100644
index 0000000..42df107
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/NextPrevAllTest.java
@@ -0,0 +1,96 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class NextPrevAllTest {
+
+	private static final String PAR_PATH = "home/java/labels/jcr:content/par";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testNextAll() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_0")).nextAll();
+		assertResourceSetEquals(query.iterator(), "configvalue_1", "configvalue_2");
+	}
+
+	@Test
+	public void testPrevAll() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_0")).prevAll();
+		assertResourceSetEquals(query.iterator(), "richtext", "configvalue");
+	}
+
+	@Test
+	public void testNextAllFiltered() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).nextAll(
+				"demo/core/components/configValue");
+		assertResourceSetEquals(query.iterator(), "configvalue_0", "configvalue_1", "configvalue_2");
+	}
+
+	@Test
+	public void testPrevAllFiltered() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_0")).prevAll(
+				"demo/core/components/richtext");
+		assertResourceSetEquals(query.iterator(), "richtext");
+	}
+
+	@Test
+	public void testNextAllInvalidFiltered() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).nextAll("cq:Undefined");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testPrevAllInvalidFiltered() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).prevAll("cq:Undefined");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testNextAllOnLast() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_2")).nextAll();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testPrevAllOnFirst() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("richtext")).prevAll();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testNextAllOnRoot() {
+		SlingQuery query = $(tree).nextAll();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testPrevAllOnRoot() {
+		SlingQuery query = $(tree).prevAll();
+		assertEmptyIterator(query.iterator());
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/query/NextPrevTest.java b/src/test/java/org/apache/sling/query/NextPrevTest.java
new file mode 100644
index 0000000..ca6c303
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/NextPrevTest.java
@@ -0,0 +1,96 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class NextPrevTest {
+
+	private static final String PAR_PATH = "home/java/labels/jcr:content/par";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testNext() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).next();
+		assertResourceSetEquals(query.iterator(), "configvalue_0");
+	}
+
+	@Test
+	public void testPrev() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).prev();
+		assertResourceSetEquals(query.iterator(), "richtext");
+	}
+
+	@Test
+	public void testNextFiltered() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).next(
+				"demo/core/components/configValue");
+		assertResourceSetEquals(query.iterator(), "configvalue_0");
+	}
+
+	@Test
+	public void testPrevFiltered() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).prev(
+				"demo/core/components/richtext");
+		assertResourceSetEquals(query.iterator(), "richtext");
+	}
+
+	@Test
+	public void testNextInvalidFiltered() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).next("cq:Undefined");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testPrevInvalidFiltered() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).prev("cq:Undefined");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testNextOnLast() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_2")).next();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testPrevOnFirst() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("richtext")).prev();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testNextOnRoot() {
+		SlingQuery query = $(tree).next();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testPrevOnRoot() {
+		SlingQuery query = $(tree).prev();
+		assertEmptyIterator(query.iterator());
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/query/NextPrevUntilTest.java b/src/test/java/org/apache/sling/query/NextPrevUntilTest.java
new file mode 100644
index 0000000..ed0b47f
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/NextPrevUntilTest.java
@@ -0,0 +1,97 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class NextPrevUntilTest {
+
+	private static final String PAR_PATH = "home/java/labels/jcr:content/par";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testNextUntil() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).nextUntil("[key=unknownKey]");
+		assertResourceSetEquals(query.iterator(), "configvalue_0", "configvalue_1");
+	}
+
+	@Test
+	public void testNextUntilResource() {
+		Resource resource = tree.getChild(PAR_PATH).getChild("configvalue_2");
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue")).nextUntil($(resource));
+		assertResourceSetEquals(query.iterator(), "configvalue_0", "configvalue_1");
+	}
+
+	@Test
+	public void testPrevUntil() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_2")).prevUntil("[key=helloWorld]");
+		assertResourceSetEquals(query.iterator(), "configvalue_0", "configvalue_1");
+	}
+
+	@Test
+	public void testPrevUntilResource() {
+		Resource resource = tree.getChild(PAR_PATH).getChild("configvalue");
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_2")).prevUntil($(resource));
+		assertResourceSetEquals(query.iterator(), "configvalue_0", "configvalue_1");
+	}
+
+	@Test
+	public void testNextUntilOnLast() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_2")).nextUntil("[key=unknownKey]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testPrevUntilOnFirst() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("richtext")).prevUntil("[key=helloWorld]");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testNextUntilOnRoot() {
+		SlingQuery query = $(tree).nextUntil("cq:Page");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testPrevUntilOnRoot() {
+		SlingQuery query = $(tree).prevUntil("cq:Page");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testNextUntilInvalid() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("richtext")).nextUntil("cq:Undefined");
+		assertResourceSetEquals(query.iterator(), "configvalue", "configvalue_0", "configvalue_1",
+				"configvalue_2");
+	}
+
+	@Test
+	public void testPrevUntilInvalid() {
+		SlingQuery query = $(tree.getChild(PAR_PATH).getChild("configvalue_2")).prevUntil("cq:Undefined");
+		assertResourceSetEquals(query.iterator(), "configvalue", "configvalue_0", "configvalue_1", "richtext");
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/query/NotTest.java b/src/test/java/org/apache/sling/query/NotTest.java
new file mode 100644
index 0000000..c8d796f
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/NotTest.java
@@ -0,0 +1,65 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import java.util.Collections;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class NotTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testNot() {
+		SlingQuery query = $(tree).children().not("cq:Page");
+		assertResourceSetEquals(query.iterator(), "jcr:content");
+	}
+
+	@Test
+	public void testNotResources() {
+		SlingQuery query = $(tree).children().not(
+				$(tree.getChild("jcr:content"), tree.getChild("application")));
+		assertResourceSetEquals(query.iterator(), "home");
+	}
+
+	@Test
+	public void testNotEmptyResources() {
+		SlingQuery query = $(tree).children().not(Collections.<Resource>emptyList());
+		assertResourceSetEquals(query.iterator(), "jcr:content", "application", "home");
+	}
+
+	@Test
+	public void testNotWithModifier() {
+		SlingQuery query = $(tree).children("cq:Page").not(":first");
+		assertResourceSetEquals(query.iterator(), "home");
+	}
+
+	@Test
+	public void testNotOnEmptyCollection() {
+		SlingQuery query = $(tree).children("cq:Undefined").not("cq:Page");
+		assertEmptyIterator(query.iterator());
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/ParentTest.java b/src/test/java/org/apache/sling/query/ParentTest.java
new file mode 100644
index 0000000..a984e8d
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/ParentTest.java
@@ -0,0 +1,44 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class ParentTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testParent() {
+		SlingQuery query = $(tree.getChild("application/configuration/labels")).parent();
+		assertResourceSetEquals(query.iterator(), "configuration");
+	}
+
+	@Test
+	public void testRootParent() {
+		SlingQuery query = $(tree).parent();
+		assertEmptyIterator(query.iterator());
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/ParentsTest.java b/src/test/java/org/apache/sling/query/ParentsTest.java
new file mode 100644
index 0000000..b0b8445
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/ParentsTest.java
@@ -0,0 +1,60 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class ParentsTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testParents() {
+		SlingQuery query = $(tree.getChild("application/configuration/labels/jcr:content")).parents();
+		assertResourceSetEquals(query.iterator(), "labels", "configuration", "application", "/");
+	}
+
+	@Test
+	public void testFilteredParents() {
+		SlingQuery query = $(
+				tree.getChild("application/configuration/labels/jcr:content/configParsys/tab/items"))
+				.parents("cq:Page");
+		assertResourceSetEquals(query.iterator(), "labels", "configuration", "application", "/");
+	}
+
+	@Test
+	public void testNoParents() {
+		SlingQuery query = $(
+				tree.getChild("application/configuration/labels/jcr:content/configParsys/tab/items"))
+				.parents("cq:Undefined");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testRootParents() {
+		SlingQuery query = $(tree).parents();
+		assertEmptyIterator(query.iterator());
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/ParentsUntilTest.java b/src/test/java/org/apache/sling/query/ParentsUntilTest.java
new file mode 100644
index 0000000..80a9ca8
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/ParentsUntilTest.java
@@ -0,0 +1,54 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class ParentsUntilTest {
+
+	private static final String PAR_PATH = "application/configuration/labels/jcr:content/configParsys/tab/items";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testParentsUntilMatch() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).parentsUntil("cq:Page");
+		assertResourceSetEquals(query.iterator(), "jcr:content", "configParsys", "tab");
+	}
+
+	@Test
+	public void testParentsUntilNoMatch() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).parentsUntil("cq:Undefined");
+		assertResourceSetEquals(query.iterator(), "application", "configuration", "labels", "jcr:content",
+				"configParsys", "tab", "/");
+	}
+
+	@Test
+	public void testParentsUntilResource() {
+		Resource resource = tree.getChild("application/configuration");
+		SlingQuery query = $(tree.getChild(PAR_PATH)).parentsUntil($(resource));
+		assertResourceSetEquals(query.iterator(), "tab", "configParsys", "jcr:content", "labels");
+	}
+
+}
diff --git a/src/test/java/org/apache/sling/query/SelectorFunctionTest.java b/src/test/java/org/apache/sling/query/SelectorFunctionTest.java
new file mode 100644
index 0000000..c3d0b40
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/SelectorFunctionTest.java
@@ -0,0 +1,121 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.api.SearchStrategy;
+import org.junit.Test;
+
+public class SelectorFunctionTest {
+
+	// children with indexes:
+	// 0 - richtext
+	// 1 - configvalue
+	// 2 - configvalue_0
+	// 3 - configvalue_1
+	// 4 - configvalue_2
+	private static final String PAR_PATH = "home/java/labels/jcr:content/par";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testEq() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":eq(2)");
+		assertResourceSetEquals(query.iterator(), "configvalue_0");
+	}
+
+	@Test
+	public void testFirst() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":first");
+		assertResourceSetEquals(query.iterator(), "richtext");
+	}
+
+	@Test
+	public void testLast() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":last");
+		assertResourceSetEquals(query.iterator(), "configvalue_2");
+	}
+
+	@Test
+	public void testGt() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":gt(2)");
+		assertResourceSetEquals(query.iterator(), "configvalue_1", "configvalue_2");
+	}
+
+	@Test
+	public void testLt() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":lt(3)");
+		assertResourceSetEquals(query.iterator(), "richtext", "configvalue", "configvalue_0");
+	}
+
+	@Test
+	public void testHas() {
+		SlingQuery query = $(tree.getChild("home/java")).searchStrategy(SearchStrategy.DFS).children(
+				":has([key=helloWorld])");
+		assertResourceSetEquals(query.iterator(), "labels");
+	}
+
+	@Test
+	public void testParent() {
+		SlingQuery query = $(tree.getChild("home/java/email/jcr:content/par")).children(":parent");
+		assertResourceSetEquals(query.iterator(), "email");
+	}
+
+	@Test
+	public void testOdd() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":odd");
+		assertResourceSetEquals(query.iterator(), "configvalue", "configvalue_1");
+	}
+
+	@Test
+	public void testEven() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":even");
+		assertResourceSetEquals(query.iterator(), "richtext", "configvalue_0", "configvalue_2");
+	}
+
+	@Test
+	public void testSimpleNot() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":not(demo/core/components/richtext)");
+		assertResourceSetEquals(query.iterator(), "configvalue", "configvalue_0", "configvalue_1",
+				"configvalue_2");
+	}
+
+	@Test
+	public void testNotFirst() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":not(:first)");
+		assertResourceSetEquals(query.iterator(), "configvalue", "configvalue_0", "configvalue_1",
+				"configvalue_2");
+	}
+
+	@Test
+	public void testNotLast() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":not(:last)");
+		assertResourceSetEquals(query.iterator(), "richtext", "configvalue", "configvalue_0", "configvalue_1");
+	}
+
+	@Test
+	public void testComplexNot() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children(":not(:first):not(:last)");
+		assertResourceSetEquals(query.iterator(), "configvalue", "configvalue_0", "configvalue_1");
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/query/SiblingsTest.java b/src/test/java/org/apache/sling/query/SiblingsTest.java
new file mode 100644
index 0000000..fe3092b
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/SiblingsTest.java
@@ -0,0 +1,56 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class SiblingsTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testSiblings() {
+		SlingQuery query = $(tree.getChild("application")).siblings();
+		assertResourceSetEquals(query.iterator(), "jcr:content", "application", "home");
+	}
+
+	@Test
+	public void testFilteredChildren() {
+		SlingQuery query = $(tree.getChild("application")).siblings("cq:Page");
+		assertResourceSetEquals(query.iterator(), "application", "home");
+	}
+
+	@Test
+	public void testNoSiblings() {
+		SlingQuery query = $(tree.getChild("application")).siblings("cq:Undefined");
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testRootSiblings() {
+		SlingQuery query = $(tree).siblings();
+		assertResourceSetEquals(query.iterator(), "/");
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/SliceTest.java b/src/test/java/org/apache/sling/query/SliceTest.java
new file mode 100644
index 0000000..692b4ee
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/SliceTest.java
@@ -0,0 +1,105 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertEmptyIterator;
+import static org.apache.sling.query.TestUtils.assertResourceSetEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class SliceTest {
+
+	// children with indexes:
+	// 0 - richtext
+	// 1 - configvalue
+	// 2 - configvalue_0
+	// 3 - configvalue_1
+	// 4 - configvalue_2
+	private static final String PAR_PATH = "home/java/labels/jcr:content/par";
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testSlice() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children().slice(2, 4);
+		assertResourceSetEquals(query.iterator(), "configvalue_0", "configvalue_1", "configvalue_2");
+	}
+
+	@Test
+	public void testSliceOne() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children().slice(2, 2);
+		assertResourceSetEquals(query.iterator(), "configvalue_0");
+	}
+
+	@Test
+	public void testEq() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children().eq(2);
+		assertResourceSetEquals(query.iterator(), "configvalue_0");
+	}
+
+	@Test
+	public void testEqOnEmpty() {
+		SlingQuery query = $(tree).children("cq:Undefined").eq(0);
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testFirst() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children().first();
+		assertResourceSetEquals(query.iterator(), "richtext");
+	}
+
+	@Test
+	public void testFirstOnEmpty() {
+		SlingQuery query = $(tree).children("cq:Undefined").first();
+		assertEmptyIterator(query.iterator());
+	}
+
+	@Test
+	public void testSliceAll() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children().slice(0, 4);
+		assertResourceSetEquals(query.iterator(), "richtext", "configvalue", "configvalue_0",
+				"configvalue_1", "configvalue_2");
+	}
+
+	@Test
+	public void testSliceAllBigTo() {
+		SlingQuery query = $(tree.getChild(PAR_PATH)).children().slice(0, 10);
+		assertResourceSetEquals(query.iterator(), "richtext", "configvalue", "configvalue_0",
+				"configvalue_1", "configvalue_2");
+	}
+
+	@Test(expected = IndexOutOfBoundsException.class)
+	public void testSliceNegativeFrom() {
+		$(tree.getChild(PAR_PATH)).children().slice(-1);
+	}
+
+	@Test(expected = IndexOutOfBoundsException.class)
+	public void testSliceNegativeFrom2() {
+		$(tree.getChild(PAR_PATH)).children().slice(-1, 2);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testSliceFromGreaterThanTo() {
+		$(tree.getChild(PAR_PATH)).children().slice(2, 1);
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/query/TestUtils.java b/src/test/java/org/apache/sling/query/TestUtils.java
new file mode 100644
index 0000000..b2bd9ba
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/TestUtils.java
@@ -0,0 +1,96 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.mock.json.JsonToResource;
+import org.junit.Assert;
+
+public final class TestUtils {
+	private TestUtils() {
+	}
+
+	public static Resource getTree() {
+		InputStream jsonStream = TestUtils.class.getClassLoader().getResourceAsStream("sample_tree.json");
+		try {
+			Resource resource = JsonToResource.parse(jsonStream);
+			jsonStream.close();
+			return resource;
+		} catch (IOException e) {
+			return null;
+		}
+	}
+
+	public static <T> List<T> iteratorToList(Iterator<T> iterator) {
+		List<T> list = new ArrayList<T>();
+		while (iterator.hasNext()) {
+			list.add(iterator.next());
+		}
+		return list;
+	}
+
+	public static <T> List<Option<T>> optionList(List<T> list) {
+		List<Option<T>> result = new ArrayList<Option<T>>();
+		int index = 0;
+		for (T element : list) {
+			result.add(Option.of(element, index++));
+		}
+		return result;
+	}
+
+	public static void assertEmptyIterator(Iterator<?> iterator) {
+		if (iterator.hasNext()) {
+			Assert.fail(String.format("Iterator should be empty, but %s is returned", iterator.next()
+					.toString()));
+		}
+	}
+
+	public static void assertResourceSetEquals(Iterator<Resource> iterator, String... names) {
+		Set<String> expectedSet = new LinkedHashSet<String>(Arrays.asList(names));
+		Set<String> actualSet = new LinkedHashSet<String>(getResourceNames(iterator));
+		Assert.assertEquals(expectedSet, actualSet);
+	}
+
+	public static void assertResourceListEquals(Iterator<Resource> iterator, String... names) {
+		Assert.assertEquals(Arrays.asList(names), getResourceNames(iterator));
+	}
+
+	public static List<String> l(String... args) {
+		return Arrays.asList(args);
+	}
+
+	private static List<String> getResourceNames(Iterator<Resource> iterator) {
+		List<String> resourceNames = new ArrayList<String>();
+		while (iterator.hasNext()) {
+			resourceNames.add(iterator.next().getName());
+		}
+		return resourceNames;
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/UniqueTest.java b/src/test/java/org/apache/sling/query/UniqueTest.java
new file mode 100644
index 0000000..78966a1
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/UniqueTest.java
@@ -0,0 +1,41 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query;
+
+import static org.apache.sling.query.SlingQuery.$;
+import static org.apache.sling.query.TestUtils.assertResourceListEquals;
+
+import org.apache.sling.api.resource.Resource;
+import org.junit.Test;
+
+public class UniqueTest {
+
+	private Resource tree = TestUtils.getTree();
+
+	@Test
+	public void testUnique() {
+		Resource r1 = tree.getChild("home");
+		Resource r2 = tree.getChild("application");
+		Resource r3 = tree.getChild("home/java");
+
+		SlingQuery query = $(r1, r1, r1, r2, r2, r3, r3, r3, r1).unique();
+		assertResourceListEquals(query.iterator(), "home", "application", "java", "home");
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/iterator/ReverseTest.java b/src/test/java/org/apache/sling/query/iterator/ReverseTest.java
new file mode 100644
index 0000000..10876dd
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/iterator/ReverseTest.java
@@ -0,0 +1,53 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.TestUtils;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.function.IdentityFunction;
+import org.apache.sling.query.function.SliceFunction;
+import org.apache.sling.query.iterator.ReverseIterator;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.sling.query.TestUtils.l;
+
+public class ReverseTest {
+
+	@Test
+	public void testReverse() {
+		test(l("a", "b", "c", "d", "e"), l(null, null, null, null, null), new IdentityFunction<String>());
+		test(l("a", "b", "c", "d", "e"), l("a", null, null, null, null), new SliceFunction<String>(1));
+		test(l("a", "b", "c", "d", "e"), l(null, null, null, "d", "e"), new SliceFunction<String>(0, 2));
+	}
+
+	private static <T> void test(List<T> input, List<T> output, IteratorToIteratorFunction<T> function) {
+		List<Option<T>> optionInput = TestUtils.optionList(input);
+		List<Option<T>> expectedOutput = TestUtils.optionList(output);
+		Iterator<Option<T>> actualOutputIterator = new ReverseIterator<T>(function, optionInput.iterator());
+		List<Option<T>> actualOutput = TestUtils.iteratorToList(actualOutputIterator);
+		Assert.assertEquals(expectedOutput, actualOutput);
+	}
+
+}
diff --git a/src/test/java/org/apache/sling/query/iterator/SuppTest.java b/src/test/java/org/apache/sling/query/iterator/SuppTest.java
new file mode 100644
index 0000000..de4f326
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/iterator/SuppTest.java
@@ -0,0 +1,136 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.iterator;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.query.TestUtils;
+import org.apache.sling.query.api.function.ElementToIteratorFunction;
+import org.apache.sling.query.api.function.IteratorToIteratorFunction;
+import org.apache.sling.query.api.function.Option;
+import org.apache.sling.query.function.IdentityFunction;
+import org.apache.sling.query.function.IteratorToIteratorFunctionWrapper;
+import org.apache.sling.query.function.SliceFunction;
+import org.apache.sling.query.iterator.SuppIterator;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.sling.query.TestUtils.l;
+
+public class SuppTest {
+
+	@Test
+	public void testIdentity() {
+		test(l("a", "b", "c", "d", "e"), l("a", "b", "c", "d", "e"), new IdentityFunction<String>());
+	}
+
+	@Test
+	public void testNoFirst() {
+		test(l("a", "b", "c", "d", "e"), l(null, "b", "c", "d", "e"), new SliceFunction<String>(1));
+	}
+
+	@Test
+	public void testNoSecond() {
+		testExpanding(l("a", "---"), l("a"));
+	}
+
+	@Test
+	public void testNoTwoFirst() {
+		test(l("a", "b", "c", "d", "e"), l(null, null, "c", "d", "e"), new SliceFunction<String>(2));
+	}
+
+	@Test
+	public void testNoLast() {
+		test(l("a", "b", "c", "d", "e"), l("a", "b", "c", "d"), new SliceFunction<String>(0, 3));
+	}
+
+	@Test
+	public void testNoTwoLast() {
+		test(l("a", "b", "c", "d", "e"), l("a", "b", "c"), new SliceFunction<String>(0, 2));
+	}
+
+	@Test
+	public void testJustFirst() {
+		test(l("a", "b", "c", "d", "e"), l("a"), new SliceFunction<String>(0, 0));
+	}
+
+	@Test
+	public void testExpandFirst() {
+		testExpanding(l("+", "b", "c", "d", "e"), l("+", "b", "c", "d", "e"));
+	}
+
+	@Test
+	public void testExpandMiddle() {
+		testExpanding(l("a", "b", "+", "d", "e"), l("a", "b", "+", "d", "e"));
+	}
+
+	@Test
+	public void testExpandLast() {
+		testExpanding(l("a", "b", "c", "d", "+"), l("a", "b", "c", "d", "+"));
+	}
+
+	@Test
+	public void testRemoveFirst() {
+		testExpanding(l("-", "b", "c", "d", "e"), l(null, "b", "c", "d", "e"));
+		testExpanding(l("---", "b", "c", "d", "e"), l(null, "b", "c", "d", "e"));
+	}
+
+	@Test
+	public void testRemoveMiddle() {
+		testExpanding(l("a", "b", "-", "d", "e"), l("a", "b", null, "d", "e"));
+		testExpanding(l("a", "b", "---", "d", "e"), l("a", "b", null, "d", "e"));
+	}
+
+	@Test
+	public void testRemoveLast() {
+		testExpanding(l("a", "b", "c", "d", "-"), l("a", "b", "c", "d"));
+		testExpanding(l("a", "b", "c", "d", "---"), l("a", "b", "c", "d"));
+	}
+
+	private static void testExpanding(List<String> input, List<String> output) {
+		test(input, output, EXPANDING_FUNCTION);
+	}
+
+	private static <T> void test(List<T> input, List<T> output, IteratorToIteratorFunction<T> function) {
+		List<Option<T>> optionInput = TestUtils.optionList(input);
+		List<Option<T>> expectedOutput = TestUtils.optionList(output);
+		Iterator<Option<T>> actualOutputIterator = new SuppIterator<T>(optionInput, function);
+		List<Option<T>> actualOutput = TestUtils.iteratorToList(actualOutputIterator);
+		Assert.assertEquals(expectedOutput, actualOutput);
+	}
+
+	private static final IteratorToIteratorFunctionWrapper<String> EXPANDING_FUNCTION = new IteratorToIteratorFunctionWrapper<String>(
+			new ElementToIteratorFunction<String>() {
+				@Override
+				public Iterator<String> apply(String input) {
+					if ("+".equals(input)) {
+						return Arrays.asList("a", "b", "c").iterator();
+					} else if ("-".equals(input)) {
+						return Arrays.<String> asList().iterator();
+					} else if ("---".equals(input)) {
+						return Arrays.<String> asList(null, null, null).iterator();
+					} else {
+						return Arrays.asList(input).iterator();
+					}
+				}
+			});
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/query/mock/MockTypeResolver.java b/src/test/java/org/apache/sling/query/mock/MockTypeResolver.java
new file mode 100644
index 0000000..37ba3d0
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/mock/MockTypeResolver.java
@@ -0,0 +1,49 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.mock;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.sling.query.resource.jcr.JcrTypeResolver;
+
+public class MockTypeResolver implements JcrTypeResolver {
+
+	private static final List<String> TYPE_HIERARCHY = Arrays.asList("nt:base", "nt:unstructured", "cq:Page",
+			"cq:Type");
+
+	private static final List<String> OTHER_TYPES = Arrays.asList("jcr:otherType", "jcr:someType");
+
+	@Override
+	public boolean isJcrType(String name) {
+		return TYPE_HIERARCHY.contains(name) || OTHER_TYPES.contains(name);
+	}
+
+	@Override
+	public boolean isSubtype(String supertype, String subtype) {
+		int i1 = TYPE_HIERARCHY.indexOf(supertype);
+		int i2 = TYPE_HIERARCHY.indexOf(subtype);
+		if (i1 == -1 || i2 == -1) {
+			return false;
+		}
+		return i1 < i2;
+	}
+
+}
diff --git a/src/test/java/org/apache/sling/query/mock/ResourceMock.java b/src/test/java/org/apache/sling/query/mock/ResourceMock.java
new file mode 100644
index 0000000..6d8c524
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/mock/ResourceMock.java
@@ -0,0 +1,144 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.mock;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+
+public class ResourceMock implements Resource {
+
+	private final String name;
+
+	private final Map<String, Resource> children;
+
+	private final Map<String, String> properties;
+
+	private final Resource parent;
+
+	public ResourceMock(Resource parent, String name) {
+		this.name = name;
+		this.parent = parent;
+		this.children = new LinkedHashMap<String, Resource>();
+		this.properties = new LinkedHashMap<String, String>();
+	}
+
+	public void setProperty(String name, String value) {
+		properties.put(name, value);
+	}
+
+	public void addChild(Resource resource) {
+		children.put(resource.getName(), resource);
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public Resource getParent() {
+		return parent;
+	}
+
+	@Override
+	public Iterator<Resource> listChildren() {
+		return children.values().iterator();
+	}
+
+	@Override
+	public Resource getChild(String relPath) {
+		if (StringUtils.contains(relPath, '/')) {
+			String firstPart = StringUtils.substringBefore(relPath, "/");
+			String rest = StringUtils.substringAfter(relPath, "/");
+			if (children.containsKey(firstPart)) {
+				return children.get(firstPart).getChild(rest);
+			}
+		} else {
+			if (children.containsKey(relPath)) {
+				return children.get(relPath);
+			} else if (properties.containsKey(relPath)) {
+				return new StringResourceMock(this, relPath, properties.get(relPath));
+			}
+		}
+		return null;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+		if (type.isAssignableFrom(Map.class)) {
+			return (AdapterType) properties;
+		} else {
+			return null;
+		}
+	}
+
+	@Override
+	public boolean isResourceType(String resourceType) {
+		return StringUtils.isNotBlank(resourceType)
+				&& (resourceType.equals(properties.get("sling:resourceType")) || resourceType
+						.equals(properties.get("jcr:primaryType")));
+	}
+
+	@Override
+	public String getResourceType() {
+		if (properties.containsKey("sling:resourceType")) {
+			return properties.get("sling:resourceType");
+		} else {
+			return properties.get("jcr:primaryType");
+		}
+	}
+
+	@Override
+	public String getPath() {
+		if (parent == null) {
+			return "";
+		} else {
+			return String.format("%s/%s", parent.getPath(), name);
+		}
+	}
+
+	@Override
+	public String getResourceSuperType() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public ResourceMetadata getResourceMetadata() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public ResourceResolver getResourceResolver() {
+		return null;
+	}
+
+	@Override
+	public String toString() {
+		return String.format("ResourceMock[%s]", name);
+	}
+
+}
diff --git a/src/test/java/org/apache/sling/query/mock/StringResourceMock.java b/src/test/java/org/apache/sling/query/mock/StringResourceMock.java
new file mode 100644
index 0000000..8407e78
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/mock/StringResourceMock.java
@@ -0,0 +1,107 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.mock;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+
+public class StringResourceMock implements Resource {
+
+	private final String name;
+
+	private final Resource parent;
+
+	private final String value;
+
+	public StringResourceMock(Resource parent, String name, String value) {
+		this.parent = parent;
+		this.name = name;
+		this.value = value;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+		if (type.isAssignableFrom(String.class)) {
+			return (AdapterType) value;
+		} else {
+			return null;
+		}
+	}
+
+	@Override
+	public String getPath() {
+		if (parent == null) {
+			return "";
+		} else {
+			return String.format("%s/%s", parent.getPath(), name);
+		}
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public Resource getParent() {
+		return parent;
+	}
+
+	@Override
+	public Iterator<Resource> listChildren() {
+		return Arrays.<Resource> asList().iterator();
+	}
+
+	@Override
+	public Resource getChild(String relPath) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public String getResourceType() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public String getResourceSuperType() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public boolean isResourceType(String resourceType) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public ResourceMetadata getResourceMetadata() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public ResourceResolver getResourceResolver() {
+		throw new UnsupportedOperationException();
+	}
+
+}
diff --git a/src/test/java/org/apache/sling/query/mock/json/JsonToResource.java b/src/test/java/org/apache/sling/query/mock/json/JsonToResource.java
new file mode 100644
index 0000000..b7a30fc
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/mock/json/JsonToResource.java
@@ -0,0 +1,65 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.mock.json;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.query.mock.ResourceMock;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+public final class JsonToResource {
+	private JsonToResource() {
+	}
+
+	public static Resource parse(InputStream inputStream) {
+		JsonElement element = new JsonParser().parse(new InputStreamReader(inputStream));
+		return parseResource(element.getAsJsonObject(), "/", null);
+	}
+
+	private static Resource parseResource(JsonObject object, String name, Resource parent) {
+		Map<String, String> properties = new LinkedHashMap<String, String>();
+		Map<String, JsonObject> children = new LinkedHashMap<String, JsonObject>();
+		for (Entry<String, JsonElement> entry : object.entrySet()) {
+			JsonElement value = entry.getValue();
+			if (value.isJsonPrimitive()) {
+				properties.put(entry.getKey(), value.getAsString());
+			} else if (value.isJsonObject()) {
+				children.put(entry.getKey(), value.getAsJsonObject());
+			}
+		}
+
+		ResourceMock resource = new ResourceMock(parent, name);
+		for (Entry<String, String> entry : properties.entrySet()) {
+			resource.setProperty(entry.getKey(), entry.getValue());
+		}
+		for (Entry<String, JsonObject> entry : children.entrySet()) {
+			resource.addChild(parseResource(entry.getValue(), entry.getKey(), resource));
+		}
+		return resource;
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/selector/parser/JcrMultiSelectorTest.java b/src/test/java/org/apache/sling/query/selector/parser/JcrMultiSelectorTest.java
new file mode 100644
index 0000000..aee6c5c
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/selector/parser/JcrMultiSelectorTest.java
@@ -0,0 +1,58 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import static org.apache.sling.query.selector.parser.JcrSelectorTest.parse;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JcrMultiSelectorTest {
+	@Test
+	public void typeHierarchy() {
+		String selector = "cq:Page, cq:Type";
+		String jcrQuery = "SELECT * FROM [cq:Page] AS s";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+
+		selector = "cq:Type, cq:Page";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void incompatibleTypes() {
+		final String selector = "jcr:someType, cq:Type";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void attributes() {
+		final String selector = "[x=y][y=z], [a=b][c=d]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE ((s.[x] = 'y' AND s.[y] = 'z') OR (s.[a] = 'b' AND s.[c] = 'd'))";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void attributesWithPath() {
+		final String selector = "[x=y][y=z], [a=b][c=d]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE (ISDESCENDANTNODE([/content]) AND ((s.[x] = 'y' AND s.[y] = 'z') OR (s.[a] = 'b' AND s.[c] = 'd')))";
+		Assert.assertEquals(jcrQuery, parse(selector, "/content"));
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/selector/parser/JcrSelectorTest.java b/src/test/java/org/apache/sling/query/selector/parser/JcrSelectorTest.java
new file mode 100644
index 0000000..e0d7c6e
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/selector/parser/JcrSelectorTest.java
@@ -0,0 +1,136 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import java.util.List;
+
+import org.apache.sling.query.mock.MockTypeResolver;
+import org.apache.sling.query.resource.jcr.query.JcrQueryBuilder;
+import org.apache.sling.query.selector.parser.Selector;
+import org.apache.sling.query.selector.parser.SelectorParser;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JcrSelectorTest {
+	@Test
+	public void parseResourceType() {
+		final String selector = "foundation/components/parsys";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE s.[sling:resourceType] = 'foundation/components/parsys'";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parsePrimaryType() {
+		final String selector = "cq:Page";
+		final String jcrQuery = "SELECT * FROM [cq:Page] AS s";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parsePath() {
+		final String selector = "cq:Page";
+		final String jcrQuery = "SELECT * FROM [cq:Page] AS s WHERE ISDESCENDANTNODE([/content])";
+		Assert.assertEquals(jcrQuery, parse(selector, "/content"));
+	}
+
+	@Test
+	public void parseEmptySelector() {
+		final String selector = "";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseEquals() {
+		final String selector = "[key1=value]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE s.[key1] = 'value'";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseNotEquals() {
+		final String selector = "[key1!=value]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE s.[key1] != 'value'";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseContains() {
+		final String selector = "[key1*=value]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE s.[key1] LIKE '%value%'";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseContainsWord() {
+		final String selector = "[key1~=value]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE s.[key1] LIKE '%value%'";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseStartsWith() {
+		final String selector = "[key1^=value]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE s.[key1] LIKE 'value%'";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseEndsWith() {
+		final String selector = "[key1$=value]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE s.[key1] LIKE '%value'";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseEscapedValue() {
+		final String selector = "[key1=value'123]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE s.[key1] = 'value''123'";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseMultipleConditions() {
+		final String selector = "[key1=value1][key2=value2]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE (s.[key1] = 'value1' AND s.[key2] = 'value2')";
+		Assert.assertEquals(jcrQuery, parse(selector, "/"));
+	}
+
+	@Test
+	public void parseMultipleConditionsWithResourceTypeAndPath() {
+		final String selector = "foundation/components/parsys[key1=value1][key2=value2]";
+		final String jcrQuery = "SELECT * FROM [nt:base] AS s WHERE (ISDESCENDANTNODE([/content]) AND (s.[sling:resourceType] = 'foundation/components/parsys' AND s.[key1] = 'value1' AND s.[key2] = 'value2'))";
+		Assert.assertEquals(jcrQuery, parse(selector, "/content"));
+	}
+
+	@Test
+	public void parseAttributeWithSubresource() {
+		final String selector = "cq:Page[jcr:content/cq:template=xyz]";
+		final String jcrQuery = "SELECT * FROM [cq:Page] AS s WHERE ISDESCENDANTNODE([/content])";
+		Assert.assertEquals(jcrQuery, parse(selector, "/content"));
+	}
+
+	static String parse(String selector, String root) {
+		List<Selector> selectors = SelectorParser.parse(selector);
+		List<SelectorSegment> segments = SelectorParser.getFirstSegmentFromEachSelector(selectors);
+		return new JcrQueryBuilder(new MockTypeResolver()).buildQuery(segments, root);
+	}
+}
diff --git a/src/test/java/org/apache/sling/query/selector/parser/SelectorTest.java b/src/test/java/org/apache/sling/query/selector/parser/SelectorTest.java
new file mode 100644
index 0000000..4f74186
--- /dev/null
+++ b/src/test/java/org/apache/sling/query/selector/parser/SelectorTest.java
@@ -0,0 +1,265 @@
+/*-
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.query.selector.parser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.sling.query.selector.parser.Attribute;
+import org.apache.sling.query.selector.parser.Modifier;
+import org.apache.sling.query.selector.parser.SelectorParser;
+import org.apache.sling.query.selector.parser.SelectorSegment;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SelectorTest {
+	@Test
+	public void parseResourceType() {
+		SelectorSegment selector = getFirstSegment("my/resource/type");
+		Assert.assertEquals(selector.getType(), "my/resource/type");
+	}
+
+	@Test
+	public void parseProperty() {
+		SelectorSegment selector = getFirstSegment("[key=value]");
+		Assert.assertEquals(Arrays.asList(pp("key", "value")), selector.getAttributes());
+	}
+
+	@Test
+	public void parseProperties() {
+		SelectorSegment selector = getFirstSegment("[key=value][key2=value2]");
+		Assert.assertEquals(Arrays.asList(pp("key", "value"), pp("key2", "value2")), selector.getAttributes());
+	}
+
+	@Test
+	public void parseResourceTypeAndName() {
+		SelectorSegment selector = getFirstSegment("my/resource/type#some-name");
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals("some-name", selector.getName());
+	}
+
+	@Test
+	public void parseResourceTypeAndProperty() {
+		SelectorSegment selector = getFirstSegment("my/resource/type[key=value]");
+		Assert.assertEquals(Arrays.asList(pp("key", "value")), selector.getAttributes());
+		Assert.assertEquals("my/resource/type", selector.getType());
+	}
+
+	@Test
+	public void parseResourceTypeAndNameAndProperty() {
+		SelectorSegment selector = getFirstSegment("my/resource/type#some-name[key=value]");
+		Assert.assertEquals(Arrays.asList(pp("key", "value")), selector.getAttributes());
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals("some-name", selector.getName());
+	}
+
+	@Test
+	public void parseResourceTypeAndProperties() {
+		SelectorSegment selector = getFirstSegment("my/resource/type[key=value][key2=value2]");
+		Assert.assertEquals(Arrays.asList(pp("key", "value"), pp("key2", "value2")), selector.getAttributes());
+		Assert.assertEquals("my/resource/type", selector.getType());
+	}
+
+	@Test
+	public void parseResourceTypeAndNameAndProperties() {
+		SelectorSegment selector = getFirstSegment("my/resource/type#some-name[key=value][key2=value2]");
+		Assert.assertEquals(Arrays.asList(pp("key", "value"), pp("key2", "value2")), selector.getAttributes());
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals("some-name", selector.getName());
+	}
+
+	@Test
+	public void parseFunction() {
+		SelectorSegment selector = getFirstSegment(":eq(12)");
+		Assert.assertEquals(Arrays.asList(f("eq", "12")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseNameAndFunction() {
+		SelectorSegment selector = getFirstSegment("#some-name:eq(12)");
+		Assert.assertEquals("some-name", selector.getName());
+		Assert.assertEquals(Arrays.asList(f("eq", "12")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseEscapedNameAndFunction() {
+		SelectorSegment selector = getFirstSegment("#'jcr:content':eq(12)");
+		Assert.assertEquals("jcr:content", selector.getName());
+		Assert.assertEquals(Arrays.asList(f("eq", "12")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseFunctionWithFilter() {
+		SelectorSegment selector = getFirstSegment(":has([key=value])");
+		Assert.assertEquals(Arrays.asList(f("has", "[key=value]")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseNameAndFunctionWithFilter() {
+		SelectorSegment selector = getFirstSegment("#some-name:has([key=value])");
+		Assert.assertEquals(Arrays.asList(f("has", "[key=value]")), selector.getModifiers());
+		Assert.assertEquals("some-name", selector.getName());
+	}
+
+	@Test
+	public void parseNestedFunction() {
+		SelectorSegment selector = getFirstSegment(":not(:has(cq:Page))");
+		Assert.assertEquals(Arrays.asList(f("not", ":has(cq:Page)")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseFunctionWithoutArgument() {
+		SelectorSegment selector = getFirstSegment(":first");
+		Assert.assertEquals(Arrays.asList(f("first", null)), selector.getModifiers());
+	}
+
+	@Test
+	public void parseFunctions() {
+		SelectorSegment selector = getFirstSegment(":eq(12):first");
+		Assert.assertEquals(Arrays.asList(f("eq", "12"), f("first", null)), selector.getModifiers());
+	}
+
+	@Test
+	public void parsePrimaryTypeAndFunction() {
+		SelectorSegment selector = getFirstSegment("cq:Page:first");
+		Assert.assertEquals("cq:Page", selector.getType());
+		Assert.assertEquals(Arrays.asList(f("first", null)), selector.getModifiers());
+	}
+
+	@Test
+	public void parsePrimaryTypeAndFunctions() {
+		SelectorSegment selector = getFirstSegment("cq:Page:first:eq(12)");
+		Assert.assertEquals("cq:Page", selector.getType());
+		Assert.assertEquals(Arrays.asList(f("first", null), f("eq", "12")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseResourceTypeAndFunction() {
+		SelectorSegment selector = getFirstSegment("my/resource/type:first");
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals(Arrays.asList(f("first", null)), selector.getModifiers());
+	}
+
+	@Test
+	public void parseResourceTypeAndNameAndFunction() {
+		SelectorSegment selector = getFirstSegment("my/resource/type#some-name:first");
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals(Arrays.asList(f("first", null)), selector.getModifiers());
+		Assert.assertEquals("some-name", selector.getName());
+	}
+
+	@Test
+	public void parseResourceTypeAndFunctions() {
+		SelectorSegment selector = getFirstSegment("my/resource/type:first:eq(12)");
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals(Arrays.asList(f("first", null), f("eq", "12")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseResourceTypeAndPropertyAndFunction() {
+		SelectorSegment selector = getFirstSegment("my/resource/type[key=value]:first");
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals(Arrays.asList(pp("key", "value")), selector.getAttributes());
+		Assert.assertEquals(Arrays.asList(f("first", null)), selector.getModifiers());
+	}
+
+	@Test
+	public void parseResourceTypeAndNameAndPropertyAndFunction() {
+		SelectorSegment selector = getFirstSegment("my/resource/type#some-name[key=value]:first");
+		Assert.assertEquals(selector.getType(), "my/resource/type");
+		Assert.assertEquals(Arrays.asList(pp("key", "value")), selector.getAttributes());
+		Assert.assertEquals(Arrays.asList(f("first", null)), selector.getModifiers());
+		Assert.assertEquals("some-name", selector.getName());
+	}
+
+	@Test
+	public void parseResourceTypeAndPropertiesAndFunction() {
+		SelectorSegment selector = getFirstSegment("my/resource/type[key=value][key2=value2]:first");
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals(Arrays.asList(pp("key", "value"), pp("key2", "value2")), selector.getAttributes());
+		Assert.assertEquals(Arrays.asList(f("first", null)), selector.getModifiers());
+	}
+
+	@Test
+	public void parseResourceTypeAndPropertyAndFunctions() {
+		SelectorSegment selector = getFirstSegment("my/resource/type[key=value]:first:eq(12)");
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals(Arrays.asList(pp("key", "value")), selector.getAttributes());
+		Assert.assertEquals(Arrays.asList(f("first", null), f("eq", "12")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseResourceTypeAndPropertiesAndFunctions() {
+		SelectorSegment selector = getFirstSegment("my/resource/type[key=value][key2=value2]:first:eq(12)");
+		Assert.assertEquals("my/resource/type", selector.getType());
+		Assert.assertEquals(Arrays.asList(pp("key", "value"), pp("key2", "value2")), selector.getAttributes());
+		Assert.assertEquals(Arrays.asList(f("first", null), f("eq", "12")), selector.getModifiers());
+	}
+
+	@Test
+	public void parseMultiSegments() {
+		List<SelectorSegment> segments = getSegments("cq:Page cq:Page");
+		Assert.assertEquals(getSegments("cq:Page", " ", "cq:Page"), segments);
+
+		segments = getSegments("cq:Page > cq:Page");
+		Assert.assertEquals(getSegments("cq:Page", ">", "cq:Page"), segments);
+
+		segments = getSegments("cq:Page ~ cq:Page");
+		Assert.assertEquals(getSegments("cq:Page", "~", "cq:Page"), segments);
+
+		segments = getSegments("cq:Page + cq:Page");
+		Assert.assertEquals(getSegments("cq:Page", "+", "cq:Page"), segments);
+
+		segments = getSegments("cq:Page   cq:Page2 +  cq:Page3");
+		Assert.assertEquals(getSegments("cq:Page", " ", "cq:Page2", "+", "cq:Page3"), segments);
+	}
+
+	private static Attribute pp(String key, String value) {
+		return new Attribute(key, "=", value);
+	}
+
+	private static Modifier f(String functionId, String argument) {
+		return new Modifier(functionId, argument);
+	}
+
+	private static List<SelectorSegment> getSegments(String selector) {
+		return SelectorParser.parse(selector).get(0).getSegments();
+	}
+
+	private static SelectorSegment getFirstSegment(String selector) {
+		return getSegments(selector).get(0);
+	}
+
+	private static List<SelectorSegment> getSegments(String... segments) {
+		List<SelectorSegment> list = new ArrayList<SelectorSegment>();
+		if (segments.length > 0) {
+			list.add(getFirstSegment(segments[0]));
+		}
+		for (int i = 1; i < segments.length; i += 2) {
+			SelectorSegment parsed = getFirstSegment(segments[i + 1]);
+			char operator = segments[i].charAt(0);
+			SelectorSegment segment = new SelectorSegment(parsed.getType(), null, parsed.getAttributes(),
+					parsed.getModifiers(), operator);
+			list.add(segment);
+		}
+		return list;
+	}
+}
\ No newline at end of file
diff --git a/src/test/resources/sample_tree.json b/src/test/resources/sample_tree.json
new file mode 100644
index 0000000..03cab87
--- /dev/null
+++ b/src/test/resources/sample_tree.json
@@ -0,0 +1,439 @@
+{
+  "jcr:createdBy": "admin",
+  "jcr:created": "Wed Nov 20 2013 11:01:57 GMT+0100",
+  "jcr:primaryType": "cq:Page",
+  "jcr:content": {
+    "redirectTarget": "/content/cqc-demo/home",
+    "sling:resourceType": "demo/core/renderers/redirectRenderer",
+    "cq:allowedTemplates": [
+      "/apps/demo/.*/templates(.*)?",
+      "/apps/cq-commons(.*)?"
+    ],
+    "jcr:title": "CQ Commons demo",
+    "cq:designPath": "/etc/designs/demo",
+    "jcr:createdBy": "admin",
+    "jcr:created": "Wed Nov 20 2013 11:01:57 GMT+0100",
+    "jcr:primaryType": "cq:PageContent",
+    "cq:template": "/apps/demo/core/templates/redirectTemplate"
+  },
+  "application": {
+    "jcr:createdBy": "admin",
+    "jcr:created": "Wed Nov 20 2013 11:04:42 GMT+0100",
+    "jcr:primaryType": "cq:Page",
+    "jcr:content": {
+      "redirectTarget": "/content/cqc-demo/home",
+      "sling:resourceType": "demo/core/renderers/redirectRenderer",
+      "jcr:title": "Application",
+      "jcr:createdBy": "admin",
+      "cq:allowedTemplates": [
+        "/apps/demo/core/templates/configuration(.*)?",
+        "/apps/cq-commons(.*)?"
+      ],
+      "cq:designPath": "/etc/designs/demo",
+      "jcr:created": "Wed Nov 20 2013 11:04:42 GMT+0100",
+      "jcr:primaryType": "cq:PageContent",
+      "cq:template": "/apps/demo/core/templates/redirectTemplate"
+    },
+    "configuration": {
+      "jcr:createdBy": "admin",
+      "jcr:created": "Wed Nov 20 2013 11:04:42 GMT+0100",
+      "jcr:primaryType": "cq:Page",
+      "jcr:content": {
+        "redirectTarget": "/content/cqc-demo/home",
+        "sling:resourceType": "demo/core/renderers/redirectRenderer",
+        "jcr:createdBy": "admin",
+        "cq:designPath": "/etc/designs/demo",
+        "cq:allowedTemplates": [
+          "/apps/demo/core/templates/configuration(.*)?",
+          "/apps/cq-commons(.*)?"
+        ],
+        "jcr:title": "Configuration",
+        "jcr:created": "Wed Nov 20 2013 11:04:42 GMT+0100",
+        "jcr:primaryType": "cq:PageContent",
+        "cq:template": "/apps/demo/core/templates/redirectTemplate"
+      },
+      "labels": {
+        "jcr:createdBy": "admin",
+        "jcr:created": "Wed Nov 20 2013 11:04:42 GMT+0100",
+        "jcr:primaryType": "cq:Page",
+        "jcr:content": {
+          "sling:resourceType": "cq-commons/config/renderers/configPageRenderer",
+          "jcr:title": "Labels",
+          "jcr:createdBy": "admin",
+          "cq:designPath": "/etc/designs/cq-commons",
+          "jcr:created": "Wed Nov 20 2013 11:04:42 GMT+0100",
+          "jcr:primaryType": "cq:PageContent",
+          "cq:template": "/apps/cq-commons/config/templates/configPageTemplate",
+          "configParsys": {
+            "sling:resourceType": "cq-commons/config/components/configParsys",
+            "jcr:primaryType": "nt:unstructured",
+            "tab": {
+              "tabName": "First tab",
+              "sling:resourceType": "cq-commons/config/components/tab",
+              "jcr:primaryType": "nt:unstructured",
+              "items": {
+                "sling:resourceType": "foundation/components/parsys",
+                "jcr:primaryType": "nt:unstructured",
+                "localizedtext": {
+                  "key": "helloWorld",
+                  "title": "The greeting",
+                  "sling:resourceType": "cq-commons/config/components/localizedText",
+                  "jcr:primaryType": "nt:unstructured",
+                  "lang": {
+                    "en_US": "Hello world",
+                    "jcr:primaryType": "nt:unstructured"
+                  }
+                },
+                "text": {
+                  "key": "otherLabel",
+                  "title": "Some other field",
+                  "sling:resourceType": "cq-commons/config/components/text",
+                  "value": "random text",
+                  "jcr:primaryType": "nt:unstructured"
+                }
+              }
+            },
+            "tab_0": {
+              "tabName": "Second tab",
+              "sling:resourceType": "cq-commons/config/components/tab",
+              "jcr:primaryType": "nt:unstructured",
+              "items": {
+                "sling:resourceType": "foundation/components/parsys",
+                "jcr:primaryType": "nt:unstructured",
+                "text_0": {
+                  "key": "theAnswer",
+                  "title": "The Answer",
+                  "sling:resourceType": "cq-commons/config/components/number",
+                  "value": "42",
+                  "jcr:primaryType": "nt:unstructured"
+                },
+                "text": {
+                  "key": "thisCmsName",
+                  "title": "CMS name",
+                  "sling:resourceType": "cq-commons/config/components/text",
+                  "value": "Adobe CQ",
+                  "jcr:primaryType": "nt:unstructured"
+                }
+              }
+            }
+          }
+        }
+      },
+      "sample-email": {
+        "jcr:createdBy": "admin",
+        "jcr:created": "Tue Dec 03 2013 12:24:00 GMT+0100",
+        "jcr:primaryType": "cq:Page",
+        "jcr:content": {
+          "sling:resourceType": "cq-commons/email/renderers/emailRenderer",
+          "cq:designPath": "/etc/designs/cq-commons",
+          "jcr:title": "Sample email",
+          "jcr:createdBy": "admin",
+          "cq:lastModified": "Tue Dec 03 2013 10:04:16 GMT+0100",
+          "jcr:created": "Tue Dec 03 2013 12:24:00 GMT+0100",
+          "jcr:primaryType": "cq:PageContent",
+          "cq:template": "/apps/cq-commons/email/templates/emailTemplate",
+          "cq:lastModifiedBy": "admin",
+          "email": {
+            "jcr:lastModifiedBy": "admin",
+            "sling:resourceType": "cq-commons/email/components/emailConfig",
+            "type": "combined",
+            "subject": "CQ Commons ${subjectPlaceholder}",
+            "fromEmail": "noreply@example.com",
+            "fromName": "CQ Commons",
+            "jcr:lastModified": "Tue Dec 03 2013 10:04:16 GMT+0100",
+            "jcr:primaryType": "nt:unstructured",
+            "richText1": {
+              "jcr:lastModifiedBy": "admin",
+              "sling:resourceType": "cq-commons/email/components/richtext",
+              "jcr:lastModified": "Tue Dec 03 2013 10:03:59 GMT+0100",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "images": {
+              "jcr:primaryType": "nt:unstructured",
+              "item_1": {
+                "id": "coglogo",
+                "path": "/content/dam/cqc-demo/slogan.png",
+                "jcr:primaryType": "nt:unstructured"
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "home": {
+    "jcr:createdBy": "admin",
+    "jcr:created": "Wed Nov 20 2013 11:04:42 GMT+0100",
+    "jcr:primaryType": "cq:Page",
+    "jcr:content": {
+      "sling:resourceType": "demo/core/renderers/basicRenderer",
+      "jcr:createdBy": "admin",
+      "jcr:title": "Home",
+      "cq:designPath": "/etc/designs/demo",
+      "jcr:created": "Wed Nov 20 2013 11:04:42 GMT+0100",
+      "jcr:primaryType": "cq:PageContent",
+      "cq:template": "/apps/demo/core/templates/basicTemplate",
+      "par": {
+        "sling:resourceType": "foundation/components/parsys",
+        "jcr:primaryType": "nt:unstructured",
+        "richtext": {
+          "sling:resourceType": "demo/core/components/richtext",
+          "jcr:primaryType": "nt:unstructured"
+        }
+      }
+    },
+    "java": {
+      "jcr:createdBy": "admin",
+      "jcr:created": "Wed Nov 20 2013 14:03:23 GMT+0100",
+      "jcr:primaryType": "cq:Page",
+      "jcr:content": {
+        "redirectTarget": "/content/cqc-demo/home",
+        "sling:resourceType": "demo/core/renderers/redirectRenderer",
+        "cq:allowedTemplates": [
+          "/apps/demo/.*/templates(.*)?",
+          "/apps/cq-commons(.*)?"
+        ],
+        "cq:designPath": "/etc/designs/demo",
+        "jcr:createdBy": "admin",
+        "jcr:title": "Java",
+        "jcr:created": "Wed Nov 20 2013 14:03:23 GMT+0100",
+        "jcr:primaryType": "cq:PageContent",
+        "cq:template": "/apps/demo/core/templates/redirectTemplate"
+      },
+      "email": {
+        "jcr:createdBy": "admin",
+        "jcr:created": "Thu Nov 28 2013 18:53:32 GMT+0100",
+        "jcr:primaryType": "cq:Page",
+        "jcr:content": {
+          "sling:resourceType": "demo/core/renderers/basicRenderer",
+          "jcr:title": "E-mail",
+          "jcr:createdBy": "admin",
+          "cq:designPath": "/etc/designs/demo",
+          "cq:lastModified": "Tue Dec 03 2013 10:04:29 GMT+0100",
+          "jcr:created": "Thu Nov 28 2013 18:53:32 GMT+0100",
+          "jcr:primaryType": "cq:PageContent",
+          "cq:lastModifiedBy": "admin",
+          "cq:template": "/apps/demo/core/templates/basicTemplate",
+          "par": {
+            "sling:resourceType": "foundation/components/parsys",
+            "jcr:primaryType": "nt:unstructured",
+            "richtext": {
+              "sling:resourceType": "demo/core/components/richtext",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "email": {
+              "template": "/content/cqc-demo/application/configuration/sample-email",
+              "jcr:lastModifiedBy": "admin",
+              "sling:resourceType": "demo/core/components/email",
+              "jcr:lastModified": "Tue Dec 03 2013 10:04:29 GMT+0100",
+              "jcr:primaryType": "nt:unstructured",
+              "values": {
+                "jcr:primaryType": "nt:unstructured",
+                "item_1": {
+                  "key": "polishPangram",
+                  "value": "Pchnąć w tę łódź jeża lub ośm skrzyń fig.",
+                  "jcr:primaryType": "nt:unstructured"
+                },
+                "item_2": {
+                  "key": "englishPangram",
+                  "value": "The quick brown fox jumps over the lazy dog",
+                  "jcr:primaryType": "nt:unstructured"
+                },
+                "item_3": {
+                  "key": "subjectPlaceholder",
+                  "value": "sample e-mail",
+                  "jcr:primaryType": "nt:unstructured"
+                }
+              }
+            }
+          }
+        }
+      },
+      "labels": {
+        "jcr:createdBy": "admin",
+        "jcr:created": "Wed Nov 20 2013 14:03:23 GMT+0100",
+        "jcr:primaryType": "cq:Page",
+        "jcr:content": {
+          "sling:resourceType": "demo/core/renderers/basicRenderer",
+          "cq:designPath": "/etc/designs/demo",
+          "jcr:title": "Labels",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Wed Nov 20 2013 14:03:23 GMT+0100",
+          "jcr:primaryType": "cq:PageContent",
+          "cq:template": "/apps/demo/core/templates/basicTemplate",
+          "par": {
+            "sling:resourceType": "foundation/components/parsys",
+            "jcr:primaryType": "nt:unstructured",
+            "richtext": {
+              "sling:resourceType": "demo/core/components/richtext",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "configvalue": {
+              "key": "helloWorld",
+              "description": "Greeting",
+              "sling:resourceType": "demo/core/components/configValue",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "configvalue_0": {
+              "key": "theAnswer",
+              "description": "The answer",
+              "sling:resourceType": "demo/core/components/configValue",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "configvalue_1": {
+              "key": "thisCmsName",
+              "description": "This CMS name",
+              "sling:resourceType": "demo/core/components/configValue",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "configvalue_2": {
+              "key": "unknownKey",
+              "description": "Some unknown key",
+              "sling:resourceType": "demo/core/components/configValue",
+              "jcr:primaryType": "nt:unstructured"
+            }
+          }
+        }
+      },
+      "navigation": {
+        "jcr:createdBy": "admin",
+        "jcr:created": "Thu Nov 21 2013 11:06:05 GMT+0100",
+        "jcr:primaryType": "cq:Page",
+        "jcr:content": {
+          "sling:resourceType": "demo/core/renderers/basicRenderer",
+          "cq:designPath": "/etc/designs/demo",
+          "jcr:title": "Navigation",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Thu Nov 21 2013 11:06:05 GMT+0100",
+          "jcr:primaryType": "cq:PageContent",
+          "cq:template": "/apps/demo/core/templates/basicTemplate",
+          "par": {
+            "sling:resourceType": "foundation/components/parsys",
+            "jcr:primaryType": "nt:unstructured",
+            "richtext": {
+              "sling:resourceType": "demo/core/components/richtext",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "navigation": {
+              "mode": "parent",
+              "sling:resourceType": "demo/core/components/navigation",
+              "maxDepth": "0",
+              "root": "/content/cqc-demo/home",
+              "jcr:primaryType": "nt:unstructured"
+            }
+          }
+        }
+      }
+    },
+    "js": {
+      "jcr:createdBy": "admin",
+      "jcr:created": "Wed Nov 20 2013 14:03:23 GMT+0100",
+      "jcr:primaryType": "cq:Page",
+      "jcr:content": {
+        "redirectTarget": "/content/cqc-demo/home",
+        "sling:resourceType": "demo/core/renderers/redirectRenderer",
+        "cq:allowedTemplates": [
+          "/apps/demo/.*/templates(.*)?",
+          "/apps/cq-commons(.*)?"
+        ],
+        "jcr:title": "JS",
+        "jcr:createdBy": "admin",
+        "cq:designPath": "/etc/designs/demo",
+        "jcr:created": "Wed Nov 20 2013 14:03:23 GMT+0100",
+        "jcr:primaryType": "cq:PageContent",
+        "cq:template": "/apps/demo/core/templates/redirectTemplate"
+      }
+    },
+    "extjs": {
+      "jcr:createdBy": "admin",
+      "jcr:created": "Wed Nov 20 2013 14:03:23 GMT+0100",
+      "jcr:primaryType": "cq:Page",
+      "jcr:content": {
+        "redirectTarget": "/content/cqc-demo/home",
+        "sling:resourceType": "demo/core/renderers/redirectRenderer",
+        "cq:allowedTemplates": [
+          "/apps/demo/.*/templates(.*)?",
+          "/apps/cq-commons(.*)?"
+        ],
+        "jcr:createdBy": "admin",
+        "cq:designPath": "/etc/designs/demo",
+        "jcr:title": "ExtJS",
+        "jcr:created": "Wed Nov 20 2013 14:03:23 GMT+0100",
+        "jcr:primaryType": "cq:PageContent",
+        "cq:template": "/apps/demo/core/templates/redirectTemplate"
+      },
+      "complex-multifield": {
+        "jcr:createdBy": "admin",
+        "jcr:created": "Wed Nov 20 2013 14:26:51 GMT+0100",
+        "jcr:primaryType": "cq:Page",
+        "jcr:content": {
+          "sling:resourceType": "demo/core/renderers/basicRenderer",
+          "jcr:title": "Complex multifield",
+          "jcr:createdBy": "admin",
+          "cq:designPath": "/etc/designs/demo",
+          "jcr:created": "Wed Nov 20 2013 14:26:51 GMT+0100",
+          "jcr:primaryType": "cq:PageContent",
+          "cq:template": "/apps/demo/core/templates/basicTemplate",
+          "par": {
+            "sling:resourceType": "foundation/components/parsys",
+            "jcr:primaryType": "nt:unstructured",
+            "richtext": {
+              "sling:resourceType": "demo/core/components/richtext",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "complexMultiField": {
+              "sling:resourceType": "demo/core/components/complexMultiField",
+              "jcr:primaryType": "nt:unstructured",
+              "items": {
+                "jcr:primaryType": "nt:unstructured",
+                "item_1": {
+                  "secondSub": "Cognifide",
+                  "firstSub": "Company",
+                  "jcr:primaryType": "nt:unstructured"
+                },
+                "item_2": {
+                  "secondSub": "Poznań",
+                  "firstSub": "City",
+                  "jcr:primaryType": "nt:unstructured"
+                },
+                "item_3": {
+                  "secondSub": "Poland",
+                  "firstSub": "Country",
+                  "jcr:primaryType": "nt:unstructured"
+                }
+              }
+            }
+          }
+        }
+      },
+      "checkbox": {
+        "jcr:createdBy": "admin",
+        "jcr:created": "Fri Nov 22 2013 10:59:38 GMT+0100",
+        "jcr:primaryType": "cq:Page",
+        "jcr:content": {
+          "sling:resourceType": "demo/core/renderers/basicRenderer",
+          "jcr:createdBy": "admin",
+          "jcr:title": "Checkbox",
+          "cq:designPath": "/etc/designs/demo",
+          "jcr:created": "Fri Nov 22 2013 10:59:38 GMT+0100",
+          "jcr:primaryType": "cq:PageContent",
+          "cq:template": "/apps/demo/core/templates/basicTemplate",
+          "par": {
+            "sling:resourceType": "foundation/components/parsys",
+            "jcr:primaryType": "nt:unstructured",
+            "richtext": {
+              "sling:resourceType": "demo/core/components/richtext",
+              "jcr:primaryType": "nt:unstructured"
+            },
+            "checkbox": {
+              "sling:resourceType": "demo/core/components/checkbox",
+              "checkbox": "",
+              "jcr:primaryType": "nt:unstructured",
+              "cogcheckbox": "false"
+            }
+          }
+        }
+      }
+    }
+  }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.