2 * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.api.schema.stream;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.collect.Collections2;
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.ImmutableSet;
16 import com.google.common.collect.Iterables;
17 import java.io.IOException;
18 import java.util.List;
19 import javax.xml.transform.dom.DOMSource;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
29 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
33 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
35 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
42 * Utility for emitting a {@link YangInstanceIdentifier} into a {@link NormalizedNodeStreamWriter} as a set of
43 * {@code startXXXNode} events. An example of usage would be something along the lines of:
47 * YangInstanceIdentifier id;
48 * var result = new NormalizedNodeResult();
49 * try (var writer = ImmutableNormalizedNodeStreamWriter.from(result)) {
50 * try (var iidWriter = YangInstanceIdentifierWriter.open(writer, ctx, id)) {
51 * // Here the state of 'writer' reflects the nodes in 'id'
53 * // Here the writer is back to its initial state
56 * // NormalizedNode result, including the structure created from YangInstanceIdentifier
57 * var node = result.getResult();
61 public final class YangInstanceIdentifierWriter implements AutoCloseable {
62 private NormalizedNodeStreamWriter writer;
63 private final int endNodeCount;
65 private YangInstanceIdentifierWriter(final NormalizedNodeStreamWriter writer, final int endNodeCount) {
66 this.writer = requireNonNull(writer);
67 this.endNodeCount = endNodeCount;
71 * Open a writer, emitting events in target {@link NormalizedNodeStreamWriter}.
73 * @param writer Writer to enter
74 * @param root Root container
75 * @param path Path to enter
76 * @return A writer instance
77 * @throws IOException if the path cannot be entered
79 public static @NonNull YangInstanceIdentifierWriter open(final NormalizedNodeStreamWriter writer,
80 final DataNodeContainer root, final YangInstanceIdentifier path) throws IOException {
81 final var it = path.getPathArguments().iterator();
83 return new YangInstanceIdentifierWriter(writer, 0);
89 boolean reuse = false;
90 boolean terminal = false;
94 throw new IOException(parent + " is a terminal node, cannot resolve " + ImmutableList.copyOf(it));
97 final var arg = it.next();
98 if (arg instanceof AugmentationIdentifier) {
99 if (!(parent instanceof AugmentationTarget)) {
100 throw new IOException(parent + " does not support augmentations, cannot resolve" + arg);
103 throw new IOException(parent + " is expecting a nested item, cannot resolve " + arg);
106 final var augId = (AugmentationIdentifier) arg;
107 parent = enterAugmentation((AugmentationTarget) parent, augId);
108 writer.startAugmentationNode(augId);
109 } else if (arg instanceof NodeWithValue) {
110 if (!(parent instanceof LeafListSchemaNode)) {
111 throw new IOException(parent + " does not support leaf-list entry " + arg);
114 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
119 writer.startLeafSetEntryNode((NodeWithValue<?>) arg);
120 } else if (arg instanceof NodeIdentifierWithPredicates) {
121 if (!(parent instanceof ListSchemaNode)) {
122 throw new IOException(parent + " does not support map entry " + arg);
125 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
128 final var nodeId = (NodeIdentifierWithPredicates) arg;
129 final var list = (ListSchemaNode) parent;
130 if (!list.getQName().equals(nodeId.getNodeType())) {
131 throw new IOException(parent + " expects a matching map entry, cannot enter " + arg);
134 final var key = list.getKeyDefinition();
136 throw new IOException(parent + " does not expect map entry " + arg);
138 if (key.size() != nodeId.size()) {
139 throw new IOException(parent + " expects " + key.size() + " predicates, cannot use " + arg);
143 writer.startMapEntryNode(normalizePredicates(nodeId, key), 1);
144 } else if (arg instanceof NodeIdentifier) {
145 final var nodeId = (NodeIdentifier) arg;
148 if (!(parent instanceof ListSchemaNode)) {
149 throw new IOException(parent + " expects an identifiable entry, cannot enter " + arg);
152 final var list = (ListSchemaNode) parent;
153 if (!list.getKeyDefinition().isEmpty()) {
154 throw new IOException(parent + " expects a map entry, cannot enter " + arg);
156 if (!list.getQName().equals(nodeId.getNodeType())) {
157 throw new IOException(parent + " expects a matching entry, cannot enter " + arg);
161 writer.startUnkeyedListItem(nodeId, 1);
166 final DataSchemaNode child;
167 if (parent instanceof DataNodeContainer) {
168 child = ((DataNodeContainer) parent).dataChildByName(nodeId.getNodeType());
169 } else if (parent instanceof ChoiceSchemaNode) {
170 child = ((ChoiceSchemaNode) parent).findDataSchemaChild(nodeId.getNodeType()).orElse(null);
172 throw new IOException("Unhandled parent " + parent + " when looking up " + arg);
176 throw new IOException("Failed to find child " + arg + " in parent " + parent);
179 // FIXME: check & repair augmentations (brr!)
181 if (child instanceof ContainerLike) {
183 writer.startContainerNode(nodeId, 1);
184 } else if (child instanceof ListSchemaNode) {
187 final var list = (ListSchemaNode) child;
188 if (list.getKeyDefinition().isEmpty()) {
189 writer.startUnkeyedList(nodeId, 1);
190 } else if (list.isUserOrdered()) {
191 writer.startOrderedMapNode(nodeId, 1);
193 writer.startMapNode(nodeId, 1);
195 } else if (child instanceof LeafSchemaNode) {
198 writer.startLeafNode(nodeId);
199 } else if (child instanceof ChoiceSchemaNode) {
201 writer.startChoiceNode(nodeId, 1);
202 } else if (child instanceof LeafListSchemaNode) {
205 if (((LeafListSchemaNode) child).isUserOrdered()) {
206 writer.startOrderedLeafSet(nodeId, 1);
208 writer.startLeafSet(nodeId, 1);
210 } else if (child instanceof AnydataSchemaNode) {
213 writer.startAnydataNode(nodeId, NormalizedAnydata.class);
214 } else if (child instanceof AnyxmlSchemaNode) {
217 writer.startAnyxmlNode(nodeId, DOMSource.class);
219 throw new IOException("Unhandled child " + child);
222 throw new IOException("Unhandled argument " + arg);
226 } while (it.hasNext());
228 return new YangInstanceIdentifierWriter(writer, endNodes);
232 public void close() throws IOException {
233 if (writer != null) {
234 for (int i = 0; i < endNodeCount; ++i) {
241 private static NodeIdentifierWithPredicates normalizePredicates(final NodeIdentifierWithPredicates input,
242 final List<QName> key) throws IOException {
243 if (Iterables.elementsEqual(input.keySet(), key)) {
247 final var builder = ImmutableMap.<QName, Object>builderWithExpectedSize(key.size());
248 for (var qname : key) {
249 final var value = input.getValue(qname);
251 throw new IOException("Cannot normalize " + input + " to " + key + ", missing value for " + qname);
253 builder.put(qname, value);
256 return NodeIdentifierWithPredicates.of(input.getNodeType(), ImmutableOffsetMap.orderedCopyOf(builder.build()));
259 private static AugmentationSchemaNode enterAugmentation(final AugmentationTarget target,
260 final AugmentationIdentifier id) throws IOException {
261 final var augs = target.getAvailableAugmentations();
262 for (var augment : augs) {
263 if (id.equals(augmentationIdentifierFrom(augment))) {
267 throw new IOException("Cannot find augmentation " + id + " in " + target + ", available: "
268 + Collections2.transform(augs, YangInstanceIdentifierWriter::augmentationIdentifierFrom));
271 // FIXME: duplicate of data.util.DataSchemaContextNode.augmentationIdentifierFrom()
272 static @NonNull AugmentationIdentifier augmentationIdentifierFrom(final AugmentationSchemaNode schema) {
273 return new AugmentationIdentifier(
274 schema.getChildNodes().stream().map(DataSchemaNode::getQName).collect(ImmutableSet.toImmutableSet()));