Maintain SchemaInferenceStack in SchemaTracker
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / codec / SchemaTracker.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.data.impl.codec;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.annotations.Beta;
15 import com.google.common.collect.Iterables;
16 import java.io.IOException;
17 import java.util.ArrayDeque;
18 import java.util.Deque;
19 import java.util.Optional;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
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.stream.NormalizedNodeStreamWriter;
27 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils;
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.AugmentationSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
32 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
35 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
38 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
39 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
40 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
44 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
46 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
47 import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
48 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
49 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
52 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
53 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
54 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * Utility class for tracking the underlying state of the underlying schema node.
60  */
61 @Beta
62 public final class SchemaTracker {
63     private static final Logger LOG = LoggerFactory.getLogger(SchemaTracker.class);
64
65     private final Deque<WithStatus> schemaStack = new ArrayDeque<>();
66     private final SchemaInferenceStack dataTree;
67     private final DataNodeContainer root;
68
69     private SchemaTracker(final EffectiveModelContext context) {
70         root = requireNonNull(context);
71         dataTree = SchemaInferenceStack.of(context);
72     }
73
74     private SchemaTracker(final SchemaInferenceStack dataTree) {
75         this.dataTree = requireNonNull(dataTree);
76         if (!dataTree.isEmpty()) {
77             final EffectiveStatement<?, ?> current = dataTree.currentStatement();
78             checkArgument(current instanceof DataNodeContainer, "Cannot instantiate on %s", current);
79
80             root = (DataNodeContainer) current;
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 SchemaTracker create(final EffectiveStatementInference root) {
94         return new SchemaTracker(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 SchemaTracker create(final Inference root) {
105         return new SchemaTracker(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 SchemaTracker create(final EffectiveModelContext context) {
116         return new SchemaTracker(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 SchemaTracker}
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 SchemaTracker create(final EffectiveModelContext context, final Absolute path) {
129         return new SchemaTracker(SchemaInferenceStack.of(context, path));
130     }
131
132     /**
133      * Create a new writer with the specified context and rooted in the specified schema path.
134      *
135      * @param context Associated {@link EffectiveModelContext}
136      * @param path schema path
137      * @return A new {@link SchemaTracker}
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 SchemaTracker create(final EffectiveModelContext context, final SchemaPath path) {
142         return new SchemaTracker(SchemaInferenceStack.ofInstantiatedPath(context, path));
143     }
144
145     /**
146      * Create a new writer with the specified context and rooted in the specified schema path.
147      *
148      * @param context Associated {@link EffectiveModelContext}
149      * @param operation Operation schema path
150      * @param qname Input/Output container QName
151      * @return A new {@link NormalizedNodeStreamWriter}
152      * @throws NullPointerException if any argument is null
153      * @throws IllegalArgumentException if {@code operation} does not point to an actual operation or if {@code qname}
154      *                                  does not identify a valid root underneath it.
155      */
156     public static @NonNull SchemaTracker forOperation(final EffectiveModelContext context, final Absolute operation,
157             final QName qname) {
158         final SchemaInferenceStack stack = SchemaInferenceStack.of(context, operation);
159         final EffectiveStatement<?, ?> current = stack.currentStatement();
160         checkArgument(current instanceof RpcEffectiveStatement || current instanceof ActionEffectiveStatement,
161             "Path %s resolved into non-operation %s", operation, current);
162         stack.enterSchemaTree(qname);
163         return new SchemaTracker(stack);
164     }
165
166     public Object getParent() {
167         final WithStatus schema = schemaStack.peek();
168         return schema == null ? root : schema;
169     }
170
171     private SchemaNode enterDataTree(final PathArgument name) {
172         final QName qname = name.getNodeType();
173         final DataTreeEffectiveStatement<?> stmt = dataTree.enterDataTree(qname);
174         verify(stmt instanceof SchemaNode, "Unexpected result %s", stmt);
175         final SchemaNode ret = (SchemaNode) stmt;
176         final Object parent = getParent();
177         if (parent instanceof ChoiceSchemaNode) {
178             final DataSchemaNode check = ((ChoiceSchemaNode) parent).findDataSchemaChild(qname).orElse(null);
179             verify(check == ret, "Data tree result %s does not match choice result %s", ret, check);
180         }
181         return ret;
182     }
183
184     public void startList(final PathArgument name) {
185         final SchemaNode schema = enterDataTree(name);
186         checkArgument(schema instanceof ListSchemaNode, "Node %s is not a list", schema);
187         schemaStack.push(schema);
188     }
189
190     public void startListItem(final PathArgument name) throws IOException {
191         final Object schema = getParent();
192         checkArgument(schema instanceof ListSchemaNode, "List item is not appropriate");
193         schemaStack.push((ListSchemaNode) schema);
194     }
195
196     public void startLeafNode(final NodeIdentifier name) throws IOException {
197         final SchemaNode schema = enterDataTree(name);
198         checkArgument(schema instanceof LeafSchemaNode, "Node %s is not a leaf", schema);
199         schemaStack.push(schema);
200     }
201
202     public LeafListSchemaNode startLeafSet(final NodeIdentifier name) {
203         final SchemaNode schema = enterDataTree(name);
204         checkArgument(schema instanceof LeafListSchemaNode, "Node %s is not a leaf-list", schema);
205         schemaStack.push(schema);
206         return (LeafListSchemaNode) schema;
207     }
208
209     public LeafListSchemaNode leafSetEntryNode(final QName qname) {
210         final Object parent = getParent();
211         if (parent instanceof LeafListSchemaNode) {
212             return (LeafListSchemaNode) parent;
213         }
214
215         // FIXME: when would this trigger?
216         final SchemaNode child = SchemaUtils.findDataChildSchemaByQName((SchemaNode) parent, qname);
217         checkArgument(child instanceof LeafListSchemaNode,
218             "Node %s is neither a leaf-list nor currently in a leaf-list", child);
219         return (LeafListSchemaNode) child;
220     }
221
222     public void startLeafSetEntryNode(final NodeWithValue<?> name) {
223         schemaStack.push(leafSetEntryNode(name.getNodeType()));
224     }
225
226     public ChoiceSchemaNode startChoiceNode(final NodeIdentifier name) {
227         LOG.debug("Enter choice {}", name);
228         final ChoiceEffectiveStatement stmt = dataTree.enterChoice(name.getNodeType());
229         verify(stmt instanceof ChoiceSchemaNode, "Node %s is not a choice", stmt);
230         final ChoiceSchemaNode ret = (ChoiceSchemaNode) stmt;
231         schemaStack.push(ret);
232         return ret;
233     }
234
235     public SchemaNode startContainerNode(final NodeIdentifier name) {
236         LOG.debug("Enter container {}", name);
237         final SchemaNode schema = enterDataTree(name);
238         checkArgument(schema instanceof ContainerLike || schema instanceof NotificationDefinition,
239             "Node %s is not a container nor a notification", schema);
240         schemaStack.push(schema);
241         return schema;
242     }
243
244     public AugmentationSchemaNode startAugmentationNode(final AugmentationIdentifier identifier) {
245         LOG.debug("Enter augmentation {}", identifier);
246         Object parent = getParent();
247
248         checkArgument(parent instanceof AugmentationTarget, "Augmentation not allowed under %s", parent);
249         if (parent instanceof ChoiceSchemaNode) {
250             final QName name = Iterables.get(identifier.getPossibleChildNames(), 0);
251             parent = findCaseByChild((ChoiceSchemaNode) parent, name);
252         }
253         checkArgument(parent instanceof DataNodeContainer, "Augmentation allowed only in DataNodeContainer", parent);
254         final AugmentationSchemaNode schema = SchemaUtils.findSchemaForAugment((AugmentationTarget) parent,
255             identifier.getPossibleChildNames());
256         final AugmentationSchemaNode resolvedSchema = EffectiveAugmentationSchema.create(schema,
257             (DataNodeContainer) parent);
258         schemaStack.push(resolvedSchema);
259         return resolvedSchema;
260     }
261
262     private static SchemaNode findCaseByChild(final ChoiceSchemaNode parent, final QName qname) {
263         for (final CaseSchemaNode caze : parent.getCases()) {
264             final Optional<DataSchemaNode> potential = caze.findDataChildByName(qname);
265             if (potential.isPresent()) {
266                 return caze;
267             }
268         }
269         return null;
270     }
271
272     public void startAnyxmlNode(final NodeIdentifier name) {
273         final SchemaNode schema = enterDataTree(name);
274         checkArgument(schema instanceof AnyxmlSchemaNode, "Node %s is not anyxml", schema);
275         schemaStack.push(schema);
276     }
277
278     public void startAnydataNode(final NodeIdentifier name) {
279         final SchemaNode schema = enterDataTree(name);
280         checkArgument(schema instanceof AnydataSchemaNode, "Node %s is not anydata", schema);
281         schemaStack.push(schema);
282     }
283
284     public Object endNode() {
285         final Object ret = schemaStack.pop();
286         // If this is a data tree node, make sure it is updated. Before that, though, we need to check if this is not
287         // actually listEntry -> list or leafListEntry -> leafList exit.
288         if (!(ret instanceof AugmentationSchemaNode) && getParent() != ret) {
289             dataTree.exit();
290         }
291         return ret;
292     }
293 }