42be874d27fc6f3d06c7dd7b0dd0377c22a46910
[yangtools.git] / data / yang-data-util / src / main / java / org / opendaylight / yangtools / yang / data / util / NormalizedNodeStreamWriterStack.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  * Copyright (c) 2021 PANTHEON.tech, s.r.o.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9 package org.opendaylight.yangtools.yang.data.util;
10
11 import static com.google.common.base.Preconditions.checkArgument;
12 import static com.google.common.base.Verify.verify;
13 import static java.util.Objects.requireNonNull;
14
15 import com.google.common.annotations.Beta;
16 import java.io.IOException;
17 import java.util.ArrayDeque;
18 import java.util.Deque;
19 import org.eclipse.jdt.annotation.NonNull;
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.NodeWithValue;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
25 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
27 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
32 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
35 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
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.api.NotificationDefinition;
41 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
44 import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
45 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
46 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
47 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
48 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
49 import org.opendaylight.yangtools.yang.model.util.LeafrefResolver;
50 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
51 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * Utility class for tracking schema state underlying a {@link NormalizedNode} structure.
57  */
58 @Beta
59 public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
60     private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeStreamWriterStack.class);
61
62     private final Deque<WithStatus> schemaStack = new ArrayDeque<>();
63     private final SchemaInferenceStack dataTree;
64     private final DataNodeContainer root;
65
66     private NormalizedNodeStreamWriterStack(final EffectiveModelContext context) {
67         dataTree = SchemaInferenceStack.of(context);
68         root = requireNonNull(context);
69     }
70
71     private NormalizedNodeStreamWriterStack(final SchemaInferenceStack dataTree) {
72         this.dataTree = requireNonNull(dataTree);
73         if (!dataTree.isEmpty()) {
74             final var current = dataTree.currentStatement();
75             if (current instanceof DataNodeContainer container) {
76                 root = container;
77             } else {
78                 throw new IllegalArgumentException("Cannot instantiate on " + current);
79             }
80         } else {
81             root = dataTree.getEffectiveModelContext();
82         }
83     }
84
85     /**
86      * Create a new writer with the specified inference state as its root.
87      *
88      * @param root Root inference state
89      * @return A new {@link NormalizedNodeStreamWriter}
90      * @throws NullPointerException if {@code root} is null
91      */
92     public static @NonNull NormalizedNodeStreamWriterStack of(final EffectiveStatementInference root) {
93         return new NormalizedNodeStreamWriterStack(SchemaInferenceStack.ofInference(root));
94     }
95
96     /**
97      * Create a new writer with the specified inference state as its root.
98      *
99      * @param root Root inference state
100      * @return A new {@link NormalizedNodeStreamWriter}
101      * @throws NullPointerException if {@code root} is null
102      */
103     public static @NonNull NormalizedNodeStreamWriterStack of(final Inference root) {
104         return new NormalizedNodeStreamWriterStack(root.toSchemaInferenceStack());
105     }
106
107     /**
108      * Create a new writer at the root of specified {@link EffectiveModelContext}.
109      *
110      * @param context effective model context
111      * @return A new {@link NormalizedNodeStreamWriter}
112      * @throws NullPointerException if {@code context} is null
113      */
114     public static @NonNull NormalizedNodeStreamWriterStack of(final EffectiveModelContext context) {
115         return new NormalizedNodeStreamWriterStack(context);
116     }
117
118     /**
119      * Create a new writer with the specified context and rooted in the specified schema path.
120      *
121      * @param context Associated {@link EffectiveModelContext}
122      * @param path schema path
123      * @return A new {@link NormalizedNodeStreamWriterStack}
124      * @throws NullPointerException if any argument is null
125      * @throws IllegalArgumentException if {@code path} does not point to a valid root
126      */
127     public static @NonNull NormalizedNodeStreamWriterStack of(final EffectiveModelContext context,
128             final Absolute path) {
129         return new NormalizedNodeStreamWriterStack(SchemaInferenceStack.of(context, path));
130     }
131
132     /**
133      * Create a new writer with the specified context and rooted in the specified {@link YangInstanceIdentifier}..
134      *
135      * @param context Associated {@link EffectiveModelContext}
136      * @param path Normalized path
137      * @return A new {@link NormalizedNodeStreamWriterStack}
138      * @throws NullPointerException if any argument is null
139      * @throws IllegalArgumentException if {@code path} does not point to a valid root
140      */
141     public static @NonNull NormalizedNodeStreamWriterStack of(final EffectiveModelContext context,
142             final YangInstanceIdentifier path) {
143         return new NormalizedNodeStreamWriterStack(DataSchemaContextTree.from(context)
144             .enterPath(path)
145             .orElseThrow()
146             .stack());
147     }
148
149     /**
150      * Create a new writer with the specified context and rooted in the specified schema path.
151      *
152      * @param context Associated {@link EffectiveModelContext}
153      * @param operation Operation schema path
154      * @param qname Input/Output container QName
155      * @return A new {@link NormalizedNodeStreamWriter}
156      * @throws NullPointerException if any argument is null
157      * @throws IllegalArgumentException if {@code operation} does not point to an actual operation or if {@code qname}
158      *                                  does not identify a valid root underneath it.
159      */
160     public static @NonNull NormalizedNodeStreamWriterStack ofOperation(final EffectiveModelContext context,
161             final Absolute operation, final QName qname) {
162         final SchemaInferenceStack stack = SchemaInferenceStack.of(context, operation);
163         final EffectiveStatement<?, ?> current = stack.currentStatement();
164         checkArgument(current instanceof RpcEffectiveStatement || current instanceof ActionEffectiveStatement,
165             "Path %s resolved into non-operation %s", operation, current);
166         stack.enterSchemaTree(qname);
167         return new NormalizedNodeStreamWriterStack(stack);
168     }
169
170     @Override
171     public TypeDefinition<?> resolveLeafref(final LeafrefTypeDefinition type) {
172         return dataTree.resolveLeafref(type);
173     }
174
175     public Object getParent() {
176         final var schema = schemaStack.peek();
177         return schema == null ? root : schema;
178     }
179
180     private @NonNull SchemaNode enterDataTree(final PathArgument name) {
181         final QName qname = name.getNodeType();
182         final DataTreeEffectiveStatement<?> stmt = dataTree.enterDataTree(qname);
183         verify(stmt instanceof SchemaNode, "Unexpected result %s", stmt);
184         final SchemaNode ret = (SchemaNode) stmt;
185         final Object parent = getParent();
186         if (parent instanceof ChoiceSchemaNode choice) {
187             final DataSchemaNode check = choice.findDataSchemaChild(qname).orElse(null);
188             verify(check == ret, "Data tree result %s does not match choice result %s", ret, check);
189         }
190         return ret;
191     }
192
193     private <T extends DataSchemaNode> @NonNull T enterDataTree(final PathArgument name,
194             final @NonNull Class<T> expectedClass, final @NonNull String humanString) {
195         final var schema = enterDataTree(name);
196         final T casted;
197         try {
198             casted = expectedClass.cast(schema);
199         } catch (ClassCastException e) {
200             throw new IllegalArgumentException("Node " + schema + " is not " + humanString, e);
201         }
202         schemaStack.push(casted);
203         return casted;
204     }
205
206     public void startList(final PathArgument name) {
207         enterDataTree(name, ListSchemaNode.class, "a list");
208     }
209
210     public void startListItem(final PathArgument name) throws IOException {
211         final Object schema = getParent();
212         checkArgument(schema instanceof ListSchemaNode, "List item is not appropriate");
213         schemaStack.push((ListSchemaNode) schema);
214     }
215
216     public void startLeafNode(final NodeIdentifier name) throws IOException {
217         enterDataTree(name, LeafSchemaNode.class, "a leaf");
218     }
219
220     public void startLeafSet(final NodeIdentifier name) {
221         enterDataTree(name, LeafListSchemaNode.class, "a leaf-list");
222     }
223
224     private @NonNull LeafListSchemaNode leafSetEntryNode(final QName qname) {
225         final Object parent = getParent();
226         if (parent instanceof LeafListSchemaNode leafList) {
227             return leafList;
228         }
229         if (parent instanceof DataNodeContainer parentContainer) {
230             final var child = parentContainer.dataChildByName(qname);
231             if (child instanceof LeafListSchemaNode childLeafList) {
232                 return childLeafList;
233             }
234             throw new IllegalArgumentException(
235                 "Node " + child + " is neither a leaf-list nor currently in a leaf-list");
236         }
237         throw new IllegalArgumentException("Cannot lookup " + qname + " in parent " + parent);
238     }
239
240     public void startLeafSetEntryNode(final NodeWithValue<?> name) {
241         schemaStack.push(leafSetEntryNode(name.getNodeType()));
242     }
243
244     public void startChoiceNode(final NodeIdentifier name) {
245         LOG.debug("Enter choice {}", name);
246         final var stmt = dataTree.enterChoice(name.getNodeType());
247         verify(stmt instanceof ChoiceSchemaNode, "Node %s is not a choice", stmt);
248         schemaStack.push((ChoiceSchemaNode) stmt);
249     }
250
251     public SchemaNode startContainerNode(final NodeIdentifier name) {
252         LOG.debug("Enter container {}", name);
253
254         final SchemaNode schema;
255         if (schemaStack.isEmpty() && root instanceof NotificationDefinition notification) {
256             // Special case for stacks initialized at notification. We pretend the first container is contained within
257             // itself.
258             // FIXME: 8.0.0: factor this special case out to something more reasonable, like being initialized at the
259             //               Notification's parent and knowing to enterSchemaTree() instead of enterDataTree().
260             schema = notification;
261             schemaStack.push(schema);
262         } else {
263             schema = enterDataTree(name, ContainerLike.class, "a container");
264         }
265
266         return schema;
267     }
268
269     public void startAnyxmlNode(final NodeIdentifier name) {
270         enterDataTree(name, AnyxmlSchemaNode.class, "anyxml");
271     }
272
273     public void startAnydataNode(final NodeIdentifier name) {
274         enterDataTree(name, AnydataSchemaNode.class, "anydata");
275     }
276
277     public Object endNode() {
278         final var ret = schemaStack.pop();
279         // If this is a data tree node, make sure it is updated. Before that, though, we need to check if this is not
280         // actually listEntry -> list or leafListEntry -> leafList exit.
281         if (!(ret instanceof AugmentationSchemaNode) && getParent() != ret) {
282             dataTree.exit();
283         }
284         return ret;
285     }
286 }