Support for a recurive create in AsyncDocumentTree + Javadoc clean up

Change-Id: I2a4a961e24ff34aa106c93d3a8cb9093f10cee72
diff --git a/core/api/src/main/java/org/onosproject/store/service/AsyncDocumentTree.java b/core/api/src/main/java/org/onosproject/store/service/AsyncDocumentTree.java
index 136aa6c..976772e 100644
--- a/core/api/src/main/java/org/onosproject/store/service/AsyncDocumentTree.java
+++ b/core/api/src/main/java/org/onosproject/store/service/AsyncDocumentTree.java
@@ -37,81 +37,94 @@
     DocumentPath root();
 
     /**
-     * Returns the child values for this node.
+     * Returns the children of node at specified path in the form of a mapping from child name to child value.
      *
      * @param path path to the node
-     * @return future for mapping from a child name to its value
-     * @throws NoSuchDocumentPathException if the path does not point to a valid node
+     * @return future for mapping from child name to child value
+     * @throws {@code NoSuchDocumentPathException} if the path does not point to a valid node
      */
     CompletableFuture<Map<String, Versioned<V>>> getChildren(DocumentPath path);
 
     /**
-     * Returns a value (and version) of the tree node at specified path.
+     * Returns the value of the tree node at specified path.
      *
-     * @param path path to node
-     * @return future for node value or {@code null} if path does not point to a valid node
+     * @param path path to the node
+     * @return future that will be either be completed with node value or {@code null} if path
+     * does not point to a valid node
      */
     CompletableFuture<Versioned<V>> get(DocumentPath path);
 
     /**
      * Creates or updates a document tree node.
      *
-     * @param path path for the node to create or update
-     * @param value the non-null value to be associated with the key
-     * @return future for the previous mapping or {@code null} if there was no previous mapping. Future will
-     * be completed with a NoSuchDocumentPathException if the parent node (for the node to create/update) does not exist
+     * @param path path to the node
+     * @param value the non-null value to be associated with the node
+     * @return future that will either be completed with the previous node value or {@code null} if there was no
+     * node previously at that path. Future will be completed with a {@code IllegalDocumentModificationException}
+     * if the parent node (for the node to create/update) does not exist
      */
     CompletableFuture<Versioned<V>> set(DocumentPath path, V value);
 
     /**
      * Creates a document tree node if one does not exist already.
      *
-     * @param path path for the node to create
-     * @param value the non-null value to be associated with the key
-     * @return future that is completed with {@code true} if the mapping could be added
-     * successfully; {@code false} otherwise. Future will be completed with a
-     * IllegalDocumentModificationException if the parent node (for the node to create) does not exist
+     * @param path path to the node
+     * @param value the non-null value to be associated with the node
+     * @return future that is completed with {@code true} if the new node was successfully
+     * created. Future will be completed with {@code false} if a node already exists at the specified path.
+     * Future will be completed exceptionally with a {@code IllegalDocumentModificationException} if the parent
+     * node (for the node to create) does not exist
      */
     CompletableFuture<Boolean> create(DocumentPath path, V value);
 
     /**
+     * Creates a document tree node recursively by creating all missing intermediate nodes in the path.
+     *
+     * @param path path to the node
+     * @param value the non-null value to be associated with the key
+     * @return future that is completed with {@code true} if the new node was successfully
+     * created. Future will be completed with {@code false} if a node already exists at the specified path
+     */
+    CompletableFuture<Boolean> createRecursive(DocumentPath path, V value);
+
+    /**
      * Conditionally updates a tree node if the current version matches a specified version.
      *
-     * @param path path for the node to create
-     * @param newValue the non-null value to be associated with the key
-     * @param version current version of the value for update to occur
-     * @return future that is completed with {@code true} if the update was made and the tree was
-     * modified, {@code false} otherwise.
+     * @param path path to the node
+     * @param newValue the non-null value to be associated with the node
+     * @param version current version of the node for update to occur
+     * @return future that is either completed with {@code true} if the update was made and the tree was
+     * modified or {@code false} if update did not happen
      */
     CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version);
 
     /**
-     * Conditionally updates a tree node if the current value matches a specified value.
+     * Conditionally updates a tree node if the current node value matches a specified version.
      *
-     * @param path path for the node to create
-     * @param newValue the non-null value to be associated with the key
-     * @param currentValue current value for update to occur
-     * @return future that is completed with {@code true} if the update was made and the tree was
-     * modified, {@code false} otherwise.
+     * @param path path to the node
+     * @param newValue the non-null value to be associated with the node
+     * @param currentValue current value of the node for update to occur
+     * @return future that is either completed with {@code true} if the update was made and the tree was
+     * modified or {@code false} if update did not happen
      */
     CompletableFuture<Boolean> replace(DocumentPath path, V newValue, V currentValue);
 
     /**
      * Removes the node with the specified path.
      *
-     * @param path path for the node to remove
+     * @param path path to the node
      * @return future for the previous value. Future will be completed with a
-     * IllegalDocumentModificationException if the node to be removed is either the root
+     * {@code IllegalDocumentModificationException} if the node to be removed is either the root
      * node or has one or more children. Future will be completed with a
-     * NoSuchDocumentPathException if the node to be removed does not exist
+     * {@code NoSuchDocumentPathException} if the node to be removed does not exist
      */
     CompletableFuture<Versioned<V>> removeNode(DocumentPath path);
 
     /**
-     * Registers a listener to be notified when a subtree rooted at the specified path
+     * Registers a listener to be notified when the subtree rooted at the specified path
      * is modified.
      *
-     * @param path path to root of subtree to monitor for updates
+     * @param  path path to the node
      * @param listener listener to be notified
      * @return a future that is completed when the operation completes
      */
diff --git a/core/api/src/main/java/org/onosproject/store/service/DocumentTree.java b/core/api/src/main/java/org/onosproject/store/service/DocumentTree.java
index 4255b65..e8e4746 100644
--- a/core/api/src/main/java/org/onosproject/store/service/DocumentTree.java
+++ b/core/api/src/main/java/org/onosproject/store/service/DocumentTree.java
@@ -73,6 +73,17 @@
     boolean create(DocumentPath path, V value);
 
     /**
+     * Creates a document tree node by first creating any missing intermediate nodes in the path.
+     *
+     * @param path path for the node to create
+     * @param value the non-null value to be associated with the key
+     * @return returns {@code true} if the mapping could be added successfully, {@code false} if
+     * a node already exists at that path
+     * @throws IllegalDocumentModificationException if {@code path} points to root
+     */
+    boolean createRecursive(DocumentPath path, V value);
+
+    /**
      * Conditionally updates a tree node if the current version matches a specified version.
      *
      * @param path path for the node to create
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/DefaultDistributedDocumentTree.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/DefaultDistributedDocumentTree.java
index f58f220..758d9a0 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/DefaultDistributedDocumentTree.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/DefaultDistributedDocumentTree.java
@@ -88,6 +88,11 @@
     }
 
     @Override
+    public CompletableFuture<Boolean> createRecursive(DocumentPath path, V value) {
+        return backingTree.createRecursive(path, serializer.encode(value));
+    }
+
+    @Override
     public CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version) {
         return backingTree.replace(path, serializer.encode(newValue), version);
     }
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTree.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTree.java
index 939d3f0..8f0c73a 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTree.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTree.java
@@ -17,6 +17,9 @@
 package org.onosproject.store.primitives.resources.impl;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION;
+import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.INVALID_PATH;
+import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.OK;
 import io.atomix.copycat.client.CopycatClient;
 import io.atomix.resource.AbstractResource;
 import io.atomix.resource.ResourceTypeInfo;
@@ -107,9 +110,9 @@
     public CompletableFuture<Versioned<byte[]>> set(DocumentPath path, byte[] value) {
         return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.any(), Match.any()))
                 .thenCompose(result -> {
-                    if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
+                    if (result.status() == INVALID_PATH) {
                         return Tools.exceptionalFuture(new NoSuchDocumentPathException());
-                    } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
+                    } else if (result.status() == ILLEGAL_MODIFICATION) {
                         return Tools.exceptionalFuture(new IllegalDocumentModificationException());
                     } else {
                         return CompletableFuture.completedFuture(result);
@@ -119,16 +122,25 @@
 
     @Override
     public CompletableFuture<Boolean> create(DocumentPath path, byte[] value) {
-        return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any()))
-                .thenCompose(result -> {
-                    if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
-                        return Tools.exceptionalFuture(new NoSuchDocumentPathException());
-                    } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
+        return createInternal(path, value)
+                .thenCompose(status -> {
+                    if (status == ILLEGAL_MODIFICATION) {
                         return Tools.exceptionalFuture(new IllegalDocumentModificationException());
-                    } else {
-                        return CompletableFuture.completedFuture(result);
                     }
-                }).thenApply(result -> result.created());
+                    return CompletableFuture.completedFuture(true);
+                });
+    }
+
+    @Override
+    public CompletableFuture<Boolean> createRecursive(DocumentPath path, byte[] value) {
+        return createInternal(path, value)
+                .thenCompose(status -> {
+                    if (status == ILLEGAL_MODIFICATION) {
+                        return createRecursive(path.parent(), new byte[0])
+                                    .thenCompose(r -> createInternal(path, value).thenApply(v -> true));
+                    }
+                    return CompletableFuture.completedFuture(status == OK);
+                });
     }
 
     @Override
@@ -141,9 +153,9 @@
     public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, byte[] currentValue) {
         return client.submit(new Update(checkNotNull(path), newValue, Match.ifValue(currentValue), Match.any()))
                 .thenCompose(result -> {
-                    if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
+                    if (result.status() == INVALID_PATH) {
                         return Tools.exceptionalFuture(new NoSuchDocumentPathException());
-                    } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
+                    } else if (result.status() == ILLEGAL_MODIFICATION) {
                         return Tools.exceptionalFuture(new IllegalDocumentModificationException());
                     } else {
                         return CompletableFuture.completedFuture(result);
@@ -158,9 +170,9 @@
         }
         return client.submit(new Update(checkNotNull(path), null, Match.ifNotNull(), Match.any()))
                 .thenCompose(result -> {
-                    if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) {
+                    if (result.status() == INVALID_PATH) {
                         return Tools.exceptionalFuture(new NoSuchDocumentPathException());
-                    } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) {
+                    } else if (result.status() == ILLEGAL_MODIFICATION) {
                         return Tools.exceptionalFuture(new IllegalDocumentModificationException());
                     } else {
                         return CompletableFuture.completedFuture(result);
@@ -191,6 +203,11 @@
         return CompletableFuture.completedFuture(null);
     }
 
+    private CompletableFuture<DocumentTreeUpdateResult.Status> createInternal(DocumentPath path, byte[] value) {
+        return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any()))
+                     .thenApply(result -> result.status());
+    }
+
     private boolean isListening() {
         return !eventListeners.isEmpty();
     }
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTree.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTree.java
index 6589a98..a351ad5 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTree.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTree.java
@@ -106,6 +106,25 @@
     }
 
     @Override
+    public boolean createRecursive(DocumentPath path, V value) {
+        checkRootModification(path);
+        DocumentTreeNode<V> node = getNode(path);
+        if (node != null) {
+            return false;
+        }
+        DocumentPath parentPath = path.parent();
+        if (getNode(parentPath) == null) {
+            createRecursive(parentPath, null);
+        }
+        DefaultDocumentTreeNode<V> parentNode =  getNode(parentPath);
+        if (parentNode == null) {
+            throw new IllegalDocumentModificationException();
+        }
+        parentNode.addChild(simpleName(path), value, versionSupplier.get());
+        return true;
+    }
+
+    @Override
     public boolean replace(DocumentPath path, V newValue, long version) {
         checkRootModification(path);
         DocumentTreeNode<V> node = getNode(path);
diff --git a/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTreeTest.java b/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTreeTest.java
index 653b61c..dbaabd8 100644
--- a/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTreeTest.java
+++ b/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/AtomixDocumentTreeTest.java
@@ -93,6 +93,24 @@
     }
 
     /**
+     * Tests recursive create.
+     */
+    @Test
+    public void testRecursiveCreate() throws Throwable {
+        AtomixDocumentTree tree = createAtomixClient().getResource(UUID.randomUUID().toString(),
+                AtomixDocumentTree.class).join();
+        tree.createRecursive(DocumentPath.from("root.a.b.c"), "abc".getBytes()).join();
+        Versioned<byte[]> a = tree.get(DocumentPath.from("root.a")).join();
+        assertArrayEquals(new byte[0], a.value());
+
+        Versioned<byte[]> ab = tree.get(DocumentPath.from("root.a.b")).join();
+        assertArrayEquals(new byte[0], ab.value());
+
+        Versioned<byte[]> abc = tree.get(DocumentPath.from("root.a.b.c")).join();
+        assertArrayEquals("abc".getBytes(), abc.value());
+    }
+
+    /**
      * Tests set.
      */
     @Test
diff --git a/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTreeTest.java b/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTreeTest.java
index f111197..60a95d9 100644
--- a/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTreeTest.java
+++ b/core/store/primitives/src/test/java/org/onosproject/store/primitives/resources/impl/DefaultDocumentTreeTest.java
@@ -49,6 +49,21 @@
         Assert.assertTrue(tree.create(path("root.a.b"), "baz"));
     }
 
+    @Test
+    public void testCreateRecursive() {
+        DocumentTree<String> tree = new DefaultDocumentTree<>();
+        tree.createRecursive(path("root.a.b.c"), "bar");
+        Assert.assertEquals(tree.get(path("root.a.b.c")).value(), "bar");
+        Assert.assertNull(tree.get(path("root.a.b")).value());
+        Assert.assertNull(tree.get(path("root.a")).value());
+    }
+
+    @Test(expected = IllegalDocumentModificationException.class)
+    public void testCreateRecursiveRoot() {
+        DocumentTree<String> tree = new DefaultDocumentTree<>();
+        tree.createRecursive(path("root"), "bar");;
+    }
+
     @Test(expected = IllegalDocumentModificationException.class)
     public void testCreateNodeFailure() {
         DocumentTree<String> tree = new DefaultDocumentTree<>();