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