Fix mandatory enforcer failure on augmented nodes
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / ImmutableNormalizedNodeStreamWriter.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.schema;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12 import static java.util.Objects.requireNonNull;
13
14 import java.io.IOException;
15 import java.util.ArrayDeque;
16 import java.util.Deque;
17 import javax.xml.transform.dom.DOMSource;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyxmlSchemaNode;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
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.data.impl.schema.builder.api.DataContainerNodeBuilder;
28 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
29 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
30 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableAnyXmlNodeBuilder;
31 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableAnydataNodeBuilder;
32 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableAugmentationNodeBuilder;
33 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableChoiceNodeBuilder;
34 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
35 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder;
36 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetEntryNodeBuilder;
37 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder;
38 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
39 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder;
40 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableOrderedLeafSetNodeBuilder;
41 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableOrderedMapNodeBuilder;
42 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListEntryNodeBuilder;
43 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListNodeBuilder;
44 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableYangModeledAnyXmlNodeBuilder;
45 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
46
47 /**
48  * Implementation of {@link NormalizedNodeStreamWriter}, which constructs immutable instances of
49  * {@link NormalizedNode}s.
50  *
51  * <p>
52  * This writer supports two modes of behaviour one is using {@link #from(NormalizedNodeResult)} where resulting
53  * NormalizedNode will be stored in supplied result object.
54  *
55  * <p>
56  * Other mode of operation is using {@link #from(NormalizedNodeContainerBuilder)}, where all created nodes will be
57  * written to this builder.
58  *
59  * <p>
60  * This class is not final for purposes of customization, normal users should not need to subclass it.
61  */
62 public class ImmutableNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter {
63     @SuppressWarnings("rawtypes")
64     private final Deque<NormalizedNodeBuilder> builders = new ArrayDeque<>();
65
66     private DataSchemaNode nextSchema;
67
68     @SuppressWarnings("rawtypes")
69     protected ImmutableNormalizedNodeStreamWriter(final NormalizedNodeBuilder topLevelBuilder) {
70         builders.push(topLevelBuilder);
71     }
72
73     protected ImmutableNormalizedNodeStreamWriter(final NormalizedNodeResult result) {
74         this(new NormalizedNodeResultBuilder(result));
75     }
76
77     /**
78      * Creates a {@link NormalizedNodeStreamWriter} which creates instances of supplied {@link NormalizedNode}s
79      * and writes them to supplied builder as child nodes.
80      *
81      * <p>
82      * Type of supplied {@link NormalizedNodeContainerBuilder} affects, which events could be emitted in order
83      * to ensure proper construction of data.
84      *
85      * @param builder Builder to which data will be written.
86      * @return {@link NormalizedNodeStreamWriter} which writes data
87      */
88     public static @NonNull NormalizedNodeStreamWriter from(final NormalizedNodeContainerBuilder<?, ?, ?, ?> builder) {
89         return new ImmutableNormalizedNodeStreamWriter(builder);
90     }
91
92     /**
93      * Creates a {@link NormalizedNodeStreamWriter} which creates one instance of top-level {@link NormalizedNode}
94      * (type of NormalizedNode) is determined by first start event.
95      *
96      * <p>
97      * Result is built when {@link #endNode()} associated with that start event is emitted.
98      *
99      * <p>
100      * Writer properly creates also nested {@link NormalizedNode} instances, if their are supported inside the scope
101      * of the first event.
102      *
103      * <p>
104      * This method is useful for clients, which knows there will be one top-level node written, but does not know which
105      * type of {@link NormalizedNode} will be written.
106      *
107      * @param result {@link NormalizedNodeResult} object which will hold result value.
108      * @return {@link NormalizedNodeStreamWriter} which will write item to supplied result holder.
109      */
110     public static @NonNull NormalizedNodeStreamWriter from(final NormalizedNodeResult result) {
111         return result instanceof NormalizedNodeMetadataResult ? from((NormalizedNodeMetadataResult) result)
112                 : new ImmutableNormalizedNodeStreamWriter(result);
113     }
114
115     /**
116      * Creates a {@link NormalizedNodeStreamWriter} which creates one instance of top-level {@link NormalizedNode}
117      * (type of NormalizedNode) is determined by first start event.
118      *
119      * <p>
120      * Result is built when {@link #endNode()} associated with that start event is emitted.
121      *
122      * <p>
123      * Writer properly creates also nested {@link NormalizedNode} instances, if their are supported inside the scope
124      * of the first event.
125      *
126      * <p>
127      * This method is useful for clients, which knows there will be one top-level node written, but does not know which
128      * type of {@link NormalizedNode} will be written.
129      *
130      * @param result {@link NormalizedNodeResult} object which will hold result value.
131      * @return {@link NormalizedNodeStreamWriter} which will write item to supplied result holder.
132      */
133     public static @NonNull NormalizedNodeStreamWriter from(final NormalizedNodeMetadataResult result) {
134         return new ImmutableMetadataNormalizedNodeStreamWriter(result);
135     }
136
137     @Override
138     public void startLeafNode(final NodeIdentifier name) {
139         checkDataNodeContainer();
140         enter(name, leafNodeBuilder(nextSchema));
141         nextSchema = null;
142     }
143
144     @Override
145     public void startLeafSet(final NodeIdentifier name, final int childSizeHint) {
146         checkDataNodeContainer();
147         enter(name, UNKNOWN_SIZE == childSizeHint ? InterningLeafSetNodeBuilder.create(nextSchema)
148                 : InterningLeafSetNodeBuilder.create(nextSchema, childSizeHint));
149     }
150
151     @Override
152     public void startLeafSetEntryNode(final NodeWithValue<?> name) {
153         final NormalizedNodeBuilder<?, ?, ?> current = current();
154         checkArgument(current instanceof ImmutableLeafSetNodeBuilder
155             || current instanceof ImmutableOrderedLeafSetNodeBuilder || current instanceof NormalizedNodeResultBuilder,
156             "LeafSetEntryNode is not valid for parent %s", current);
157         enter(name, leafsetEntryNodeBuilder());
158         nextSchema = null;
159     }
160
161     @Override
162     public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) {
163         checkDataNodeContainer();
164         enter(name, Builders.orderedLeafSetBuilder());
165     }
166
167     @Override
168     public boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) {
169         checkDataNodeContainer();
170         if (DOMSource.class.isAssignableFrom(objectModel)) {
171             enter(name, ImmutableAnyXmlNodeBuilder.create());
172             nextSchema = null;
173             return true;
174         }
175         return false;
176     }
177
178     @Override
179     public void startContainerNode(final NodeIdentifier name, final int childSizeHint) {
180         checkDataNodeContainer();
181         enter(name, UNKNOWN_SIZE == childSizeHint ? ImmutableContainerNodeBuilder.create()
182                 : ImmutableContainerNodeBuilder.create(childSizeHint));
183     }
184
185     @Override
186     public void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint) {
187         checkDataNodeContainer();
188
189         checkArgument(nextSchema instanceof YangModeledAnyxmlSchemaNode,
190                 "Schema of this node should be instance of YangModeledAnyxmlSchemaNode");
191         final YangModeledAnyxmlSchemaNode anyxmlSchema = (YangModeledAnyxmlSchemaNode) nextSchema;
192         enter(name, UNKNOWN_SIZE == childSizeHint ? ImmutableYangModeledAnyXmlNodeBuilder.create(anyxmlSchema)
193                 : ImmutableYangModeledAnyXmlNodeBuilder.create(anyxmlSchema, childSizeHint));
194     }
195
196     @Override
197     public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) {
198         checkDataNodeContainer();
199         enter(name, UNKNOWN_SIZE == childSizeHint ? ImmutableUnkeyedListNodeBuilder.create()
200                 : ImmutableUnkeyedListNodeBuilder.create(childSizeHint));
201     }
202
203     @Override
204     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) {
205         final NormalizedNodeBuilder<?, ?, ?> current = current();
206         checkArgument(current instanceof ImmutableUnkeyedListNodeBuilder
207             || current instanceof NormalizedNodeResultBuilder);
208         enter(name, UNKNOWN_SIZE == childSizeHint ? ImmutableUnkeyedListEntryNodeBuilder.create()
209                 : ImmutableUnkeyedListEntryNodeBuilder.create(childSizeHint));
210     }
211
212     @Override
213     public void startMapNode(final NodeIdentifier name, final int childSizeHint) {
214         checkDataNodeContainer();
215         enter(name, UNKNOWN_SIZE == childSizeHint ? ImmutableMapNodeBuilder.create()
216                 : ImmutableMapNodeBuilder.create(childSizeHint));
217     }
218
219     @Override
220     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint) {
221         final NormalizedNodeBuilder<?, ?, ?> current = current();
222         checkArgument(current instanceof ImmutableMapNodeBuilder || current instanceof ImmutableOrderedMapNodeBuilder
223             || current instanceof NormalizedNodeResultBuilder);
224
225         enter(identifier, UNKNOWN_SIZE == childSizeHint ? ImmutableMapEntryNodeBuilder.create()
226                 : ImmutableMapEntryNodeBuilder.create(childSizeHint));
227     }
228
229     @Override
230     public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) {
231         checkDataNodeContainer();
232         enter(name, UNKNOWN_SIZE == childSizeHint ? ImmutableOrderedMapNodeBuilder.create()
233                 : ImmutableOrderedMapNodeBuilder.create(childSizeHint));
234     }
235
236     @Override
237     public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) {
238         checkDataNodeContainer();
239         enter(name, UNKNOWN_SIZE == childSizeHint ? ImmutableChoiceNodeBuilder.create()
240                 : ImmutableChoiceNodeBuilder.create(childSizeHint));
241     }
242
243     @Override
244     public void startAugmentationNode(final AugmentationIdentifier identifier) {
245         checkDataNodeContainer();
246         checkArgument(!(current() instanceof ImmutableAugmentationNodeBuilder));
247         enter(identifier, Builders.augmentationBuilder());
248     }
249
250     @Override
251     public void flush() {
252         // no-op
253     }
254
255     @Override
256     public void close() {
257         // no-op
258     }
259
260     @Override
261     public void nextDataSchemaNode(final DataSchemaNode schema) {
262         nextSchema = requireNonNull(schema);
263     }
264
265     @Override
266     public void scalarValue(final Object value) {
267         currentScalar().withValue(value);
268     }
269
270     @Override
271     public void domSourceValue(final DOMSource value) {
272         currentScalar().withValue(value);
273     }
274
275     @Override
276     @SuppressWarnings({ "rawtypes", "unchecked" })
277     public void endNode() {
278         final NormalizedNodeBuilder finishedBuilder = builders.poll();
279         checkState(finishedBuilder != null, "Node which should be closed does not exists.");
280         final NormalizedNode<PathArgument, ?> product = finishedBuilder.build();
281         nextSchema = null;
282
283         writeChild(product);
284     }
285
286     @Override
287     public boolean startAnydataNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
288         checkDataNodeContainer();
289         enter(name, ImmutableAnydataNodeBuilder.create(objectModel));
290         // We support all object models
291         return true;
292     }
293
294     /**
295      * Add a child not to the currently-open builder.
296      *
297      * @param child A new child
298      * @throws NullPointerException if {@code child} is null
299      * @throws IllegalStateException if there is no open builder
300      */
301     @SuppressWarnings({ "rawtypes", "unchecked" })
302     protected final void writeChild(final NormalizedNode<?, ?> child) {
303         final NormalizedNodeContainerBuilder current = currentContainer();
304         checkState(current != null, "Reached top level node, which could not be closed in this writer.");
305         current.addChild(requireNonNull(child));
306     }
307
308     // Exposed for ImmutableMetadataNormalizedNodeStreamWriter
309     @SuppressWarnings("rawtypes")
310     void enter(final PathArgument identifier, final NormalizedNodeBuilder next) {
311         builders.push(next.withNodeIdentifier(identifier));
312         nextSchema = null;
313     }
314
315     // Exposed for ImmutableMetadataNormalizedNodeStreamWriter
316     protected final NormalizedNodeBuilder popBuilder() {
317         return builders.pop();
318     }
319
320     final void reset(final NormalizedNodeResultBuilder builder) {
321         nextSchema = null;
322         builders.clear();
323         builders.push(builder);
324     }
325
326     private <T> ImmutableLeafNodeBuilder<T> leafNodeBuilder(final DataSchemaNode schema) {
327         final InterningLeafNodeBuilder<T> interning = InterningLeafNodeBuilder.forSchema(schema);
328         return interning != null ? interning : leafNodeBuilder();
329     }
330
331     <T> ImmutableLeafNodeBuilder<T> leafNodeBuilder() {
332         return new ImmutableLeafNodeBuilder<>();
333     }
334
335     <T> ImmutableLeafSetEntryNodeBuilder<T> leafsetEntryNodeBuilder() {
336         return ImmutableLeafSetEntryNodeBuilder.create();
337     }
338
339     private void checkDataNodeContainer() {
340         @SuppressWarnings("rawtypes")
341         final NormalizedNodeContainerBuilder current = currentContainer();
342         if (!(current instanceof NormalizedNodeResultBuilder)) {
343             checkArgument(current instanceof DataContainerNodeBuilder<?, ?>, "Invalid nesting of data.");
344         }
345     }
346
347     @SuppressWarnings("rawtypes")
348     private NormalizedNodeBuilder current() {
349         return builders.peek();
350     }
351
352     @SuppressWarnings("rawtypes")
353     private NormalizedNodeContainerBuilder currentContainer() {
354         final NormalizedNodeBuilder current = current();
355         if (current == null) {
356             return null;
357         }
358         checkState(current instanceof NormalizedNodeContainerBuilder, "%s is not a node container", current);
359         return (NormalizedNodeContainerBuilder) current;
360     }
361
362     @SuppressWarnings("rawtypes")
363     private NormalizedNodeBuilder currentScalar() {
364         final NormalizedNodeBuilder current = current();
365         checkState(!(current instanceof NormalizedNodeContainerBuilder), "Unexpected node container %s", current);
366         return current;
367     }
368 }