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