Move implementation of DataTree to Yangtools 68/8268/5
authorBasheeruddin Ahmed <syedbahm@cisco.com>
Mon, 23 Jun 2014 23:09:08 +0000 (16:09 -0700)
committerTony Tkacik <ttkacik@cisco.com>
Wed, 25 Jun 2014 07:00:26 +0000 (07:00 +0000)
Analysis showed that implementation of DataTree
in MD-SAL Data Store, is generic and can
be used outside of Data Store as part of
YANG Data component. This patch
moves existing code created by
 - Robert Varga <rovargs@cisco.com>
 - Tony Tkacik <ttkacik@cisco.com>

To new location

yang-data-api - Data structure & data tree API definitions
yang-data-impl - Data structures and data tree logic

Change-Id: I0a93714677b5315534630d942e35e0262fa8d41d
Signed-off-by: Basheeruddin Ahmed <syedbahm@cisco.com>
Signed-off-by: Tony Tkacik <ttkacik@cisco.com>
Signed-off-by: Robert Varga <rovarga@cisco.com>
43 files changed:
common/parent/pom.xml
yang/yang-data-api/pom.xml
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/ConflictingModificationAppliedException.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTree.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidate.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidateNode.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeFactory.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModification.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshot.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataValidationFailedException.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/IncorrectDataStructureException.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/ModificationType.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/StoreTreeNode.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/AbstractTreeNode.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/ContainerNode.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/MutableTreeNode.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/TreeNode.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/TreeNodeFactory.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/ValueNode.java [new file with mode: 0644]
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/Version.java [new file with mode: 0644]
yang/yang-data-impl/pom.xml
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractDataTreeCandidate.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AlwaysFailOperation.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataNodeContainerModificationStrategy.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeCandidate.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeFactory.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshot.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModificationApplyOperation.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NodeModification.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NoopDataTreeCandidate.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NormalizedNodeContainerModificationStrategy.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StoreUtils.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/TreeNodeUtils.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ValueNodeModificationStrategy.java [new file with mode: 0644]
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModificationMetadataTreeTest.java [new file with mode: 0644]
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperationRoot.java [new file with mode: 0644]
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/TestModel.java [new file with mode: 0644]
yang/yang-data-impl/src/test/resources/odl-datastore-test.yang [new file with mode: 0644]

index 5d0c9fdad96e3de7967b8f7c9759e96aef680988..d21b9c5ef6c3d4076e4b3398c33a7f2c1153e42a 100644 (file)
@@ -41,6 +41,9 @@
         <opendaylight.l2.types.version>2013.08.27.4-SNAPSHOT</opendaylight.l2.types.version>
         <yang.ext.version>2013.09.07.4-SNAPSHOT</yang.ext.version>
         <maven.javadoc.version>2.9.1</maven.javadoc.version>
+        <jsr305.version>2.0.1</jsr305.version>
+
+
     </properties>
 
     <dependencyManagement>
index df63489646cbc3e1df8a8bdeea644abfe3f8efc7..a44c527e2073793c018fcc5984cde15b664c8924 100644 (file)
     <description>${project.artifactId}</description>
 
     <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>util</artifactId>
+        </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
             <artifactId>yang-common</artifactId>
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/ConflictingModificationAppliedException.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/ConflictingModificationAppliedException.java
new file mode 100644 (file)
index 0000000..ee1ee8c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+/**
+ * Exception thrown when a proposed change fails validation before being
+ * applied into the Data Tree because the Data Tree has been modified
+ * in way that a conflicting
+ * node is present.
+ */
+public class ConflictingModificationAppliedException extends DataValidationFailedException {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    public ConflictingModificationAppliedException(final InstanceIdentifier path, final String message, final Throwable cause) {
+        super(path, message, cause);
+    }
+
+    public ConflictingModificationAppliedException(final InstanceIdentifier path, final String message) {
+        super(path, message);
+    }
+
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTree.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTree.java
new file mode 100644 (file)
index 0000000..7b0ae04
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Interface representing a data tree which can be modified in an MVCC fashion.
+ */
+public interface DataTree {
+    /**
+     * Take a read-only point-in-time snapshot of the tree.
+     *
+     * @return Data tree snapshot.
+     */
+    DataTreeSnapshot takeSnapshot();
+
+    /**
+     * Make the data tree use a new schema context. The context will be used
+     * only by subsequent operations.
+     *
+     * @param newSchemaContext new SchemaContext
+     * @throws IllegalArgumentException if the new context is incompatible
+     */
+    void setSchemaContext(SchemaContext newSchemaContext);
+
+    /**
+     * Validate whether a particular modification can be applied to the data tree.
+     */
+    void validate(DataTreeModification modification) throws DataValidationFailedException;
+
+    /**
+     * Prepare a modification for commit.
+     *
+     * @param modification
+     * @return candidate data tree
+     */
+    DataTreeCandidate prepare(DataTreeModification modification);
+
+    /**
+     * Commit a data tree candidate.
+     *
+     * @param candidate data tree candidate
+     */
+    void commit(DataTreeCandidate candidate);
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidate.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidate.java
new file mode 100644 (file)
index 0000000..bc9fb33
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+/**
+ * An encapsulation of a validated data tree modification. This candidate
+ * is ready for atomic commit to the datastore. It allows access to before-
+ * and after-state as it will be seen in to subsequent commit. This capture
+ * can be accessed for reference, but cannot be modified and the content
+ * is limited to nodes which were affected by the modification from which
+ * this instance originated.
+ */
+public interface DataTreeCandidate {
+    /**
+     * Get the candidate tree root node.
+     *
+     * @return Candidate tree root node
+     */
+    DataTreeCandidateNode getRootNode();
+
+    /**
+     * Get the candidate tree root path. This is the path of the root node
+     * relative to the root of InstanceIdentifier namespace.
+     *
+     * @return Relative path of the root node
+     */
+    InstanceIdentifier getRootPath();
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidateNode.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeCandidateNode.java
new file mode 100644 (file)
index 0000000..67c7533
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+import com.google.common.base.Optional;
+
+/**
+ * A single node within a {@link DataTreeCandidate}. The nodes are organized
+ * in tree hierarchy, reflecting the modification from which this candidate
+ * was created. The node itself exposes the before- and after-image of the
+ * tree restricted to the modified nodes.
+ */
+public interface DataTreeCandidateNode {
+    /**
+     * Get the node identifier.
+     *
+     * @return The node identifier.
+     */
+    PathArgument getIdentifier();
+
+    /**
+     * Get an unmodifiable iterable of modified child nodes.
+     *
+     * @return Unmodifiable iterable of modified child nodes.
+     */
+    Iterable<DataTreeCandidateNode> getChildNodes();
+
+    /**
+     * Return the type of modification this node is undergoing.
+     *
+     * @return Node modification type.
+     */
+    ModificationType getModificationType();
+
+    /**
+     * Return the before-image of data corresponding to the node.
+     *
+     * @return Node data as they were present in the tree before
+     *         the modification was applied.
+     */
+    Optional<NormalizedNode<?, ?>> getDataAfter();
+
+    /**
+     * Return the after-image of data corresponding to the node.
+     *
+     * @return Node data as they will be present in the tree after
+     *         the modification is applied.
+     */
+    Optional<NormalizedNode<?, ?>> getDataBefore();
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeFactory.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeFactory.java
new file mode 100644 (file)
index 0000000..84f5017
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+/**
+ * Factory interface for creating data trees.
+ */
+public interface DataTreeFactory {
+    /**
+     * Create a new data tree.
+     *
+     * @return A data tree instance.
+     */
+    DataTree create();
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModification.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeModification.java
new file mode 100644 (file)
index 0000000..8b0dc2e
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * Class encapsulation of set of modifications to a base tree. This tree is backed
+ * by a read-only snapshot and tracks modifications on top of that. The modification
+ * has the ability to rebase itself to a new snapshot.
+ */
+public interface DataTreeModification extends DataTreeSnapshot {
+    /**
+     * Delete the node at specified path.
+     *
+     * @param path Node path
+     */
+    void delete(InstanceIdentifier path);
+
+    /**
+     * Merge the specified data with the currently-present data
+     * at specified path.
+     *
+     * @param path Node path
+     * @param data Data to be merged
+     */
+    void merge(InstanceIdentifier path, NormalizedNode<?, ?> data);
+
+    /**
+     * Replace the data at specified path with supplied data.
+     *
+     * @param path Node path
+     * @param data New node data
+     */
+    void write(InstanceIdentifier path, NormalizedNode<?, ?> data);
+
+    /**
+     * Finish creation of a modification, making it ready for application
+     * to the data tree. Any calls to this object's methods will result
+     * in undefined behavior, possibly with an
+     * {@link IllegalStateException} being thrown.
+     */
+    void ready();
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshot.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataTreeSnapshot.java
new file mode 100644 (file)
index 0000000..4eb268a
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+import com.google.common.base.Optional;
+
+/**
+ * Read-only snapshot of a {@link DataTree}. The snapshot is stable and isolated,
+ * e.g. data tree changes occurring after the snapshot has been taken are not
+ * visible through the snapshot.
+ */
+public interface DataTreeSnapshot {
+    /**
+     * Read a particular node from the snapshot.
+     *
+     * @param path Path of the node
+     * @return Optional result encapsulating the presence and value of the node
+     */
+    Optional<NormalizedNode<?, ?>> readNode(InstanceIdentifier path);
+
+    /**
+     * Create a new data tree modification based on this snapshot, using the
+     * specified data application strategy.
+     *
+     * @param strategy data modification strategy
+     * @return A new data tree modification
+     */
+    DataTreeModification newModification();
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataValidationFailedException.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/DataValidationFailedException.java
new file mode 100644 (file)
index 0000000..f444164
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Exception thrown when a proposed change fails validation before being
+ * applied into the datastore. This can have multiple reasons, for example
+ * the datastore has been concurrently modified such that a conflicting
+ * node is present, or the modification is structurally incorrect.
+ */
+public class DataValidationFailedException extends Exception {
+    private static final long serialVersionUID = 1L;
+    private final InstanceIdentifier path;
+
+    /**
+     * Create a new instance.
+     *
+     * @param path Object path which caused this exception
+     * @param message Specific message describing the failure
+     */
+    public DataValidationFailedException(final InstanceIdentifier path, final String message) {
+        this(path, message, null);
+    }
+    /**
+     * Create a new instance, initializing
+     *
+     * @param path Object path which caused this exception
+     * @param message Specific message describing the failure
+     * @param cause Exception which triggered this failure, may be null
+     */
+    public DataValidationFailedException(final InstanceIdentifier path, final String message, final Throwable cause) {
+        super(message, cause);
+        this.path = Preconditions.checkNotNull(path);
+    }
+
+    /**
+     * Returns the offending object path.
+     *
+     * @return Path of the offending object
+     */
+    public InstanceIdentifier getPath() {
+        return path;
+    }
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/IncorrectDataStructureException.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/IncorrectDataStructureException.java
new file mode 100644 (file)
index 0000000..c816a94
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+/**
+ * Exception thrown when a proposed change fails validation before being
+ * applied into the datastore because of incorrect structure of user supplied
+ * data.
+ *
+ */
+public class IncorrectDataStructureException extends DataValidationFailedException {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    public IncorrectDataStructureException(final InstanceIdentifier path, final String message, final Throwable cause) {
+        super(path, message, cause);
+    }
+
+    public IncorrectDataStructureException(final InstanceIdentifier path, final String message) {
+        super(path, message);
+    }
+
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/ModificationType.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/ModificationType.java
new file mode 100644 (file)
index 0000000..047d71e
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+/**
+ * Enumeration of all possible node modification states. These are used in
+ * data tree modification context to quickly assess what sort of modification
+ * the node is undergoing.
+ */
+public enum ModificationType {
+    /**
+     * Node is currently unmodified.
+     */
+    UNMODIFIED,
+
+    /**
+     * A child node, either direct or indirect, has been modified. This means
+     * that the data representation of this node has potentially changed.
+     */
+    SUBTREE_MODIFIED,
+
+    /**
+     * This node has been placed into the tree, potentially completely replacing
+     * pre-existing contents.
+     */
+    WRITE,
+
+    /**
+     * This node has been deleted along with any of its child nodes.
+     */
+    DELETE,
+
+    /**
+     * Node has been written into the tree, but instead of replacing pre-existing
+     * contents, it has been merged. This means that any incoming nodes which
+     * were present in the tree have been replaced, but their child nodes have
+     * been retained.
+     */
+    MERGE,
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/StoreTreeNode.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/StoreTreeNode.java
new file mode 100644 (file)
index 0000000..9d283e2
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+
+import com.google.common.base.Optional;
+
+/**
+ * A tree node which has references to its child leaves. This are typically
+ * internal non-data leaves, such as containers, lists, etc.
+ *
+ * @param <C> Final node type
+ */
+public interface StoreTreeNode<C extends StoreTreeNode<C>> {
+
+    /**
+     * Returns a direct child of the node
+     *
+     * @param child Identifier of child
+     * @return Optional with node if the child is existing, {@link Optional#absent()} otherwise.
+     */
+    Optional<C> getChild(PathArgument child);
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/AbstractTreeNode.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/AbstractTreeNode.java
new file mode 100644 (file)
index 0000000..4304d4d
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree.spi;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A very basic data tree node.
+ */
+abstract class AbstractTreeNode implements TreeNode {
+    private final NormalizedNode<?, ?> data;
+    private final Version version;
+
+    protected AbstractTreeNode(final NormalizedNode<?, ?> data, final Version version) {
+        this.data = Preconditions.checkNotNull(data);
+        this.version = Preconditions.checkNotNull(version);
+    }
+
+    @Override
+    public PathArgument getIdentifier() {
+        return data.getIdentifier();
+    }
+
+    @Override
+    public final Version getVersion() {
+        return version;
+    }
+
+    @Override
+    public final NormalizedNode<?, ?> getData() {
+        return data;
+    }
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/ContainerNode.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/ContainerNode.java
new file mode 100644 (file)
index 0000000..03b416b
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree.spi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.opendaylight.yangtools.util.MapAdaptor;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedNodeContainer;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+/**
+ * A TreeNode capable of holding child nodes. The fact that any of the children
+ * changed is tracked by the subtree version.
+ */
+final class ContainerNode extends AbstractTreeNode {
+    private final Map<PathArgument, TreeNode> children;
+    private final Version subtreeVersion;
+
+    protected ContainerNode(final NormalizedNode<?, ?> data, final Version version,
+            final Map<PathArgument, TreeNode> children, final Version subtreeVersion) {
+        super(data, version);
+        this.children = Preconditions.checkNotNull(children);
+        this.subtreeVersion = Preconditions.checkNotNull(subtreeVersion);
+    }
+
+    @Override
+    public Version getSubtreeVersion() {
+        return subtreeVersion;
+    }
+
+    @Override
+    public Optional<TreeNode> getChild(final PathArgument key) {
+        return Optional.fromNullable(children.get(key));
+    }
+
+    @Override
+    public MutableTreeNode mutable() {
+        return new Mutable(this);
+    }
+
+    private static final class Mutable implements MutableTreeNode {
+        private final Version version;
+        private Map<PathArgument, TreeNode> children;
+        private NormalizedNode<?, ?> data;
+        private Version subtreeVersion;
+
+        private Mutable(final ContainerNode parent) {
+            this.data = parent.getData();
+            this.children = MapAdaptor.getDefaultInstance().takeSnapshot(parent.children);
+            this.subtreeVersion = parent.getSubtreeVersion();
+            this.version = parent.getVersion();
+        }
+
+        @Override
+        public Optional<TreeNode> getChild(final PathArgument child) {
+            return Optional.fromNullable(children.get(child));
+        }
+
+        @Override
+        public void setSubtreeVersion(final Version subtreeVersion) {
+            this.subtreeVersion = Preconditions.checkNotNull(subtreeVersion);
+        }
+
+        @Override
+        public void addChild(final TreeNode child) {
+            children.put(child.getIdentifier(), child);
+        }
+
+        @Override
+        public void removeChild(final PathArgument id) {
+            children.remove(id);
+        }
+
+        @Override
+        public TreeNode seal() {
+            final TreeNode ret = new ContainerNode(data, version, MapAdaptor.getDefaultInstance().optimize(children), subtreeVersion);
+
+            // This forces a NPE if this class is accessed again. Better than corruption.
+            children = null;
+            return ret;
+        }
+
+        @Override
+        public void setData(final NormalizedNode<?, ?> data) {
+            this.data = Preconditions.checkNotNull(data);
+        }
+    }
+
+    private static ContainerNode create(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));
+        }
+
+        return new ContainerNode(data, version, map, version);
+    }
+
+    public static ContainerNode create(final Version version, final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> container) {
+        return create(version, container, container.getValue());
+    }
+
+    public static ContainerNode create(final Version version, final OrderedNodeContainer<NormalizedNode<?, ?>> container) {
+        return create(version, container, container.getValue());
+    }
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/MutableTreeNode.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/MutableTreeNode.java
new file mode 100644 (file)
index 0000000..c9f7f41
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree.spi;
+
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+
+/**
+ * A mutable tree node. This is a transient view materialized from a pre-existing
+ * node. Modifications are isolated. Once this object is {@link #seal()}-ed,
+ * any interactions with it will result in undefined behavior.
+ */
+public interface MutableTreeNode extends StoreTreeNode<TreeNode> {
+    /**
+     * Set the data component of the node.
+     *
+     * @param data New data component, may not be null.
+     */
+    void setData(NormalizedNode<?, ?> data);
+
+    /**
+     * Set the new subtree version. This is typically invoked when the user
+     * has modified some of this node's children.
+     *
+     * @param subtreeVersion New subtree version.
+     */
+    void setSubtreeVersion(Version subtreeVersion);
+
+    /**
+     * Add a new child node. This acts as add-or-replace operation, e.g. it
+     * succeeds even if a conflicting child is already present.
+     *
+     * @param child New child node.
+     */
+    void addChild(TreeNode child);
+
+    /**
+     * Remove a child node. This acts as delete-or-nothing operation, e.g. it
+     * succeeds even if the corresponding child is not present.
+     *
+     * @param id Child identificator.
+     */
+    void removeChild(PathArgument id);
+
+    /**
+     * Finish node modification and return a read-only view of this node. After
+     * this method is invoked, any further calls to this object's method result
+     * in undefined behavior.
+     *
+     * @return Read-only view of this node.
+     */
+    TreeNode seal();
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/TreeNode.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/TreeNode.java
new file mode 100644 (file)
index 0000000..d97f143
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree.spi;
+
+
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+
+/**
+ * A very basic data tree node. It has a version (when it was last modified),
+ * a subtree version (when any of its children were modified) and some read-only
+ * data.
+ */
+public interface TreeNode extends Identifiable<PathArgument>, StoreTreeNode<TreeNode> {
+    /**
+     * Get the data node version.
+     *
+     * @return Current data node version.
+     */
+    Version getVersion();
+
+    /**
+     * Get the subtree version.
+     *
+     * @return Current subtree version.
+     */
+    Version getSubtreeVersion();
+
+    /**
+     * Get a read-only view of the underlying data.
+     *
+     * @return Unmodifiable view of the underlying data.
+     */
+    NormalizedNode<?, ?> getData();
+
+    /**
+     * Get a mutable, isolated copy of the node.
+     *
+     * @return Mutable copy
+     */
+    MutableTreeNode mutable();
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/TreeNodeFactory.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/TreeNodeFactory.java
new file mode 100644 (file)
index 0000000..6568141
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree.spi;
+
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedNodeContainer;
+
+/**
+ * Public entrypoint for other packages. Allows instantiating a tree node
+ * with specified version.
+ */
+public final class TreeNodeFactory {
+    private TreeNodeFactory() {
+        throw new UnsupportedOperationException("Utility class should not be instantiated");
+    }
+
+    /**
+     * Create a new AbstractTreeNode from a data node, descending recursively as needed.
+     * This method should only ever be used for new data.
+     *
+     * @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.create(version, container);
+
+        }
+        if (data instanceof OrderedNodeContainer<?>) {
+            @SuppressWarnings("unchecked")
+            OrderedNodeContainer<NormalizedNode<?, ?>> container = (OrderedNodeContainer<NormalizedNode<?, ?>>) data;
+            return ContainerNode.create(version, container);
+        }
+
+        return new ValueNode(data, version);
+    }
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/ValueNode.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/ValueNode.java
new file mode 100644 (file)
index 0000000..c0a586b
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree.spi;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;import java.lang.Override;import java.lang.String;import java.lang.UnsupportedOperationException;
+
+/**
+ * Concretization of AbstractTreeNode for leaf nodes which only contain data.
+ * Instances of this class report all children as absent, subtree version
+ * equal to this node's version and do not support mutable view.
+ */
+final class ValueNode extends AbstractTreeNode {
+    private static final Logger LOG = LoggerFactory.getLogger(ValueNode.class);
+
+    protected ValueNode(final NormalizedNode<?, ?> data, final Version version) {
+        super(data, version);
+    }
+
+    @Override
+    public Optional<TreeNode> getChild(final PathArgument childId) {
+        LOG.warn("Attempted to access child {} of value-node {}", childId, this);
+        return Optional.absent();
+    }
+
+    @Override
+    public Version getSubtreeVersion() {
+        return getVersion();
+    }
+
+    @Override
+    public MutableTreeNode mutable() {
+        /**
+         * Value nodes can only we read/written/delete, which does a straight
+         * replace. That means they don't haver need to be made mutable.
+         */
+        throw new UnsupportedOperationException(String.format("Attempted to mutate value-node %s", this));
+    }
+}
diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/Version.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/Version.java
new file mode 100644 (file)
index 0000000..90ae3c0
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2014 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.api.schema.tree.spi;
+
+/**
+ * The concept of a version, either node version, or a subtree version. The
+ * only interface contract this class has is that no two versions are the
+ * same.
+ */
+public final class Version {
+    private Version() {
+
+    }
+
+    /**
+     * Create a new version, distinct from any other version.
+     *
+     * @return a new version.
+     */
+    public Version next() {
+        return new Version();
+    }
+
+    /**
+     * Create an initial version.
+     *
+     * @return a new version.
+     */
+    public static final Version initial() {
+        return new Version();
+    }
+}
index 8543c2be35a44e908fa657062ecd210727efd696..f265fabe90cdc3086f79ffa0ce1ce273fb1561c5 100644 (file)
             <groupId>commons-lang</groupId>
             <artifactId>commons-lang</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+
     </dependencies>
 </project>
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractDataTreeCandidate.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractDataTreeCandidate.java
new file mode 100644 (file)
index 0000000..3cd12c6
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+
+import com.google.common.base.Preconditions;
+
+abstract class AbstractDataTreeCandidate implements DataTreeCandidate {
+    private final InstanceIdentifier rootPath;
+
+    protected AbstractDataTreeCandidate(final InstanceIdentifier rootPath) {
+        this.rootPath = Preconditions.checkNotNull(rootPath);
+    }
+
+    @Override
+    public final InstanceIdentifier getRootPath() {
+        return rootPath;
+    }
+
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AlwaysFailOperation.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AlwaysFailOperation.java
new file mode 100644 (file)
index 0000000..5d1152c
--- /dev/null
@@ -0,0 +1,37 @@
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+
+/**
+ * An implementation of apply operation which fails to do anything,
+ * consistently. An instance of this class is used by the data tree
+ * if it does not have a SchemaContext attached and hence cannot
+ * perform anything meaningful.
+ */
+final class AlwaysFailOperation implements ModificationApplyOperation {
+    @Override
+    public Optional<TreeNode> apply(final ModifiedNode modification,
+            final Optional<TreeNode> storeMeta, final Version version) {
+        throw new IllegalStateException("Schema Context is not available.");
+    }
+
+    @Override
+    public void checkApplicable(final InstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> storeMetadata) {
+        throw new IllegalStateException("Schema Context is not available.");
+    }
+
+    @Override
+    public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+        throw new IllegalStateException("Schema Context is not available.");
+    }
+
+    @Override
+    public void verifyStructure(final ModifiedNode modification) {
+        throw new IllegalStateException("Schema Context is not available.");
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataNodeContainerModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataNodeContainerModificationStrategy.java
new file mode 100644 (file)
index 0000000..30af8fc
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableAugmentationNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.AugmentationSchemaProxy;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+/**
+ * Base strategy for applying changes to a ContainerNode, irrespective of its
+ * actual type.
+ *
+ * @param <T> Type of the container node
+ */
+abstract class DataNodeContainerModificationStrategy<T extends DataNodeContainer> extends NormalizedNodeContainerModificationStrategy {
+
+    private final T schema;
+    private final LoadingCache<PathArgument, ModificationApplyOperation> childCache = CacheBuilder.newBuilder()
+            .build(CacheLoader.from(new Function<PathArgument, ModificationApplyOperation>() {
+
+                @Override
+                public ModificationApplyOperation apply(final PathArgument identifier) {
+                    if (identifier instanceof AugmentationIdentifier && schema instanceof AugmentationTarget) {
+                        return SchemaAwareApplyOperation.from(schema, (AugmentationTarget) schema, (AugmentationIdentifier) identifier);
+                    }
+
+                    DataSchemaNode child = schema.getDataChildByName(identifier.getNodeType());
+                    if (child == null) {
+                        return null;
+                    }
+                    return SchemaAwareApplyOperation.from(child);
+                }
+            }));
+
+    protected DataNodeContainerModificationStrategy(final T schema,
+            final Class<? extends NormalizedNode<?, ?>> nodeClass) {
+        super(nodeClass);
+        this.schema = schema;
+    }
+
+    protected T getSchema() {
+        return schema;
+    }
+
+    @Override
+    public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+        try {
+            return Optional.<ModificationApplyOperation> fromNullable(childCache.get(identifier));
+        } catch (ExecutionException e) {
+            return Optional.absent();
+        }
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    protected abstract DataContainerNodeBuilder createBuilder(NormalizedNode<?, ?> original);
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [" + schema + "]";
+    }
+
+    public static class AugmentationModificationStrategy extends DataNodeContainerModificationStrategy<AugmentationSchema> {
+
+        protected AugmentationModificationStrategy(final AugmentationSchema schema, final DataNodeContainer resolved) {
+            super(createAugmentProxy(schema,resolved), AugmentationNode.class);
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof AugmentationNode);
+            return ImmutableAugmentationNodeBuilder.create((AugmentationNode) original);
+        }
+
+
+        private static AugmentationSchema createAugmentProxy(final AugmentationSchema schema, final DataNodeContainer resolved) {
+            Set<DataSchemaNode> realChildSchemas = new HashSet<>();
+            for(DataSchemaNode augChild : schema.getChildNodes()) {
+                realChildSchemas.add(resolved.getDataChildByName(augChild.getQName()));
+            }
+            return new AugmentationSchemaProxy(schema, realChildSchemas);
+        }
+    }
+
+    public static class ContainerModificationStrategy extends DataNodeContainerModificationStrategy<ContainerSchemaNode> {
+
+        public ContainerModificationStrategy(final ContainerSchemaNode schemaNode) {
+            super(schemaNode, ContainerNode.class);
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof ContainerNode);
+            return ImmutableContainerNodeBuilder.create((ContainerNode) original);
+        }
+    }
+
+    public static class ListEntryModificationStrategy extends DataNodeContainerModificationStrategy<ListSchemaNode> {
+
+        protected ListEntryModificationStrategy(final ListSchemaNode schema) {
+            super(schema, MapEntryNode.class);
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected final DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof MapEntryNode);
+            return ImmutableMapEntryNodeBuilder.create((MapEntryNode) original);
+        }
+    }
+
+    public static class UnkeyedListItemModificationStrategy extends DataNodeContainerModificationStrategy<ListSchemaNode> {
+
+        public UnkeyedListItemModificationStrategy(final ListSchemaNode schemaNode) {
+            super(schemaNode, UnkeyedListEntryNode.class);
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof UnkeyedListEntryNode);
+            return ImmutableUnkeyedListEntryNodeBuilder.create((UnkeyedListEntryNode) original);
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java
new file mode 100644 (file)
index 0000000..c5303e1
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Read-only snapshot of the data tree.
+ */
+final class InMemoryDataTree implements DataTree {
+    private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTree.class);
+    private static final InstanceIdentifier PUBLIC_ROOT_PATH = InstanceIdentifier.builder().build();
+
+    private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
+    private ModificationApplyOperation applyOper = new AlwaysFailOperation();
+    private SchemaContext currentSchemaContext;
+    private TreeNode rootNode;
+
+    public InMemoryDataTree(final TreeNode rootNode, final SchemaContext schemaContext) {
+        this.rootNode = Preconditions.checkNotNull(rootNode);
+
+        if (schemaContext != null) {
+            // Also sets applyOper
+            setSchemaContext(schemaContext);
+        }
+    }
+
+    @Override
+    public synchronized void setSchemaContext(final SchemaContext newSchemaContext) {
+        Preconditions.checkNotNull(newSchemaContext);
+
+        LOG.info("Attepting to install schema context {}", newSchemaContext);
+
+        /*
+         * FIXME: we should walk the schema contexts, both current and new and see
+         *        whether they are compatible here. Reject incompatible changes.
+         */
+
+        // Instantiate new apply operation, this still may fail
+        final ModificationApplyOperation newApplyOper = SchemaAwareApplyOperation.from(newSchemaContext);
+
+        // Ready to change the context now, make sure no operations are running
+        rwLock.writeLock().lock();
+        try {
+            this.applyOper = newApplyOper;
+            this.currentSchemaContext = newSchemaContext;
+        } finally {
+            rwLock.writeLock().unlock();
+        }
+    }
+
+    @Override
+    public InMemoryDataTreeSnapshot takeSnapshot() {
+        rwLock.readLock().lock();
+        try {
+            return new InMemoryDataTreeSnapshot(currentSchemaContext, rootNode, applyOper);
+        } finally {
+            rwLock.readLock().unlock();
+        }
+    }
+
+    @Override
+    public void validate(final DataTreeModification modification) throws DataValidationFailedException {
+        Preconditions.checkArgument(modification instanceof InMemoryDataTreeModification, "Invalid modification class %s", modification.getClass());
+
+        final InMemoryDataTreeModification m = (InMemoryDataTreeModification)modification;
+        m.getStrategy().checkApplicable(PUBLIC_ROOT_PATH, m.getRootModification(), Optional.<TreeNode>of(rootNode));
+    }
+
+    @Override
+    public synchronized DataTreeCandidate prepare(final DataTreeModification modification) {
+        Preconditions.checkArgument(modification instanceof InMemoryDataTreeModification, "Invalid modification class %s", modification.getClass());
+
+        final InMemoryDataTreeModification m = (InMemoryDataTreeModification)modification;
+        final ModifiedNode root = m.getRootModification();
+
+        if (root.getType() == ModificationType.UNMODIFIED) {
+            return new NoopDataTreeCandidate(PUBLIC_ROOT_PATH, root);
+        }
+
+        rwLock.writeLock().lock();
+        try {
+            final Optional<TreeNode> newRoot = m.getStrategy().apply(m.getRootModification(),
+                    Optional.<TreeNode>of(rootNode), rootNode.getSubtreeVersion().next());
+            Preconditions.checkState(newRoot.isPresent(), "Apply strategy failed to produce root node");
+            return new InMemoryDataTreeCandidate(PUBLIC_ROOT_PATH, root, rootNode, newRoot.get());
+        } finally {
+            rwLock.writeLock().unlock();
+        }
+    }
+
+    @Override
+    public synchronized void commit(final DataTreeCandidate candidate) {
+        if (candidate instanceof NoopDataTreeCandidate) {
+            return;
+        }
+
+        Preconditions.checkArgument(candidate instanceof InMemoryDataTreeCandidate, "Invalid candidate class %s", candidate.getClass());
+        final InMemoryDataTreeCandidate c = (InMemoryDataTreeCandidate)candidate;
+
+        LOG.debug("Updating datastore from {} to {}", rootNode, c.getAfterRoot());
+
+        if (LOG.isTraceEnabled()) {
+            LOG.trace("Data Tree is {}", StoreUtils.toStringTree(c.getAfterRoot().getData()));
+        }
+
+        // Ready to change the context now, make sure no operations are running
+        rwLock.writeLock().lock();
+        try {
+            Preconditions.checkState(c.getBeforeRoot() == rootNode,
+                    String.format("Store tree %s and candidate base %s differ.", rootNode, c.getBeforeRoot()));
+            this.rootNode = c.getAfterRoot();
+        } finally {
+            rwLock.writeLock().unlock();
+        }
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeCandidate.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeCandidate.java
new file mode 100644 (file)
index 0000000..1f6b0f0
--- /dev/null
@@ -0,0 +1,125 @@
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+
+final class InMemoryDataTreeCandidate extends AbstractDataTreeCandidate {
+    private static abstract class AbstractNode implements DataTreeCandidateNode {
+        private final ModifiedNode mod;
+        private final TreeNode newMeta;
+        private final TreeNode oldMeta;
+
+        protected AbstractNode(final ModifiedNode mod,
+                final TreeNode oldMeta, final TreeNode newMeta) {
+            this.newMeta = newMeta;
+            this.oldMeta = oldMeta;
+            this.mod = Preconditions.checkNotNull(mod);
+        }
+
+        protected final ModifiedNode getMod() {
+            return mod;
+        }
+
+        protected final TreeNode getNewMeta() {
+            return newMeta;
+        }
+
+        protected final TreeNode getOldMeta() {
+            return oldMeta;
+        }
+
+        private static final TreeNode childMeta(final TreeNode parent, final PathArgument id) {
+            if (parent != null) {
+                return parent.getChild(id).orNull();
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public Iterable<DataTreeCandidateNode> getChildNodes() {
+            return Iterables.transform(mod.getChildren(), new Function<ModifiedNode, DataTreeCandidateNode>() {
+                @Override
+                public DataTreeCandidateNode apply(final ModifiedNode input) {
+                    final PathArgument id = input.getIdentifier();
+                    return new ChildNode(input, childMeta(oldMeta, id), childMeta(newMeta, id));
+                }
+            });
+        }
+
+        @Override
+        public ModificationType getModificationType() {
+            return mod.getType();
+        }
+
+        private Optional<NormalizedNode<?, ?>> optionalData(final TreeNode meta) {
+            if (meta != null) {
+                return Optional.<NormalizedNode<?,?>>of(meta.getData());
+            } else {
+                return Optional.absent();
+            }
+        }
+
+        @Override
+        public Optional<NormalizedNode<?, ?>> getDataAfter() {
+            return optionalData(newMeta);
+        }
+
+        @Override
+        public Optional<NormalizedNode<?, ?>> getDataBefore() {
+            return optionalData(oldMeta);
+        }
+    }
+
+    private static final class ChildNode extends AbstractNode {
+        public ChildNode(final ModifiedNode mod, final TreeNode oldMeta, final TreeNode newMeta) {
+            super(mod, oldMeta, newMeta);
+        }
+
+        @Override
+        public PathArgument getIdentifier() {
+            return getMod().getIdentifier();
+        }
+    }
+
+    private static final class RootNode extends AbstractNode {
+        public RootNode(final ModifiedNode mod, final TreeNode oldMeta, final TreeNode newMeta) {
+            super(mod, oldMeta, newMeta);
+        }
+
+        @Override
+        public PathArgument getIdentifier() {
+            throw new IllegalStateException("Attempted to get identifier of the root node");
+        }
+    }
+
+    private final RootNode root;
+
+    InMemoryDataTreeCandidate(final InstanceIdentifier rootPath, final ModifiedNode modificationRoot,
+            final TreeNode beforeRoot, final TreeNode afterRoot) {
+        super(rootPath);
+        this.root = new RootNode(modificationRoot, beforeRoot, afterRoot);
+    }
+
+    TreeNode getAfterRoot() {
+        return root.getNewMeta();
+    }
+
+    TreeNode getBeforeRoot() {
+        return root.getOldMeta();
+    }
+
+    @Override
+    public DataTreeCandidateNode getRootNode() {
+        return root;
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeFactory.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeFactory.java
new file mode 100644 (file)
index 0000000..887a4bf
--- /dev/null
@@ -0,0 +1,38 @@
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeFactory;
+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.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * A factory for creating in-memory data trees.
+ */
+public final class InMemoryDataTreeFactory implements DataTreeFactory {
+    private static final InMemoryDataTreeFactory INSTANCE = new InMemoryDataTreeFactory();
+
+    private InMemoryDataTreeFactory() {
+        // Never instantiated externally
+    }
+
+    @Override
+    public InMemoryDataTree create() {
+        final NodeIdentifier root = new NodeIdentifier(SchemaContext.NAME);
+        final NormalizedNode<?, ?> data = Builders.containerBuilder().withNodeIdentifier(root).build();
+
+        return new InMemoryDataTree(TreeNodeFactory.createTreeNode(data, Version.initial()), null);
+    }
+
+    /**
+     * Get an instance of this factory. This method cannot fail.
+     *
+     * @return Data tree factory instance.
+     */
+    public static final InMemoryDataTreeFactory getInstance() {
+        return INSTANCE;
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeModification.java
new file mode 100644 (file)
index 0000000..44397cc
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.util.Map.Entry;
+
+final class InMemoryDataTreeModification implements DataTreeModification {
+    private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModification.class);
+    private final ModificationApplyOperation strategyTree;
+    private final InMemoryDataTreeSnapshot snapshot;
+    private final ModifiedNode rootNode;
+
+    @GuardedBy("this")
+    private boolean sealed = false;
+
+    InMemoryDataTreeModification(final InMemoryDataTreeSnapshot snapshot, final ModificationApplyOperation resolver) {
+        this.snapshot = Preconditions.checkNotNull(snapshot);
+        this.strategyTree = Preconditions.checkNotNull(resolver);
+        this.rootNode = ModifiedNode.createUnmodified(snapshot.getRootNode());
+    }
+
+    ModifiedNode getRootModification() {
+        return rootNode;
+    }
+
+    ModificationApplyOperation getStrategy() {
+        return strategyTree;
+    }
+
+    @Override
+    public synchronized void write(final InstanceIdentifier path, final NormalizedNode<?, ?> value) {
+        checkSealed();
+        resolveModificationFor(path).write(value);
+    }
+
+    @Override
+    public synchronized void merge(final InstanceIdentifier path, final NormalizedNode<?, ?> data) {
+        checkSealed();
+        mergeImpl(resolveModificationFor(path),data);
+    }
+
+    private void mergeImpl(final OperationWithModification op,final NormalizedNode<?,?> data) {
+
+        if(data instanceof NormalizedNodeContainer<?,?,?>) {
+            @SuppressWarnings({ "rawtypes", "unchecked" })
+            NormalizedNodeContainer<?,?,NormalizedNode<PathArgument, ?>> dataContainer = (NormalizedNodeContainer) data;
+            for(NormalizedNode<PathArgument, ?> child : dataContainer.getValue()) {
+                PathArgument childId = child.getIdentifier();
+                mergeImpl(op.forChild(childId), child);
+            }
+        }
+        op.merge(data);
+    }
+
+    @Override
+    public synchronized void delete(final InstanceIdentifier path) {
+        checkSealed();
+        resolveModificationFor(path).delete();
+    }
+
+    @Override
+    public synchronized Optional<NormalizedNode<?, ?>> readNode(final InstanceIdentifier path) {
+        /*
+         * Walk the tree from the top, looking for the first node between root and
+         * the requested path which has been modified. If no such node exists,
+         * we use the node itself.
+         */
+        final Entry<InstanceIdentifier, ModifiedNode> entry = TreeNodeUtils.findClosestsOrFirstMatch(rootNode, path, ModifiedNode.IS_TERMINAL_PREDICATE);
+        final InstanceIdentifier key = entry.getKey();
+        final ModifiedNode mod = entry.getValue();
+
+        final Optional<TreeNode> result = resolveSnapshot(key, mod);
+        if (result.isPresent()) {
+            NormalizedNode<?, ?> data = result.get().getData();
+            return NormalizedNodeUtils.findNode(key, data, path);
+        } else {
+            return Optional.absent();
+        }
+    }
+
+    private Optional<TreeNode> resolveSnapshot(final InstanceIdentifier path,
+            final ModifiedNode modification) {
+        final Optional<Optional<TreeNode>> potentialSnapshot = modification.getSnapshotCache();
+        if(potentialSnapshot.isPresent()) {
+            return potentialSnapshot.get();
+        }
+
+        try {
+            return resolveModificationStrategy(path).apply(modification, modification.getOriginal(),
+                    snapshot.getRootNode().getSubtreeVersion().next());
+        } catch (Exception e) {
+            LOG.error("Could not create snapshot for {}:{}", path,modification,e);
+            throw e;
+        }
+    }
+
+    private ModificationApplyOperation resolveModificationStrategy(final InstanceIdentifier path) {
+        LOG.trace("Resolving modification apply strategy for {}", path);
+        return TreeNodeUtils.findNodeChecked(strategyTree, path);
+    }
+
+    private OperationWithModification resolveModificationFor(final InstanceIdentifier path) {
+        ModifiedNode modification = rootNode;
+        // We ensure strategy is present.
+        ModificationApplyOperation operation = resolveModificationStrategy(path);
+        for (PathArgument pathArg : path.getPath()) {
+            modification = modification.modifyChild(pathArg);
+        }
+        return OperationWithModification.from(operation, modification);
+    }
+
+    @Override
+    public synchronized void ready() {
+        Preconditions.checkState(!sealed, "Attempted to seal an already-sealed Data Tree.");
+        sealed = true;
+        rootNode.seal();
+    }
+
+    @GuardedBy("this")
+    private void checkSealed() {
+        Preconditions.checkState(!sealed, "Data Tree is sealed. No further modifications allowed.");
+    }
+
+    @Override
+    public String toString() {
+        return "MutableDataTree [modification=" + rootNode + "]";
+    }
+
+    @Override
+    public synchronized DataTreeModification newModification() {
+        Preconditions.checkState(sealed, "Attempted to chain on an unsealed modification");
+
+        if(rootNode.getType() == ModificationType.UNMODIFIED) {
+            return snapshot.newModification();
+        }
+
+        /*
+         *  FIXME: Add advanced transaction chaining for modification of not rebased
+         *  modification.
+         *
+         *  Current computation of tempRoot may yeld incorrect subtree versions
+         *  if there are multiple concurrent transactions, which may break
+         *  versioning preconditions for modification of previously occured write,
+         *  directly nested under parent node, since node version is derived from
+         *  subtree version.
+         *
+         *  For deeper nodes subtree version is derived from their respective metadata
+         *  nodes, so this incorrect root subtree version is not affecting us.
+         */
+        TreeNode originalSnapshotRoot = snapshot.getRootNode();
+        Optional<TreeNode> tempRoot = strategyTree.apply(rootNode, Optional.of(originalSnapshotRoot), originalSnapshotRoot.getSubtreeVersion().next());
+
+        InMemoryDataTreeSnapshot tempTree = new InMemoryDataTreeSnapshot(snapshot.getSchemaContext(), tempRoot.get(), strategyTree);
+        return tempTree.newModification();
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshot.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTreeSnapshot.java
new file mode 100644 (file)
index 0000000..7c1c171
--- /dev/null
@@ -0,0 +1,48 @@
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeUtils;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+final class InMemoryDataTreeSnapshot implements DataTreeSnapshot {
+    private final ModificationApplyOperation applyOper;
+    private final SchemaContext schemaContext;
+    private final TreeNode rootNode;
+
+    InMemoryDataTreeSnapshot(final SchemaContext schemaContext, final TreeNode rootNode,
+            final ModificationApplyOperation applyOper) {
+        this.schemaContext = Preconditions.checkNotNull(schemaContext);
+        this.rootNode = Preconditions.checkNotNull(rootNode);
+        this.applyOper = Preconditions.checkNotNull(applyOper);
+    }
+
+    TreeNode getRootNode() {
+        return rootNode;
+    }
+
+    SchemaContext getSchemaContext() {
+        return schemaContext;
+    }
+
+    @Override
+    public Optional<NormalizedNode<?, ?>> readNode(final InstanceIdentifier path) {
+        return NormalizedNodeUtils.findNode(rootNode.getData(), path);
+    }
+
+    @Override
+    public InMemoryDataTreeModification newModification() {
+        return new InMemoryDataTreeModification(this, applyOper);
+    }
+
+    @Override
+    public String toString() {
+        return rootNode.getSubtreeVersion().toString();
+    }
+
+}
\ No newline at end of file
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModificationApplyOperation.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModificationApplyOperation.java
new file mode 100644 (file)
index 0000000..f2e13ce
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+
+/**
+ *
+ * Operation responsible for applying {@link ModifiedNode} on tree.
+ *
+ * Operation is composite - operation on top level node consists of
+ * suboperations on child nodes. This allows to walk operation hierarchy and
+ * invoke suboperations independently.
+ *
+ * <b>Implementation notes</b>
+ * <ul>
+ * <li>
+ * Implementations MUST expose all nested suboperations which operates on child
+ * nodes expose via {@link #getChild(PathArgument)} method.
+ * <li>Same suboperations SHOULD be used when invoked via
+ * {@link #apply(ModifiedNode, Optional)} if applicable.
+ *
+ *
+ * Hierarchical composite operation which is responsible for applying
+ * modification on particular subtree and creating updated subtree
+ *
+ *
+ */
+interface ModificationApplyOperation extends StoreTreeNode<ModificationApplyOperation> {
+
+    /**
+     *
+     * Implementation of this operation must be stateless and must not change
+     * state of this object.
+     *
+     * @param modification
+     *            NodeModification to be applied
+     * @param storeMeta
+     *            Store Metadata Node on which NodeModification should be
+     *            applied
+     * @param version New subtree version of parent node
+     * @throws IllegalArgumentException
+     *             If it is not possible to apply Operation on provided Metadata
+     *             node
+     * @return new {@link StoreMetadataNode} if operation resulted in updating
+     *         node, {@link Optional#absent()} if {@link ModifiedNode}
+     *         resulted in deletion of this node.
+     */
+    Optional<TreeNode> apply(ModifiedNode modification, Optional<TreeNode> storeMeta, Version version);
+
+    /**
+     *
+     * Performs structural verification of NodeModification, such as writen values / types
+     * uses right structural elements.
+     *
+     * @param modification to be verified.
+     * @throws IllegalArgumentException If provided NodeModification does not adhere to the structure.
+     */
+    void verifyStructure(ModifiedNode modification) throws IllegalArgumentException;
+
+    /**
+     * Returns a suboperation for specified tree node
+     *
+     * @return Reference to suboperation for specified tree node, {@link Optional#absent()}
+     *    if suboperation is not supported for specified tree node.
+     */
+    @Override
+    Optional<ModificationApplyOperation> getChild(PathArgument child);
+
+    /**
+     *
+     * Checks if provided node modification could be applied to current metadata node.
+     *
+     * @param modification Modification
+     * @param current Metadata Node to which modification should be applied
+     * @return true if modification is applicable
+     *         false if modification is no applicable
+     * @throws DataValidationFailedException
+     */
+    void checkApplicable(InstanceIdentifier path, NodeModification modification, Optional<TreeNode> current) throws DataValidationFailedException;
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java
new file mode 100644 (file)
index 0000000..a8739a6
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Node Modification Node and Tree
+ *
+ * Tree which structurally resembles data tree and captures client modifications
+ * to the data store tree.
+ *
+ * This tree is lazily created and populated via {@link #modifyChild(PathArgument)}
+ * and {@link StoreMetadataNode} which represents original state {@link #getOriginal()}.
+ */
+final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<PathArgument>, NodeModification {
+
+    public static final Predicate<ModifiedNode> IS_TERMINAL_PREDICATE = new Predicate<ModifiedNode>() {
+        @Override
+        public boolean apply(final ModifiedNode input) {
+            switch (input.getType()) {
+            case DELETE:
+            case MERGE:
+            case WRITE:
+                return true;
+            case SUBTREE_MODIFIED:
+            case UNMODIFIED:
+                return false;
+            }
+
+            throw new IllegalArgumentException(String.format("Unhandled modification type %s", input.getType()));
+        }
+    };
+
+    private final Map<PathArgument, ModifiedNode> children = new LinkedHashMap<>();
+    private final Optional<TreeNode> original;
+    private final PathArgument identifier;
+    private ModificationType modificationType = ModificationType.UNMODIFIED;
+    private Optional<TreeNode> snapshotCache;
+    private NormalizedNode<?, ?> value;
+
+    private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original) {
+        this.identifier = identifier;
+        this.original = original;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public NormalizedNode<?, ?> getWrittenValue() {
+        return value;
+    }
+
+    @Override
+    public PathArgument getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     *
+     * Returns original store metadata
+     * @return original store metadata
+     */
+    @Override
+    public Optional<TreeNode> getOriginal() {
+        return original;
+    }
+
+    /**
+     * Returns modification type
+     *
+     * @return modification type
+     */
+    @Override
+    public ModificationType getType() {
+        return modificationType;
+    }
+
+    /**
+     *
+     * Returns child modification if child was modified
+     *
+     * @return Child modification if direct child or it's subtree
+     *  was modified.
+     *
+     */
+    @Override
+    public Optional<ModifiedNode> getChild(final PathArgument child) {
+        return Optional.<ModifiedNode> fromNullable(children.get(child));
+    }
+
+    /**
+     *
+     * Returns child modification if child was modified, creates {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.data.ModifiedNode}
+     * for child otherwise.
+     *
+     * If this node's {@link ModificationType} is {@link ModificationType#UNMODIFIED}
+     * changes modification type to {@link ModificationType#SUBTREE_MODIFIED}
+     *
+     * @param child
+     * @return {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.data.ModifiedNode} for specified child, with {@link #getOriginal()}
+     *         containing child metadata if child was present in original data.
+     */
+    public ModifiedNode modifyChild(final PathArgument child) {
+        clearSnapshot();
+        if (modificationType == ModificationType.UNMODIFIED) {
+            updateModificationType(ModificationType.SUBTREE_MODIFIED);
+        }
+        final ModifiedNode potential = children.get(child);
+        if (potential != null) {
+            return potential;
+        }
+
+        final Optional<TreeNode> currentMetadata;
+        if (original.isPresent()) {
+            final TreeNode orig = original.get();
+            currentMetadata = orig.getChild(child);
+        } else {
+            currentMetadata = Optional.absent();
+        }
+
+        ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata);
+        children.put(child, newlyCreated);
+        return newlyCreated;
+    }
+
+    /**
+     *
+     * Returns all recorded direct child modification
+     *
+     * @return all recorded direct child modifications
+     */
+    @Override
+    public Iterable<ModifiedNode> getChildren() {
+        return children.values();
+    }
+
+    /**
+     *
+     * Records a delete for associated node.
+     *
+     */
+    public void delete() {
+        clearSnapshot();
+        updateModificationType(ModificationType.DELETE);
+        children.clear();
+        this.value = null;
+    }
+
+    /**
+     *
+     * Records a write for associated node.
+     *
+     * @param value
+     */
+    public void write(final NormalizedNode<?, ?> value) {
+        clearSnapshot();
+        updateModificationType(ModificationType.WRITE);
+        children.clear();
+        this.value = value;
+    }
+
+    public void merge(final NormalizedNode<?, ?> data) {
+        clearSnapshot();
+        updateModificationType(ModificationType.MERGE);
+        // FIXME: Probably merge with previous value.
+        this.value = data;
+    }
+
+    void seal() {
+        clearSnapshot();
+        for (ModifiedNode child : children.values()) {
+            child.seal();
+        }
+    }
+
+    private void clearSnapshot() {
+        snapshotCache = null;
+    }
+
+    public Optional<TreeNode> storeSnapshot(final Optional<TreeNode> snapshot) {
+        snapshotCache = snapshot;
+        return snapshot;
+    }
+
+    public Optional<Optional<TreeNode>> getSnapshotCache() {
+        return Optional.fromNullable(snapshotCache);
+    }
+
+    @GuardedBy("this")
+    private void updateModificationType(final ModificationType type) {
+        modificationType = type;
+        clearSnapshot();
+    }
+
+    @Override
+    public String toString() {
+        return "NodeModification [identifier=" + identifier + ", modificationType="
+                + modificationType + ", childModification=" + children + "]";
+    }
+
+    public static ModifiedNode createUnmodified(final TreeNode metadataTree) {
+        return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree));
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NodeModification.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NodeModification.java
new file mode 100644 (file)
index 0000000..ad8dbd1
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+
+/**
+ * Internal interface representing a modification action of a particular node.
+ * It is used by the validation code to allow for a read-only view of the
+ * modification tree as we should never modify that during validation.
+ */
+interface NodeModification extends Identifiable<PathArgument> {
+    /**
+     * Get the type of modification.
+     *
+     * @return Modification type.
+     */
+    ModificationType getType();
+
+    /**
+     * Get the original tree node to which the modification is to be applied.
+     *
+     * @return The original node, or {@link Optional#absent()} if the node is
+     *         a new node.
+     */
+    Optional<TreeNode> getOriginal();
+
+    /**
+     * Get a read-only view of children nodes.
+     *
+     * @return Iterable of all children nodes.
+     */
+    Iterable<? extends NodeModification> getChildren();
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NoopDataTreeCandidate.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NoopDataTreeCandidate.java
new file mode 100644 (file)
index 0000000..a936b2c
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+
+import java.util.Collections;
+
+/**
+ * Internal utility class for an empty candidate. We instantiate this class
+ * for empty modifications, saving memory and processing speed. Instances
+ * of this class are explicitly recognized and processing of them is skipped.
+ */
+final class NoopDataTreeCandidate extends AbstractDataTreeCandidate {
+    private static final DataTreeCandidateNode ROOT = new DataTreeCandidateNode() {
+        @Override
+        public ModificationType getModificationType() {
+            return ModificationType.UNMODIFIED;
+        }
+
+        @Override
+        public Iterable<DataTreeCandidateNode> getChildNodes() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public PathArgument getIdentifier() {
+            throw new IllegalStateException("Attempted to read identifier of the no-operation change");
+        }
+
+        @Override
+        public Optional<NormalizedNode<?, ?>> getDataAfter() {
+            return Optional.absent();
+        }
+
+        @Override
+        public Optional<NormalizedNode<?, ?>> getDataBefore() {
+            return Optional.absent();
+        }
+    };
+
+    protected NoopDataTreeCandidate(final InstanceIdentifier rootPath, final ModifiedNode modificationRoot) {
+        super(rootPath);
+        Preconditions.checkArgument(modificationRoot.getType() == ModificationType.UNMODIFIED);
+    }
+
+    @Override
+    public DataTreeCandidateNode getRootNode() {
+        return ROOT;
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NormalizedNodeContainerModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/NormalizedNodeContainerModificationStrategy.java
new file mode 100644 (file)
index 0000000..e868dc9
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+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.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+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.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableChoiceNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableOrderedLeafSetNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableOrderedMapNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+abstract class NormalizedNodeContainerModificationStrategy extends SchemaAwareApplyOperation {
+
+    private final Class<? extends NormalizedNode<?, ?>> nodeClass;
+
+    protected NormalizedNodeContainerModificationStrategy(final Class<? extends NormalizedNode<?, ?>> nodeClass) {
+        this.nodeClass = nodeClass;
+    }
+
+    @Override
+    public void verifyStructure(final ModifiedNode modification) throws IllegalArgumentException {
+        if (modification.getType() == ModificationType.WRITE) {
+
+        }
+        for (ModifiedNode childModification : modification.getChildren()) {
+            resolveChildOperation(childModification.getIdentifier()).verifyStructure(childModification);
+        }
+    }
+
+    @Override
+    protected void checkWriteApplicable(final InstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> current) throws DataValidationFailedException {
+        // FIXME: Implement proper write check for replacement of node container
+        //        prerequisite is to have transaction chain available for clients
+        //        otherwise this will break chained writes to same node.
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    protected void verifyWrittenStructure(final NormalizedNode<?, ?> writtenValue) {
+        checkArgument(nodeClass.isInstance(writtenValue), "Node should must be of type %s", nodeClass);
+        checkArgument(writtenValue instanceof NormalizedNodeContainer);
+
+        NormalizedNodeContainer container = (NormalizedNodeContainer) writtenValue;
+        for (Object child : container.getValue()) {
+            checkArgument(child instanceof NormalizedNode);
+
+            /*
+             * FIXME: fail-fast semantics:
+             *
+             * We can validate the data structure here, aborting the commit
+             * before it ever progresses to being committed.
+             */
+        }
+    }
+
+    @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;
+        }
+
+        /*
+         * 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.
+         */
+        final MutableTreeNode mutable = newValueMeta.mutable();
+        mutable.setSubtreeVersion(version);
+
+        @SuppressWarnings("rawtypes")
+        final NormalizedNodeContainerBuilder dataBuilder = createBuilder(newValue);
+
+        return mutateChildren(mutable, dataBuilder, version, modification.getChildren());
+    }
+
+    @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
+    protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
+            final Version version) {
+        // For Node Containers - merge is same as subtree change - we only replace children.
+        return applySubtreeChange(modification, currentMeta, version);
+    }
+
+    @Override
+    public TreeNode applySubtreeChange(final ModifiedNode modification,
+            final TreeNode currentMeta, final Version version) {
+        final MutableTreeNode newMeta = currentMeta.mutable();
+        newMeta.setSubtreeVersion(version);
+
+        @SuppressWarnings("rawtypes")
+        NormalizedNodeContainerBuilder dataBuilder = createBuilder(currentMeta.getData());
+
+        return mutateChildren(newMeta, dataBuilder, version, modification.getChildren());
+    }
+
+    @Override
+    protected void checkSubtreeModificationApplicable(final InstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> current) throws DataValidationFailedException {
+        SchemaAwareApplyOperation.checkConflicting(path, current.isPresent(), "Node was deleted by other transaction.");
+        checkChildPreconditions(path, modification, current);
+    }
+
+    private void checkChildPreconditions(final InstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+        final TreeNode currentMeta = current.get();
+        for (NodeModification childMod : modification.getChildren()) {
+            final PathArgument childId = childMod.getIdentifier();
+            final Optional<TreeNode> childMeta = currentMeta.getChild(childId);
+
+            InstanceIdentifier childPath = path.node(childId);
+            resolveChildOperation(childId).checkApplicable(childPath, childMod, childMeta);
+        }
+    }
+
+    @Override
+    protected void checkMergeApplicable(final InstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> current) throws DataValidationFailedException {
+        if(current.isPresent()) {
+            checkChildPreconditions(path, modification,current);
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    protected abstract NormalizedNodeContainerBuilder createBuilder(NormalizedNode<?, ?> original);
+
+    public static class ChoiceModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+        private final Map<PathArgument, ModificationApplyOperation> childNodes;
+
+        public ChoiceModificationStrategy(final ChoiceNode schemaNode) {
+            super(org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode.class);
+            ImmutableMap.Builder<PathArgument, ModificationApplyOperation> child = ImmutableMap.builder();
+
+            for (ChoiceCaseNode caze : schemaNode.getCases()) {
+                for (DataSchemaNode cazeChild : caze.getChildNodes()) {
+                    SchemaAwareApplyOperation childNode = SchemaAwareApplyOperation.from(cazeChild);
+                    child.put(new NodeIdentifier(cazeChild.getQName()), childNode);
+                }
+            }
+            childNodes = child.build();
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+            return Optional.fromNullable(childNodes.get(child));
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        protected DataContainerNodeBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode);
+            return ImmutableChoiceNodeBuilder.create((org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode) original);
+        }
+    }
+
+    public static class OrderedLeafSetModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+        private final Optional<ModificationApplyOperation> entryStrategy;
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected OrderedLeafSetModificationStrategy(final LeafListSchemaNode schema) {
+            super((Class) LeafSetNode.class);
+            entryStrategy = Optional.<ModificationApplyOperation> of(new ValueNodeModificationStrategy.LeafSetEntryModificationStrategy(schema));
+        }
+
+        @SuppressWarnings("rawtypes")
+        @Override
+        protected NormalizedNodeContainerBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof OrderedLeafSetNode<?>);
+            return ImmutableOrderedLeafSetNodeBuilder.create((OrderedLeafSetNode<?>) original);
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+            if (identifier instanceof NodeWithValue) {
+                return entryStrategy;
+            }
+            return Optional.absent();
+        }
+    }
+
+    public static class OrderedMapModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+        private final Optional<ModificationApplyOperation> entryStrategy;
+
+        protected OrderedMapModificationStrategy(final ListSchemaNode schema) {
+            super(OrderedMapNode.class);
+            entryStrategy = Optional.<ModificationApplyOperation> of(new DataNodeContainerModificationStrategy.ListEntryModificationStrategy(schema));
+        }
+
+        @SuppressWarnings("rawtypes")
+        @Override
+        protected NormalizedNodeContainerBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof OrderedMapNode);
+            return ImmutableOrderedMapNodeBuilder.create((OrderedMapNode) original);
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+            if (identifier instanceof NodeIdentifierWithPredicates) {
+                return entryStrategy;
+            }
+            return Optional.absent();
+        }
+
+        @Override
+        public String toString() {
+            return "OrderedMapModificationStrategy [entry=" + entryStrategy + "]";
+        }
+    }
+
+    public static class UnorderedLeafSetModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+        private final Optional<ModificationApplyOperation> entryStrategy;
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected UnorderedLeafSetModificationStrategy(final LeafListSchemaNode schema) {
+            super((Class) LeafSetNode.class);
+            entryStrategy = Optional.<ModificationApplyOperation> of(new ValueNodeModificationStrategy.LeafSetEntryModificationStrategy(schema));
+        }
+
+        @SuppressWarnings("rawtypes")
+        @Override
+        protected NormalizedNodeContainerBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof LeafSetNode<?>);
+            return ImmutableLeafSetNodeBuilder.create((LeafSetNode<?>) original);
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+            if (identifier instanceof NodeWithValue) {
+                return entryStrategy;
+            }
+            return Optional.absent();
+        }
+    }
+
+    public static class UnorderedMapModificationStrategy extends NormalizedNodeContainerModificationStrategy {
+
+        private final Optional<ModificationApplyOperation> entryStrategy;
+
+        protected UnorderedMapModificationStrategy(final ListSchemaNode schema) {
+            super(MapNode.class);
+            entryStrategy = Optional.<ModificationApplyOperation> of(new DataNodeContainerModificationStrategy.ListEntryModificationStrategy(schema));
+        }
+
+        @SuppressWarnings("rawtypes")
+        @Override
+        protected NormalizedNodeContainerBuilder createBuilder(final NormalizedNode<?, ?> original) {
+            checkArgument(original instanceof MapNode);
+            return ImmutableMapNodeBuilder.create((MapNode) original);
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument identifier) {
+            if (identifier instanceof NodeIdentifierWithPredicates) {
+                return entryStrategy;
+            }
+            return Optional.absent();
+        }
+
+        @Override
+        public String toString() {
+            return "UnorderedMapModificationStrategy [entry=" + entryStrategy + "]";
+        }
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/OperationWithModification.java
new file mode 100644 (file)
index 0000000..2cd757d
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+
+final class OperationWithModification {
+
+    private final ModifiedNode modification;
+
+    private final ModificationApplyOperation applyOperation;
+
+    private OperationWithModification(final ModificationApplyOperation op, final ModifiedNode mod) {
+        this.modification = mod;
+        this.applyOperation = op;
+    }
+
+    public OperationWithModification write(final NormalizedNode<?, ?> value) {
+        modification.write(value);
+        applyOperation.verifyStructure(modification);
+        return this;
+    }
+
+    public OperationWithModification delete() {
+        modification.delete();
+        return this;
+    }
+
+    public ModifiedNode getModification() {
+        return modification;
+    }
+
+    public ModificationApplyOperation getApplyOperation() {
+        return applyOperation;
+    }
+
+    public Optional<TreeNode> apply(final Optional<TreeNode> data, final Version version) {
+        return applyOperation.apply(modification, data, version);
+    }
+
+    public static OperationWithModification from(final ModificationApplyOperation operation,
+            final ModifiedNode modification) {
+        return new OperationWithModification(operation, modification);
+
+    }
+
+    public void merge(final NormalizedNode<?, ?> data) {
+        modification.merge(data);
+        applyOperation.verifyStructure(modification);
+
+    }
+
+    public OperationWithModification forChild(final PathArgument childId) {
+        ModifiedNode childMod = modification.modifyChild(childId);
+        Optional<ModificationApplyOperation> childOp = applyOperation.getChild(childId);
+        return from(childOp.get(),childMod);
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java
new file mode 100644 (file)
index 0000000..af0b973
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+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.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.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+abstract class SchemaAwareApplyOperation implements ModificationApplyOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(SchemaAwareApplyOperation.class);
+
+    public static SchemaAwareApplyOperation from(final DataSchemaNode schemaNode) {
+        if (schemaNode instanceof ContainerSchemaNode) {
+            return new DataNodeContainerModificationStrategy.ContainerModificationStrategy((ContainerSchemaNode) schemaNode);
+        } else if (schemaNode instanceof ListSchemaNode) {
+            return fromListSchemaNode((ListSchemaNode) schemaNode);
+        } else if (schemaNode instanceof ChoiceNode) {
+            return new NormalizedNodeContainerModificationStrategy.ChoiceModificationStrategy((ChoiceNode) schemaNode);
+        } else if (schemaNode instanceof LeafListSchemaNode) {
+            return fromLeafListSchemaNode((LeafListSchemaNode) schemaNode);
+        } else if (schemaNode instanceof LeafSchemaNode) {
+            return new ValueNodeModificationStrategy.LeafModificationStrategy((LeafSchemaNode) schemaNode);
+        }
+        throw new IllegalArgumentException("Not supported schema node type for " + schemaNode.getClass());
+    }
+
+    public static SchemaAwareApplyOperation from(final DataNodeContainer resolvedTree,
+            final AugmentationTarget augSchemas, final AugmentationIdentifier identifier) {
+        AugmentationSchema augSchema = null;
+
+        allAugments:
+            for (AugmentationSchema potential : augSchemas.getAvailableAugmentations()) {
+                for (DataSchemaNode child : potential.getChildNodes()) {
+                    if (identifier.getPossibleChildNames().contains(child.getQName())) {
+                        augSchema = potential;
+                        break allAugments;
+                    }
+                }
+            }
+
+        if (augSchema != null) {
+            return new DataNodeContainerModificationStrategy.AugmentationModificationStrategy(augSchema, resolvedTree);
+        }
+        return null;
+    }
+
+    public static boolean checkConflicting(final InstanceIdentifier path, final boolean condition, final String message) throws ConflictingModificationAppliedException {
+        if(!condition) {
+            throw new ConflictingModificationAppliedException(path, message);
+        }
+        return condition;
+    }
+
+    private static SchemaAwareApplyOperation fromListSchemaNode(final ListSchemaNode schemaNode) {
+        List<QName> keyDefinition = schemaNode.getKeyDefinition();
+        if (keyDefinition == null || keyDefinition.isEmpty()) {
+            return new UnkeyedListModificationStrategy(schemaNode);
+        }
+        if (schemaNode.isUserOrdered()) {
+            return new NormalizedNodeContainerModificationStrategy.OrderedMapModificationStrategy(schemaNode);
+        }
+
+        return new NormalizedNodeContainerModificationStrategy.UnorderedMapModificationStrategy(schemaNode);
+    }
+
+    private static SchemaAwareApplyOperation fromLeafListSchemaNode(final LeafListSchemaNode schemaNode) {
+        if(schemaNode.isUserOrdered()) {
+            return new NormalizedNodeContainerModificationStrategy.OrderedLeafSetModificationStrategy(schemaNode);
+        } else {
+            return new NormalizedNodeContainerModificationStrategy.UnorderedLeafSetModificationStrategy(schemaNode);
+        }
+    }
+
+    private static final void checkNotConflicting(final InstanceIdentifier path, final TreeNode original, final TreeNode current) throws ConflictingModificationAppliedException {
+        checkConflicting(path, original.getVersion().equals(current.getVersion()),
+                "Node was replaced by other transaction.");
+        checkConflicting(path, original.getSubtreeVersion().equals(current.getSubtreeVersion()),
+                "Node children was modified by other transaction");
+    }
+
+    protected final ModificationApplyOperation resolveChildOperation(final PathArgument child) {
+        Optional<ModificationApplyOperation> potential = getChild(child);
+        Preconditions.checkArgument(potential.isPresent(), "Operation for child %s is not defined.", child);
+        return potential.get();
+    }
+
+    @Override
+    public void verifyStructure(final ModifiedNode modification) throws IllegalArgumentException {
+        if (modification.getType() == ModificationType.WRITE) {
+            verifyWrittenStructure(modification.getWrittenValue());
+        }
+    }
+
+    @Override
+    public final void checkApplicable(final InstanceIdentifier path,final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+        switch (modification.getType()) {
+        case DELETE:
+            checkDeleteApplicable(modification, current);
+        case SUBTREE_MODIFIED:
+            checkSubtreeModificationApplicable(path, modification, current);
+            return;
+        case WRITE:
+            checkWriteApplicable(path, modification, current);
+            return;
+        case MERGE:
+            checkMergeApplicable(path, modification, current);
+            return;
+        case UNMODIFIED:
+            return;
+        default:
+            throw new UnsupportedOperationException("Suplied modification type "+ modification.getType()+ "is not supported.");
+        }
+
+    }
+
+    protected void checkMergeApplicable(final InstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+        Optional<TreeNode> original = modification.getOriginal();
+        if (original.isPresent() && current.isPresent()) {
+            /*
+             * We need to do conflict detection only and only if the value of leaf changed
+             * before two transactions. If value of leaf is unchanged between two transactions
+             * it should not cause transaction to fail, since result of this merge
+             * leads to same data.
+             */
+            if(!original.get().getData().equals(current.get().getData())) {
+                checkNotConflicting(path, original.get(), current.get());
+            }
+        }
+    }
+
+    protected void checkWriteApplicable(final InstanceIdentifier 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());
+        } else if(original.isPresent()) {
+            throw new ConflictingModificationAppliedException(path,"Node was deleted by other transaction.");
+        }
+    }
+
+    private void checkDeleteApplicable(final NodeModification modification, final Optional<TreeNode> current) {
+        // Delete is always applicable, we do not expose it to subclasses
+        if (current.isPresent()) {
+            LOG.trace("Delete operation turned to no-op on missing node {}", modification);
+        }
+    }
+
+    @Override
+    public final Optional<TreeNode> apply(final ModifiedNode modification,
+            final Optional<TreeNode> currentMeta, final Version version) {
+
+        switch (modification.getType()) {
+        case DELETE:
+            return modification.storeSnapshot(Optional.<TreeNode> absent());
+        case SUBTREE_MODIFIED:
+            Preconditions.checkArgument(currentMeta.isPresent(), "Metadata not available for modification",
+                    modification);
+            return modification.storeSnapshot(Optional.of(applySubtreeChange(modification, currentMeta.get(),
+                    version)));
+        case MERGE:
+            if(currentMeta.isPresent()) {
+                return modification.storeSnapshot(Optional.of(applyMerge(modification,currentMeta.get(), version)));
+            } // Fallback to write is intentional - if node is not preexisting merge is same as write
+        case WRITE:
+            return modification.storeSnapshot(Optional.of(applyWrite(modification, currentMeta, version)));
+        case UNMODIFIED:
+            return currentMeta;
+        default:
+            throw new IllegalArgumentException("Provided modification type is not supported.");
+        }
+    }
+
+    protected abstract TreeNode applyMerge(ModifiedNode modification,
+            TreeNode currentMeta, Version version);
+
+    protected abstract TreeNode applyWrite(ModifiedNode modification,
+            Optional<TreeNode> currentMeta, Version version);
+
+    protected abstract TreeNode applySubtreeChange(ModifiedNode modification,
+            TreeNode currentMeta, Version version);
+
+    /**
+     *
+     * Checks is supplied {@link NodeModification} is applicable for Subtree Modification.
+     *
+     * @param path Path to current node
+     * @param modification Node modification which should be applied.
+     * @param current Current state of data tree
+     * @throws ConflictingModificationAppliedException If subtree was changed in conflicting way
+     * @throws IncorrectDataStructureException If subtree modification is not applicable (e.g. leaf node).
+     */
+    protected abstract void checkSubtreeModificationApplicable(InstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> current) throws DataValidationFailedException;
+
+    protected abstract void verifyWrittenStructure(NormalizedNode<?, ?> writtenValue);
+
+    public static class UnkeyedListModificationStrategy extends SchemaAwareApplyOperation {
+
+        private final Optional<ModificationApplyOperation> entryStrategy;
+
+        protected UnkeyedListModificationStrategy(final ListSchemaNode schema) {
+            entryStrategy = Optional.<ModificationApplyOperation> of(new DataNodeContainerModificationStrategy.UnkeyedListItemModificationStrategy(schema));
+        }
+
+        @Override
+        protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
+                final Version version) {
+            return applyWrite(modification, Optional.of(currentMeta), version);
+        }
+
+        @Override
+        protected TreeNode applySubtreeChange(final ModifiedNode modification,
+                final TreeNode currentMeta, final Version version) {
+            throw new UnsupportedOperationException("UnkeyedList does not support subtree change.");
+        }
+
+        @Override
+        protected TreeNode applyWrite(final ModifiedNode modification,
+                final Optional<TreeNode> currentMeta, final Version version) {
+            return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+        }
+
+        @Override
+        public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+            if (child instanceof NodeIdentifier) {
+                return entryStrategy;
+            }
+            return Optional.absent();
+        }
+
+        @Override
+        protected void verifyWrittenStructure(final NormalizedNode<?, ?> writtenValue) {
+
+        }
+
+        @Override
+        protected void checkSubtreeModificationApplicable(final InstanceIdentifier path, final NodeModification modification,
+                final Optional<TreeNode> current) throws IncorrectDataStructureException {
+            throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
+        }
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StoreUtils.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StoreUtils.java
new file mode 100644 (file)
index 0000000..bca1684
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+
+import com.google.common.base.Strings;
+
+/**
+ * Data store tree manipulation utilities.
+ */
+public final class StoreUtils {
+    private static final int STRINGTREE_INDENT = 4;
+
+    private StoreUtils() {
+        throw new UnsupportedOperationException("Utility class should not be instantiated");
+    }
+
+    /**
+     * Convert a data subtree under a node into a human-readable string format.
+     *
+     * @param node Data subtree root
+     * @return String containing a human-readable form of the subtree.
+     */
+    public static String toStringTree(final NormalizedNode<?, ?> node) {
+        final StringBuilder builder = new StringBuilder();
+        toStringTree(builder, node, 0);
+        return builder.toString();
+    }
+
+    private static void toStringTree(final StringBuilder builder, final NormalizedNode<?, ?> node, final int offset) {
+        final String prefix = Strings.repeat(" ", offset);
+
+        builder.append(prefix).append(toStringTree(node.getIdentifier()));
+        if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
+            final NormalizedNodeContainer<?, ?, ?> container = (NormalizedNodeContainer<?, ?, ?>) node;
+
+            builder.append(" {\n");
+            for (NormalizedNode<?, ?> child : container.getValue()) {
+                toStringTree(builder, child, offset + STRINGTREE_INDENT);
+            }
+
+            builder.append(prefix).append('}');
+        } else {
+            builder.append(' ').append(node.getValue());
+        }
+        builder.append('\n');
+    }
+
+    private static String toStringTree(final PathArgument identifier) {
+        if (identifier instanceof NodeIdentifierWithPredicates) {
+            StringBuilder builder = new StringBuilder();
+            builder.append(identifier.getNodeType().getLocalName());
+            builder.append(((NodeIdentifierWithPredicates) identifier).getKeyValues().values());
+            return builder.toString();
+        } else if (identifier instanceof AugmentationIdentifier) {
+            return "augmentation";
+        }
+        return identifier.getNodeType().getLocalName();
+    }
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/TreeNodeUtils.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/TreeNodeUtils.java
new file mode 100644 (file)
index 0000000..2df4ffd
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
+
+/**
+ * A set of utility methods for interacting with {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.spi.TreeNode} objects.
+ */
+public final class TreeNodeUtils {
+    private TreeNodeUtils() {
+        throw new UnsupportedOperationException("Utility class should not be instantiated");
+    }
+
+    /**
+     * Finds a node in tree
+     *
+     * @param tree Data Tree
+     * @param path Path to the node
+     * @return Optional with node if the node is present in tree, {@link Optional#absent()} otherwise.
+     */
+    public static <T extends StoreTreeNode<T>> Optional<T> findNode(final T tree, final InstanceIdentifier path) {
+        Optional<T> current = Optional.<T> of(tree);
+        Iterator<PathArgument> pathIter = path.getPath().iterator();
+        while (current.isPresent() && pathIter.hasNext()) {
+            current = current.get().getChild(pathIter.next());
+        }
+        return current;
+    }
+
+    public static <T extends StoreTreeNode<T>> T findNodeChecked(final T tree, final InstanceIdentifier path) {
+        T current = tree;
+        List<PathArgument> nested = new ArrayList<>(path.getPath().size());
+        for(PathArgument pathArg : path.getPath()) {
+            Optional<T> potential = current.getChild(pathArg);
+            nested.add(pathArg);
+            Preconditions.checkArgument(potential.isPresent(),"Child %s is not present in tree.",nested);
+            current = potential.get();
+        }
+        return current;
+    }
+
+    /**
+     * Finds a node or closest parent in  the tree
+     *
+     * @param tree Data Tree
+     * @param path Path to the node
+     * @return Map.Entry Entry with key which is path to closest parent and value is parent node.
+     *
+     */
+    public static <T extends StoreTreeNode<T>> Map.Entry<InstanceIdentifier, T> findClosest(final T tree, final InstanceIdentifier path) {
+        return findClosestsOrFirstMatch(tree, path, Predicates.<T>alwaysFalse());
+    }
+
+    public static <T extends StoreTreeNode<T>> Map.Entry<InstanceIdentifier, T> findClosestsOrFirstMatch(final T tree, final InstanceIdentifier path, final Predicate<T> predicate) {
+        Optional<T> parent = Optional.<T>of(tree);
+        Optional<T> current = Optional.<T> of(tree);
+
+        int nesting = 0;
+        Iterator<PathArgument> pathIter = path.getPath().iterator();
+        while (current.isPresent() && pathIter.hasNext() && !predicate.apply(current.get())) {
+            parent = current;
+            current = current.get().getChild(pathIter.next());
+            nesting++;
+        }
+        if(current.isPresent()) {
+            final InstanceIdentifier currentPath = new InstanceIdentifier(path.getPath().subList(0, nesting));
+            return new SimpleEntry<InstanceIdentifier,T>(currentPath,current.get());
+        }
+
+        /*
+         * Subtracting 1 from nesting level at this point is safe, because we
+         * cannot reach here with nesting == 0: that would mean the above check
+         * for current.isPresent() failed, which it cannot, as current is always
+         * present. At any rate we check state just to be on the safe side.
+         */
+        Preconditions.checkState(nesting > 0);
+        final InstanceIdentifier parentPath = new InstanceIdentifier(path.getPath().subList(0, nesting - 1));
+
+        return new SimpleEntry<InstanceIdentifier,T>(parentPath,parent.get());
+    }
+
+    public static <T extends StoreTreeNode<T>> Optional<T> getChild(final Optional<T> parent,final PathArgument child) {
+        if(parent.isPresent()) {
+            return parent.get().getChild(child);
+        }
+        return Optional.absent();
+    }
+
+}
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ValueNodeModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ValueNodeModificationStrategy.java
new file mode 100644 (file)
index 0000000..a352aec
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.IncorrectDataStructureException;
+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.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+abstract class ValueNodeModificationStrategy<T extends DataSchemaNode> extends SchemaAwareApplyOperation {
+
+    private final T schema;
+    private final Class<? extends NormalizedNode<?, ?>> nodeClass;
+
+    protected ValueNodeModificationStrategy(final T schema, final Class<? extends NormalizedNode<?, ?>> nodeClass) {
+        super();
+        this.schema = schema;
+        this.nodeClass = nodeClass;
+    }
+
+    @Override
+    protected void verifyWrittenStructure(final NormalizedNode<?, ?> writtenValue) {
+        checkArgument(nodeClass.isInstance(writtenValue), "Node should must be of type %s", nodeClass);
+    }
+
+    @Override
+    public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+        throw new UnsupportedOperationException("Node " + schema.getPath()
+                + "is leaf type node. Child nodes not allowed");
+    }
+
+    @Override
+    protected TreeNode applySubtreeChange(final ModifiedNode modification,
+            final TreeNode currentMeta, final Version version) {
+        throw new UnsupportedOperationException("Node " + schema.getPath()
+                + "is leaf type node. Subtree change is not allowed.");
+    }
+
+    @Override
+    protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta,
+            final Version version) {
+        // Just overwrite whatever was there
+        return applyWrite(modification, null, version);
+    }
+
+    @Override
+    protected TreeNode applyWrite(final ModifiedNode modification,
+            final Optional<TreeNode> currentMeta, final Version version) {
+        return TreeNodeFactory.createTreeNode(modification.getWrittenValue(), version);
+    }
+
+    @Override
+    protected void checkSubtreeModificationApplicable(final InstanceIdentifier path, final NodeModification modification,
+            final Optional<TreeNode> current) throws IncorrectDataStructureException {
+        throw new IncorrectDataStructureException(path, "Subtree modification is not allowed.");
+    }
+
+    public static class LeafSetEntryModificationStrategy extends ValueNodeModificationStrategy<LeafListSchemaNode> {
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected LeafSetEntryModificationStrategy(final LeafListSchemaNode schema) {
+            super(schema, (Class) LeafSetEntryNode.class);
+        }
+    }
+
+    public static class LeafModificationStrategy extends ValueNodeModificationStrategy<LeafSchemaNode> {
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected LeafModificationStrategy(final LeafSchemaNode schema) {
+            super(schema, (Class) LeafNode.class);
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModificationMetadataTreeTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModificationMetadataTreeTest.java
new file mode 100644 (file)
index 0000000..54a4a88
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import com.google.common.base.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+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.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+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.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntry;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapEntryBuilder;
+import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.mapNodeBuilder;
+
+
+
+
+
+
+/**
+ *
+ * Schema structure of document is
+ *
+ * <pre>
+ * container root { 
+ *      list list-a {
+ *              key leaf-a;
+ *              leaf leaf-a;
+ *              choice choice-a {
+ *                      case one {
+ *                              leaf one;
+ *                      }
+ *                      case two-three {
+ *                              leaf two;
+ *                              leaf three;
+ *                      }
+ *              }
+ *              list list-b {
+ *                      key leaf-b;
+ *                      leaf leaf-b;
+ *              }
+ *      }
+ * }
+ * </pre>
+ *
+ */
+public class ModificationMetadataTreeTest {
+
+    private static final Short ONE_ID = 1;
+    private static final Short TWO_ID = 2;
+    private static final String TWO_ONE_NAME = "one";
+    private static final String TWO_TWO_NAME = "two";
+
+    private static final InstanceIdentifier OUTER_LIST_1_PATH = InstanceIdentifier.builder(TestModel.OUTER_LIST_PATH)
+            .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, ONE_ID) //
+            .build();
+
+    private static final InstanceIdentifier OUTER_LIST_2_PATH = InstanceIdentifier.builder(TestModel.OUTER_LIST_PATH)
+            .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, TWO_ID) //
+            .build();
+
+    private static final InstanceIdentifier TWO_TWO_PATH = InstanceIdentifier.builder(OUTER_LIST_2_PATH)
+            .node(TestModel.INNER_LIST_QNAME) //
+            .nodeWithKey(TestModel.INNER_LIST_QNAME, TestModel.NAME_QNAME, TWO_TWO_NAME) //
+            .build();
+
+    private static final InstanceIdentifier TWO_TWO_VALUE_PATH = InstanceIdentifier.builder(TWO_TWO_PATH)
+            .node(TestModel.VALUE_QNAME) //
+            .build();
+
+    private static final MapEntryNode BAR_NODE = mapEntryBuilder(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, TWO_ID) //
+            .withChild(mapNodeBuilder(TestModel.INNER_LIST_QNAME) //
+                    .withChild(mapEntry(TestModel.INNER_LIST_QNAME, TestModel.NAME_QNAME, TWO_ONE_NAME)) //
+                    .withChild(mapEntry(TestModel.INNER_LIST_QNAME,TestModel.NAME_QNAME, TWO_TWO_NAME)) //
+                    .build()) //
+                    .build();
+
+    private SchemaContext schemaContext;
+    private ModificationApplyOperation applyOper;
+
+    @Before
+    public void prepare() {
+        schemaContext = TestModel.createTestContext();
+        assertNotNull("Schema context must not be null.", schemaContext);
+        applyOper = SchemaAwareApplyOperation.from(schemaContext);
+    }
+
+    /**
+     * Returns a test document
+     *
+     * <pre>
+     * test
+     *     outer-list
+     *          id 1
+     *     outer-list
+     *          id 2
+     *          inner-list
+     *                  name "one"
+     *          inner-list
+     *                  name "two"
+     *
+     * </pre>
+     *
+     * @return
+     */
+    public NormalizedNode<?, ?> createDocumentOne() {
+        return ImmutableContainerNodeBuilder
+                .create()
+                .withNodeIdentifier(new NodeIdentifier(schemaContext.getQName()))
+                .withChild(createTestContainer()).build();
+
+    }
+
+    private ContainerNode createTestContainer() {
+        return ImmutableContainerNodeBuilder
+                .create()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
+                .withChild(
+                        mapNodeBuilder(TestModel.OUTER_LIST_QNAME)
+                        .withChild(mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, ONE_ID))
+                        .withChild(BAR_NODE).build()).build();
+    }
+
+    @Test
+    public void basicReadWrites() {
+        DataTreeModification modificationTree = new InMemoryDataTreeModification(new InMemoryDataTreeSnapshot(schemaContext,
+                TreeNodeFactory.createTreeNode(createDocumentOne(), Version.initial()), applyOper),
+                new SchemaAwareApplyOperationRoot(schemaContext));
+        Optional<NormalizedNode<?, ?>> originalBarNode = modificationTree.readNode(OUTER_LIST_2_PATH);
+        assertTrue(originalBarNode.isPresent());
+        assertSame(BAR_NODE, originalBarNode.get());
+
+        // writes node to /outer-list/1/inner_list/two/value
+        modificationTree.write(TWO_TWO_VALUE_PATH, ImmutableNodes.leafNode(TestModel.VALUE_QNAME, "test"));
+
+        // reads node to /outer-list/1/inner_list/two/value
+        // and checks if node is already present
+        Optional<NormalizedNode<?, ?>> barTwoCModified = modificationTree.readNode(TWO_TWO_VALUE_PATH);
+        assertTrue(barTwoCModified.isPresent());
+        assertEquals(ImmutableNodes.leafNode(TestModel.VALUE_QNAME, "test"), barTwoCModified.get());
+
+        // delete node to /outer-list/1/inner_list/two/value
+        modificationTree.delete(TWO_TWO_VALUE_PATH);
+        Optional<NormalizedNode<?, ?>> barTwoCAfterDelete = modificationTree.readNode(TWO_TWO_VALUE_PATH);
+        assertFalse(barTwoCAfterDelete.isPresent());
+    }
+
+
+    public DataTreeModification createEmptyModificationTree() {
+        /**
+         * Creates empty Snapshot with associated schema context.
+         */
+        DataTree t = InMemoryDataTreeFactory.getInstance().create();
+        t.setSchemaContext(schemaContext);
+
+        /**
+         *
+         * Creates Mutable Data Tree based on provided snapshot and schema
+         * context.
+         *
+         */
+        return t.takeSnapshot().newModification();
+    }
+
+    @Test
+    public void createFromEmptyState() {
+
+        DataTreeModification modificationTree = createEmptyModificationTree();
+        /**
+         * Writes empty container node to /test
+         *
+         */
+        modificationTree.write(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+
+        /**
+         * Writes empty list node to /test/outer-list
+         */
+        modificationTree.write(TestModel.OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build());
+
+        /**
+         * Reads list node from /test/outer-list
+         */
+        Optional<NormalizedNode<?, ?>> potentialOuterList = modificationTree.readNode(TestModel.OUTER_LIST_PATH);
+        assertTrue(potentialOuterList.isPresent());
+
+        /**
+         * Reads container node from /test and verifies that it contains test
+         * node
+         */
+        Optional<NormalizedNode<?, ?>> potentialTest = modificationTree.readNode(TestModel.TEST_PATH);
+        ContainerNode containerTest = assertPresentAndType(potentialTest, ContainerNode.class);
+
+        /**
+         *
+         * Gets list from returned snapshot of /test and verifies it contains
+         * outer-list
+         *
+         */
+        assertPresentAndType(containerTest.getChild(new NodeIdentifier(TestModel.OUTER_LIST_QNAME)), MapNode.class);
+
+    }
+
+    @Test
+    public void writeSubtreeReadChildren() {
+        DataTreeModification modificationTree = createEmptyModificationTree();
+        modificationTree.write(TestModel.TEST_PATH, createTestContainer());
+        Optional<NormalizedNode<?, ?>> potential = modificationTree.readNode(TWO_TWO_PATH);
+        assertPresentAndType(potential, MapEntryNode.class);
+    }
+
+    @Test
+    public void writeSubtreeDeleteChildren() {
+        DataTreeModification modificationTree = createEmptyModificationTree();
+        modificationTree.write(TestModel.TEST_PATH, createTestContainer());
+
+        // We verify data are present
+        Optional<NormalizedNode<?, ?>> potentialBeforeDelete = modificationTree.readNode(TWO_TWO_PATH);
+        assertPresentAndType(potentialBeforeDelete, MapEntryNode.class);
+
+        modificationTree.delete(TWO_TWO_PATH);
+        Optional<NormalizedNode<?, ?>> potentialAfterDelete = modificationTree.readNode(TWO_TWO_PATH);
+        assertFalse(potentialAfterDelete.isPresent());
+
+    }
+
+    private static <T> T assertPresentAndType(final Optional<?> potential, final Class<T> type) {
+        assertNotNull(potential);
+        assertTrue(potential.isPresent());
+        assertTrue(type.isInstance(potential.get()));
+        return type.cast(potential.get());
+    }
+
+}
diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperationRoot.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperationRoot.java
new file mode 100644 (file)
index 0000000..bf3ece3
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class SchemaAwareApplyOperationRoot extends DataNodeContainerModificationStrategy<ContainerSchemaNode> {
+    private final SchemaContext context;
+
+    public SchemaAwareApplyOperationRoot(final SchemaContext context) {
+        super(context,ContainerNode.class);
+        this.context = context;
+    }
+
+    public SchemaContext getContext() {
+        return context;
+    }
+
+    @Override
+    public String toString() {
+        return "SchemaAwareApplyOperationRoot [context=" + context + "]";
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    protected DataContainerNodeBuilder createBuilder(NormalizedNode<?, ?> original) {
+        return ImmutableContainerNodeBuilder.create((ContainerNode) original);
+    }
+}
diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/TestModel.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/TestModel.java
new file mode 100644 (file)
index 0000000..5c81b6c
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014 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.schema.tree;
+
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Set;
+
+public class TestModel {
+
+    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 InstanceIdentifier TEST_PATH = InstanceIdentifier.of(TEST_QNAME);
+    public static final InstanceIdentifier OUTER_LIST_PATH = InstanceIdentifier.builder(TEST_PATH).node(OUTER_LIST_QNAME).build();
+    public static final QName TWO_QNAME = QName.create(TEST_QNAME, "two");
+    public static final QName THREE_QNAME = QName.create(TEST_QNAME, "three");
+
+
+    public static final InputStream getDatastoreTestInputStream() {
+        return getInputStream(DATASTORE_TEST_YANG);
+    }
+
+    private static InputStream getInputStream(final String resourceName) {
+        return TestModel.class.getResourceAsStream(DATASTORE_TEST_YANG);
+    }
+
+    public static SchemaContext createTestContext() {
+        YangParserImpl parser = new YangParserImpl();
+        Set<Module> modules = parser.parseYangModelsFromStreams(Collections.singletonList(getDatastoreTestInputStream()));
+        return parser.resolveSchemaContext(modules);
+    }
+}
diff --git a/yang/yang-data-impl/src/test/resources/odl-datastore-test.yang b/yang/yang-data-impl/src/test/resources/odl-datastore-test.yang
new file mode 100644 (file)
index 0000000..17541fe
--- /dev/null
@@ -0,0 +1,42 @@
+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 uint16;
+            }
+            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 string;
+                }
+                leaf value {
+                    type string;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file