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