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