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.LeafListSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
36 * Utility for emitting a {@link YangInstanceIdentifier} into a {@link NormalizedNodeStreamWriter} as a set of
37 * {@code startXXXNode} events. An example of usage would be something along the lines of:
41 * YangInstanceIdentifier id;
42 * var result = new NormalizedNodeResult();
43 * try (var writer = ImmutableNormalizedNodeStreamWriter.from(result)) {
44 * try (var iidWriter = YangInstanceIdentifierWriter.open(writer, ctx, id)) {
45 * // Here the state of 'writer' reflects the nodes in 'id'
47 * // Here the writer is back to its initial state
50 * // NormalizedNode result, including the structure created from YangInstanceIdentifier
51 * var node = result.getResult();
55 public final class YangInstanceIdentifierWriter implements AutoCloseable {
56 private NormalizedNodeStreamWriter writer;
57 private final int endNodeCount;
59 private YangInstanceIdentifierWriter(final NormalizedNodeStreamWriter writer, final int endNodeCount) {
60 this.writer = requireNonNull(writer);
61 this.endNodeCount = endNodeCount;
65 * Open a writer, emitting events in target {@link NormalizedNodeStreamWriter}.
67 * @param writer Writer to enter
68 * @param root Root container
69 * @param path Path to enter
70 * @return A writer instance
71 * @throws IOException if the path cannot be entered
73 public static @NonNull YangInstanceIdentifierWriter open(final NormalizedNodeStreamWriter writer,
74 final DataNodeContainer root, final YangInstanceIdentifier path) throws IOException {
75 final var it = path.getPathArguments().iterator();
77 return new YangInstanceIdentifierWriter(writer, 0);
83 boolean reuse = false;
84 boolean terminal = false;
88 throw new IOException(parent + " is a terminal node, cannot resolve " + ImmutableList.copyOf(it));
91 final var arg = it.next();
93 case 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);
105 case NodeIdentifierWithPredicates nodeId -> {
106 if (!(parent instanceof ListSchemaNode list)) {
107 throw new IOException(parent + " does not support map entry " + arg);
110 throw new IOException(parent + " is already at its entry, cannot enter " + arg);
112 if (!list.getQName().equals(nodeId.getNodeType())) {
113 throw new IOException(parent + " expects a matching map entry, cannot enter " + arg);
116 final var key = list.getKeyDefinition();
118 throw new IOException(parent + " does not expect map entry " + arg);
120 if (key.size() != nodeId.size()) {
121 throw new IOException(parent + " expects " + key.size() + " predicates, cannot use " + arg);
125 writer.startMapEntryNode(normalizePredicates(nodeId, key), 1);
127 case NodeIdentifier nodeId -> {
129 if (!(parent instanceof ListSchemaNode list)) {
130 throw new IOException(parent + " expects an identifiable entry, cannot enter " + arg);
133 if (!list.getKeyDefinition().isEmpty()) {
134 throw new IOException(parent + " expects a map entry, cannot enter " + arg);
136 if (!list.getQName().equals(nodeId.getNodeType())) {
137 throw new IOException(parent + " expects a matching entry, cannot enter " + arg);
141 writer.startUnkeyedListItem(nodeId, 1);
146 final var child = switch (parent) {
147 case DataNodeContainer container -> container.dataChildByName(nodeId.getNodeType());
148 case ChoiceSchemaNode choice -> choice.findDataSchemaChild(nodeId.getNodeType()).orElse(null);
149 default -> throw new IOException("Unhandled parent " + parent + " when looking up " + arg);
152 parent = switch (child) {
153 case null -> throw new IOException("Failed to find child " + arg + " in parent " + parent);
154 case AnydataSchemaNode anydata -> {
155 writer.startAnydataNode(nodeId, NormalizedAnydata.class);
159 case AnyxmlSchemaNode anyxml -> {
160 writer.startAnyxmlNode(nodeId, DOMSource.class);
164 case ChoiceSchemaNode choice -> {
165 writer.startChoiceNode(nodeId, 1);
168 case ContainerLike containerLike -> {
169 writer.startContainerNode(nodeId, 1);
172 case LeafSchemaNode leaf -> {
173 writer.startLeafNode(nodeId);
177 case LeafListSchemaNode leafList -> {
178 if (leafList.isUserOrdered()) {
179 writer.startOrderedLeafSet(nodeId, 1);
181 writer.startLeafSet(nodeId, 1);
186 case ListSchemaNode list -> {
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);
197 default -> throw new IOException("Unhandled child " + child);
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()));