Now the TreeNode elements are instantiated lazily per applyWrite/applyMerge/applySubtreeChange operations.
Implemented createTreeNode in TreeNodeFactory to create non recursively single node instance in TreeNode
Renamed create to createTreeNodeRecursively.
Added benchmarks project directly into the root of yangtools project.
Benchmarks project contains benchmark for testing performance of InMemoryDataTree implementation.
Tool used for benchmarks is JHM.
Change-Id: I70c9745fc9ed8751c643d9bd3e9d58be10aa4a0e
Signed-off-by: Lukas Sedlak <lsedlak@cisco.com>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>yangtools-aggregator</artifactId>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <version>0.6.2-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>benchmarks</artifactId>
+
+ <properties>
+ <yangtools.version>0.6.2-SNAPSHOT</yangtools.version>
+ <yang.maven.plugin.version>0.6.2-SNAPSHOT</yang.maven.plugin.version>
+ <java.source.version>1.7</java.source.version>
+ <java.target.version>1.7</java.target.version>
+ <jmh.version>0.9.7</jmh.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>yang-data-impl</artifactId>
+ <version>${yangtools.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>yang-parser-impl</artifactId>
+ <version>${yangtools.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ <version>${jmh.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <version>${jmh.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${java.source.version}</source>
+ <target>${java.source.version}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
--- /dev/null
+package org.opendaylight.yangtools.yang.data.impl.tree;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Set;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+
+/**
+ * @author Lukas Sedlak <lsedlak@cisco.com>
+ */
+public class BenchmarkModel {
+
+ public static final QName TEST_QNAME = QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test", "2014-03-13",
+ "test");
+ public static final QName OUTER_LIST_QNAME = QName.create(TEST_QNAME, "outer-list");
+ public static final QName INNER_LIST_QNAME = QName.create(TEST_QNAME, "inner-list");
+ public static final QName OUTER_CHOICE_QNAME = QName.create(TEST_QNAME, "outer-choice");
+ public static final QName ID_QNAME = QName.create(TEST_QNAME, "id");
+ public static final QName NAME_QNAME = QName.create(TEST_QNAME, "name");
+ public static final QName VALUE_QNAME = QName.create(TEST_QNAME, "value");
+ private static final String DATASTORE_TEST_YANG = "/odl-datastore-test.yang";
+
+ public static final YangInstanceIdentifier TEST_PATH = YangInstanceIdentifier.of(TEST_QNAME);
+ public static final YangInstanceIdentifier OUTER_LIST_PATH = YangInstanceIdentifier.builder(TEST_PATH).node(OUTER_LIST_QNAME).build();
+
+ public static final InputStream getDatastoreBenchmarkInputStream() {
+ return getInputStream(DATASTORE_TEST_YANG);
+ }
+
+ private static InputStream getInputStream(final String resourceName) {
+ return BenchmarkModel.class.getResourceAsStream(resourceName);
+ }
+
+ public static SchemaContext createTestContext() {
+ YangParserImpl parser = new YangParserImpl();
+ Set<Module> modules = parser.parseYangModelsFromStreams(Collections.singletonList(
+ getDatastoreBenchmarkInputStream()));
+ return parser.resolveSchemaContext(modules);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.tree;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.*;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+/**
+ * Benchmarking of InMemoryDataTree performance.
+ *
+ * JMH is used for microbenchmarking.
+ *
+ * @author Lukas Sedlak <lsedlak@cisco.com>
+ *
+ * @see <a href="http://openjdk.java.net/projects/code-tools/jmh/">JMH</a>
+ */
+@State(Scope.Thread)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+public class InMemoryDataTreeBenchmark {
+
+ private static final int OUTER_LIST_100K = 100000;
+ private static final int OUTER_LIST_50K = 50000;
+ private static final int OUTER_LIST_10K = 10000;
+
+ private static final YangInstanceIdentifier[] OUTER_LIST_100K_PATHS = initOuterListPaths(OUTER_LIST_100K);
+ private static final YangInstanceIdentifier[] OUTER_LIST_50K_PATHS = initOuterListPaths(OUTER_LIST_50K);
+ private static final YangInstanceIdentifier[] OUTER_LIST_10K_PATHS = initOuterListPaths(OUTER_LIST_10K);
+
+ private static YangInstanceIdentifier[] initOuterListPaths(final int outerListPathsCount) {
+ final YangInstanceIdentifier[] paths = new YangInstanceIdentifier[outerListPathsCount];
+
+ for (int outerListKey = 0; outerListKey < outerListPathsCount; ++outerListKey) {
+ paths[outerListKey] = YangInstanceIdentifier.builder(BenchmarkModel.OUTER_LIST_PATH)
+ .nodeWithKey(BenchmarkModel.OUTER_LIST_QNAME, BenchmarkModel.ID_QNAME, outerListKey)
+ .build();
+ }
+ return paths;
+ }
+
+ private static final MapNode ONE_ITEM_INNER_LIST = initInnerListItems(1);
+ private static final MapNode TWO_ITEM_INNER_LIST = initInnerListItems(2);
+ private static final MapNode TEN_ITEM_INNER_LIST = initInnerListItems(10);
+
+ private static MapNode initInnerListItems(final int count) {
+ final CollectionNodeBuilder<MapEntryNode, MapNode> mapEntryBuilder = ImmutableNodes
+ .mapNodeBuilder(BenchmarkModel.INNER_LIST_QNAME);
+
+ for (int i = 1; i <= count; ++i) {
+ mapEntryBuilder
+ .withChild(ImmutableNodes.mapEntry(BenchmarkModel.INNER_LIST_QNAME, BenchmarkModel.NAME_QNAME, i));
+ }
+
+ return mapEntryBuilder.build();
+ }
+
+ private static final NormalizedNode<?, ?>[] OUTER_LIST_ONE_ITEM_INNER_LIST = initOuterListItems(OUTER_LIST_100K, ONE_ITEM_INNER_LIST);
+ private static final NormalizedNode<?, ?>[] OUTER_LIST_TWO_ITEM_INNER_LIST = initOuterListItems(OUTER_LIST_50K, TWO_ITEM_INNER_LIST);
+ private static final NormalizedNode<?, ?>[] OUTER_LIST_TEN_ITEM_INNER_LIST = initOuterListItems(OUTER_LIST_10K, TEN_ITEM_INNER_LIST);
+
+ private static NormalizedNode<?,?>[] initOuterListItems(int outerListItemsCount, MapNode innerList) {
+ final NormalizedNode<?,?>[] outerListItems = new NormalizedNode[outerListItemsCount];
+
+ for (int i = 0; i < outerListItemsCount; ++i) {
+ int outerListKey = i;
+ outerListItems[i] = ImmutableNodes.mapEntryBuilder(BenchmarkModel.OUTER_LIST_QNAME, BenchmarkModel.ID_QNAME, outerListKey)
+ .withChild(innerList).build();
+ }
+ return outerListItems;
+ }
+
+ private SchemaContext schemaContext;
+ private DataTree datastore;
+
+ public static void main(String... args) throws IOException, RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(".*" + InMemoryDataTreeBenchmark.class.getSimpleName() + ".*")
+ .forks(1)
+ .build();
+
+ new Runner(opt).run();
+ }
+
+ @Setup(Level.Trial)
+ public void setup() throws DataValidationFailedException {
+ schemaContext = BenchmarkModel.createTestContext();
+ final InMemoryDataTreeFactory factory = InMemoryDataTreeFactory.getInstance();
+ datastore = factory.create();
+ datastore.setSchemaContext(schemaContext);
+ final DataTreeSnapshot snapshot = datastore.takeSnapshot();
+ initTestNode(snapshot);
+ }
+
+ @TearDown
+ public void tearDown() {
+ schemaContext = null;
+ datastore = null;
+ }
+
+ private void initTestNode(final DataTreeSnapshot snapshot) throws DataValidationFailedException {
+ final DataTreeModification modification = snapshot.newModification();
+ final YangInstanceIdentifier testPath = YangInstanceIdentifier.builder(BenchmarkModel.TEST_PATH)
+ .build();
+
+ modification.write(testPath, provideOuterListNode());
+ datastore.validate(modification);
+ final DataTreeCandidate candidate = datastore.prepare(modification);
+ datastore.commit(candidate);
+ }
+
+ private DataContainerChild<?, ?> provideOuterListNode() {
+ return ImmutableContainerNodeBuilder
+ .create()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(BenchmarkModel.TEST_QNAME))
+ .withChild(
+ ImmutableNodes.mapNodeBuilder(BenchmarkModel.OUTER_LIST_QNAME)
+ .build()).build();
+ }
+
+ @Benchmark
+ @Warmup(iterations = 10, timeUnit = TimeUnit.MILLISECONDS)
+ @Measurement(iterations = 20, timeUnit = TimeUnit.MILLISECONDS)
+ public void singleNodes100KWriteBenchmark() throws Exception {
+ applyWriteSingleNode(OUTER_LIST_100K);
+ }
+
+ private void applyWriteSingleNode(final int reps) throws DataValidationFailedException {
+ final DataTreeSnapshot snapshot = datastore.takeSnapshot();
+ final DataTreeModification modification = snapshot.newModification();
+ for (int outerListKey = 0; outerListKey < reps; ++outerListKey) {
+ modification.write(OUTER_LIST_100K_PATHS[outerListKey], OUTER_LIST_ONE_ITEM_INNER_LIST[outerListKey]);
+ }
+ datastore.validate(modification);
+ final DataTreeCandidate candidate = datastore.prepare(modification);
+ datastore.commit(candidate);
+ }
+
+ @Benchmark
+ @Warmup(iterations = 10, timeUnit = TimeUnit.MILLISECONDS)
+ @Measurement(iterations = 20, timeUnit = TimeUnit.MILLISECONDS)
+ public void twoNodes50KWriteBenchmark() throws Exception {
+ applyWriteTwoNodes(OUTER_LIST_50K);
+ }
+
+ private void applyWriteTwoNodes(final int reps) throws DataValidationFailedException {
+ final DataTreeSnapshot snapshot = datastore.takeSnapshot();
+ final DataTreeModification modification = snapshot.newModification();
+ for (int outerListKey = 0; outerListKey < reps; ++outerListKey) {
+ modification.write(OUTER_LIST_50K_PATHS[outerListKey], OUTER_LIST_TWO_ITEM_INNER_LIST[outerListKey]);
+ }
+ datastore.validate(modification);
+ final DataTreeCandidate candidate = datastore.prepare(modification);
+ datastore.commit(candidate);
+ }
+
+ @Benchmark
+ @Warmup(iterations = 10, timeUnit = TimeUnit.MILLISECONDS)
+ @Measurement(iterations = 20, timeUnit = TimeUnit.MILLISECONDS)
+ public void tenNodes10KWriteBenchmark() throws Exception {
+ applyWriteTenNodes(OUTER_LIST_10K);
+ }
+
+ private void applyWriteTenNodes(final int reps) throws DataValidationFailedException {
+ final DataTreeSnapshot snapshot = datastore.takeSnapshot();
+ final DataTreeModification modification = snapshot.newModification();
+ for (int outerListKey = 0; outerListKey < reps; ++outerListKey) {
+ modification.write(OUTER_LIST_10K_PATHS[outerListKey], OUTER_LIST_TEN_ITEM_INNER_LIST[outerListKey]);
+ }
+ datastore.validate(modification);
+ final DataTreeCandidate candidate = datastore.prepare(modification);
+ datastore.commit(candidate);
+ }
+}
--- /dev/null
+module odl-datastore-test {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test";
+ prefix "store-test";
+
+ revision "2014-03-13" {
+ description "Initial revision.";
+ }
+
+ container test {
+ list outer-list {
+ key id;
+ leaf id {
+ type int32;
+ }
+ choice outer-choice {
+ case one {
+ leaf one {
+ type string;
+ }
+ }
+ case two-three {
+ leaf two {
+ type string;
+ }
+ leaf three {
+ type string;
+ }
+ }
+ }
+ list inner-list {
+ key name;
+ leaf name {
+ type int32;
+ }
+ leaf value {
+ type string;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
<module>restconf</module>
<module>websocket</module>
<module>yang</module>
- <!-- module>third-party</module -->
+ <module>benchmarks</module>
+ <!-- module>third-party</module -->
</modules>
<build>
@Override
public Optional<TreeNode> getChild(final PathArgument key) {
- return Optional.fromNullable(children.get(key));
+ Optional<TreeNode> explicitNode = Optional.fromNullable(children.get(key));
+ if (explicitNode.isPresent()) {
+ return explicitNode;
+ }
+ final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> castedData = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>) getData();
+ Optional<NormalizedNode<?, ?>> value = castedData.getChild(key);
+ if (value.isPresent()) {
+ //FIXME: consider caching created Tree Nodes.
+ //We are safe to not to cache them, since written Tree Nodes are in read only snapshot.
+ return Optional.of(TreeNodeFactory.createTreeNode(value.get(), getVersion()));
+ }
+ return Optional.absent();
}
@Override
this.children = MapAdaptor.getDefaultInstance().takeSnapshot(parent.children);
this.subtreeVersion = parent.getSubtreeVersion();
this.version = parent.getVersion();
+ materializeChildVersion();
+ }
+
+ /**
+ * Traverse whole data tree and instantiate children for each data node. Set version of each MutableTreeNode
+ * accordingly to version in data node.
+ *
+ * Use this method if TreeNode is lazy initialized.
+ */
+ private void materializeChildVersion() {
+ Preconditions.checkState(data instanceof NormalizedNodeContainer);
+ NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> castedData = (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) data;
+
+ for(NormalizedNode<?, ?> childData : castedData.getValue()) {
+ PathArgument id = childData.getIdentifier();
+
+ if (!children.containsKey(id)) {
+ children.put(id, TreeNodeFactory.createTreeNode(childData, version));
+ }
+ }
}
@Override
}
}
- private static ContainerNode create(final Version version, final NormalizedNode<?, ?> data,
- final Iterable<NormalizedNode<?, ?>> children) {
+ /**
+ * Method creates and returns Container root Node and whole subtree for each child node specified in children nodes.
+ * <br>
+ * Reason why is method used recursively is that for each child in children nodes there is call to
+ * {@link TreeNodeFactory#createTreeNodeRecursively}. Each call to <code>createTreeNodeRecursively</code>
+ * calls either {@link #createNormalizedNodeRecursively} or {@link #createOrderedNodeRecursively}
+ * which depends on type of child node.
+ * <br> The root node that is returned holds reference to data node and whole subtree of children also containing references
+ * to data nodes.
+ *
+ * @param version version of indexed data
+ * @param data reference to data node
+ * @param children direct children of root node that is being created
+ * @return Root node with reference to data node and whole subtree of child nodes
+ */
+ private static ContainerNode createNodeRecursively(final Version version, final NormalizedNode<?, ?> data,
+ final Iterable<NormalizedNode<?, ?>> children) {
final Map<PathArgument, TreeNode> map = new HashMap<>();
for (NormalizedNode<?, ?> child : children) {
- map.put(child.getIdentifier(), TreeNodeFactory.createTreeNode(child, version));
+ map.put(child.getIdentifier(), TreeNodeFactory.createTreeNodeRecursively(child, version));
}
return new ContainerNode(data, version, map, version);
}
- public static ContainerNode create(final Version version, final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container) {
- return create(version, container, container.getValue());
+ /**
+ * Method creates and returns Normalized Node Container as root and recursively creates whole subtree
+ * from all of the container child iterables stored in {@link org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer#getValue()}
+ * <br>
+ * The reason why is this method called recursively is that in background method calls {@link TreeNodeFactory#createTreeNodeRecursively}
+ * for each child stored in NormalizedNode and after each child is created the method calls again {@link #createNormalizedNodeRecursively} method
+ * until all of the children are resolved.
+ *
+ * @param version version of indexed data
+ * @param container Normalized Node Container
+ * @return Normalized Node Container as root and all whole subtree created from container iterables.
+ */
+ public static ContainerNode createNormalizedNodeRecursively(final Version version,
+ final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container) {
+ return createNodeRecursively(version, container, container.getValue());
+ }
+
+ /**
+ * Method creates and returns Ordered Node Container as root and recursively creates whole subtree
+ * from all of the container child iterables stored in {@link org.opendaylight.yangtools.yang.data.api.schema.OrderedNodeContainer#getValue()}
+ * <br>
+ * The reason why is this method called recursively is that in background method calls {@link TreeNodeFactory#createTreeNodeRecursively}
+ * for each child stored in NormalizedNode and after each child is created the method calls again {@link #createNormalizedNodeRecursively} method
+ * until all of the children are resolved.
+ *
+ * @param version version of indexed data
+ * @param container Ordered Node Container
+ * @return Normalized Ordered Container as root and all whole subtree created from container iterables.
+ */
+ public static ContainerNode createOrderedNodeRecursively(final Version version,
+ final OrderedNodeContainer<NormalizedNode<?, ?>> container) {
+ return createNodeRecursively(version, container, container.getValue());
}
- public static ContainerNode create(final Version version, final OrderedNodeContainer<NormalizedNode<?, ?>> container) {
- return create(version, container, container.getValue());
+ /**
+ * Creates and returns single instance of Normalized Node Container with provided version and data reference stored in NormalizedNodeContainer.
+ *
+ * @param version version of indexed data
+ * @param container Normalized Node Container
+ * @return single instance of Normalized node with provided version and data reference stored in NormalizedNodeContainer
+ */
+ public static ContainerNode createNormalizedNode(final Version version,
+ final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container) {
+ return createNode(version, container);
+ }
+
+ /**
+ * Creates and returns single instance of Ordered Node Container with provided version and data reference stored in OrderedNodeContainer.
+ *
+ * @param version version of indexed data
+ * @param container Ordered Node Container
+ * @return single instance of Ordered Node Container with provided version and data reference stored in OrderedNodeContainer.
+ */
+ public static ContainerNode createOrderedNode(final Version version,
+ final OrderedNodeContainer<NormalizedNode<?, ?>> container) {
+ return createNode(version, container);
+ }
+
+ /**
+ * Creates and returns single instance of {@link ContainerNode} with provided version and data reference stored in NormalizedNode.
+ *
+ * @param version version of indexed data
+ * @param data NormalizedNode data container
+ * @return single instance of {@link ContainerNode} with provided version and data reference stored in NormalizedNode.
+ */
+ private static ContainerNode createNode(final Version version, final NormalizedNode<?, ?> data) {
+ final Map<PathArgument, TreeNode> map = new HashMap<>();
+ return new ContainerNode(data, version, map, version);
}
}
* @param version data node version
* @return new AbstractTreeNode instance, covering the data tree provided
*/
- public static final TreeNode createTreeNode(final NormalizedNode<?, ?> data, final Version version) {
+ public static final TreeNode createTreeNodeRecursively(final NormalizedNode<?, ?> data, final Version version) {
if (data instanceof NormalizedNodeContainer<?, ?, ?>) {
@SuppressWarnings("unchecked")
NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container = (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) data;
- return ContainerNode.create(version, container);
+ return ContainerNode.createNormalizedNodeRecursively(version, container);
}
if (data instanceof OrderedNodeContainer<?>) {
@SuppressWarnings("unchecked")
OrderedNodeContainer<NormalizedNode<?, ?>> container = (OrderedNodeContainer<NormalizedNode<?, ?>>) data;
- return ContainerNode.create(version, container);
+ return ContainerNode.createOrderedNodeRecursively(version, container);
}
return new ValueNode(data, version);
}
+
+ /**
+ * Create a new AbstractTreeNode from a data node.
+ *
+ * @param data data node
+ * @param version data node version
+ * @return new AbstractTreeNode instance, covering the data tree provided
+ */
+ public static final TreeNode createTreeNode(final NormalizedNode<?, ?> data, final Version version) {
+ if (data instanceof NormalizedNodeContainer<?, ?, ?>) {
+ @SuppressWarnings("unchecked")
+ NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container = (NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>>) data;
+ return ContainerNode.createNormalizedNode(version, container);
+ }
+ if (data instanceof OrderedNodeContainer<?>) {
+ @SuppressWarnings("unchecked")
+ OrderedNodeContainer<NormalizedNode<?, ?>> container = (OrderedNodeContainer<NormalizedNode<?, ?>>) data;
+ return ContainerNode.createOrderedNode(version, container);
+ }
+ return new ValueNode(data, version);
+ }
}
final NodeIdentifier root = new NodeIdentifier(SchemaContext.NAME);
final NormalizedNode<?, ?> data = Builders.containerBuilder().withNodeIdentifier(root).build();
- return new InMemoryDataTree(TreeNodeFactory.createTreeNode(data, Version.initial()), null);
+ return new InMemoryDataTree(TreeNodeFactory.createTreeNodeRecursively(data, Version.initial()), null);
}
/**
return mutateChildren(mutable, dataBuilder, version, modification.getChildren());
}
+ /**
+ * Applies write/remove diff operation for each modification child in modification subtree.
+ * Operation also sets the Data tree references for each Tree Node (Index Node) in meta (MutableTreeNode) structure.
+ *
+ * @param meta MutableTreeNode (IndexTreeNode)
+ * @param data DataBuilder
+ * @param nodeVersion Version of TreeNode
+ * @param modifications modification operations to apply
+ * @return Sealed immutable copy of TreeNode structure with all Data Node references set.
+ */
@SuppressWarnings({ "rawtypes", "unchecked" })
private TreeNode mutateChildren(final MutableTreeNode meta, final NormalizedNodeContainerBuilder data,
final Version nodeVersion, final Iterable<ModifiedNode> modifications) {
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.ConflictingModificationAppliedException;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
import org.opendaylight.yangtools.yang.data.api.schema.tree.IncorrectDataStructureException;
import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.MutableTreeNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNodeFactory;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListEntryNodeBuilder;
import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
}
}
- protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+ /**
+ * Checks if write operation can be applied to current TreeNode.
+ * The operation checks if original tree node to which the modification is going to be applied exists and if
+ * current node in TreeNode structure exists.
+ *
+ * @param path Path from current node in TreeNode
+ * @param modification modification to apply
+ * @param current current node in TreeNode for modification to apply
+ * @throws DataValidationFailedException
+ */
+ protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification,
+ final Optional<TreeNode> current) throws DataValidationFailedException {
Optional<TreeNode> original = modification.getOriginal();
if (original.isPresent() && current.isPresent()) {
checkNotConflicting(path, original.get(), current.get());
@Override
protected TreeNode applyWrite(final ModifiedNode modification,
final Optional<TreeNode> currentMeta, final Version version) {
+ final NormalizedNode<?, ?> newValue = modification.getWrittenValue();
+ final TreeNode newValueMeta = TreeNodeFactory.createTreeNode(newValue, version);
+
+ if (Iterables.isEmpty(modification.getChildren())) {
+ return newValueMeta;
+ }
+
/*
- * FIXME: BUG-1258: This is inefficient: it needlessly creates index nodes for the entire subtree.
- * We can determine the depth into which metadata need to be created from the modification
- * -- if it does not have children, no need to bother with metadata.
+ * This is where things get interesting. The user has performed a write and
+ * then she applied some more modifications to it. So we need to make sense
+ * of that an apply the operations on top of the written value. We could have
+ * done it during the write, but this operation is potentially expensive, so
+ * we have left it out of the fast path.
+ *
+ * As it turns out, once we materialize the written data, we can share the
+ * code path with the subtree change. So let's create an unsealed TreeNode
+ * and run the common parts on it -- which end with the node being sealed.
*/
- return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+ final MutableTreeNode mutable = newValueMeta.mutable();
+ mutable.setSubtreeVersion(version);
+
+ @SuppressWarnings("rawtypes")
+ final NormalizedNodeContainerBuilder dataBuilder = ImmutableUnkeyedListEntryNodeBuilder
+ .create((UnkeyedListEntryNode) newValue);
+
+ return mutateChildren(mutable, dataBuilder, version, modification.getChildren());
+ }
+
+ /**
+ * Applies write/remove diff operation for each modification child in modification subtree.
+ * Operation also sets the Data tree references for each Tree Node (Index Node) in meta (MutableTreeNode) structure.
+ *
+ * @param meta MutableTreeNode (IndexTreeNode)
+ * @param data DataBuilder
+ * @param nodeVersion Version of TreeNode
+ * @param modifications modification operations to apply
+ * @return Sealed immutable copy of TreeNode structure with all Data Node references set.
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private TreeNode mutateChildren(final MutableTreeNode meta, final NormalizedNodeContainerBuilder data,
+ final Version nodeVersion, final Iterable<ModifiedNode> modifications) {
+
+ for (ModifiedNode mod : modifications) {
+ final PathArgument id = mod.getIdentifier();
+ final Optional<TreeNode> cm = meta.getChild(id);
+
+ Optional<TreeNode> result = resolveChildOperation(id).apply(mod, cm, nodeVersion);
+ if (result.isPresent()) {
+ final TreeNode tn = result.get();
+ meta.addChild(tn);
+ data.addChild(tn.getData());
+ } else {
+ meta.removeChild(id);
+ data.removeChild(id);
+ }
+ }
+
+ meta.setData(data.build());
+ return meta.seal();
}
@Override
@Override
protected TreeNode applyWrite(final ModifiedNode modification,
final Optional<TreeNode> currentMeta, final Version version) {
- return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+ return TreeNodeFactory.createTreeNodeRecursively(modification.getWrittenValue(), version);
}
@Override
@Test
public void basicReadWrites() {
DataTreeModification modificationTree = new InMemoryDataTreeModification(new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper),
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper),
rootOper);
Optional<NormalizedNode<?, ?>> originalBarNode = modificationTree.readNode(OUTER_LIST_2_PATH);
assertTrue(originalBarNode.isPresent());
@Test
public void findNodeTestNodeFound() {
InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
Optional<TreeNode> node = TreeNodeUtils.findNode(rootNode, OUTER_LIST_1_PATH);
assertPresentAndType(node, TreeNode.class);
@Test
public void findNodeTestNodeNotFound() {
InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
final YangInstanceIdentifier outerList1InvalidPath = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH)
.nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3) //
@Test
public void findNodeCheckedTestNodeFound() {
InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
TreeNode foundNode = null;
try {
@Test
public void findNodeCheckedTestNodeNotFound() {
InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
final YangInstanceIdentifier outerList1InvalidPath = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH)
.nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 3) //
@Test
public void findClosestOrFirstMatchTestNodeExists() {
InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
Optional<TreeNode> expectedNode = TreeNodeUtils.findNode(rootNode, TWO_TWO_PATH);
assertPresentAndType(expectedNode, TreeNode.class);
@Test
public void findClosestOrFirstMatchTestNodeDoesNotExist() {
InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
final YangInstanceIdentifier outerListInnerListPath = YangInstanceIdentifier.builder(OUTER_LIST_2_PATH)
.node(TestModel.INNER_LIST_QNAME)
@Test
public void getChildTestChildFound() {
InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
Optional<TreeNode> node = TreeNodeUtils.getChild(Optional.fromNullable(rootNode),
TestModel.TEST_PATH.getLastPathArgument());
@Test
public void getChildTestChildNotFound() {
InMemoryDataTreeSnapshot inMemoryDataTreeSnapshot = new InMemoryDataTreeSnapshot(schemaContext,
- TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), rootOper);
+ TreeNodeFactory.createTreeNodeRecursively(createDocumentOne(), Version.initial()), rootOper);
TreeNode rootNode = inMemoryDataTreeSnapshot.getRootNode();
Optional<TreeNode> node = TreeNodeUtils.getChild(Optional.fromNullable(rootNode),
TestModel.OUTER_LIST_PATH.getLastPathArgument());