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