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