Support for a recurive create in AsyncDocumentTree + Javadoc clean up

Change-Id: I2a4a961e24ff34aa106c93d3a8cb9093f10cee72
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<>();