455c1fe23bcfc77c82d175fc65b5daa086fcddac
[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     /**
167      * Return a copy of this tracker's state as an {@link SchemaInferenceStack}.
168      *
169      * @return A SchemaInferenceStack
170      */
171     public @NonNull SchemaInferenceStack toSchemaInferenceStack() {
172         return dataTree.copy();
173     }
174
175     public Object getParent() {
176         final WithStatus schema = schemaStack.peek();
177         return schema == null ? root : schema;
178     }
179
180     private 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) {
187             final DataSchemaNode check = ((ChoiceSchemaNode) parent).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     public void startList(final PathArgument name) {
194         final SchemaNode schema = enterDataTree(name);
195         checkArgument(schema instanceof ListSchemaNode, "Node %s is not a list", schema);
196         schemaStack.push(schema);
197     }
198
199     public void startListItem(final PathArgument name) throws IOException {
200         final Object schema = getParent();
201         checkArgument(schema instanceof ListSchemaNode, "List item is not appropriate");
202         schemaStack.push((ListSchemaNode) schema);
203     }
204
205     public void startLeafNode(final NodeIdentifier name) throws IOException {
206         final SchemaNode schema = enterDataTree(name);
207         checkArgument(schema instanceof LeafSchemaNode, "Node %s is not a leaf", schema);
208         schemaStack.push(schema);
209     }
210
211     public LeafListSchemaNode startLeafSet(final NodeIdentifier name) {
212         final SchemaNode schema = enterDataTree(name);
213         checkArgument(schema instanceof LeafListSchemaNode, "Node %s is not a leaf-list", schema);
214         schemaStack.push(schema);
215         return (LeafListSchemaNode) schema;
216     }
217
218     public LeafListSchemaNode leafSetEntryNode(final QName qname) {
219         final Object parent = getParent();
220         if (parent instanceof LeafListSchemaNode) {
221             return (LeafListSchemaNode) parent;
222         }
223
224         // FIXME: when would this trigger?
225         final SchemaNode child = SchemaUtils.findDataChildSchemaByQName((SchemaNode) parent, 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         final SchemaNode schema = enterDataTree(name);
247         checkArgument(schema instanceof ContainerLike || schema instanceof NotificationDefinition,
248             "Node %s is not a container nor a notification", schema);
249         schemaStack.push(schema);
250         return schema;
251     }
252
253     public AugmentationSchemaNode startAugmentationNode(final AugmentationIdentifier identifier) {
254         LOG.debug("Enter augmentation {}", identifier);
255         Object parent = getParent();
256
257         checkArgument(parent instanceof AugmentationTarget, "Augmentation not allowed under %s", parent);
258         if (parent instanceof ChoiceSchemaNode) {
259             final QName name = Iterables.get(identifier.getPossibleChildNames(), 0);
260             parent = findCaseByChild((ChoiceSchemaNode) parent, name);
261         }
262         checkArgument(parent instanceof DataNodeContainer, "Augmentation allowed only in DataNodeContainer", parent);
263         final AugmentationSchemaNode schema = SchemaUtils.findSchemaForAugment((AugmentationTarget) parent,
264             identifier.getPossibleChildNames());
265         final AugmentationSchemaNode resolvedSchema = EffectiveAugmentationSchema.create(schema,
266             (DataNodeContainer) parent);
267         schemaStack.push(resolvedSchema);
268         return resolvedSchema;
269     }
270
271     private static SchemaNode findCaseByChild(final ChoiceSchemaNode parent, final QName qname) {
272         for (final CaseSchemaNode caze : parent.getCases()) {
273             final Optional<DataSchemaNode> potential = caze.findDataChildByName(qname);
274             if (potential.isPresent()) {
275                 return caze;
276             }
277         }
278         return null;
279     }
280
281     public void startAnyxmlNode(final NodeIdentifier name) {
282         final SchemaNode schema = enterDataTree(name);
283         checkArgument(schema instanceof AnyxmlSchemaNode, "Node %s is not anyxml", schema);
284         schemaStack.push(schema);
285     }
286
287     public void startAnydataNode(final NodeIdentifier name) {
288         final SchemaNode schema = enterDataTree(name);
289         checkArgument(schema instanceof AnydataSchemaNode, "Node %s is not anydata", schema);
290         schemaStack.push(schema);
291     }
292
293     public Object endNode() {
294         final Object 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 (!(ret instanceof AugmentationSchemaNode) && getParent() != ret) {
298             dataTree.exit();
299         }
300         return ret;
301     }
302 }