Populate data/ hierarchy
[yangtools.git] / data / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / schema / tree / DataTreeCandidates.java
1 /*
2  * Copyright (c) 2015 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.api.schema.tree;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Optional;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
23 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 /**
28  * Utility class holding methods useful when dealing with {@link DataTreeCandidate} instances.
29  */
30 @Beta
31 public final class DataTreeCandidates {
32     private static final Logger LOG = LoggerFactory.getLogger(DataTreeCandidates.class);
33
34     private DataTreeCandidates() {
35         // Hidden on purpose
36     }
37
38     public static @NonNull DataTreeCandidate newDataTreeCandidate(final YangInstanceIdentifier rootPath,
39                                                                   final DataTreeCandidateNode rootNode) {
40         return new DefaultDataTreeCandidate(rootPath, rootNode);
41     }
42
43     public static @NonNull DataTreeCandidate fromNormalizedNode(final YangInstanceIdentifier rootPath,
44                                                                 final NormalizedNode node) {
45         return new DefaultDataTreeCandidate(rootPath, new NormalizedNodeDataTreeCandidateNode(node));
46     }
47
48     public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidate candidate) {
49         DataTreeCandidateNodes.applyToCursor(cursor, candidate.getRootNode());
50     }
51
52     public static void applyToModification(final DataTreeModification modification, final DataTreeCandidate candidate) {
53         if (modification instanceof CursorAwareDataTreeModification) {
54             applyToCursorAwareModification((CursorAwareDataTreeModification) modification, candidate);
55             return;
56         }
57
58         final DataTreeCandidateNode node = candidate.getRootNode();
59         final YangInstanceIdentifier path = candidate.getRootPath();
60         switch (node.getModificationType()) {
61             case DELETE:
62                 modification.delete(path);
63                 LOG.debug("Modification {} deleted path {}", modification, path);
64                 break;
65             case SUBTREE_MODIFIED:
66                 LOG.debug("Modification {} modified path {}", modification, path);
67
68                 NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
69                 do {
70                     iterator = iterator.next(modification);
71                 } while (iterator != null);
72                 break;
73             case UNMODIFIED:
74                 LOG.debug("Modification {} unmodified path {}", modification, path);
75                 // No-op
76                 break;
77             case WRITE:
78                 modification.write(path, node.getDataAfter().get());
79                 LOG.debug("Modification {} written path {}", modification, path);
80                 break;
81             default:
82                 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
83         }
84     }
85
86     /**
87      * Compress a list of DataTreeCandidates into a single DataTreeCandidate. The resulting candidate is a summarization
88      * of changes recorded in the input candidates.
89      *
90      * @param candidates Input list, must be non-empty
91      * @return Summarized DataTreeCandidate
92      * @throws IllegalArgumentException if candidates is empty, or contains candidates with mismatched root path
93      * @throws NullPointerException     if {@code candidates} is null or contains a null entry
94      */
95     public static @NonNull DataTreeCandidate aggregate(@NonNull final List<? extends DataTreeCandidate> candidates) {
96         final Iterator<? extends DataTreeCandidate> it = candidates.iterator();
97         checkArgument(it.hasNext(), "Input must not be empty");
98         final DataTreeCandidate first = requireNonNull(it.next(), "Input must not contain null entries");
99         if (!it.hasNext()) {
100             // Short-circuit
101             return first;
102         }
103         final YangInstanceIdentifier rootPath = first.getRootPath();
104         final List<DataTreeCandidateNode> roots = new ArrayList<>(candidates.size());
105         roots.add(first.getRootNode());
106         it.forEachRemaining(candidate -> {
107             final YangInstanceIdentifier root = candidate.getRootPath();
108             checkArgument(rootPath.equals(root), "Expecting root path %s, encountered %s", rootPath, root);
109             roots.add(candidate.getRootNode());
110         });
111         return DataTreeCandidates.newDataTreeCandidate(rootPath, fastCompressNode(roots.get(0), roots));
112     }
113
114     private static DataTreeCandidateNode fastCompressNode(final DataTreeCandidateNode first,
115                                                           final List<DataTreeCandidateNode> input) {
116         final DataTreeCandidateNode last = input.get(input.size() - 1);
117         ModificationType nodeModification = last.getModificationType();
118         Optional<NormalizedNode> dataBefore = first.getDataBefore();
119         Optional<NormalizedNode> dataAfter = last.getDataAfter();
120         switch (nodeModification) {
121             case DELETE:
122                 ModificationType previous = first.getModificationType();
123                 // Check if node had data before
124                 if (previous == ModificationType.DELETE
125                         || previous == ModificationType.DISAPPEARED
126                         || previous == ModificationType.UNMODIFIED && dataBefore.isEmpty()) {
127                     illegalModification(ModificationType.DELETE, ModificationType.DELETE);
128                 }
129                 if (dataBefore.isEmpty()) {
130                     return new TerminalDataTreeCandidateNode(null, ModificationType.UNMODIFIED, null, null);
131                 }
132                 return new TerminalDataTreeCandidateNode(null, nodeModification, dataBefore.get(), null);
133             case WRITE:
134                 return new TerminalDataTreeCandidateNode(null, nodeModification, dataBefore.orElse(null),
135                         dataAfter.orElseThrow());
136             case APPEARED:
137             case DISAPPEARED:
138             case SUBTREE_MODIFIED:
139             case UNMODIFIED:
140                 // No luck, we need to iterate
141                 return slowCompressNodes(first, input);
142             default:
143                 throw new IllegalStateException("Unsupported modification type " + nodeModification);
144         }
145     }
146
147     private static DataTreeCandidateNode slowCompressNodes(final DataTreeCandidateNode first,
148                                                            final List<DataTreeCandidateNode> input) {
149         // finalNode contains summarized changes
150         TerminalDataTreeCandidateNode finalNode = new TerminalDataTreeCandidateNode(
151                 null, first.getDataBefore().orElse(null));
152         input.forEach(node -> compressNode(finalNode, node, null));
153         finalNode.setAfter(input.get(input.size() - 1).getDataAfter().orElse(null));
154         return cleanUpTree(finalNode);
155     }
156
157     private static void compressNode(final TerminalDataTreeCandidateNode finalNode, final DataTreeCandidateNode node,
158                                      final PathArgument parent) {
159         PathArgument identifier;
160         try {
161             identifier = node.getIdentifier();
162         } catch (IllegalStateException e) {
163             identifier = null;
164         }
165         // Check if finalNode has stored any changes for node
166         if (finalNode.getNode(identifier).isEmpty()) {
167             TerminalDataTreeCandidateNode parentNode = finalNode.getNode(parent)
168                     .orElseThrow(() -> new IllegalArgumentException("No node found for " + parent + " identifier"));
169             TerminalDataTreeCandidateNode childNode = new TerminalDataTreeCandidateNode(
170                     identifier,
171                     node.getDataBefore().orElse(null),
172                     parentNode);
173             parentNode.addChildNode(childNode);
174         }
175
176         ModificationType nodeModification = node.getModificationType();
177         switch (nodeModification) {
178             case UNMODIFIED:
179                 // If node is unmodified there is no need iterate through its child nodes
180                 break;
181             case WRITE:
182             case DELETE:
183             case APPEARED:
184             case DISAPPEARED:
185             case SUBTREE_MODIFIED:
186                 finalNode.setModification(identifier,
187                         compressModifications(finalNode.getModification(identifier), nodeModification,
188                                 finalNode.getDataAfter(identifier).isEmpty()));
189                 finalNode.setData(identifier, node.getDataAfter().orElse(null));
190
191                 for (DataTreeCandidateNode child : node.getChildNodes()) {
192                     compressNode(finalNode, child, identifier);
193                 }
194                 break;
195             default:
196                 throw new IllegalStateException("Unsupported modification type " + nodeModification);
197         }
198     }
199
200     // Removes redundant changes
201     private static DataTreeCandidateNode cleanUpTree(final TerminalDataTreeCandidateNode finalNode) {
202         return cleanUpTree(finalNode, finalNode);
203     }
204
205     // Compare data before and after in order to find modified nodes without actual changes
206     private static DataTreeCandidateNode cleanUpTree(final TerminalDataTreeCandidateNode finalNode,
207                                                      final TerminalDataTreeCandidateNode node) {
208         PathArgument identifier = node.getIdentifier();
209         ModificationType nodeModification = node.getModificationType();
210         Collection<DataTreeCandidateNode> childNodes = node.getChildNodes();
211         for (Iterator<DataTreeCandidateNode> iterator = childNodes.iterator(); iterator.hasNext(); ) {
212             cleanUpTree(finalNode, (TerminalDataTreeCandidateNode) iterator.next());
213         }
214         Optional<NormalizedNode> dataBefore = finalNode.getDataBefore(identifier);
215
216         switch (nodeModification) {
217             case UNMODIFIED:
218                 finalNode.deleteNode(identifier);
219                 return finalNode;
220             case WRITE:
221                 return finalNode;
222             case DELETE:
223                 if (dataBefore.isEmpty()) {
224                     finalNode.deleteNode(identifier);
225                 }
226                 return finalNode;
227             case APPEARED:
228                 if (dataBefore.isPresent()) {
229                     illegalModification(ModificationType.APPEARED, ModificationType.WRITE);
230                 }
231                 if (childNodes.isEmpty()) {
232                     finalNode.deleteNode(identifier);
233                 }
234                 return finalNode;
235             case DISAPPEARED:
236                 if (dataBefore.isEmpty() || childNodes.isEmpty()) {
237                     finalNode.deleteNode(identifier);
238                 }
239                 return finalNode;
240             case SUBTREE_MODIFIED:
241                 if (dataBefore.isEmpty()) {
242                     illegalModification(ModificationType.SUBTREE_MODIFIED, ModificationType.DELETE);
243                 }
244                 if (childNodes.isEmpty()) {
245                     finalNode.deleteNode(identifier);
246                 }
247                 return finalNode;
248             default:
249                 throw new IllegalStateException("Unsupported modification type " + nodeModification);
250         }
251     }
252
253     private static ModificationType compressModifications(final ModificationType firstModification,
254                                                           final ModificationType secondModification,
255                                                           final boolean hasNoDataBefore) {
256         switch (firstModification) {
257             case UNMODIFIED:
258                 if (hasNoDataBefore) {
259                     switch (secondModification) {
260                         case UNMODIFIED:
261                         case WRITE:
262                         case APPEARED:
263                             return secondModification;
264                         case DELETE:
265                             return illegalModification(ModificationType.DELETE, ModificationType.DELETE);
266                         case SUBTREE_MODIFIED:
267                             return illegalModification(ModificationType.SUBTREE_MODIFIED, ModificationType.DELETE);
268                         case DISAPPEARED:
269                             return illegalModification(ModificationType.DISAPPEARED, ModificationType.DELETE);
270                         default:
271                             throw new IllegalStateException("Unsupported modification type " + secondModification);
272                     }
273                 }
274                 if (secondModification == ModificationType.APPEARED) {
275                     return illegalModification(ModificationType.APPEARED, ModificationType.WRITE);
276                 }
277                 return secondModification;
278             case WRITE:
279                 switch (secondModification) {
280                     case UNMODIFIED:
281                     case WRITE:
282                     case SUBTREE_MODIFIED:
283                         return ModificationType.WRITE;
284                     case DELETE:
285                         return ModificationType.DELETE;
286                     case DISAPPEARED:
287                         return ModificationType.DISAPPEARED;
288                     case APPEARED:
289                         return illegalModification(ModificationType.APPEARED, firstModification);
290                     default:
291                         throw new IllegalStateException("Unsupported modification type " + secondModification);
292                 }
293             case DELETE:
294                 switch (secondModification) {
295                     case UNMODIFIED:
296                         return ModificationType.DELETE;
297                     case WRITE:
298                     case APPEARED:
299                         return ModificationType.WRITE;
300                     case DELETE:
301                         return illegalModification(ModificationType.DELETE, firstModification);
302                     case DISAPPEARED:
303                         return illegalModification(ModificationType.DISAPPEARED, firstModification);
304                     case SUBTREE_MODIFIED:
305                         return illegalModification(ModificationType.SUBTREE_MODIFIED, firstModification);
306                     default:
307                         throw new IllegalStateException("Unsupported modification type " + secondModification);
308                 }
309             case APPEARED:
310                 switch (secondModification) {
311                     case UNMODIFIED:
312                     case SUBTREE_MODIFIED:
313                         return ModificationType.APPEARED;
314                     case DELETE:
315                     case DISAPPEARED:
316                         return ModificationType.UNMODIFIED;
317                     case WRITE:
318                         return ModificationType.WRITE;
319                     case APPEARED:
320                         return illegalModification(ModificationType.APPEARED, firstModification);
321                     default:
322                         throw new IllegalStateException("Unsupported modification type " + secondModification);
323                 }
324             case DISAPPEARED:
325                 switch (secondModification) {
326                     case UNMODIFIED:
327                     case WRITE:
328                         return secondModification;
329                     case APPEARED:
330                         return ModificationType.SUBTREE_MODIFIED;
331                     case DELETE:
332                         return illegalModification(ModificationType.DELETE, firstModification);
333                     case DISAPPEARED:
334                         return illegalModification(ModificationType.DISAPPEARED, firstModification);
335
336                     case SUBTREE_MODIFIED:
337                         return illegalModification(ModificationType.SUBTREE_MODIFIED, firstModification);
338                     default:
339                         throw new IllegalStateException("Unsupported modification type " + secondModification);
340                 }
341             case SUBTREE_MODIFIED:
342                 switch (secondModification) {
343                     case UNMODIFIED:
344                     case SUBTREE_MODIFIED:
345                         return ModificationType.SUBTREE_MODIFIED;
346                     case WRITE:
347                     case DELETE:
348                     case DISAPPEARED:
349                         return secondModification;
350                     case APPEARED:
351                         return illegalModification(ModificationType.APPEARED, firstModification);
352                     default:
353                         throw new IllegalStateException("Unsupported modification type " + secondModification);
354                 }
355             default:
356                 throw new IllegalStateException("Unsupported modification type " + secondModification);
357         }
358     }
359
360     private static ModificationType illegalModification(final ModificationType first, final ModificationType second) {
361         throw new IllegalArgumentException(first + " modification event on " + second + " node");
362     }
363
364     private static void applyToCursorAwareModification(final CursorAwareDataTreeModification modification,
365                                                        final DataTreeCandidate candidate) {
366         final YangInstanceIdentifier candidatePath = candidate.getRootPath();
367         final YangInstanceIdentifier parent = candidatePath.getParent();
368         if (parent == null) {
369             try (DataTreeModificationCursor cursor = modification.openCursor()) {
370                 DataTreeCandidateNodes.applyRootToCursor(cursor, candidate.getRootNode());
371             }
372         } else {
373             try (DataTreeModificationCursor cursor = modification.openCursor(parent).orElseThrow()) {
374                 DataTreeCandidateNodes.applyRootedNodeToCursor(cursor, candidatePath, candidate.getRootNode());
375             }
376         }
377     }
378
379     private static final class NodeIterator {
380         private final Iterator<DataTreeCandidateNode> iterator;
381         private final YangInstanceIdentifier path;
382         private final NodeIterator parent;
383
384         NodeIterator(final @Nullable NodeIterator parent, final YangInstanceIdentifier path,
385                      final Iterator<DataTreeCandidateNode> iterator) {
386             this.iterator = requireNonNull(iterator);
387             this.path = requireNonNull(path);
388             this.parent = parent;
389         }
390
391         NodeIterator next(final DataTreeModification modification) {
392             while (iterator.hasNext()) {
393                 final DataTreeCandidateNode node = iterator.next();
394                 final YangInstanceIdentifier child = path.node(node.getIdentifier());
395
396                 switch (node.getModificationType()) {
397                     case DELETE:
398                         modification.delete(child);
399                         LOG.debug("Modification {} deleted path {}", modification, child);
400                         break;
401                     case APPEARED:
402                     case DISAPPEARED:
403                     case SUBTREE_MODIFIED:
404                         LOG.debug("Modification {} modified path {}", modification, child);
405                         return new NodeIterator(this, child, node.getChildNodes().iterator());
406                     case UNMODIFIED:
407                         LOG.debug("Modification {} unmodified path {}", modification, child);
408                         // No-op
409                         break;
410                     case WRITE:
411                         modification.write(child, node.getDataAfter().get());
412                         LOG.debug("Modification {} written path {}", modification, child);
413                         break;
414                     default:
415                         throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
416                 }
417             }
418             return parent;
419         }
420     }
421 }