Support for a recurive create in AsyncDocumentTree + Javadoc clean up
Change-Id: I2a4a961e24ff34aa106c93d3a8cb9093f10cee72
Showing
7 changed files
with
143 additions
and
45 deletions
| ... | @@ -37,81 +37,94 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive { | ... | @@ -37,81 +37,94 @@ public interface AsyncDocumentTree<V> extends DistributedPrimitive { |
| 37 | DocumentPath root(); | 37 | DocumentPath root(); |
| 38 | 38 | ||
| 39 | /** | 39 | /** |
| 40 | - * Returns the child values for this node. | 40 | + * Returns the children of node at specified path in the form of a mapping from child name to child value. |
| 41 | * | 41 | * |
| 42 | * @param path path to the node | 42 | * @param path path to the node |
| 43 | - * @return future for mapping from a child name to its value | 43 | + * @return future for mapping from child name to child value |
| 44 | - * @throws NoSuchDocumentPathException if the path does not point to a valid node | 44 | + * @throws {@code NoSuchDocumentPathException} if the path does not point to a valid node |
| 45 | */ | 45 | */ |
| 46 | CompletableFuture<Map<String, Versioned<V>>> getChildren(DocumentPath path); | 46 | CompletableFuture<Map<String, Versioned<V>>> getChildren(DocumentPath path); |
| 47 | 47 | ||
| 48 | /** | 48 | /** |
| 49 | - * Returns a value (and version) of the tree node at specified path. | 49 | + * Returns the value of the tree node at specified path. |
| 50 | * | 50 | * |
| 51 | - * @param path path to node | 51 | + * @param path path to the node |
| 52 | - * @return future for node value or {@code null} if path does not point to a valid node | 52 | + * @return future that will be either be completed with node value or {@code null} if path |
| 53 | + * does not point to a valid node | ||
| 53 | */ | 54 | */ |
| 54 | CompletableFuture<Versioned<V>> get(DocumentPath path); | 55 | CompletableFuture<Versioned<V>> get(DocumentPath path); |
| 55 | 56 | ||
| 56 | /** | 57 | /** |
| 57 | * Creates or updates a document tree node. | 58 | * Creates or updates a document tree node. |
| 58 | * | 59 | * |
| 59 | - * @param path path for the node to create or update | 60 | + * @param path path to the node |
| 60 | - * @param value the non-null value to be associated with the key | 61 | + * @param value the non-null value to be associated with the node |
| 61 | - * @return future for the previous mapping or {@code null} if there was no previous mapping. Future will | 62 | + * @return future that will either be completed with the previous node value or {@code null} if there was no |
| 62 | - * be completed with a NoSuchDocumentPathException if the parent node (for the node to create/update) does not exist | 63 | + * node previously at that path. Future will be completed with a {@code IllegalDocumentModificationException} |
| 64 | + * if the parent node (for the node to create/update) does not exist | ||
| 63 | */ | 65 | */ |
| 64 | CompletableFuture<Versioned<V>> set(DocumentPath path, V value); | 66 | CompletableFuture<Versioned<V>> set(DocumentPath path, V value); |
| 65 | 67 | ||
| 66 | /** | 68 | /** |
| 67 | * Creates a document tree node if one does not exist already. | 69 | * Creates a document tree node if one does not exist already. |
| 68 | * | 70 | * |
| 69 | - * @param path path for the node to create | 71 | + * @param path path to the node |
| 70 | - * @param value the non-null value to be associated with the key | 72 | + * @param value the non-null value to be associated with the node |
| 71 | - * @return future that is completed with {@code true} if the mapping could be added | 73 | + * @return future that is completed with {@code true} if the new node was successfully |
| 72 | - * successfully; {@code false} otherwise. Future will be completed with a | 74 | + * created. Future will be completed with {@code false} if a node already exists at the specified path. |
| 73 | - * IllegalDocumentModificationException if the parent node (for the node to create) does not exist | 75 | + * Future will be completed exceptionally with a {@code IllegalDocumentModificationException} if the parent |
| 76 | + * node (for the node to create) does not exist | ||
| 74 | */ | 77 | */ |
| 75 | CompletableFuture<Boolean> create(DocumentPath path, V value); | 78 | CompletableFuture<Boolean> create(DocumentPath path, V value); |
| 76 | 79 | ||
| 77 | /** | 80 | /** |
| 81 | + * Creates a document tree node recursively by creating all missing intermediate nodes in the path. | ||
| 82 | + * | ||
| 83 | + * @param path path to the node | ||
| 84 | + * @param value the non-null value to be associated with the key | ||
| 85 | + * @return future that is completed with {@code true} if the new node was successfully | ||
| 86 | + * created. Future will be completed with {@code false} if a node already exists at the specified path | ||
| 87 | + */ | ||
| 88 | + CompletableFuture<Boolean> createRecursive(DocumentPath path, V value); | ||
| 89 | + | ||
| 90 | + /** | ||
| 78 | * Conditionally updates a tree node if the current version matches a specified version. | 91 | * Conditionally updates a tree node if the current version matches a specified version. |
| 79 | * | 92 | * |
| 80 | - * @param path path for the node to create | 93 | + * @param path path to the node |
| 81 | - * @param newValue the non-null value to be associated with the key | 94 | + * @param newValue the non-null value to be associated with the node |
| 82 | - * @param version current version of the value for update to occur | 95 | + * @param version current version of the node for update to occur |
| 83 | - * @return future that is completed with {@code true} if the update was made and the tree was | 96 | + * @return future that is either completed with {@code true} if the update was made and the tree was |
| 84 | - * modified, {@code false} otherwise. | 97 | + * modified or {@code false} if update did not happen |
| 85 | */ | 98 | */ |
| 86 | CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version); | 99 | CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version); |
| 87 | 100 | ||
| 88 | /** | 101 | /** |
| 89 | - * Conditionally updates a tree node if the current value matches a specified value. | 102 | + * Conditionally updates a tree node if the current node value matches a specified version. |
| 90 | * | 103 | * |
| 91 | - * @param path path for the node to create | 104 | + * @param path path to the node |
| 92 | - * @param newValue the non-null value to be associated with the key | 105 | + * @param newValue the non-null value to be associated with the node |
| 93 | - * @param currentValue current value for update to occur | 106 | + * @param currentValue current value of the node for update to occur |
| 94 | - * @return future that is completed with {@code true} if the update was made and the tree was | 107 | + * @return future that is either completed with {@code true} if the update was made and the tree was |
| 95 | - * modified, {@code false} otherwise. | 108 | + * modified or {@code false} if update did not happen |
| 96 | */ | 109 | */ |
| 97 | CompletableFuture<Boolean> replace(DocumentPath path, V newValue, V currentValue); | 110 | CompletableFuture<Boolean> replace(DocumentPath path, V newValue, V currentValue); |
| 98 | 111 | ||
| 99 | /** | 112 | /** |
| 100 | * Removes the node with the specified path. | 113 | * Removes the node with the specified path. |
| 101 | * | 114 | * |
| 102 | - * @param path path for the node to remove | 115 | + * @param path path to the node |
| 103 | * @return future for the previous value. Future will be completed with a | 116 | * @return future for the previous value. Future will be completed with a |
| 104 | - * IllegalDocumentModificationException if the node to be removed is either the root | 117 | + * {@code IllegalDocumentModificationException} if the node to be removed is either the root |
| 105 | * node or has one or more children. Future will be completed with a | 118 | * node or has one or more children. Future will be completed with a |
| 106 | - * NoSuchDocumentPathException if the node to be removed does not exist | 119 | + * {@code NoSuchDocumentPathException} if the node to be removed does not exist |
| 107 | */ | 120 | */ |
| 108 | CompletableFuture<Versioned<V>> removeNode(DocumentPath path); | 121 | CompletableFuture<Versioned<V>> removeNode(DocumentPath path); |
| 109 | 122 | ||
| 110 | /** | 123 | /** |
| 111 | - * Registers a listener to be notified when a subtree rooted at the specified path | 124 | + * Registers a listener to be notified when the subtree rooted at the specified path |
| 112 | * is modified. | 125 | * is modified. |
| 113 | * | 126 | * |
| 114 | - * @param path path to root of subtree to monitor for updates | 127 | + * @param path path to the node |
| 115 | * @param listener listener to be notified | 128 | * @param listener listener to be notified |
| 116 | * @return a future that is completed when the operation completes | 129 | * @return a future that is completed when the operation completes |
| 117 | */ | 130 | */ | ... | ... |
| ... | @@ -73,6 +73,17 @@ public interface DocumentTree<V> { | ... | @@ -73,6 +73,17 @@ public interface DocumentTree<V> { |
| 73 | boolean create(DocumentPath path, V value); | 73 | boolean create(DocumentPath path, V value); |
| 74 | 74 | ||
| 75 | /** | 75 | /** |
| 76 | + * Creates a document tree node by first creating any missing intermediate nodes in the path. | ||
| 77 | + * | ||
| 78 | + * @param path path for the node to create | ||
| 79 | + * @param value the non-null value to be associated with the key | ||
| 80 | + * @return returns {@code true} if the mapping could be added successfully, {@code false} if | ||
| 81 | + * a node already exists at that path | ||
| 82 | + * @throws IllegalDocumentModificationException if {@code path} points to root | ||
| 83 | + */ | ||
| 84 | + boolean createRecursive(DocumentPath path, V value); | ||
| 85 | + | ||
| 86 | + /** | ||
| 76 | * Conditionally updates a tree node if the current version matches a specified version. | 87 | * Conditionally updates a tree node if the current version matches a specified version. |
| 77 | * | 88 | * |
| 78 | * @param path path for the node to create | 89 | * @param path path for the node to create | ... | ... |
| ... | @@ -88,6 +88,11 @@ public class DefaultDistributedDocumentTree<V> implements AsyncDocumentTree<V> { | ... | @@ -88,6 +88,11 @@ public class DefaultDistributedDocumentTree<V> implements AsyncDocumentTree<V> { |
| 88 | } | 88 | } |
| 89 | 89 | ||
| 90 | @Override | 90 | @Override |
| 91 | + public CompletableFuture<Boolean> createRecursive(DocumentPath path, V value) { | ||
| 92 | + return backingTree.createRecursive(path, serializer.encode(value)); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + @Override | ||
| 91 | public CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version) { | 96 | public CompletableFuture<Boolean> replace(DocumentPath path, V newValue, long version) { |
| 92 | return backingTree.replace(path, serializer.encode(newValue), version); | 97 | return backingTree.replace(path, serializer.encode(newValue), version); |
| 93 | } | 98 | } | ... | ... |
| ... | @@ -17,6 +17,9 @@ | ... | @@ -17,6 +17,9 @@ |
| 17 | package org.onosproject.store.primitives.resources.impl; | 17 | package org.onosproject.store.primitives.resources.impl; |
| 18 | 18 | ||
| 19 | import static com.google.common.base.Preconditions.checkNotNull; | 19 | import static com.google.common.base.Preconditions.checkNotNull; |
| 20 | +import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION; | ||
| 21 | +import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.INVALID_PATH; | ||
| 22 | +import static org.onosproject.store.primitives.resources.impl.DocumentTreeUpdateResult.Status.OK; | ||
| 20 | import io.atomix.copycat.client.CopycatClient; | 23 | import io.atomix.copycat.client.CopycatClient; |
| 21 | import io.atomix.resource.AbstractResource; | 24 | import io.atomix.resource.AbstractResource; |
| 22 | import io.atomix.resource.ResourceTypeInfo; | 25 | import io.atomix.resource.ResourceTypeInfo; |
| ... | @@ -107,9 +110,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> | ... | @@ -107,9 +110,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> |
| 107 | public CompletableFuture<Versioned<byte[]>> set(DocumentPath path, byte[] value) { | 110 | public CompletableFuture<Versioned<byte[]>> set(DocumentPath path, byte[] value) { |
| 108 | return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.any(), Match.any())) | 111 | return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.any(), Match.any())) |
| 109 | .thenCompose(result -> { | 112 | .thenCompose(result -> { |
| 110 | - if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) { | 113 | + if (result.status() == INVALID_PATH) { |
| 111 | return Tools.exceptionalFuture(new NoSuchDocumentPathException()); | 114 | return Tools.exceptionalFuture(new NoSuchDocumentPathException()); |
| 112 | - } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) { | 115 | + } else if (result.status() == ILLEGAL_MODIFICATION) { |
| 113 | return Tools.exceptionalFuture(new IllegalDocumentModificationException()); | 116 | return Tools.exceptionalFuture(new IllegalDocumentModificationException()); |
| 114 | } else { | 117 | } else { |
| 115 | return CompletableFuture.completedFuture(result); | 118 | return CompletableFuture.completedFuture(result); |
| ... | @@ -119,16 +122,25 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> | ... | @@ -119,16 +122,25 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> |
| 119 | 122 | ||
| 120 | @Override | 123 | @Override |
| 121 | public CompletableFuture<Boolean> create(DocumentPath path, byte[] value) { | 124 | public CompletableFuture<Boolean> create(DocumentPath path, byte[] value) { |
| 122 | - return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any())) | 125 | + return createInternal(path, value) |
| 123 | - .thenCompose(result -> { | 126 | + .thenCompose(status -> { |
| 124 | - if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) { | 127 | + if (status == ILLEGAL_MODIFICATION) { |
| 125 | - return Tools.exceptionalFuture(new NoSuchDocumentPathException()); | ||
| 126 | - } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) { | ||
| 127 | return Tools.exceptionalFuture(new IllegalDocumentModificationException()); | 128 | return Tools.exceptionalFuture(new IllegalDocumentModificationException()); |
| 128 | - } else { | ||
| 129 | - return CompletableFuture.completedFuture(result); | ||
| 130 | } | 129 | } |
| 131 | - }).thenApply(result -> result.created()); | 130 | + return CompletableFuture.completedFuture(true); |
| 131 | + }); | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + @Override | ||
| 135 | + public CompletableFuture<Boolean> createRecursive(DocumentPath path, byte[] value) { | ||
| 136 | + return createInternal(path, value) | ||
| 137 | + .thenCompose(status -> { | ||
| 138 | + if (status == ILLEGAL_MODIFICATION) { | ||
| 139 | + return createRecursive(path.parent(), new byte[0]) | ||
| 140 | + .thenCompose(r -> createInternal(path, value).thenApply(v -> true)); | ||
| 141 | + } | ||
| 142 | + return CompletableFuture.completedFuture(status == OK); | ||
| 143 | + }); | ||
| 132 | } | 144 | } |
| 133 | 145 | ||
| 134 | @Override | 146 | @Override |
| ... | @@ -141,9 +153,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> | ... | @@ -141,9 +153,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> |
| 141 | public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, byte[] currentValue) { | 153 | public CompletableFuture<Boolean> replace(DocumentPath path, byte[] newValue, byte[] currentValue) { |
| 142 | return client.submit(new Update(checkNotNull(path), newValue, Match.ifValue(currentValue), Match.any())) | 154 | return client.submit(new Update(checkNotNull(path), newValue, Match.ifValue(currentValue), Match.any())) |
| 143 | .thenCompose(result -> { | 155 | .thenCompose(result -> { |
| 144 | - if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) { | 156 | + if (result.status() == INVALID_PATH) { |
| 145 | return Tools.exceptionalFuture(new NoSuchDocumentPathException()); | 157 | return Tools.exceptionalFuture(new NoSuchDocumentPathException()); |
| 146 | - } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) { | 158 | + } else if (result.status() == ILLEGAL_MODIFICATION) { |
| 147 | return Tools.exceptionalFuture(new IllegalDocumentModificationException()); | 159 | return Tools.exceptionalFuture(new IllegalDocumentModificationException()); |
| 148 | } else { | 160 | } else { |
| 149 | return CompletableFuture.completedFuture(result); | 161 | return CompletableFuture.completedFuture(result); |
| ... | @@ -158,9 +170,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> | ... | @@ -158,9 +170,9 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> |
| 158 | } | 170 | } |
| 159 | return client.submit(new Update(checkNotNull(path), null, Match.ifNotNull(), Match.any())) | 171 | return client.submit(new Update(checkNotNull(path), null, Match.ifNotNull(), Match.any())) |
| 160 | .thenCompose(result -> { | 172 | .thenCompose(result -> { |
| 161 | - if (result.status() == DocumentTreeUpdateResult.Status.INVALID_PATH) { | 173 | + if (result.status() == INVALID_PATH) { |
| 162 | return Tools.exceptionalFuture(new NoSuchDocumentPathException()); | 174 | return Tools.exceptionalFuture(new NoSuchDocumentPathException()); |
| 163 | - } else if (result.status() == DocumentTreeUpdateResult.Status.ILLEGAL_MODIFICATION) { | 175 | + } else if (result.status() == ILLEGAL_MODIFICATION) { |
| 164 | return Tools.exceptionalFuture(new IllegalDocumentModificationException()); | 176 | return Tools.exceptionalFuture(new IllegalDocumentModificationException()); |
| 165 | } else { | 177 | } else { |
| 166 | return CompletableFuture.completedFuture(result); | 178 | return CompletableFuture.completedFuture(result); |
| ... | @@ -191,6 +203,11 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> | ... | @@ -191,6 +203,11 @@ public class AtomixDocumentTree extends AbstractResource<AtomixDocumentTree> |
| 191 | return CompletableFuture.completedFuture(null); | 203 | return CompletableFuture.completedFuture(null); |
| 192 | } | 204 | } |
| 193 | 205 | ||
| 206 | + private CompletableFuture<DocumentTreeUpdateResult.Status> createInternal(DocumentPath path, byte[] value) { | ||
| 207 | + return client.submit(new Update(checkNotNull(path), checkNotNull(value), Match.ifNull(), Match.any())) | ||
| 208 | + .thenApply(result -> result.status()); | ||
| 209 | + } | ||
| 210 | + | ||
| 194 | private boolean isListening() { | 211 | private boolean isListening() { |
| 195 | return !eventListeners.isEmpty(); | 212 | return !eventListeners.isEmpty(); |
| 196 | } | 213 | } | ... | ... |
| ... | @@ -106,6 +106,25 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> { | ... | @@ -106,6 +106,25 @@ public class DefaultDocumentTree<V> implements DocumentTree<V> { |
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | @Override | 108 | @Override |
| 109 | + public boolean createRecursive(DocumentPath path, V value) { | ||
| 110 | + checkRootModification(path); | ||
| 111 | + DocumentTreeNode<V> node = getNode(path); | ||
| 112 | + if (node != null) { | ||
| 113 | + return false; | ||
| 114 | + } | ||
| 115 | + DocumentPath parentPath = path.parent(); | ||
| 116 | + if (getNode(parentPath) == null) { | ||
| 117 | + createRecursive(parentPath, null); | ||
| 118 | + } | ||
| 119 | + DefaultDocumentTreeNode<V> parentNode = getNode(parentPath); | ||
| 120 | + if (parentNode == null) { | ||
| 121 | + throw new IllegalDocumentModificationException(); | ||
| 122 | + } | ||
| 123 | + parentNode.addChild(simpleName(path), value, versionSupplier.get()); | ||
| 124 | + return true; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + @Override | ||
| 109 | public boolean replace(DocumentPath path, V newValue, long version) { | 128 | public boolean replace(DocumentPath path, V newValue, long version) { |
| 110 | checkRootModification(path); | 129 | checkRootModification(path); |
| 111 | DocumentTreeNode<V> node = getNode(path); | 130 | DocumentTreeNode<V> node = getNode(path); | ... | ... |
| ... | @@ -93,6 +93,24 @@ public class AtomixDocumentTreeTest extends AtomixTestBase { | ... | @@ -93,6 +93,24 @@ public class AtomixDocumentTreeTest extends AtomixTestBase { |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | /** | 95 | /** |
| 96 | + * Tests recursive create. | ||
| 97 | + */ | ||
| 98 | + @Test | ||
| 99 | + public void testRecursiveCreate() throws Throwable { | ||
| 100 | + AtomixDocumentTree tree = createAtomixClient().getResource(UUID.randomUUID().toString(), | ||
| 101 | + AtomixDocumentTree.class).join(); | ||
| 102 | + tree.createRecursive(DocumentPath.from("root.a.b.c"), "abc".getBytes()).join(); | ||
| 103 | + Versioned<byte[]> a = tree.get(DocumentPath.from("root.a")).join(); | ||
| 104 | + assertArrayEquals(new byte[0], a.value()); | ||
| 105 | + | ||
| 106 | + Versioned<byte[]> ab = tree.get(DocumentPath.from("root.a.b")).join(); | ||
| 107 | + assertArrayEquals(new byte[0], ab.value()); | ||
| 108 | + | ||
| 109 | + Versioned<byte[]> abc = tree.get(DocumentPath.from("root.a.b.c")).join(); | ||
| 110 | + assertArrayEquals("abc".getBytes(), abc.value()); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + /** | ||
| 96 | * Tests set. | 114 | * Tests set. |
| 97 | */ | 115 | */ |
| 98 | @Test | 116 | @Test | ... | ... |
| ... | @@ -49,6 +49,21 @@ public class DefaultDocumentTreeTest { | ... | @@ -49,6 +49,21 @@ public class DefaultDocumentTreeTest { |
| 49 | Assert.assertTrue(tree.create(path("root.a.b"), "baz")); | 49 | Assert.assertTrue(tree.create(path("root.a.b"), "baz")); |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | + @Test | ||
| 53 | + public void testCreateRecursive() { | ||
| 54 | + DocumentTree<String> tree = new DefaultDocumentTree<>(); | ||
| 55 | + tree.createRecursive(path("root.a.b.c"), "bar"); | ||
| 56 | + Assert.assertEquals(tree.get(path("root.a.b.c")).value(), "bar"); | ||
| 57 | + Assert.assertNull(tree.get(path("root.a.b")).value()); | ||
| 58 | + Assert.assertNull(tree.get(path("root.a")).value()); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @Test(expected = IllegalDocumentModificationException.class) | ||
| 62 | + public void testCreateRecursiveRoot() { | ||
| 63 | + DocumentTree<String> tree = new DefaultDocumentTree<>(); | ||
| 64 | + tree.createRecursive(path("root"), "bar");; | ||
| 65 | + } | ||
| 66 | + | ||
| 52 | @Test(expected = IllegalDocumentModificationException.class) | 67 | @Test(expected = IllegalDocumentModificationException.class) |
| 53 | public void testCreateNodeFailure() { | 68 | public void testCreateNodeFailure() { |
| 54 | DocumentTree<String> tree = new DefaultDocumentTree<>(); | 69 | DocumentTree<String> tree = new DefaultDocumentTree<>(); | ... | ... |
-
Please register or login to post a comment