Add LazyDOMQueryResult
[mdsal.git] / dom / mdsal-dom-spi / src / main / java / org / opendaylight / mdsal / dom / spi / query / LazyDOMQueryResultIterator.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.mdsal.dom.spi.query;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import java.util.AbstractMap.SimpleImmutableEntry;
14 import java.util.ArrayDeque;
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Map.Entry;
19 import java.util.Optional;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.opendaylight.mdsal.dom.api.query.DOMQuery;
22 import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
26 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
30
31 @NonNullByDefault
32 final class LazyDOMQueryResultIterator implements Iterator<Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> {
33     // Current data item
34     private final ArrayDeque<NormalizedNode<?, ?>> currentData = new ArrayDeque<>();
35     // Absolute path from root of current data item
36     private final ArrayDeque<PathArgument> currentPath;
37     // Steps remaining in the select part of the query
38     private final ArrayDeque<PathArgument> selectSteps;
39     // The query which needs to be executed
40     private final DOMQuery query;
41
42     // FIXME: MDSAL-610: this needs to be eliminated
43     private final Iterator<Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> iter;
44
45     LazyDOMQueryResultIterator(final DOMQuery query, final NormalizedNode<?, ?> queryRoot) {
46         this.query = requireNonNull(query);
47         currentPath = new ArrayDeque<>(query.getRoot().getPathArguments());
48         selectSteps = new ArrayDeque<>(query.getSelect().getPathArguments());
49         currentData.push(queryRoot);
50
51         // FIXME: MDSAL-610: this is a recursive algo, filling provided list. Turn it around into a state mutator.
52         final List<Entry<YangInstanceIdentifier, NormalizedNode<?,?>>> result = new ArrayList<>();
53         evalPath(result);
54         this.iter = result.iterator();
55         currentPath.clear();
56         selectSteps.clear();
57     }
58
59     @Override
60     public boolean hasNext() {
61         return iter.hasNext();
62     }
63
64     @Override
65     public Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> next() {
66         return iter.next();
67     }
68
69     private void evalPath(final List<Entry<YangInstanceIdentifier, NormalizedNode<?,?>>> result) {
70         final NormalizedNode<?, ?> data = currentData.pop();
71         final PathArgument next = selectSteps.poll();
72         if (next == null) {
73             if (matches(data, query)) {
74                 result.add(new SimpleImmutableEntry<>(YangInstanceIdentifier.create(currentPath), data));
75             }
76             return;
77         }
78
79         if (data instanceof MapNode && next instanceof NodeIdentifier) {
80             checkArgument(data.getIdentifier().equals(next), "Unexpected step %s", next);
81             for (MapEntryNode child : ((MapNode) data).getValue()) {
82                 evalChild(result, child);
83             }
84         } else {
85             NormalizedNodes.getDirectChild(data, next).ifPresent(child -> evalChild(result, child));
86         }
87         selectSteps.push(next);
88     }
89
90     private void evalChild(final List<Entry<YangInstanceIdentifier, NormalizedNode<?,?>>> result,
91             final NormalizedNode<?, ?> child) {
92         currentPath.addLast(child.getIdentifier());
93         currentData.push(child);
94         evalPath(result);
95         currentPath.removeLast();
96     }
97
98     static boolean matches(final NormalizedNode<?, ?> data, final DOMQuery query) {
99         for (DOMQueryPredicate pred : query.getPredicates()) {
100             // Okay, now we need to deal with predicates, but do it in a smart fashion, so we do not end up iterating
101             // all over the place. Typically we will be matching just a leaf.
102             final YangInstanceIdentifier path = pred.getPath();
103             final Optional<NormalizedNode<?, ?>> node;
104             if (path.coerceParent().isEmpty()) {
105                 node = NormalizedNodes.getDirectChild(data, path.getLastPathArgument());
106             } else {
107                 node = NormalizedNodes.findNode(data, path);
108             }
109
110             if (!pred.test(node.orElse(null))) {
111                 return false;
112             }
113         }
114         return true;
115     }
116 }