Remove Augmentation{Identifier,Node}
[yangtools.git] / data / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / schema / stream / YangInstanceIdentifierWriter.java
1 /*
2  * Copyright (c) 2022 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.yangtools.yang.data.api.schema.stream;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.Iterables;
15 import java.io.IOException;
16 import java.util.List;
17 import javax.xml.transform.dom.DOMSource;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
20 import org.opendaylight.yangtools.yang.common.QName;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
25 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
26 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
30 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
31 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
35
36 /**
37  * Utility for emitting a {@link YangInstanceIdentifier} into a {@link NormalizedNodeStreamWriter} as a set of
38  * {@code startXXXNode} events. An example of usage would be something along the lines of:
39  * <pre>
40  *   <code>
41  *       YangModelContext
42  *       YangInstanceIdentifier id;
43  *       var result = new NormalizedNodeResult();
44  *       try (var writer = ImmutableNormalizedNodeStreamWriter.from(result)) {
45  *           try (var iidWriter = YangInstanceIdentifierWriter.open(writer, ctx, id)) {
46  *               // Here the state of 'writer' reflects the nodes in 'id'
47  *           }
48  *           // Here the writer is back to its initial state
49  *       }
50  *
51  *       // NormalizedNode result, including the structure created from YangInstanceIdentifier
52  *       var node = result.getResult();
53  *   </code>
54  * </pre>
55  */
56 public final class YangInstanceIdentifierWriter implements AutoCloseable {
57     private NormalizedNodeStreamWriter writer;
58     private final int endNodeCount;
59
60     private YangInstanceIdentifierWriter(final NormalizedNodeStreamWriter writer, final int endNodeCount) {
61         this.writer = requireNonNull(writer);
62         this.endNodeCount = endNodeCount;
63     }
64
65     /**
66      * Open a writer, emitting events in target {@link NormalizedNodeStreamWriter}.
67      *
68      * @param writer Writer to enter
69      * @param root Root container
70      * @param path Path to enter
71      * @return A writer instance
72      * @throws IOException if the path cannot be entered
73      */
74     public static @NonNull YangInstanceIdentifierWriter open(final NormalizedNodeStreamWriter writer,
75             final DataNodeContainer root, final YangInstanceIdentifier path) throws IOException {
76         final var it = path.getPathArguments().iterator();
77         if (!it.hasNext()) {
78             return new YangInstanceIdentifierWriter(writer, 0);
79         }
80
81         // State tracking
82         int endNodes = 0;
83         Object parent = root;
84         boolean reuse = false;
85         boolean terminal = false;
86
87         do {
88             if (terminal) {
89                 throw new IOException(parent + " is a terminal node, cannot resolve " + ImmutableList.copyOf(it));
90             }
91
92             final var arg = it.next();
93             if (arg instanceof NodeWithValue<?> nodeId) {
94                 if (!(parent instanceof LeafListSchemaNode)) {
95                     throw new IOException(parent + " does not support leaf-list entry " + arg);
96                 }
97                 if (!reuse) {
98                     throw new IOException(parent + " is already at its entry, cannot enter " + arg);
99                 }
100
101                 reuse = false;
102                 terminal = true;
103                 writer.startLeafSetEntryNode(nodeId);
104             } else if (arg instanceof NodeIdentifierWithPredicates nodeId) {
105                 if (!(parent instanceof ListSchemaNode list)) {
106                     throw new IOException(parent + " does not support map entry " + arg);
107                 }
108                 if (!reuse) {
109                     throw new IOException(parent + " is already at its entry, cannot enter " + arg);
110                 }
111                 if (!list.getQName().equals(nodeId.getNodeType())) {
112                     throw new IOException(parent + " expects a matching map entry, cannot enter " + arg);
113                 }
114
115                 final var key = list.getKeyDefinition();
116                 if (key.isEmpty()) {
117                     throw new IOException(parent + " does not expect map entry " + arg);
118                 }
119                 if (key.size() != nodeId.size()) {
120                     throw new IOException(parent + " expects " + key.size() + " predicates, cannot use " + arg);
121                 }
122
123                 reuse = false;
124                 writer.startMapEntryNode(normalizePredicates(nodeId, key), 1);
125             } else if (arg instanceof NodeIdentifier nodeId) {
126                 if (reuse) {
127                     if (!(parent instanceof ListSchemaNode list)) {
128                         throw new IOException(parent + " expects an identifiable entry, cannot enter " + arg);
129                     }
130
131                     if (!list.getKeyDefinition().isEmpty()) {
132                         throw new IOException(parent + " expects a map entry, cannot enter " + arg);
133                     }
134                     if (!list.getQName().equals(nodeId.getNodeType())) {
135                         throw new IOException(parent + " expects a matching entry, cannot enter " + arg);
136                     }
137
138                     reuse = false;
139                     writer.startUnkeyedListItem(nodeId, 1);
140                     endNodes++;
141                     continue;
142                 }
143
144                 final DataSchemaNode child;
145                 if (parent instanceof DataNodeContainer container) {
146                     child = container.dataChildByName(nodeId.getNodeType());
147                 } else if (parent instanceof ChoiceSchemaNode choice) {
148                     child = choice.findDataSchemaChild(nodeId.getNodeType()).orElse(null);
149                 } else {
150                     throw new IOException("Unhandled parent " + parent + " when looking up " + arg);
151                 }
152
153                 if (child == null) {
154                     throw new IOException("Failed to find child " + arg + " in parent " + parent);
155                 }
156
157                 // FIXME: check & repair augmentations (brr!)
158
159                 if (child instanceof ContainerLike) {
160                     parent = child;
161                     writer.startContainerNode(nodeId, 1);
162                 } else if (child instanceof ListSchemaNode list) {
163                     parent = child;
164                     reuse = true;
165                     if (list.getKeyDefinition().isEmpty()) {
166                         writer.startUnkeyedList(nodeId, 1);
167                     } else if (list.isUserOrdered()) {
168                         writer.startOrderedMapNode(nodeId, 1);
169                     } else {
170                         writer.startMapNode(nodeId, 1);
171                     }
172                 } else if (child instanceof LeafSchemaNode) {
173                     parent = child;
174                     terminal = true;
175                     writer.startLeafNode(nodeId);
176                 } else if (child instanceof ChoiceSchemaNode) {
177                     parent = child;
178                     writer.startChoiceNode(nodeId, 1);
179                 } else if (child instanceof LeafListSchemaNode leafList) {
180                     parent = child;
181                     reuse = true;
182                     if (leafList.isUserOrdered()) {
183                         writer.startOrderedLeafSet(nodeId, 1);
184                     } else {
185                         writer.startLeafSet(nodeId, 1);
186                     }
187                 } else if (child instanceof AnydataSchemaNode) {
188                     parent = child;
189                     terminal = true;
190                     writer.startAnydataNode(nodeId, NormalizedAnydata.class);
191                 } else if (child instanceof AnyxmlSchemaNode) {
192                     parent = child;
193                     terminal = true;
194                     writer.startAnyxmlNode(nodeId, DOMSource.class);
195                 } else {
196                     throw new IOException("Unhandled child " + child);
197                 }
198             } else {
199                 throw new IOException("Unhandled argument " + arg);
200             }
201
202             endNodes++;
203         } while (it.hasNext());
204
205         return new YangInstanceIdentifierWriter(writer, endNodes);
206     }
207
208     @Override
209     public void close() throws IOException {
210         if (writer != null) {
211             for (int i = 0; i < endNodeCount; ++i) {
212                 writer.endNode();
213             }
214             writer = null;
215         }
216     }
217
218     private static NodeIdentifierWithPredicates normalizePredicates(final NodeIdentifierWithPredicates input,
219             final List<QName> key) throws IOException {
220         if (Iterables.elementsEqual(input.keySet(), key)) {
221             return input;
222         }
223
224         final var builder = ImmutableMap.<QName, Object>builderWithExpectedSize(key.size());
225         for (var qname : key) {
226             final var value = input.getValue(qname);
227             if (value == null) {
228                 throw new IOException("Cannot normalize " + input + " to " + key + ", missing value for " + qname);
229             }
230             builder.put(qname, value);
231         }
232
233         return NodeIdentifierWithPredicates.of(input.getNodeType(), ImmutableOffsetMap.orderedCopyOf(builder.build()));
234     }
235 }