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