Merge "Bug 1475: Correctly handle case of typedef that extends boolean"
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / ModifiedNode.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.Optional;
11 import com.google.common.base.Predicate;
12 import org.opendaylight.yangtools.concepts.Identifiable;
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.tree.ModificationType;
16 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
17 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
18
19 import javax.annotation.concurrent.GuardedBy;
20 import java.util.LinkedHashMap;
21 import java.util.Map;
22
23 /**
24  * Node Modification Node and Tree
25  *
26  * Tree which structurally resembles data tree and captures client modifications
27  * to the data store tree.
28  *
29  * This tree is lazily created and populated via {@link #modifyChild(PathArgument)}
30  * and {@link StoreMetadataNode} which represents original state {@link #getOriginal()}.
31  */
32 final class ModifiedNode implements StoreTreeNode<ModifiedNode>, Identifiable<PathArgument>, NodeModification {
33
34     public static final Predicate<ModifiedNode> IS_TERMINAL_PREDICATE = new Predicate<ModifiedNode>() {
35         @Override
36         public boolean apply(final ModifiedNode input) {
37             switch (input.getType()) {
38             case DELETE:
39             case MERGE:
40             case WRITE:
41                 return true;
42             case SUBTREE_MODIFIED:
43             case UNMODIFIED:
44                 return false;
45             }
46
47             throw new IllegalArgumentException(String.format("Unhandled modification type %s", input.getType()));
48         }
49     };
50
51     private final Map<PathArgument, ModifiedNode> children = new LinkedHashMap<>();
52     private final Optional<TreeNode> original;
53     private final PathArgument identifier;
54     private ModificationType modificationType = ModificationType.UNMODIFIED;
55     private Optional<TreeNode> snapshotCache;
56     private NormalizedNode<?, ?> value;
57
58     private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original) {
59         this.identifier = identifier;
60         this.original = original;
61     }
62
63     /**
64      *
65      *
66      * @return
67      */
68     public NormalizedNode<?, ?> getWrittenValue() {
69         return value;
70     }
71
72     @Override
73     public PathArgument getIdentifier() {
74         return identifier;
75     }
76
77     /**
78      *
79      * Returns original store metadata
80      * @return original store metadata
81      */
82     @Override
83     public Optional<TreeNode> getOriginal() {
84         return original;
85     }
86
87     /**
88      * Returns modification type
89      *
90      * @return modification type
91      */
92     @Override
93     public ModificationType getType() {
94         return modificationType;
95     }
96
97     /**
98      *
99      * Returns child modification if child was modified
100      *
101      * @return Child modification if direct child or it's subtree
102      *  was modified.
103      *
104      */
105     @Override
106     public Optional<ModifiedNode> getChild(final PathArgument child) {
107         return Optional.<ModifiedNode> fromNullable(children.get(child));
108     }
109
110     /**
111      *
112      * Returns child modification if child was modified, creates {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.data.ModifiedNode}
113      * for child otherwise.
114      *
115      * If this node's {@link ModificationType} is {@link ModificationType#UNMODIFIED}
116      * changes modification type to {@link ModificationType#SUBTREE_MODIFIED}
117      *
118      * @param child
119      * @return {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.data.ModifiedNode} for specified child, with {@link #getOriginal()}
120      *         containing child metadata if child was present in original data.
121      */
122     public ModifiedNode modifyChild(final PathArgument child) {
123         clearSnapshot();
124         if (modificationType == ModificationType.UNMODIFIED) {
125             updateModificationType(ModificationType.SUBTREE_MODIFIED);
126         }
127         final ModifiedNode potential = children.get(child);
128         if (potential != null) {
129             return potential;
130         }
131
132         final Optional<TreeNode> currentMetadata;
133         if (original.isPresent()) {
134             final TreeNode orig = original.get();
135             currentMetadata = orig.getChild(child);
136         } else {
137             currentMetadata = Optional.absent();
138         }
139
140         ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata);
141         children.put(child, newlyCreated);
142         return newlyCreated;
143     }
144
145     /**
146      *
147      * Returns all recorded direct child modification
148      *
149      * @return all recorded direct child modifications
150      */
151     @Override
152     public Iterable<ModifiedNode> getChildren() {
153         return children.values();
154     }
155
156     /**
157      *
158      * Records a delete for associated node.
159      *
160      */
161     public void delete() {
162         clearSnapshot();
163         updateModificationType(ModificationType.DELETE);
164         children.clear();
165         this.value = null;
166     }
167
168     /**
169      *
170      * Records a write for associated node.
171      *
172      * @param value
173      */
174     public void write(final NormalizedNode<?, ?> value) {
175         clearSnapshot();
176         updateModificationType(ModificationType.WRITE);
177         children.clear();
178         this.value = value;
179     }
180
181     public void merge(final NormalizedNode<?, ?> data) {
182         clearSnapshot();
183         updateModificationType(ModificationType.MERGE);
184         // FIXME: Probably merge with previous value.
185         this.value = data;
186     }
187
188     void seal() {
189         clearSnapshot();
190         for (ModifiedNode child : children.values()) {
191             child.seal();
192         }
193     }
194
195     private void clearSnapshot() {
196         snapshotCache = null;
197     }
198
199     public Optional<TreeNode> storeSnapshot(final Optional<TreeNode> snapshot) {
200         snapshotCache = snapshot;
201         return snapshot;
202     }
203
204     public Optional<Optional<TreeNode>> getSnapshotCache() {
205         return Optional.fromNullable(snapshotCache);
206     }
207
208     @GuardedBy("this")
209     private void updateModificationType(final ModificationType type) {
210         modificationType = type;
211         clearSnapshot();
212     }
213
214     @Override
215     public String toString() {
216         return "NodeModification [identifier=" + identifier + ", modificationType="
217                 + modificationType + ", childModification=" + children + "]";
218     }
219
220     public static ModifiedNode createUnmodified(final TreeNode metadataTree) {
221         return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree));
222     }
223 }