fbfb499c6dcd484caccebc9a41014f54c6dc8c1b
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / OperationWithModification.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
9
10 import com.google.common.base.Function;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
14 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
15 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
16 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
17 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
18
19 final class OperationWithModification {
20     private static final Function<TreeNode, NormalizedNode<?, ?>> READ_DATA = new Function<TreeNode, NormalizedNode<?, ?>>() {
21         @Override
22         public NormalizedNode<?, ?> apply(final TreeNode input) {
23             return input.getData();
24         }
25     };
26
27     private final ModifiedNode modification;
28     private final ModificationApplyOperation applyOperation;
29
30     private OperationWithModification(final ModificationApplyOperation op, final ModifiedNode mod) {
31         this.modification = Preconditions.checkNotNull(mod);
32         this.applyOperation = Preconditions.checkNotNull(op);
33     }
34
35     void write(final NormalizedNode<?, ?> value) {
36         modification.write(value);
37         /**
38          * Fast validation of structure, full validation on written data will be run during seal.
39          */
40         applyOperation.verifyStructure(value, false);
41     }
42
43     private void recursiveMerge(final NormalizedNode<?,?> data, final Version version) {
44         if (data instanceof NormalizedNodeContainer) {
45             @SuppressWarnings({ "rawtypes", "unchecked" })
46             final NormalizedNodeContainer<?,?, NormalizedNode<PathArgument, ?>> dataContainer =
47                     (NormalizedNodeContainer) data;
48
49             /*
50              * If there was write before on this node and it is of NormalizedNodeContainer type merge would overwrite
51              * our changes. So we create write modifications from data children to retain children created by previous
52              * write operation. These writes will then be pushed down in the tree while there are merge modifications
53              * on these children
54              */
55             if (modification.getOperation() == LogicalOperation.WRITE) {
56                 @SuppressWarnings({ "rawtypes", "unchecked" })
57                 final NormalizedNodeContainer<?,?, NormalizedNode<PathArgument, ?>> oldDataContainer =
58                         (NormalizedNodeContainer) modification.getWrittenValue();
59                 for (final NormalizedNode<PathArgument, ?> c : oldDataContainer.getValue()) {
60                     final PathArgument childId = c.getIdentifier();
61
62                     // Acquire the child operation type if available, fall back to NONE
63                     final Optional<ModifiedNode> maybeChild = modification.getChild(childId);
64                     if (maybeChild.isPresent()) {
65                         final ModifiedNode child = maybeChild.get();
66                         final LogicalOperation op = child.getOperation();
67                         if (op == LogicalOperation.TOUCH || op == LogicalOperation.NONE) {
68                             child.pushWrite(c);
69                         }
70                     } else {
71                         // Not present, issue a write
72                         forChild(childId, version).write(c);
73                     }
74                 }
75             }
76             for (final NormalizedNode<PathArgument, ?> child : dataContainer.getValue()) {
77                 final PathArgument childId = child.getIdentifier();
78                 forChild(childId, version).recursiveMerge(child, version);
79             }
80         }
81
82         modification.merge(data);
83     }
84
85     void merge(final NormalizedNode<?, ?> data, final Version version) {
86         /*
87          * A merge operation will end up overwriting parts of the tree, retaining others. We want to
88          * make sure we do not validate the complete resulting structure, but rather just what was
89          * written. In order to do that, we first pretend the data was written, run verification and
90          * then perform the merge -- with the explicit assumption that adding the newly-validated
91          * data with the previously-validated data will not result in invalid data.
92          *
93          * FIXME: Should be this moved to recursive merge and run for each node?
94          */
95         applyOperation.verifyStructure(data, false);
96         recursiveMerge(data, version);
97     }
98
99     void delete() {
100         modification.delete();
101     }
102
103     /**
104      * Read a particular child. If the child has been modified and does not have a stable
105      * view, one will we instantiated with specified version.
106      *
107      * @param child
108      * @param version
109      * @return
110      */
111     Optional<NormalizedNode<?, ?>> read(final PathArgument child, final Version version) {
112         final Optional<ModifiedNode> maybeChild = modification.getChild(child);
113         if (maybeChild.isPresent()) {
114             final ModifiedNode childNode = maybeChild.get();
115
116             Optional<TreeNode> snapshot = childNode.getSnapshot();
117             if (snapshot == null) {
118                 // Snapshot is not present, force instantiation
119                 snapshot = applyOperation.getChild(child).get().apply(childNode, childNode.getOriginal(), version);
120             }
121
122             return snapshot.transform(READ_DATA);
123         }
124
125         Optional<TreeNode> snapshot = modification.getSnapshot();
126         if (snapshot == null) {
127             snapshot = apply(modification.getOriginal(), version);
128         }
129
130         if (snapshot.isPresent()) {
131             return snapshot.get().getChild(child).transform(READ_DATA);
132         }
133
134         return Optional.absent();
135     }
136
137     public ModifiedNode getModification() {
138         return modification;
139     }
140
141     public ModificationApplyOperation getApplyOperation() {
142         return applyOperation;
143     }
144
145     public Optional<TreeNode> apply(final Optional<TreeNode> data, final Version version) {
146         return applyOperation.apply(modification, data, version);
147     }
148
149     public static OperationWithModification from(final ModificationApplyOperation operation,
150             final ModifiedNode modification) {
151         return new OperationWithModification(operation, modification);
152     }
153
154     private OperationWithModification forChild(final PathArgument childId, final Version version) {
155         final Optional<ModificationApplyOperation> maybeChildOp = applyOperation.getChild(childId);
156         Preconditions.checkArgument(maybeChildOp.isPresent(),
157             "Attempted to apply operation to non-existent child %s", childId);
158
159         final ModificationApplyOperation childOp = maybeChildOp.get();
160         final ModifiedNode childMod = modification.modifyChild(childId, childOp.getChildPolicy(), version);
161
162         return from(childOp, childMod);
163     }
164 }