SLING-8570 - Extract a generic Content Parser API from org.apache.sling.jcr.contentparser with pluggable implementations

* extracted a test utils module, which can be used by all contentparser
implementations
* added a XML Content Parser implementation
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2424af1
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>35</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>org.apache.sling.contentparser.testutils</artifactId>
+    <version>0.9.0-SNAPSHOT</version>
+
+    <name>Apache Sling Content Parser Test Utilities</name>
+    <description>
+        Apache Sling Content Parser Test Utilities for the Content Parser API implementations
+    </description>
+
+    <scm>
+        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-testutils.git</connection>
+        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-testutils.git</developerConnection>
+        <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-contentparser-testutils.git</url>
+        <tag>HEAD</tag>
+    </scm>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.contentparser.api</artifactId>
+            <version>0.9.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.8</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/contentparser/testutils/TestUtils.java b/src/main/java/org/apache/sling/contentparser/testutils/TestUtils.java
new file mode 100644
index 0000000..339f8c5
--- /dev/null
+++ b/src/main/java/org/apache/sling/contentparser/testutils/TestUtils.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.contentparser.testutils;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sling.contentparser.api.ContentParser;
+import org.apache.sling.contentparser.api.ParserOptions;
+import org.apache.sling.contentparser.testutils.mapsupport.ContentElement;
+import org.apache.sling.contentparser.testutils.mapsupport.ContentElementHandler;
+
+public final class TestUtils {
+    
+    private TestUtils() {
+        // static methods only
+    }
+
+    public static ContentElement parse(ContentParser contentParser, File file) throws IOException {
+        return parse(contentParser, new ParserOptions(), file);
+    }
+
+    public static ContentElement parse(ContentParser contentParser, ParserOptions parserOptions, File file) throws IOException {
+        try (FileInputStream fis = new FileInputStream(file);
+             BufferedInputStream bis = new BufferedInputStream(fis)) {
+            ContentElementHandler handler = new ContentElementHandler();
+            contentParser.parse(handler, bis, parserOptions);
+            return handler.getRoot();
+        }
+    }
+    
+    public static ContentElement parse(ContentParser contentParser, String jsonContent) throws IOException {
+        try (ByteArrayInputStream is = new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8))) {
+            ContentElementHandler handler = new ContentElementHandler();
+            contentParser.parse(handler, is, new ParserOptions());
+            return handler.getRoot();
+        }
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElement.java b/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElement.java
new file mode 100644
index 0000000..37bb5ad
--- /dev/null
+++ b/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElement.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.contentparser.testutils.mapsupport;
+
+import java.util.Map;
+
+/**
+ * Represents a resource or node in the content hierarchy.
+ */
+public interface ContentElement {
+
+    /**
+     * @return Resource name. The root resource has no name (null).
+     */
+    String getName();
+    
+    /**
+     * Properties of this resource.
+     * @return Properties (keys, values)
+     */
+    Map<String, Object> getProperties();
+    
+    /**
+     * Get children of current resource. The Map preserves the ordering of children.
+     * @return Children (child names, child objects)
+     */
+    Map<String, ContentElement> getChildren();
+    
+    /**
+     * Get child or descendant
+     * @param path Relative path to address child or one of it's descendants (use "/" as hierarchy separator).
+     * @return Child or null if no child found with this path
+     */
+    ContentElement getChild(String path);
+    
+}
diff --git a/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElementHandler.java b/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElementHandler.java
new file mode 100644
index 0000000..c6e3f54
--- /dev/null
+++ b/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElementHandler.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.contentparser.testutils.mapsupport;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.contentparser.api.ContentHandler;
+
+/**
+ * {@link ContentHandler} implementation that produces a tree of {@link ContentElement} items.
+ */
+public class ContentElementHandler implements ContentHandler {
+    
+    private ContentElement root;
+    private Pattern PATH_PATTERN = Pattern.compile("^((/[^/]+)*)(/([^/]+))$"); 
+
+    @Override
+    public void resource(String path, Map<String, Object> properties) {
+        if (StringUtils.equals(path, "/")) {
+            root = new ContentElementImpl(null, properties);
+        }
+        else {
+            if (root == null) {
+                throw new RuntimeException("Root resource not set.");
+            }
+            Matcher matcher = PATH_PATTERN.matcher(path);
+            if (!matcher.matches()) {
+                throw new RuntimeException("Unexpected path:" + path);
+            }
+            String relativeParentPath = StringUtils.stripStart(matcher.group(1), "/");
+            String name = matcher.group(4);
+            ContentElement parent;
+            if (StringUtils.isEmpty(relativeParentPath)) {
+                parent = root;
+            }
+            else {
+                parent = root.getChild(relativeParentPath);
+            }
+            if (parent == null) {
+                throw new RuntimeException("Parent '" + relativeParentPath + "' does not exist.");
+            }
+            parent.getChildren().put(name, new ContentElementImpl(name, properties));
+        }
+    }
+    
+    public ContentElement getRoot() {
+        return root;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElementImpl.java b/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElementImpl.java
new file mode 100644
index 0000000..93ec66e
--- /dev/null
+++ b/src/main/java/org/apache/sling/contentparser/testutils/mapsupport/ContentElementImpl.java
@@ -0,0 +1,68 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.contentparser.testutils.mapsupport;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+final class ContentElementImpl implements ContentElement {
+    
+    private final String name;
+    private final Map<String, Object> properties;
+    private final Map<String, ContentElement> children = new LinkedHashMap<>();
+    
+    public ContentElementImpl(String name, Map<String, Object> properties) {
+        this.name = name;
+        this.properties = properties;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public Map<String, ContentElement> getChildren() {
+        return children;
+    }
+
+    @Override
+    public ContentElement getChild(String path) {
+        String name = StringUtils.substringBefore(path, "/");
+        ContentElement child = children.get(name);
+        if (child == null) {
+          return null;
+        }
+        String remainingPath = StringUtils.substringAfter(path, "/");
+        if (StringUtils.isEmpty(remainingPath)) {
+          return child;
+        }
+        else {
+          return child.getChild(remainingPath);
+        }
+    }
+
+}