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