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