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