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;
40 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
43 * Utility for emitting a {@link YangInstanceIdentifier} into a {@link NormalizedNodeStreamWriter} as a set of
44 * {@code startXXXNode} events. An example of usage would be something along the lines of:
48 * YangInstanceIdentifier id;
49 * var result = new NormalizedNodeResult();
50 * try (var writer = ImmutableNormalizedNodeStreamWriter.from(result)) {
51 * try (var iidWriter = YangInstanceIdentifierWriter.open(writer, ctx, id)) {
52 * // Here the state of 'writer' reflects the nodes in 'id'
54 * // Here the writer is back to its initial state
57 * // NormalizedNode result, including the structure created from YangInstanceIdentifier
58 * var node = result.getResult();
62 public final class YangInstanceIdentifierWriter implements AutoCloseable {
63 private NormalizedNodeStreamWriter writer;
64 private final int endNodeCount;
66 private YangInstanceIdentifierWriter(final NormalizedNodeStreamWriter writer, final int endNodeCount) {
67 this.writer = requireNonNull(writer);
68 this.endNodeCount = endNodeCount;
72 * Open a writer, emitting events in target {@link NormalizedNodeStreamWriter}.
74 * @param writer Writer to enter
75 * @param root Root container
76 * @param path Path to enter
77 * @return A writer instance
78 * @throws IOException if the path cannot be entered
80 public static @NonNull YangInstanceIdentifierWriter open(final NormalizedNodeStreamWriter writer,
81 final DataNodeContainer root, final YangInstanceIdentifier path) throws IOException {
82 final var it = path.getPathArguments().iterator();
84 return new YangInstanceIdentifierWriter(writer, 0);
90 boolean reuse = false;
91 boolean terminal = false;
95 throw new IOException(parent + " is a terminal node, cannot resolve " + ImmutableList.copyOf(it));
98 final var arg = it.next();
99 if (arg instanceof AugmentationIdentifier) {
100 if (!(parent instanceof AugmentationTarget)) {
101 throw new IOException(parent + " does not support augmentations, cannot resolve " + arg);
104 throw new IOException(parent + " is expecting a nested item, cannot resolve " + arg);
107 final var augId = (AugmentationIdentifier) arg;
108 if (parent instanceof DataNodeContainer) {
109 parent = new EffectiveAugmentationSchema(enterAugmentation((AugmentationTarget) parent, augId),
110 (DataNodeContainer) parent);
111 } else if (parent instanceof ChoiceSchemaNode) {
112 throw new IOException(parent + " should not use addressing through " + arg);
114 throw new IOException("Unhandled parent " + parent + " while resolving " + arg);
116 writer.startAugmentationNode(augId);
117 } else if (arg instanceof NodeWithValue) {
118 if (!(parent instanceof LeafListSchemaNode)) {
119 throw new IOException(parent + " does not support leaf-list entry " + arg);
122 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
127 writer.startLeafSetEntryNode((NodeWithValue<?>) arg);
128 } else if (arg instanceof NodeIdentifierWithPredicates) {
129 if (!(parent instanceof ListSchemaNode)) {
130 throw new IOException(parent + " does not support map entry " + arg);
133 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
136 final var nodeId = (NodeIdentifierWithPredicates) arg;
137 final var list = (ListSchemaNode) parent;
138 if (!list.getQName().equals(nodeId.getNodeType())) {
139 throw new IOException(parent + " expects a matching map entry, cannot enter " + arg);
142 final var key = list.getKeyDefinition();
144 throw new IOException(parent + " does not expect map entry " + arg);
146 if (key.size() != nodeId.size()) {
147 throw new IOException(parent + " expects " + key.size() + " predicates, cannot use " + arg);
151 writer.startMapEntryNode(normalizePredicates(nodeId, key), 1);
152 } else if (arg instanceof NodeIdentifier) {
153 final var nodeId = (NodeIdentifier) arg;
156 if (!(parent instanceof ListSchemaNode)) {
157 throw new IOException(parent + " expects an identifiable entry, cannot enter " + arg);
160 final var list = (ListSchemaNode) parent;
161 if (!list.getKeyDefinition().isEmpty()) {
162 throw new IOException(parent + " expects a map entry, cannot enter " + arg);
164 if (!list.getQName().equals(nodeId.getNodeType())) {
165 throw new IOException(parent + " expects a matching entry, cannot enter " + arg);
169 writer.startUnkeyedListItem(nodeId, 1);
174 final DataSchemaNode child;
175 if (parent instanceof DataNodeContainer) {
176 child = ((DataNodeContainer) parent).dataChildByName(nodeId.getNodeType());
177 } else if (parent instanceof ChoiceSchemaNode) {
178 child = ((ChoiceSchemaNode) parent).findDataSchemaChild(nodeId.getNodeType()).orElse(null);
180 throw new IOException("Unhandled parent " + parent + " when looking up " + arg);
184 throw new IOException("Failed to find child " + arg + " in parent " + parent);
187 // FIXME: check & repair augmentations (brr!)
189 if (child instanceof ContainerLike) {
191 writer.startContainerNode(nodeId, 1);
192 } else if (child instanceof ListSchemaNode) {
195 final var list = (ListSchemaNode) child;
196 if (list.getKeyDefinition().isEmpty()) {
197 writer.startUnkeyedList(nodeId, 1);
198 } else if (list.isUserOrdered()) {
199 writer.startOrderedMapNode(nodeId, 1);
201 writer.startMapNode(nodeId, 1);
203 } else if (child instanceof LeafSchemaNode) {
206 writer.startLeafNode(nodeId);
207 } else if (child instanceof ChoiceSchemaNode) {
209 writer.startChoiceNode(nodeId, 1);
210 } else if (child instanceof LeafListSchemaNode) {
213 if (((LeafListSchemaNode) child).isUserOrdered()) {
214 writer.startOrderedLeafSet(nodeId, 1);
216 writer.startLeafSet(nodeId, 1);
218 } else if (child instanceof AnydataSchemaNode) {
221 writer.startAnydataNode(nodeId, NormalizedAnydata.class);
222 } else if (child instanceof AnyxmlSchemaNode) {
225 writer.startAnyxmlNode(nodeId, DOMSource.class);
227 throw new IOException("Unhandled child " + child);
230 throw new IOException("Unhandled argument " + arg);
234 } while (it.hasNext());
236 return new YangInstanceIdentifierWriter(writer, endNodes);
240 public void close() throws IOException {
241 if (writer != null) {
242 for (int i = 0; i < endNodeCount; ++i) {
249 private static NodeIdentifierWithPredicates normalizePredicates(final NodeIdentifierWithPredicates input,
250 final List<QName> key) throws IOException {
251 if (Iterables.elementsEqual(input.keySet(), key)) {
255 final var builder = ImmutableMap.<QName, Object>builderWithExpectedSize(key.size());
256 for (var qname : key) {
257 final var value = input.getValue(qname);
259 throw new IOException("Cannot normalize " + input + " to " + key + ", missing value for " + qname);
261 builder.put(qname, value);
264 return NodeIdentifierWithPredicates.of(input.getNodeType(), ImmutableOffsetMap.orderedCopyOf(builder.build()));
267 private static AugmentationSchemaNode enterAugmentation(final AugmentationTarget target,
268 final AugmentationIdentifier id) throws IOException {
269 final var augs = target.getAvailableAugmentations();
270 for (var augment : augs) {
271 if (id.equals(augmentationIdentifierFrom(augment))) {
275 throw new IOException("Cannot find augmentation " + id + " in " + target + ", available: "
276 + Collections2.transform(augs, YangInstanceIdentifierWriter::augmentationIdentifierFrom));
279 // FIXME: duplicate of data.util.DataSchemaContextNode.augmentationIdentifierFrom()
280 static @NonNull AugmentationIdentifier augmentationIdentifierFrom(final AugmentationSchemaNode schema) {
281 return new AugmentationIdentifier(
282 schema.getChildNodes().stream().map(DataSchemaNode::getQName).collect(ImmutableSet.toImmutableSet()));