Expose BindingDataContainerWriter
[yangtools.git] / data / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / SchemaOrderedNormalizedNodeWriter.java
1 /*
2  * Copyright (c) 2016 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 java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ArrayListMultimap;
13 import com.google.common.collect.Multimap;
14 import java.io.IOException;
15 import java.util.Collection;
16 import org.opendaylight.yangtools.yang.common.QName;
17 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
18 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
19 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
20 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
21 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
22 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
23 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
24 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
27 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
28 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
29
30 /**
31  * This is an iterator over a {@link NormalizedNode}. Unlike {@link NormalizedNodeWriter}, this iterates over elements
32  * in the order as they are defined in YANG file.
33  */
34 public class SchemaOrderedNormalizedNodeWriter extends NormalizedNodeWriter {
35     private final EffectiveModelContext modelContext;
36     private final SchemaNode root;
37
38     private SchemaNode currentSchemaNode;
39
40     /**
41      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
42      *
43      * @param writer Back-end writer
44      * @param modelContext Associated {@link EffectiveModelContext}
45      */
46     public SchemaOrderedNormalizedNodeWriter(final NormalizedNodeStreamWriter writer,
47             final EffectiveModelContext modelContext) {
48         super(writer);
49         root = this.modelContext = requireNonNull(modelContext);
50     }
51
52     private SchemaOrderedNormalizedNodeWriter(final NormalizedNodeStreamWriter writer,
53             final SchemaInferenceStack stack) {
54         super(writer);
55         modelContext = stack.modelContext();
56
57         if (!stack.isEmpty()) {
58             final var current = stack.currentStatement();
59             // FIXME: this should be one of NormalizedNodeContainer/NotificationDefinition/OperationDefinition
60             if (current instanceof SchemaNode schemaNode) {
61                 root = schemaNode;
62             } else {
63                 throw new IllegalArgumentException("Instantiating at " + current + " is not supported");
64             }
65         } else {
66             root = modelContext;
67         }
68     }
69
70     /**
71      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
72      *
73      * @param writer Back-end writer
74      * @param schemaContext Associated {@link EffectiveModelContext}
75      * @param path root path
76      */
77     public SchemaOrderedNormalizedNodeWriter(final NormalizedNodeStreamWriter writer,
78             final EffectiveModelContext schemaContext, final Absolute path) {
79         this(writer, SchemaInferenceStack.of(schemaContext, path));
80     }
81
82     /**
83      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
84      *
85      * @param writer Back-end writer
86      * @param rootInference A SchemaTreeInference pointing to the root element
87      */
88     public SchemaOrderedNormalizedNodeWriter(final NormalizedNodeStreamWriter writer,
89             final SchemaTreeInference rootInference) {
90         this(writer, SchemaInferenceStack.ofInference(rootInference));
91     }
92
93     @Override
94     public SchemaOrderedNormalizedNodeWriter write(final NormalizedNode node) throws IOException {
95         if (modelContext.equals(root)) {
96             currentSchemaNode = modelContext.dataChildByName(node.name().getNodeType());
97         } else {
98             currentSchemaNode = root;
99         }
100         return write(node, currentSchemaNode);
101     }
102
103     /**
104      * Iterate over the provided collection and emit write
105      * events to the encapsulated {@link NormalizedNodeStreamWriter}.
106      *
107      * @param nodes nodes
108      * @return NormalizedNodeWriter this
109      * @throws IOException when thrown from the backing writer.
110      */
111     public SchemaOrderedNormalizedNodeWriter write(final Collection<DataContainerChild> nodes) throws IOException {
112         currentSchemaNode = root;
113         if (writeChildren(nodes, currentSchemaNode, false)) {
114             return this;
115         }
116
117         throw new IllegalStateException("It wasn't possible to serialize nodes " + nodes);
118     }
119
120     private SchemaOrderedNormalizedNodeWriter write(final NormalizedNode node, final SchemaNode dataSchemaNode)
121             throws IOException {
122
123         //Set current schemaNode
124         try (var sns = new SchemaNodeSetter(dataSchemaNode)) {
125             if (node == null) {
126                 return this;
127             }
128
129             if (wasProcessedAsCompositeNode(node)) {
130                 return this;
131             }
132
133             if (wasProcessAsSimpleNode(node)) {
134                 return this;
135             }
136         }
137
138         throw new IllegalStateException("It wasn't possible to serialize node " + node);
139     }
140
141     private void write(final Collection<NormalizedNode> nodes, final SchemaNode dataSchemaNode) throws IOException {
142         for (var node : nodes) {
143             write(node, dataSchemaNode);
144         }
145     }
146
147     @Override
148     protected boolean writeChildren(final Iterable<? extends NormalizedNode> children) throws IOException {
149         return writeChildren(children, currentSchemaNode, true);
150     }
151
152     private boolean writeChildren(final Iterable<? extends NormalizedNode> children, final SchemaNode parentSchemaNode,
153             final boolean endParent) throws IOException {
154         // Augmentations cannot be gotten with node.getChild so create our own structure with augmentations resolved
155         final var qnameToNodes = ArrayListMultimap.<QName, NormalizedNode>create();
156         for (var child : children) {
157             putChild(qnameToNodes, child);
158         }
159
160         if (parentSchemaNode instanceof DataNodeContainer parentContainer) {
161             if (parentContainer instanceof ListSchemaNode && qnameToNodes.containsKey(parentSchemaNode.getQName())) {
162                 write(qnameToNodes.get(parentSchemaNode.getQName()), parentSchemaNode);
163             } else {
164                 for (var schemaNode : parentContainer.getChildNodes()) {
165                     write(qnameToNodes.get(schemaNode.getQName()), schemaNode);
166                 }
167             }
168         } else if (parentSchemaNode instanceof ChoiceSchemaNode parentChoice) {
169             for (var childCase : parentChoice.getCases()) {
170                 for (var childCaseChild : childCase.getChildNodes()) {
171                     final var node = qnameToNodes.asMap().get(childCaseChild.getQName());
172                     if (node != null) {
173                         write(node, childCaseChild);
174                     }
175                 }
176             }
177         } else {
178             for (var child : children) {
179                 writeLeaf(child);
180             }
181         }
182         if (endParent) {
183             getWriter().endNode();
184         }
185         return true;
186     }
187
188     private SchemaOrderedNormalizedNodeWriter writeLeaf(final NormalizedNode node) throws IOException {
189         if (wasProcessAsSimpleNode(node)) {
190             return this;
191         }
192
193         throw new IllegalStateException("It wasn't possible to serialize node " + node);
194     }
195
196     private static void putChild(final Multimap<QName, NormalizedNode> qnameToNodes, final NormalizedNode child) {
197         qnameToNodes.put(child.name().getNodeType(), child);
198     }
199
200     private final class SchemaNodeSetter implements AutoCloseable {
201         private final SchemaNode previousSchemaNode;
202
203         /**
204          * Sets current schema node new value and store old value for later restore.
205          */
206         SchemaNodeSetter(final SchemaNode schemaNode) {
207             previousSchemaNode = currentSchemaNode;
208             currentSchemaNode = schemaNode;
209         }
210
211         /**
212          * Restore previous schema node.
213          */
214         @Override
215         public void close() {
216             currentSchemaNode = previousSchemaNode;
217         }
218     }
219 }