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.ImmutableList;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.common.collect.Iterables;
16 import java.io.IOException;
17 import java.util.List;
18 import javax.xml.transform.dom.DOMSource;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
27 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
28 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
32 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
34 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
41 * Utility for emitting a {@link YangInstanceIdentifier} into a {@link NormalizedNodeStreamWriter} as a set of
42 * {@code startXXXNode} events. An example of usage would be something along the lines of:
46 * YangInstanceIdentifier id;
47 * var result = new NormalizedNodeResult();
48 * try (var writer = ImmutableNormalizedNodeStreamWriter.from(result)) {
49 * try (var iidWriter = YangInstanceIdentifierWriter.open(writer, ctx, id)) {
50 * // Here the state of 'writer' reflects the nodes in 'id'
52 * // Here the writer is back to its initial state
55 * // NormalizedNode result, including the structure created from YangInstanceIdentifier
56 * var node = result.getResult();
60 public final class YangInstanceIdentifierWriter implements AutoCloseable {
61 private NormalizedNodeStreamWriter writer;
62 private final int endNodeCount;
64 private YangInstanceIdentifierWriter(final NormalizedNodeStreamWriter writer, final int endNodeCount) {
65 this.writer = requireNonNull(writer);
66 this.endNodeCount = endNodeCount;
70 * Open a writer, emitting events in target {@link NormalizedNodeStreamWriter}.
72 * @param writer Writer to enter
73 * @param root Root container
74 * @param path Path to enter
75 * @return A writer instance
76 * @throws IOException if the path cannot be entered
78 public static @NonNull YangInstanceIdentifierWriter open(final NormalizedNodeStreamWriter writer,
79 final DataNodeContainer root, final YangInstanceIdentifier path) throws IOException {
80 final var it = path.getPathArguments().iterator();
82 return new YangInstanceIdentifierWriter(writer, 0);
88 boolean reuse = false;
89 boolean terminal = false;
93 throw new IOException(parent + " is a terminal node, cannot resolve " + ImmutableList.copyOf(it));
96 final var arg = it.next();
97 if (arg instanceof AugmentationIdentifier) {
98 if (!(parent instanceof AugmentationTarget)) {
99 throw new IOException(parent + " does not support augmentations, cannot resolve" + arg);
102 throw new IOException(parent + " is expecting a nested item, cannot resolve " + arg);
105 final var augId = (AugmentationIdentifier) arg;
106 parent = enterAugmentation((AugmentationTarget) parent, augId);
107 writer.startAugmentationNode(augId);
108 } else if (arg instanceof NodeWithValue) {
109 if (!(parent instanceof LeafListSchemaNode)) {
110 throw new IOException(parent + " does not support leaf-list entry " + arg);
113 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
118 writer.startLeafSetEntryNode((NodeWithValue<?>) arg);
119 } else if (arg instanceof NodeIdentifierWithPredicates) {
120 if (!(parent instanceof ListSchemaNode)) {
121 throw new IOException(parent + " does not support map entry " + arg);
124 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
127 final var nodeId = (NodeIdentifierWithPredicates) arg;
128 final var list = (ListSchemaNode) parent;
129 if (!list.getQName().equals(nodeId.getNodeType())) {
130 throw new IOException(parent + " expects a matching map entry, cannot enter " + arg);
133 final var key = list.getKeyDefinition();
135 throw new IOException(parent + " does not expect map entry " + arg);
137 if (key.size() != nodeId.size()) {
138 throw new IOException(parent + " expects " + key.size() + " predicates, cannot use " + arg);
142 writer.startMapEntryNode(normalizePredicates(nodeId, key), 1);
143 } else if (arg instanceof NodeIdentifier) {
144 final var nodeId = (NodeIdentifier) arg;
147 if (!(parent instanceof ListSchemaNode)) {
148 throw new IOException(parent + " expects an identifiable entry, cannot enter " + arg);
151 final var list = (ListSchemaNode) parent;
152 if (!list.getKeyDefinition().isEmpty()) {
153 throw new IOException(parent + " expects a map entry, cannot enter " + arg);
155 if (!list.getQName().equals(nodeId.getNodeType())) {
156 throw new IOException(parent + " expects a matching entry, cannot enter " + arg);
160 writer.startUnkeyedListItem(nodeId, 1);
165 final DataSchemaNode child;
166 if (parent instanceof DataNodeContainer) {
167 child = ((DataNodeContainer) parent).dataChildByName(nodeId.getNodeType());
168 } else if (parent instanceof ChoiceSchemaNode) {
169 child = ((ChoiceSchemaNode) parent).findDataSchemaChild(nodeId.getNodeType()).orElse(null);
171 throw new IOException("Unhandled parent " + parent + " when looking up " + arg);
175 throw new IOException("Failed to find child " + arg + " in parent " + parent);
178 // FIXME: check & repair augmentations (brr!)
180 if (child instanceof ContainerLike) {
182 writer.startContainerNode(nodeId, 1);
183 } else if (child instanceof ListSchemaNode) {
186 final var list = (ListSchemaNode) child;
187 if (list.getKeyDefinition().isEmpty()) {
188 writer.startUnkeyedList(nodeId, 1);
189 } else if (list.isUserOrdered()) {
190 writer.startOrderedMapNode(nodeId, 1);
192 writer.startMapNode(nodeId, 1);
194 } else if (child instanceof LeafSchemaNode) {
197 writer.startLeafNode(nodeId);
198 } else if (child instanceof ChoiceSchemaNode) {
200 writer.startChoiceNode(nodeId, 1);
201 } else if (child instanceof LeafListSchemaNode) {
204 if (((LeafListSchemaNode) child).isUserOrdered()) {
205 writer.startOrderedLeafSet(nodeId, 1);
207 writer.startLeafSet(nodeId, 1);
209 } else if (child instanceof AnydataSchemaNode) {
212 writer.startAnydataNode(nodeId, NormalizedAnydata.class);
213 } else if (child instanceof AnyxmlSchemaNode) {
216 writer.startAnyxmlNode(nodeId, DOMSource.class);
218 throw new IOException("Unhandled child " + child);
221 throw new IOException("Unhandled argument " + arg);
225 } while (it.hasNext());
227 return new YangInstanceIdentifierWriter(writer, endNodes);
231 public void close() throws IOException {
232 if (writer != null) {
233 for (int i = 0; i < endNodeCount; ++i) {
240 private static NodeIdentifierWithPredicates normalizePredicates(final NodeIdentifierWithPredicates input,
241 final List<QName> key) throws IOException {
242 if (Iterables.elementsEqual(input.keySet(), key)) {
246 final var builder = ImmutableMap.<QName, Object>builderWithExpectedSize(key.size());
247 for (var qname : key) {
248 final var value = input.getValue(qname);
250 throw new IOException("Cannot normalize " + input + " to " + key + ", missing value for " + qname);
252 builder.put(qname, value);
255 return NodeIdentifierWithPredicates.of(input.getNodeType(), ImmutableOffsetMap.orderedCopyOf(builder.build()));
258 private static AugmentationSchemaNode enterAugmentation(final AugmentationTarget target,
259 final AugmentationIdentifier id) throws IOException {
260 for (var augment : target.getAvailableAugmentations()) {
261 if (id.equals(augmentationIdentifierFrom(augment))) {
265 throw new IOException("Cannot find augmentation " + id + " in " + target);
268 // FIXME: duplicate of data.util.DataSchemaContextNode.augmentationIdentifierFrom()
269 static @NonNull AugmentationIdentifier augmentationIdentifierFrom(final AugmentationSchemaNode schema) {
270 return new AugmentationIdentifier(
271 schema.getChildNodes().stream().map(DataSchemaNode::getQName).collect(ImmutableSet.toImmutableSet()));