You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ju...@apache.org on 2012/09/17 14:54:06 UTC

svn commit: r1386591 [2/7] - in /jackrabbit/oak/trunk: ./ oak-mongomk-api/ oak-mongomk-api/src/ oak-mongomk-api/src/main/ oak-mongomk-api/src/main/java/ oak-mongomk-api/src/main/java/org/ oak-mongomk-api/src/main/java/org/apache/ oak-mongomk-api/src/ma...

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsonUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsonUtil.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsonUtil.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsonUtil.java Mon Sep 17 12:54:01 2012
@@ -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.jackrabbit.mongomk.impl.json;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.jackrabbit.mk.json.JsopBuilder;
+import org.apache.jackrabbit.mongomk.api.model.Node;
+import org.apache.jackrabbit.mongomk.impl.NodeFilter;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+
+/**
+ * FIXME - [Mete] This should really merge with MicroKernelImpl#toJson.
+ *
+ * <a href="http://en.wikipedia.org/wiki/JavaScript_Object_Notation">JSON</a> related utility classes.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class JsonUtil {
+
+    public static Object convertJsonValue(String jsonValue) throws Exception {
+        if (jsonValue == null) {
+            return null;
+        }
+
+        String dummyJson = "{dummy : " + jsonValue + "}";
+        JSONObject jsonObject = new JSONObject(dummyJson);
+        Object dummyObject = jsonObject.get("dummy");
+        return convertJsonValue(dummyObject);
+    }
+
+    public static String convertToJson(Node node, int depth, int offset, int maxChildNodes,
+            boolean inclVirtualProps, NodeFilter filter) {
+        JsopBuilder builder = new JsopBuilder();
+        convertToJson(builder, node, depth, 0, offset, maxChildNodes, inclVirtualProps, filter);
+        return builder.toString();
+    }
+
+    static void convertToJson(JsopBuilder builder, Node node, int depth, int currentDepth,
+            int offset, int maxChildNodes, boolean inclVirtualProps,  NodeFilter filter) {
+        builder.object();
+
+        Map<String, Object> properties = node.getProperties();
+        if (properties != null) {
+            for (Map.Entry<String, Object> entry : properties.entrySet()) {
+                String key = entry.getKey();
+                if (filter == null || filter.includeProperty(key)) {
+                    Object value = entry.getValue();
+                    builder.key(key);
+                    if (value instanceof String) {
+                        builder.value(value.toString());
+                    } else {
+                        builder.encodedValue(value.toString());
+                    }
+                }
+            }
+        }
+
+        long childCount = node.getChildCount();
+        if (inclVirtualProps) {
+            if (filter == null || filter.includeProperty(":childNodeCount")) {
+                // :childNodeCount is by default always included
+                // unless it is explicitly excluded in the filter
+                builder.key(":childNodeCount").value(childCount);
+            }
+            // FIXME [Mete] See if :hash is still being used.
+            /*check whether :hash has been explicitly included
+            if (filter != null) {
+                NameFilter nf = filter.getPropertyFilter();
+                if (nf != null
+                        && nf.getInclusionPatterns().contains(":hash")
+                        && !nf.getExclusionPatterns().contains(":hash")) {
+                    builder.key(":hash").value(rep.getRevisionStore().getId(node).toString());
+                }
+            }
+            */
+        }
+
+        // FIXME [Mete] There's still some more work here.
+        Iterator<Node> entries = node.getChildEntries(offset, maxChildNodes);
+        while (entries.hasNext()) {
+            Node child = entries.next();
+            int numSiblings = 0;
+            if (maxChildNodes != -1 && ++numSiblings > maxChildNodes) {
+                break;
+            }
+            builder.key(child.getName());
+            if ((depth == -1) || (currentDepth < depth)) {
+                convertToJson(builder, child, depth, currentDepth + 1, offset,
+                        maxChildNodes, inclVirtualProps, filter);
+            } else {
+                builder.object();
+                builder.endObject();
+            }
+        }
+
+        builder.endObject();
+    }
+
+    private static Object convertJsonValue(Object jsonObject) throws Exception {
+        if (jsonObject == JSONObject.NULL) {
+            return null;
+        }
+
+        if (jsonObject instanceof JSONArray) {
+            List<Object> elements = new LinkedList<Object>();
+            JSONArray dummyArray = (JSONArray) jsonObject;
+            for (int i = 0; i < dummyArray.length(); ++i) {
+                Object raw = dummyArray.get(i);
+                Object parsed = convertJsonValue(raw);
+                elements.add(parsed);
+            }
+            return elements;
+        }
+
+        return jsonObject;
+    }
+
+    private JsonUtil() {
+        // no instantiation
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsonUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsopParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsopParser.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsopParser.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsopParser.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,202 @@
+/*
+ * 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.jackrabbit.mongomk.impl.json;
+
+import javax.xml.parsers.SAXParser;
+
+import org.apache.jackrabbit.mk.json.JsopReader;
+import org.apache.jackrabbit.mk.json.JsopTokenizer;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+/**
+ * An event based parser for <a href="http://wiki.apache.org/jackrabbit/Jsop">JSOP</a>.
+ *
+ * <p>
+ * This parser is similar to a {@link SAXParser} using a callback ({@code DefaultHandler}) to inform about certain
+ * events during parsing,i.e. node was added, node was removed, etc. This relieves the implementor from the burden of
+ * performing a semantic analysis of token which are being parsed.
+ * </p>
+ *
+ * <p>
+ * The underlying token parser is the {@link JsopTokenizer}.
+ * </p>
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class JsopParser {
+
+    private final DefaultJsopHandler defaultHandler;
+    private final String path;
+    private final JsopTokenizer tokenizer;
+
+    /**
+     * Constructs a new {@link JsopParser}
+     *
+     * @param path The root path of the JSON diff.
+     * @param jsonDiff The JSON diff.
+     * @param defaultHandler The {@link DefaultJsopHandler} to use.
+     */
+    public JsopParser(String path, String jsonDiff, DefaultJsopHandler defaultHandler) {
+        this.path = path;
+        this.defaultHandler = defaultHandler;
+        tokenizer = new JsopTokenizer(jsonDiff);
+    }
+
+    /**
+     * Parses the JSON diff.
+     *
+     * @throws Exception If an error occurred while parsing.
+     */
+    public void parse() throws Exception {
+        if (path.length() > 0 && !PathUtils.isAbsolute(path)) {
+            throw new IllegalArgumentException("Absolute path expected: " + path);
+        }
+
+        while (true) {
+            int token = tokenizer.read();
+
+            if (token == JsopReader.END) {
+                break;
+            }
+
+            switch (token) {
+                case '+': {
+                    parseOpAdded(path);
+                    break;
+                }
+                case '*': {
+                    parseOpCopied();
+                    break;
+                }
+                case '>': {
+                    parseOpMoved();
+                    break;
+                }
+                case '^': {
+                    parseOpSet();
+                    break;
+                }
+                case '-': {
+                    parseOpRemoved();
+                    break;
+                }
+                default:
+                    throw new IllegalStateException("Unknown operation: " + (char) token);
+            }
+        }
+    }
+
+    private void parseOpAdded(String currentPath) throws Exception {
+        String subPath = tokenizer.readString();
+        String path = PathUtils.concat(currentPath, subPath);
+
+        tokenizer.read(':');
+
+        if (tokenizer.matches('{')) {
+            String parentPath = PathUtils.denotesRoot(path) ? "" : PathUtils.getParentPath(path);
+            String nodeName = PathUtils.denotesRoot(path) ? "/" : PathUtils.getName(path);
+            defaultHandler.nodeAdded(parentPath, nodeName);
+
+            if (!tokenizer.matches('}')) {
+                do {
+                    int pos = tokenizer.getLastPos();
+                    String propName = tokenizer.readString();
+                    tokenizer.read(':');
+
+                    if (tokenizer.matches('{')) { // parse a nested node
+                        tokenizer.setPos(pos); // resetting to last post b/c parseOpAdded expects the whole json
+                        tokenizer.read();
+                        parseOpAdded(path);
+                    } else { // parse property
+                        String valueAsString = tokenizer.readRawValue().trim();
+                        Object value = JsonUtil.convertJsonValue(valueAsString);
+
+                        defaultHandler.propertyAdded(path, propName, value);
+                    }
+                } while (tokenizer.matches(','));
+
+                tokenizer.read('}'); // explicitly close the bracket
+            }
+        }
+    }
+
+    private void parseOpCopied() throws Exception {
+        int pos = tokenizer.getLastPos();
+        String subPath = tokenizer.readString();
+        String srcPath = PathUtils.concat(path, subPath);
+        if (!PathUtils.isAbsolute(srcPath)) {
+            throw new Exception("Absolute path expected: " + srcPath + ", pos: " + pos);
+        }
+        tokenizer.read(':');
+        String targetPath = tokenizer.readString();
+        if (!PathUtils.isAbsolute(targetPath)) {
+            targetPath = PathUtils.concat(path, targetPath);
+            if (!PathUtils.isAbsolute(targetPath)) {
+                throw new Exception("Absolute path expected: " + targetPath + ", pos: " + pos);
+            }
+        }
+        defaultHandler.nodeCopied(path, srcPath, targetPath);
+    }
+
+    private void parseOpMoved() throws Exception {
+        int pos = tokenizer.getLastPos();
+        String subPath = tokenizer.readString();
+        String srcPath = PathUtils.concat(path, subPath);
+        if (!PathUtils.isAbsolute(srcPath)) {
+            throw new Exception("Absolute path expected: " + srcPath + ", pos: " + pos);
+        }
+        tokenizer.read(':');
+        pos = tokenizer.getLastPos();
+        String targetPath = tokenizer.readString();
+        if (!PathUtils.isAbsolute(targetPath)) {
+            targetPath = PathUtils.concat(path, targetPath);
+            if (!PathUtils.isAbsolute(targetPath)) {
+                throw new Exception("absolute path expected: " + targetPath + ", pos: " + pos);
+            }
+        }
+        defaultHandler.nodeMoved(path, srcPath, targetPath);
+    }
+
+    private void parseOpSet() throws Exception {
+        int pos = tokenizer.getLastPos();
+        String subPath = tokenizer.readString();
+        tokenizer.read(':');
+        String value;
+        if (tokenizer.matches(JsopReader.NULL)) {
+            value = null;
+        } else {
+            value = tokenizer.readRawValue().trim();
+        }
+        String targetPath = PathUtils.concat(path, subPath);
+        if (!PathUtils.isAbsolute(targetPath)) {
+            throw new Exception("Absolute path expected: " + targetPath + ", pos: " + pos);
+        }
+        String parentPath = PathUtils.getParentPath(targetPath);
+        String propName = PathUtils.getName(targetPath);
+        defaultHandler.propertySet(parentPath, propName, JsonUtil.convertJsonValue(value));
+    }
+
+    private void parseOpRemoved() throws Exception {
+        int pos = tokenizer.getLastPos();
+        String subPath = tokenizer.readString();
+        String targetPath = PathUtils.concat(path, subPath);
+        if (!PathUtils.isAbsolute(targetPath)) {
+            throw new Exception("Absolute path expected: " + targetPath + ", pos: " + pos);
+        }
+        defaultHandler.nodeRemoved(path, subPath);
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/json/JsopParser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddNodeInstructionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddNodeInstructionImpl.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddNodeInstructionImpl.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddNodeInstructionImpl.java Mon Sep 17 12:54:01 2012
@@ -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.jackrabbit.mongomk.impl.model;
+
+import org.apache.jackrabbit.mongomk.api.model.InstructionVisitor;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.AddNodeInstruction;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+
+/**
+ * Implementation of {@link AddNodeInstruction}.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class AddNodeInstructionImpl implements AddNodeInstruction {
+
+    private final String path;
+
+    /**
+     * Constructs a new {@code AddNodeInstructionImpl}.
+     *
+     * @param parentPath The parent path.
+     * @param name The name.
+     */
+    public AddNodeInstructionImpl(String parentPath, String name) {
+        this.path = PathUtils.concat(parentPath, name);
+    }
+
+    @Override
+    public void accept(InstructionVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("AddNodeInstructionImpl [path=");
+        builder.append(path);
+        builder.append("]");
+        return builder.toString();
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddNodeInstructionImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddPropertyInstructionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddPropertyInstructionImpl.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddPropertyInstructionImpl.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddPropertyInstructionImpl.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,78 @@
+/*
+ * 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.jackrabbit.mongomk.impl.model;
+
+import org.apache.jackrabbit.mongomk.api.model.InstructionVisitor;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.AddPropertyInstruction;
+
+/**
+ * Implementation of {@link AddPropertyInstruction}.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class AddPropertyInstructionImpl implements AddPropertyInstruction {
+
+    private final String key;
+    private final String path;
+    private final Object value;
+
+    /**
+     * Constructs a new {@code AddPropertyInstructionImpl}.
+     *
+     * @param path The path.
+     * @param key The key.
+     * @param value The value.
+     */
+    public AddPropertyInstructionImpl(String path, String key, Object value) {
+        this.path = path;
+        this.key = key;
+        this.value = value;
+    }
+
+    @Override
+    public void accept(InstructionVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public Object getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("AddPropertyInstructionImpl [path=");
+        builder.append(path);
+        builder.append(", key=");
+        builder.append(key);
+        builder.append(", value=");
+        builder.append(value);
+        builder.append("]");
+        return builder.toString();
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/AddPropertyInstructionImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CommitImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CommitImpl.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CommitImpl.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CommitImpl.java Mon Sep 17 12:54:01 2012
@@ -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.jackrabbit.mongomk.impl.model;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.jackrabbit.mongomk.api.model.Commit;
+import org.apache.jackrabbit.mongomk.api.model.Instruction;
+
+
+/**
+ * Implementation of {@link Commit}.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class CommitImpl implements Commit {
+
+    private final String diff;
+    private final List<Instruction> instructions;
+    private final String message;
+    private final String path;
+    private final long timestamp;
+    private String revisionId;
+
+    /**
+     * Constructs a new {@code CommitImpl}.
+     *
+     * @param path The path.
+     * @param diff The diff.
+     * @param message The message.
+     */
+    public CommitImpl(String path,String diff, String message) {
+        this(path, diff, message, new LinkedList<Instruction>());
+    }
+
+    /**
+     * Constructs a new {@code CommitImpl}.
+     *
+     * @param path The path.
+     * @param diff The diff.
+     * @param message The message.
+     * @param instructions The {@link Instruction}s.
+     */
+    public CommitImpl(String path, String diff, String message, List<Instruction> instructions) {
+        this.path = path;
+        this.diff = diff;
+        this.message = message;
+        this.instructions = instructions;
+        timestamp = new Date().getTime();
+    }
+
+    /**
+     * Adds the given {@link Instruction}.
+     *
+     * @param instruction The {@code Instruction}.
+     */
+    public void addInstruction(Instruction instruction) {
+        instructions.add(instruction);
+    }
+
+    public String getDiff() {
+        return diff;
+    }
+
+    public List<Instruction> getInstructions() {
+        return Collections.unmodifiableList(instructions);
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getRevisionId() {
+        return revisionId;
+    }
+
+    public void setRevisionId(String revisionId) {
+        this.revisionId = revisionId;
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CommitImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CopyNodeInstructionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CopyNodeInstructionImpl.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CopyNodeInstructionImpl.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CopyNodeInstructionImpl.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,64 @@
+/*
+ * 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.jackrabbit.mongomk.impl.model;
+
+import org.apache.jackrabbit.mongomk.api.model.InstructionVisitor;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.CopyNodeInstruction;
+
+/**
+ * Implementation of {@link CopyNodeInstruction}.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class CopyNodeInstructionImpl implements CopyNodeInstruction {
+    private final String destPath;
+    private final String path;
+    private final String sourcePath;
+
+    /**
+     * Constructs a new {@code CopyNodeInstructionImpl}.
+     *
+     * @param path The path.
+     * @param sourcePath The source path.
+     * @param destPath The destination path.
+     */
+    public CopyNodeInstructionImpl(String path, String sourcePath, String destPath) {
+        this.path = path;
+        this.sourcePath = sourcePath;
+        this.destPath = destPath;
+    }
+
+    @Override
+    public void accept(InstructionVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    @Override
+    public String getDestPath() {
+        return destPath;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String getSourcePath() {
+        return sourcePath;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/CopyNodeInstructionImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/MoveNodeInstructionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/MoveNodeInstructionImpl.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/MoveNodeInstructionImpl.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/MoveNodeInstructionImpl.java Mon Sep 17 12:54:01 2012
@@ -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.jackrabbit.mongomk.impl.model;
+
+import org.apache.jackrabbit.mongomk.api.model.InstructionVisitor;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.MoveNodeInstruction;
+
+public class MoveNodeInstructionImpl implements MoveNodeInstruction {
+    private final String destPath;
+    private final String path;
+    private final String sourcePath;
+
+    public MoveNodeInstructionImpl(String path, String sourcePath, String destPath) {
+        this.path = path;
+        this.sourcePath = sourcePath;
+        this.destPath = destPath;
+    }
+
+    @Override
+    public void accept(InstructionVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    @Override
+    public String getDestPath() {
+        return destPath;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String getSourcePath() {
+        return sourcePath;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/MoveNodeInstructionImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/NodeImpl.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/NodeImpl.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/NodeImpl.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,324 @@
+/*
+ * 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.jackrabbit.mongomk.impl.model;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.jackrabbit.mk.util.RangeIterator;
+import org.apache.jackrabbit.mongomk.api.model.Node;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+
+/**
+ * Implementation of {@link Node}.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class NodeImpl implements Node {
+
+    private static final List<Node> EMPTY = Collections.emptyList();
+
+    private long childCount;
+    private Map<String, Node> children;
+    private String name;
+    private String path;
+    private Map<String, Object> properties;
+    private String revisionId;
+
+    /**
+     * Constructs a new {@code NodeImpl}.
+     */
+    public NodeImpl() {
+    }
+
+    /**
+     * Constructs a new {@code NodeImpl}.
+     *
+     * @param path The path.
+     */
+    public NodeImpl(String path) {
+        this(path, (Set<Node>) null, null);
+    }
+
+    /**
+     * Constructs a new {@code NodeImpl}.
+     *
+     * @param path The path.
+     * @param children The children.
+     */
+    public NodeImpl(String path, Node[] children) {
+        this(path, new HashSet<Node>(Arrays.asList(children)), null);
+    }
+
+    /**
+     * Constructs a new {@code NodeImpl}.
+     *
+     * @param path The path.
+     * @param children The children.
+     * @param properties The properties.
+     */
+    public NodeImpl(String path, Set<Node> children, Map<String, Object> properties) {
+        setPath(path);
+        setChildren(children);
+        setProperties(properties);
+    }
+
+    /**
+     * Adds the given {@link Node} as child.
+     *
+     * @param child The {@code node} to add.
+     */
+    public void addChild(Node child) {
+        if (children == null) {
+            children = new HashMap<String, Node>();
+        }
+
+        children.put(child.getName(), child);
+    }
+
+    @Override
+    public long getChildCount() {
+        return childCount;
+    }
+
+    /**
+     * Sets the child count.
+     *
+     * @param childCount The child count.
+     */
+    public void setChildCount(long childCount) {
+        this.childCount = childCount;
+    }
+
+    @Override
+    public Set<Node> getChildren() {
+        return this.children != null? Collections.unmodifiableSet(new HashSet<Node>(this.children.values()))
+                : null;
+    }
+
+    /**
+     * Sets the children.
+     *
+     * @param children The children or null.
+     */
+    public void setChildren(Set<Node> children) {
+        if (children == null) {
+            this.children = null;
+            return;
+        }
+
+        this.children = new HashMap<String, Node>();
+        for (Node child : children) {
+            this.children.put(child.getName(), child);
+        }
+    }
+
+    public Iterator<Node> getChildEntries(int offset, int count) {
+        if (offset < 0 || count < -1) {
+            throw new IllegalArgumentException();
+        }
+
+        if (children == null) {
+            return EMPTY.iterator();
+        }
+
+        if (offset == 0 && count == -1) {
+            return children.values().iterator();
+        }
+
+        if (offset >= children.size() || count == 0) {
+            return EMPTY.iterator();
+        }
+
+        if (count == -1 || (offset + count) > children.size()) {
+            count = children.size() - offset;
+        }
+
+        return new RangeIterator<Node>(children.values().iterator(), offset, count);
+    }
+
+    @Override
+    public Set<Node> getDescendants(boolean includeThis) {
+        Set<Node> descendants = new HashSet<Node>();
+        if (includeThis) {
+            descendants.add(this);
+        }
+
+        getDescendantsRecursive(this, descendants);
+
+        return Collections.unmodifiableSet(descendants);
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Sets the path of this node which will automatically set the name of the node as well.
+     *
+     * @param path The path.
+     */
+    public void setPath(String path) {
+        this.path = path;
+        name = PathUtils.getName(path);
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        return this.properties != null? Collections.unmodifiableMap(this.properties) : null;
+    }
+
+    /**
+     * Sets the properties of this node.
+     *
+     * @param properties The properties.
+     */
+    public void setProperties(Map<String, Object> properties) {
+        if (properties != null) {
+            properties = new HashMap<String, Object>(properties);
+        }
+
+        this.properties = properties;
+    }
+
+    @Override
+    public String getRevisionId() {
+        return revisionId;
+    }
+
+    @Override
+    public void setRevisionId(String revisionId) {
+        this.revisionId = revisionId;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((this.children == null) ? 0 : this.children.hashCode());
+        result = (prime * result) + ((this.name == null) ? 0 : this.name.hashCode());
+        result = (prime * result) + ((this.path == null) ? 0 : this.path.hashCode());
+        result = (prime * result) + ((this.properties == null) ? 0 : this.properties.hashCode());
+        result = (prime * result) + ((this.revisionId == null) ? 0 : this.revisionId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (this.getClass() != obj.getClass()) {
+            return false;
+        }
+        NodeImpl other = (NodeImpl) obj;
+        if (this.children == null) {
+            if (other.children != null) {
+                return false;
+            }
+        } else if (!this.children.equals(other.children)) {
+            return false;
+        }
+        if (this.name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!this.name.equals(other.name)) {
+            return false;
+        }
+        if (this.path == null) {
+            if (other.path != null) {
+                return false;
+            }
+        } else if (!this.path.equals(other.path)) {
+            return false;
+        }
+        if (this.properties == null) {
+            if (other.properties != null) {
+                return false;
+            }
+        } else if (!this.properties.equals(other.properties)) {
+            return false;
+        }
+        if (this.revisionId == null) {
+            if (other.revisionId != null) {
+                return false;
+            }
+        } else if (!this.revisionId.equals(other.revisionId)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("NodeImpl [");
+        if (this.name != null) {
+            builder.append("name=");
+            builder.append(this.name);
+            builder.append(", ");
+        }
+        if (this.path != null) {
+            builder.append("path=");
+            builder.append(this.path);
+            builder.append(", ");
+        }
+        if (this.revisionId != null) {
+            builder.append("revisionId=");
+            builder.append(this.revisionId);
+            builder.append(", ");
+        }
+        if (this.properties != null) {
+            builder.append("properties=");
+            builder.append(this.properties);
+            builder.append(", ");
+        }
+        if (this.children != null) {
+            builder.append("children=");
+            builder.append(this.children);
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+
+    private void getDescendantsRecursive(Node node, Set<Node> descendants) {
+        Set<Node> children = node.getChildren();
+        if (children != null) {
+            for (Node child : children) {
+                descendants.add(child);
+                this.getDescendantsRecursive(child, descendants);
+            }
+        }
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/NodeImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/RemoveNodeInstructionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/RemoveNodeInstructionImpl.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/RemoveNodeInstructionImpl.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/RemoveNodeInstructionImpl.java Mon Sep 17 12:54:01 2012
@@ -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.jackrabbit.mongomk.impl.model;
+
+import org.apache.jackrabbit.mongomk.api.model.InstructionVisitor;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.RemoveNodeInstruction;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+
+/**
+ * Implementation of {@link RemoveNodeInstruction}.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class RemoveNodeInstructionImpl implements RemoveNodeInstruction {
+
+    private final String path;
+
+    /**
+     * Constructs a new {@code RemoveNodeInstructionImpl}.
+     *
+     * @param parentPath The parent path.
+     * @param name The name
+     */
+    public RemoveNodeInstructionImpl(String parentPath, String name) {
+        path = PathUtils.concat(parentPath, name);
+    }
+
+    @Override
+    public void accept(InstructionVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("RemoveNodeInstructionImpl [path=");
+        builder.append(path);
+        builder.append("]");
+        return builder.toString();
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/RemoveNodeInstructionImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/SetPropertyInstructionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/SetPropertyInstructionImpl.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/SetPropertyInstructionImpl.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/SetPropertyInstructionImpl.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,78 @@
+/*
+ * 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.jackrabbit.mongomk.impl.model;
+
+import org.apache.jackrabbit.mongomk.api.model.InstructionVisitor;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.SetPropertyInstruction;
+
+/**
+ * Implementation of {@link SetPropertyInstruction}.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+public class SetPropertyInstructionImpl implements SetPropertyInstruction {
+
+    private final String key;
+    private final String path;
+    private final Object value;
+
+    /**
+     * Constructs a new {@code SetPropertyInstructionImpl}.
+     *
+     * @param path The path.
+     * @param key The key.
+     * @param value The value.
+     */
+    public SetPropertyInstructionImpl(String path, String key, Object value) {
+        this.path = path;
+        this.key = key;
+        this.value = value;
+    }
+
+    @Override
+    public void accept(InstructionVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public Object getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SetPropertyInstructionImpl [path=");
+        builder.append(path);
+        builder.append(", key=");
+        builder.append(key);
+        builder.append(", value=");
+        builder.append(value);
+        builder.append("]");
+        return builder.toString();
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/main/java/org/apache/jackrabbit/mongomk/impl/model/SetPropertyInstructionImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/CommitBuilderImplTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/CommitBuilderImplTest.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/CommitBuilderImplTest.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/CommitBuilderImplTest.java Mon Sep 17 12:54:01 2012
@@ -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.jackrabbit.mongomk.builder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.List;
+
+import junit.framework.Assert;
+
+import org.apache.jackrabbit.mongomk.api.model.Commit;
+import org.apache.jackrabbit.mongomk.api.model.Instruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.AddNodeInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.AddPropertyInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.CopyNodeInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.MoveNodeInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.RemoveNodeInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.SetPropertyInstruction;
+import org.apache.jackrabbit.mongomk.impl.InstructionAssert;
+import org.apache.jackrabbit.mongomk.impl.builder.CommitBuilder;
+import org.junit.Test;
+
+
+/**
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class CommitBuilderImplTest {
+
+    private static final String MESSAGE = "This is a simple commit";
+    private static final String ROOT = "/";
+
+    @Test
+    public void testSimpleAdd() throws Exception {
+        StringBuilder sb = new StringBuilder();
+        sb.append("+\"a\" : { \"int\" : 1 } \n");
+        sb.append("+\"a/b\" : { \"string\" : \"foo\" } \n");
+        sb.append("+\"a/c\" : { \"bool\" : true }");
+
+        Commit commit = this.buildAndAssertCommit(sb.toString());
+
+        List<Instruction> instructions = commit.getInstructions();
+        Assert.assertEquals(6, instructions.size());
+        InstructionAssert.assertAddNodeInstruction((AddNodeInstruction) instructions.get(0), "/a");
+        InstructionAssert.assertAddPropertyInstruction((AddPropertyInstruction) instructions.get(1), "/a", "int", 1);
+        InstructionAssert.assertAddNodeInstruction((AddNodeInstruction) instructions.get(2), "/a/b");
+        InstructionAssert.assertAddPropertyInstruction((AddPropertyInstruction) instructions.get(3), "/a/b", "string",
+                "foo");
+        InstructionAssert.assertAddNodeInstruction((AddNodeInstruction) instructions.get(4), "/a/c");
+        InstructionAssert.assertAddPropertyInstruction((AddPropertyInstruction) instructions.get(5), "/a/c", "bool",
+                true);
+    }
+
+    @Test
+    public void testSimpleCopy() throws Exception {
+        StringBuilder sb = new StringBuilder();
+        sb.append("*\"a\" : \"b\"\n");
+        sb.append("*\"a/b\" : \"a/c\"\n");
+
+        Commit commit = this.buildAndAssertCommit(sb.toString());
+        List<Instruction> instructions = commit.getInstructions();
+        assertEquals(2, instructions.size());
+        InstructionAssert.assertCopyNodeInstruction((CopyNodeInstruction) instructions.get(0), "/", "/a", "/b");
+        InstructionAssert.assertCopyNodeInstruction((CopyNodeInstruction) instructions.get(1), "/", "/a/b", "/a/c");
+    }
+
+    @Test
+    public void testSimpleMove() throws Exception {
+        StringBuilder sb = new StringBuilder();
+        sb.append(">\"a\" : \"b\"\n");
+        sb.append(">\"a/b\" : \"a/c\"\n");
+
+        Commit commit = this.buildAndAssertCommit(sb.toString());
+        List<Instruction> instructions = commit.getInstructions();
+        assertEquals(2, instructions.size());
+        InstructionAssert.assertMoveNodeInstruction((MoveNodeInstruction) instructions.get(0), "/", "/a", "/b");
+        InstructionAssert.assertMoveNodeInstruction((MoveNodeInstruction) instructions.get(1), "/", "/a/b", "/a/c");
+    }
+
+    @Test
+    public void testSimpleRemove() throws Exception {
+        StringBuilder sb = new StringBuilder();
+        sb.append("-\"a\"");
+        // TODO properties
+
+        Commit commit = this.buildAndAssertCommit(sb.toString());
+
+        List<Instruction> instructions = commit.getInstructions();
+        assertEquals(1, instructions.size());
+        InstructionAssert.assertRemoveNodeInstruction((RemoveNodeInstruction) instructions.get(0), "/a");
+    }
+
+    @Test
+    public void testSimpleSet() throws Exception {
+        StringBuilder sb = new StringBuilder();
+        sb.append("^\"a\" : \"b\"\n");
+
+        Commit commit = this.buildAndAssertCommit(sb.toString());
+
+        List<Instruction> instructions = commit.getInstructions();
+        assertEquals(1, instructions.size());
+        InstructionAssert.assertSetPropertyInstruction((SetPropertyInstruction) instructions.get(0), "/", "a", "b");
+    }
+
+    private Commit buildAndAssertCommit(String commitString) throws Exception {
+        Commit commit = CommitBuilder.build(ROOT, commitString, MESSAGE);
+
+        assertNotNull(commit);
+        assertEquals(MESSAGE, commit.getMessage());
+        assertNull(commit.getRevisionId());
+        return commit;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/CommitBuilderImplTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/NodeBuilderTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/NodeBuilderTest.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/NodeBuilderTest.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/NodeBuilderTest.java Mon Sep 17 12:54:01 2012
@@ -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.jackrabbit.mongomk.builder;
+
+import org.apache.jackrabbit.mongomk.api.model.Node;
+import org.apache.jackrabbit.mongomk.impl.NodeAssert;
+import org.apache.jackrabbit.mongomk.impl.builder.NodeBuilder;
+import org.apache.jackrabbit.mongomk.impl.model.NodeImpl;
+import org.junit.Test;
+
+
+/**
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class NodeBuilderTest {
+
+    @Test
+    public void testBuildSimpleNodes() throws Exception {
+        String json = "{ \"/\" : { \"a\" : { \"b\" : {} , \"c\" : {} } } }";
+        Node node = NodeBuilder.build(json);
+
+        Node node_c = new NodeImpl("/a/c");
+        Node node_b = new NodeImpl("/a/b");
+        Node node_a = new NodeImpl("/a", new Node[] { node_b, node_c });
+        Node node_root = new NodeImpl("/", new Node[] { node_a });
+
+        NodeAssert.assertDeepEquals(node, node_root);
+    }
+
+    @Test
+    public void testBuildSimpleNodesWithRevisionId() throws Exception {
+        String json = "{ \"/#1\" : { \"a#1\" : { \"b#2\" : {} , \"c#2\" : {} } } }";
+        Node node = NodeBuilder.build(json);
+
+        Node node_c = new NodeImpl("/a/c");
+        node_c.setRevisionId("2");
+        Node node_b = new NodeImpl("/a/b");
+        node_b.setRevisionId("2");
+        Node node_a = new NodeImpl("/a", new Node[] { node_b, node_c });
+        node_a.setRevisionId("1");
+        Node node_root = new NodeImpl("/", new Node[] { node_a });
+        node_root.setRevisionId("1");
+
+        NodeAssert.assertDeepEquals(node, node_root);
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/builder/NodeBuilderTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/InstructionAssert.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/InstructionAssert.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/InstructionAssert.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/InstructionAssert.java Mon Sep 17 12:54:01 2012
@@ -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.jackrabbit.mongomk.impl;
+
+import static junit.framework.Assert.assertEquals;
+
+import org.apache.jackrabbit.mongomk.api.model.Instruction.AddNodeInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.AddPropertyInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.CopyNodeInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.MoveNodeInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.RemoveNodeInstruction;
+import org.apache.jackrabbit.mongomk.api.model.Instruction.SetPropertyInstruction;
+
+
+/**
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class InstructionAssert {
+
+    public static void assertAddNodeInstruction(AddNodeInstruction instruction, String path) {
+        assertEquals(path, instruction.getPath());
+    }
+
+    public static void assertAddPropertyInstruction(AddPropertyInstruction instruction, String path, String key,
+            Object value) {
+        assertEquals(path, instruction.getPath());
+        assertEquals(key, instruction.getKey());
+        assertEquals(value, instruction.getValue());
+    }
+
+    public static void assertCopyNodeInstruction(CopyNodeInstruction instruction, String path, String sourcePath,
+            String destPath) {
+        assertEquals(path, instruction.getPath());
+        assertEquals(sourcePath, instruction.getSourcePath());
+        assertEquals(destPath, instruction.getDestPath());
+    }
+
+    public static void assertMoveNodeInstruction(MoveNodeInstruction instruction, String parentPath, String oldPath,
+            String newPath) {
+        assertEquals(parentPath, instruction.getPath());
+        assertEquals(oldPath, instruction.getSourcePath());
+        assertEquals(newPath, instruction.getDestPath());
+    }
+
+    public static void assertRemoveNodeInstruction(RemoveNodeInstruction instruction, String path) {
+        assertEquals(path, instruction.getPath());
+    }
+
+    public static void assertSetPropertyInstruction(SetPropertyInstruction instruction, String path, String key,
+            Object value) {
+        assertEquals(path, instruction.getPath());
+        assertEquals(key, instruction.getKey());
+        assertEquals(value, instruction.getValue());
+    }
+
+    private InstructionAssert() {
+        // no instantiation
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/InstructionAssert.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/NodeAssert.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/NodeAssert.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/NodeAssert.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/NodeAssert.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,117 @@
+/*
+ * 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.jackrabbit.mongomk.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.jackrabbit.mongomk.api.model.Node;
+
+import junit.framework.Assert;
+
+
+/**
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class NodeAssert {
+
+    public static void assertDeepEquals(Node expected, Node actual) {
+        assertEquals(expected, actual);
+
+        Set<Node> expectedChildren = expected.getChildren();
+        Set<Node> actualChildren = actual.getChildren();
+
+        if (expectedChildren == null) {
+            Assert.assertNull(actualChildren);
+        } else {
+            Assert.assertNotNull(actualChildren);
+            Assert.assertEquals(expectedChildren.size(), actualChildren.size());
+
+            for (Node expectedChild : expectedChildren) {
+                boolean valid = false;
+                for (Node actualChild : actualChildren) {
+                    if (expectedChild.getName().equals(actualChild.getName())) {
+                        assertDeepEquals(expectedChild, actualChild);
+                        valid = true;
+
+                        break;
+                    }
+                }
+
+                Assert.assertTrue(valid);
+            }
+        }
+    }
+
+    public static void assertEquals(Collection<Node> expecteds, Collection<Node> actuals) {
+        Assert.assertEquals(expecteds.size(), actuals.size());
+
+        for (Node expected : expecteds) {
+            boolean valid = false;
+            for (Node actual : actuals) {
+                if (expected.getPath().equals(actual.getPath())) {
+                    assertEquals(expected, actual);
+                    valid = true;
+
+                    break;
+                }
+            }
+
+            Assert.assertTrue(valid);
+        }
+    }
+
+    public static void assertEquals(Node expected, Node actual) {
+        Assert.assertEquals(expected.getName(), actual.getName());
+        Assert.assertEquals(expected.getPath(), actual.getPath());
+
+        String expectedRevisionId = expected.getRevisionId();
+        String actualRevisionId = actual.getRevisionId();
+
+        if (expectedRevisionId == null) {
+            Assert.assertNull(actualRevisionId);
+        }
+        if (actualRevisionId == null) {
+            Assert.assertNull(expectedRevisionId);
+        }
+
+        if ((actualRevisionId != null) && (expectedRevisionId != null)) {
+            Assert.assertEquals(expectedRevisionId, actualRevisionId);
+        }
+
+        Map<String, Object> expectedProperties = expected.getProperties();
+        Map<String, Object> actualProperties = actual.getProperties();
+
+        if (expectedProperties == null) {
+            Assert.assertNull(actualProperties);
+        }
+
+        if (actualProperties == null) {
+            Assert.assertNull(expectedProperties);
+        }
+
+        if ((actualProperties != null) && (expectedProperties != null)) {
+            Assert.assertEquals(expectedProperties, actualProperties);
+        }
+    }
+
+    private NodeAssert() {
+        // no instantiation
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/NodeAssert.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/json/JsopParserTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/json/JsopParserTest.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/json/JsopParserTest.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/json/JsopParserTest.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,543 @@
+/*
+ * 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.jackrabbit.mongomk.impl.json;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.jackrabbit.mongomk.impl.json.DefaultJsopHandler;
+import org.apache.jackrabbit.mongomk.impl.json.JsopParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class JsopParserTest {
+
+    private static class CountingHandler extends DefaultJsopHandler {
+
+        private static class Node {
+            private final String jsop;
+            private final String path;
+
+            Node(String jsop, String path) {
+                this.jsop = jsop;
+                this.path = path;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) {
+                    return true;
+                }
+                if (obj == null) {
+                    return false;
+                }
+                if (this.getClass() != obj.getClass()) {
+                    return false;
+                }
+                Node other = (Node) obj;
+                if (this.jsop == null) {
+                    if (other.jsop != null) {
+                        return false;
+                    }
+                } else if (!this.jsop.equals(other.jsop)) {
+                    return false;
+                }
+                if (this.path == null) {
+                    if (other.path != null) {
+                        return false;
+                    }
+                } else if (!this.path.equals(other.path)) {
+                    return false;
+                }
+                return true;
+            }
+
+            @Override
+            public int hashCode() {
+                final int prime = 31;
+                int result = 1;
+                result = (prime * result) + ((this.jsop == null) ? 0 : this.jsop.hashCode());
+                result = (prime * result) + ((this.path == null) ? 0 : this.path.hashCode());
+                return result;
+            }
+        }
+
+        private static class NodeMoved {
+            private final String newPath;
+            private final String oldPath;
+            private final String rootPath;
+
+            NodeMoved(String rootPath, String oldPath, String newPath) {
+                this.rootPath = rootPath;
+                this.oldPath = oldPath;
+                this.newPath = newPath;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) {
+                    return true;
+                }
+                if (obj == null) {
+                    return false;
+                }
+                if (this.getClass() != obj.getClass()) {
+                    return false;
+                }
+                NodeMoved other = (NodeMoved) obj;
+                if (this.newPath == null) {
+                    if (other.newPath != null) {
+                        return false;
+                    }
+                } else if (!this.newPath.equals(other.newPath)) {
+                    return false;
+                }
+                if (this.oldPath == null) {
+                    if (other.oldPath != null) {
+                        return false;
+                    }
+                } else if (!this.oldPath.equals(other.oldPath)) {
+                    return false;
+                }
+                if (this.rootPath == null) {
+                    if (other.rootPath != null) {
+                        return false;
+                    }
+                } else if (!this.rootPath.equals(other.rootPath)) {
+                    return false;
+                }
+                return true;
+            }
+
+            @Override
+            public int hashCode() {
+                final int prime = 31;
+                int result = 1;
+                result = (prime * result) + ((this.newPath == null) ? 0 : this.newPath.hashCode());
+                result = (prime * result) + ((this.oldPath == null) ? 0 : this.oldPath.hashCode());
+                result = (prime * result) + ((this.rootPath == null) ? 0 : this.rootPath.hashCode());
+                return result;
+            }
+
+        }
+
+        private static class Property {
+            private final String key;
+            private final String path;
+            private final Object value;
+
+            Property(String path, String key, Object value) {
+                this.path = path;
+                this.key = key;
+                this.value = value;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) {
+                    return true;
+                }
+                if (obj == null) {
+                    return false;
+                }
+                if (this.getClass() != obj.getClass()) {
+                    return false;
+                }
+                Property other = (Property) obj;
+                if (this.key == null) {
+                    if (other.key != null) {
+                        return false;
+                    }
+                } else if (!this.key.equals(other.key)) {
+                    return false;
+                }
+                if (this.path == null) {
+                    if (other.path != null) {
+                        return false;
+                    }
+                } else if (!this.path.equals(other.path)) {
+                    return false;
+                }
+                if (this.value == null) {
+                    if (other.value != null) {
+                        return false;
+                    }
+                } else if (this.value instanceof Object[]) {
+                    return Arrays.deepEquals((Object[]) this.value, (Object[]) other.value);
+                } else if (!this.value.equals(other.value)) {
+                    return false;
+                }
+                return true;
+            }
+
+            @Override
+            public int hashCode() {
+                final int prime = 31;
+                int result = 1;
+                result = (prime * result) + ((this.key == null) ? 0 : this.key.hashCode());
+                result = (prime * result) + ((this.path == null) ? 0 : this.path.hashCode());
+                result = (prime * result) + ((this.value == null) ? 0 : this.value.hashCode());
+                return result;
+            }
+        }
+
+        private final List<Node> nodesAdded;
+        private final List<NodeMoved> nodesCopied;
+        private final List<NodeMoved> nodesMoved;
+        private final List<Node> nodesRemoved;
+        private final List<Property> propertiesAdded;
+        private final List<Property> propertiesSet;
+
+        CountingHandler() {
+            this.nodesAdded = new LinkedList<Node>();
+            this.nodesCopied = new LinkedList<NodeMoved>();
+            this.nodesMoved = new LinkedList<NodeMoved>();
+            this.nodesRemoved = new LinkedList<Node>();
+            this.propertiesAdded = new LinkedList<Property>();
+            this.propertiesSet = new LinkedList<Property>();
+        }
+
+        public void assertNodeCopied(String parentPath, String oldPath, String newPath) {
+            NodeMoved expected = new NodeMoved(parentPath, oldPath, newPath);
+
+            int firstIndex = this.nodesCopied.indexOf(expected);
+            int lastIndex = this.nodesCopied.lastIndexOf(expected);
+
+            Assert.assertTrue(firstIndex != -1);
+            Assert.assertEquals(firstIndex, lastIndex);
+        }
+
+        public void assertNodeMoved(String parentPath, String oldPath, String newPath) {
+            NodeMoved expected = new NodeMoved(parentPath, oldPath, newPath);
+
+            int firstIndex = this.nodesMoved.indexOf(expected);
+            int lastIndex = this.nodesMoved.lastIndexOf(expected);
+
+            Assert.assertTrue(firstIndex != -1);
+            Assert.assertEquals(firstIndex, lastIndex);
+        }
+
+        public void assertNodeRemoved(String path, String name) {
+            Node expected = new Node(path, name);
+
+            int firstIndex = this.nodesRemoved.indexOf(expected);
+            int lastIndex = this.nodesRemoved.lastIndexOf(expected);
+
+            Assert.assertTrue(firstIndex != -1);
+            Assert.assertEquals(firstIndex, lastIndex);
+        }
+
+        public void assertNoOfNodesCopied(int num) {
+            Assert.assertEquals(num, this.nodesCopied.size());
+        }
+
+        public void assertNoOfNodesMoved(int num) {
+            Assert.assertEquals(num, this.nodesMoved.size());
+        }
+
+        public void assertNoOfNodesRemoved(int num) {
+            Assert.assertEquals(num, this.nodesRemoved.size());
+        }
+
+        public void assertNoOfPropertiesSet(int num) {
+            Assert.assertEquals(num, this.propertiesSet.size());
+        }
+
+        public void assertPropertiesAdded(int num) {
+            Assert.assertEquals(num, this.propertiesAdded.size());
+        }
+
+        @Override
+        public void nodeAdded(String path, String name) {
+            this.nodesAdded.add(new Node(path, name));
+        }
+
+        @Override
+        public void nodeCopied(String rootPath, String oldPath, String newPath) {
+            this.nodesCopied.add(new NodeMoved(rootPath, oldPath, newPath));
+        }
+
+        @Override
+        public void nodeMoved(String rootPath, String oldPath, String newPath) {
+            this.nodesMoved.add(new NodeMoved(rootPath, oldPath, newPath));
+        }
+
+        @Override
+        public void nodeRemoved(String path, String name) {
+            this.nodesRemoved.add(new Node(path, name));
+        }
+
+        @Override
+        public void propertyAdded(String path, String key, Object value) {
+            this.propertiesAdded.add(new Property(path, key, value));
+        }
+
+        @Override
+        public void propertySet(String path, String key, Object value) {
+            this.propertiesSet.add(new Property(path, key, value));
+        }
+
+        void assertNodeAdded(String path, String name) {
+            Node expected = new Node(path, name);
+
+            int firstIndex = this.nodesAdded.indexOf(expected);
+            int lastIndex = this.nodesAdded.lastIndexOf(expected);
+
+            Assert.assertTrue(firstIndex != -1);
+            Assert.assertEquals(firstIndex, lastIndex);
+        }
+
+        void assertPropertyAdded(String path, String key, Object value) {
+            Property expected = new Property(path, key, value);
+
+            int firstIndex = this.propertiesAdded.indexOf(expected);
+            int lastIndex = this.propertiesAdded.lastIndexOf(expected);
+
+            Assert.assertTrue(firstIndex != -1);
+            Assert.assertEquals(firstIndex, lastIndex);
+        }
+
+        void assertPropertySet(String path, String key, Object value) {
+            Property expected = new Property(path, key, value);
+
+            int firstIndex = this.propertiesSet.indexOf(expected);
+            int lastIndex = this.propertiesSet.lastIndexOf(expected);
+
+            Assert.assertTrue(firstIndex != -1);
+            Assert.assertEquals(firstIndex, lastIndex);
+        }
+
+        void assetNoOfNodesAdded(int num) {
+            Assert.assertEquals(num, this.nodesAdded.size());
+        }
+
+    }
+
+    @Test
+    public void testAddNestedNodes() throws Exception {
+        String rootPath = "/";
+        StringBuilder sb = new StringBuilder();
+        sb.append("+\"a\" : { \"integer\" : 123 ,\"b\" : { \"double\" : 123.456 , \"d\" : {} } , \"c\" : { \"string\" : \"string\" }}");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+
+        jsopParser.parse();
+
+        countingHandler.assetNoOfNodesAdded(4);
+        countingHandler.assertNodeAdded("/", "a");
+        countingHandler.assertNodeAdded("/a", "b");
+        countingHandler.assertNodeAdded("/a/b", "d");
+        countingHandler.assertNodeAdded("/a", "c");
+
+        countingHandler.assertPropertiesAdded(3);
+        countingHandler.assertPropertyAdded("/a", "integer", 123);
+        countingHandler.assertPropertyAdded("/a/b", "double", 123.456);
+        countingHandler.assertPropertyAdded("/a/c", "string", "string");
+    }
+
+    @Test
+    public void testAddNodesAndProperties() throws Exception {
+        String rootPath = "/";
+        StringBuilder sb = new StringBuilder();
+        sb.append("+\"a\" : { \"int\" : 1 } \n");
+        sb.append("+\"a/b\" : { \"string\" : \"foo\" } \n");
+        sb.append("+\"a/c\" : { \"bool\" : true }");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+
+        jsopParser.parse();
+
+        countingHandler.assetNoOfNodesAdded(3);
+        countingHandler.assertNodeAdded("/", "a");
+        countingHandler.assertNodeAdded("/a", "b");
+        countingHandler.assertNodeAdded("/a", "c");
+
+        countingHandler.assertPropertiesAdded(3);
+        countingHandler.assertPropertyAdded("/a", "int", Integer.valueOf(1));
+        countingHandler.assertPropertyAdded("/a/b", "string", "foo");
+        countingHandler.assertPropertyAdded("/a/c", "bool", Boolean.TRUE);
+    }
+
+    @Test
+    public void testAddNodesAndPropertiesSeparately() throws Exception {
+        String rootPath = "/";
+        StringBuilder sb = new StringBuilder();
+        sb.append("+\"a\" : {} \n");
+        sb.append("+\"a\" : { \"int\" : 1 } \n");
+        sb.append("+\"a/b\" : {} \n");
+        sb.append("+\"a/b\" : { \"string\" : \"foo\" } \n");
+        sb.append("+\"a/c\" : {} \n");
+        sb.append("+\"a/c\" : { \"bool\" : true }");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+
+        jsopParser.parse();
+
+        countingHandler.assetNoOfNodesAdded(6);
+
+        countingHandler.assertPropertiesAdded(3);
+        countingHandler.assertPropertyAdded("/a", "int", Integer.valueOf(1));
+        countingHandler.assertPropertyAdded("/a/b", "string", "foo");
+        countingHandler.assertPropertyAdded("/a/c", "bool", Boolean.TRUE);
+    }
+
+    @Test
+    public void testAddPropertiesWithComplexArray() throws Exception {
+        String rootPath = "/";
+        String jsop = "+ \"a\" : { \"array_complex\" : [ 123, 123.456, true, false, null, \"string\", [1,2,3,4,5] ] }";
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, jsop, countingHandler);
+
+        jsopParser.parse();
+
+        countingHandler.assertPropertiesAdded(1);
+        countingHandler.assertPropertyAdded(
+                "/a",
+                "array_complex",
+                Arrays.asList(new Object[] { 123, 123.456, true, false, null, "string",
+                        Arrays.asList(new Object[] { 1, 2, 3, 4, 5 }) }));
+    }
+
+    @Test
+    public void testAddWithEmptyPath() throws Exception {
+        String rootPath = "";
+        StringBuilder sb = new StringBuilder();
+        sb.append("+\"/\" : { \"int\" : 1 } \n");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+
+        jsopParser.parse();
+
+        countingHandler.assetNoOfNodesAdded(1);
+        countingHandler.assertNodeAdded("", "/");
+
+        countingHandler.assertPropertiesAdded(1);
+        countingHandler.assertPropertyAdded("/", "int", Integer.valueOf(1));
+    }
+
+    @Test
+    public void testSimpleAddNodes() throws Exception {
+        String rootPath = "/";
+        StringBuilder sb = new StringBuilder();
+        sb.append("+\"a\" : {} \n");
+        sb.append("+\"a/b\" : {} \n");
+        sb.append("+\"a/c\" : {}");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+
+        jsopParser.parse();
+
+        countingHandler.assetNoOfNodesAdded(3);
+        countingHandler.assertNodeAdded("/", "a");
+        countingHandler.assertNodeAdded("/a", "b");
+        countingHandler.assertNodeAdded("/a", "c");
+    }
+
+    @Test
+    public void testSimpleAddProperties() throws Exception {
+        String rootPath = "/";
+        StringBuilder sb = new StringBuilder();
+        sb.append("+ \"a\" : {}");
+        sb.append("+ \"a\" : { \"integer\" : 123, \"double\" : 123.456, \"true\" : true, \"false\" : false, \"null\" : null, \"string\" : \"string\", \"array\" : [1,2,3,4,5] }");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+
+        jsopParser.parse();
+
+        countingHandler.assertPropertiesAdded(7);
+        countingHandler.assertPropertyAdded("/a", "integer", 123);
+        countingHandler.assertPropertyAdded("/a", "double", 123.456);
+        countingHandler.assertPropertyAdded("/a", "true", true);
+        countingHandler.assertPropertyAdded("/a", "false", false);
+        countingHandler.assertPropertyAdded("/a", "null", null);
+        countingHandler.assertPropertyAdded("/a", "string", "string");
+        countingHandler.assertPropertyAdded("/a", "array", Arrays.asList(new Object[] { 1, 2, 3, 4, 5 }));
+    }
+
+    @Test
+    public void testSimpleCopyNodes() throws Exception {
+        String rootPath = "/";
+        StringBuilder sb = new StringBuilder();
+        sb.append("*\"a\" : \"b\"\n");
+        sb.append("*\"a/b\" : \"a/c\"\n");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+        jsopParser.parse();
+
+        countingHandler.assertNoOfNodesCopied(2);
+        countingHandler.assertNodeCopied("/", "/a", "/b");
+        countingHandler.assertNodeCopied("/", "/a/b", "/a/c");
+    }
+
+    @Test
+    public void testSimpleMoveNodes() throws Exception {
+        String rootPath = "/";
+        StringBuilder sb = new StringBuilder();
+        sb.append(">\"a\" : \"b\"\n");
+        sb.append(">\"a/b\" : \"a/c\"\n");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+        jsopParser.parse();
+
+        countingHandler.assertNoOfNodesMoved(2);
+        countingHandler.assertNodeMoved("/", "/a", "/b");
+        countingHandler.assertNodeMoved("/", "/a/b", "/a/c");
+    }
+
+    @Test
+    public void testSimpleRemoveNodes() throws Exception {
+        String rootPath = "/";
+        String jsop = "-\"a\"";
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, jsop, countingHandler);
+
+        jsopParser.parse();
+
+        countingHandler.assertNoOfNodesRemoved(1);
+        countingHandler.assertNodeRemoved("/", "a");
+    }
+
+    @Test
+    public void testSimpleSetNodes() throws Exception {
+        String rootPath = "/";
+        StringBuilder sb = new StringBuilder();
+        sb.append("^\"a\" : \"b\"");
+
+        CountingHandler countingHandler = new CountingHandler();
+        JsopParser jsopParser = new JsopParser(rootPath, sb.toString(), countingHandler);
+        jsopParser.parse();
+
+        countingHandler.assertNoOfPropertiesSet(1);
+        // TODO - Is this correct?
+        countingHandler.assertPropertySet("/", "a", "b");
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk-impl/src/test/java/org/apache/jackrabbit/mongomk/impl/json/JsopParserTest.java
------------------------------------------------------------------------------
    svn:eol-style = native