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.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;
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:
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'
48 * // Here the writer is back to its initial state
51 * // NormalizedNode result, including the structure created from YangInstanceIdentifier
52 * var node = result.getResult();
56 public final class YangInstanceIdentifierWriter implements AutoCloseable {
57 private NormalizedNodeStreamWriter writer;
58 private final int endNodeCount;
60 private YangInstanceIdentifierWriter(final NormalizedNodeStreamWriter writer, final int endNodeCount) {
61 this.writer = requireNonNull(writer);
62 this.endNodeCount = endNodeCount;
66 * Open a writer, emitting events in target {@link NormalizedNodeStreamWriter}.
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
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();
78 return new YangInstanceIdentifierWriter(writer, 0);
84 boolean reuse = false;
85 boolean terminal = false;
89 throw new IOException(parent + " is a terminal node, cannot resolve " + ImmutableList.copyOf(it));
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);
98 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
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);
109 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
111 if (!list.getQName().equals(nodeId.getNodeType())) {
112 throw new IOException(parent + " expects a matching map entry, cannot enter " + arg);
115 final var key = list.getKeyDefinition();
117 throw new IOException(parent + " does not expect map entry " + arg);
119 if (key.size() != nodeId.size()) {
120 throw new IOException(parent + " expects " + key.size() + " predicates, cannot use " + arg);
124 writer.startMapEntryNode(normalizePredicates(nodeId, key), 1);
125 } else if (arg instanceof NodeIdentifier nodeId) {
127 if (!(parent instanceof ListSchemaNode list)) {
128 throw new IOException(parent + " expects an identifiable entry, cannot enter " + arg);
131 if (!list.getKeyDefinition().isEmpty()) {
132 throw new IOException(parent + " expects a map entry, cannot enter " + arg);
134 if (!list.getQName().equals(nodeId.getNodeType())) {
135 throw new IOException(parent + " expects a matching entry, cannot enter " + arg);
139 writer.startUnkeyedListItem(nodeId, 1);
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);
150 throw new IOException("Unhandled parent " + parent + " when looking up " + arg);
154 throw new IOException("Failed to find child " + arg + " in parent " + parent);
157 // FIXME: check & repair augmentations (brr!)
159 if (child instanceof ContainerLike) {
161 writer.startContainerNode(nodeId, 1);
162 } else if (child instanceof ListSchemaNode list) {
165 if (list.getKeyDefinition().isEmpty()) {
166 writer.startUnkeyedList(nodeId, 1);
167 } else if (list.isUserOrdered()) {
168 writer.startOrderedMapNode(nodeId, 1);
170 writer.startMapNode(nodeId, 1);
172 } else if (child instanceof LeafSchemaNode) {
175 writer.startLeafNode(nodeId);
176 } else if (child instanceof ChoiceSchemaNode) {
178 writer.startChoiceNode(nodeId, 1);
179 } else if (child instanceof LeafListSchemaNode leafList) {
182 if (leafList.isUserOrdered()) {
183 writer.startOrderedLeafSet(nodeId, 1);
185 writer.startLeafSet(nodeId, 1);
187 } else if (child instanceof AnydataSchemaNode) {
190 writer.startAnydataNode(nodeId, NormalizedAnydata.class);
191 } else if (child instanceof AnyxmlSchemaNode) {
194 writer.startAnyxmlNode(nodeId, DOMSource.class);
196 throw new IOException("Unhandled child " + child);
199 throw new IOException("Unhandled argument " + arg);
203 } while (it.hasNext());
205 return new YangInstanceIdentifierWriter(writer, endNodes);
209 public void close() throws IOException {
210 if (writer != null) {
211 for (int i = 0; i < endNodeCount; ++i) {
218 private static NodeIdentifierWithPredicates normalizePredicates(final NodeIdentifierWithPredicates input,
219 final List<QName> key) throws IOException {
220 if (Iterables.elementsEqual(input.keySet(), key)) {
224 final var builder = ImmutableMap.<QName, Object>builderWithExpectedSize(key.size());
225 for (var qname : key) {
226 final var value = input.getValue(qname);
228 throw new IOException("Cannot normalize " + input + " to " + key + ", missing value for " + qname);
230 builder.put(qname, value);
233 return NodeIdentifierWithPredicates.of(input.getNodeType(), ImmutableOffsetMap.orderedCopyOf(builder.build()));