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