Bug 8675: Fix a design flaw of the new XML parser
[yangtools.git] / yang / yang-data-util / src / main / java / org / opendaylight / yangtools / yang / data / util / CompositeNodeDataWithSchema.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.util;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.ArrayListMultimap;
12 import com.google.common.collect.Collections2;
13 import com.google.common.collect.ImmutableSet;
14 import com.google.common.collect.Multimap;
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Deque;
19 import java.util.List;
20 import java.util.Map.Entry;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
24 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
26 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
27 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
28 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.YangModeledAnyXmlSchemaNode;
35
36 /**
37  * Utility class used for tracking parser state as needed by a StAX-like parser.
38  * This class is to be used only by respective XML and JSON parsers in yang-data-codec-xml and yang-data-codec-gson.
39  *
40  * <p>
41  * Represents a node which is composed of multiple simpler nodes.
42  */
43 public class CompositeNodeDataWithSchema extends AbstractNodeDataWithSchema {
44
45     /**
46      * nodes which were added to schema via augmentation and are present in data input.
47      */
48     private final Multimap<AugmentationSchema, AbstractNodeDataWithSchema> augmentationsToChild =
49         ArrayListMultimap.create();
50
51     /**
52      * remaining data nodes (which aren't added via augment). Every of one them should have the same QName.
53      */
54     private final List<AbstractNodeDataWithSchema> children = new ArrayList<>();
55
56     public CompositeNodeDataWithSchema(final DataSchemaNode schema) {
57         super(schema);
58     }
59
60     private AbstractNodeDataWithSchema addChild(final DataSchemaNode schema) {
61         AbstractNodeDataWithSchema newChild = addSimpleChild(schema);
62         return newChild == null ? addCompositeChild(schema) : newChild;
63     }
64
65     public void addChild(final AbstractNodeDataWithSchema newChild) {
66         children.add(newChild);
67     }
68
69     public AbstractNodeDataWithSchema addChild(final Deque<DataSchemaNode> schemas) {
70         Preconditions.checkArgument(!schemas.isEmpty(), "Expecting at least one schema");
71
72         // Pop the first node...
73         final DataSchemaNode schema = schemas.pop();
74         if (schemas.isEmpty()) {
75             // Simple, direct node
76             return addChild(schema);
77         }
78
79         // The choice/case mess, reuse what we already popped
80         final DataSchemaNode choiceCandidate = schema;
81         Preconditions.checkArgument(choiceCandidate instanceof ChoiceSchemaNode,
82             "Expected node of type ChoiceNode but was %s", choiceCandidate.getClass().getSimpleName());
83         final ChoiceSchemaNode choiceNode = (ChoiceSchemaNode) choiceCandidate;
84
85         final DataSchemaNode caseCandidate = schemas.pop();
86         Preconditions.checkArgument(caseCandidate instanceof ChoiceCaseNode,
87             "Expected node of type ChoiceCaseNode but was %s", caseCandidate.getClass().getSimpleName());
88         final ChoiceCaseNode caseNode = (ChoiceCaseNode) caseCandidate;
89
90         AugmentationSchema augSchema = null;
91         if (choiceCandidate.isAugmenting()) {
92             augSchema = findCorrespondingAugment(getSchema(), choiceCandidate);
93         }
94
95         // looking for existing choice
96         final Collection<AbstractNodeDataWithSchema> childNodes;
97         if (augSchema != null) {
98             childNodes = augmentationsToChild.get(augSchema);
99         } else {
100             childNodes = children;
101         }
102
103         CompositeNodeDataWithSchema caseNodeDataWithSchema = findChoice(childNodes, choiceCandidate, caseCandidate);
104         if (caseNodeDataWithSchema == null) {
105             ChoiceNodeDataWithSchema choiceNodeDataWithSchema = new ChoiceNodeDataWithSchema(choiceNode);
106             childNodes.add(choiceNodeDataWithSchema);
107             caseNodeDataWithSchema = choiceNodeDataWithSchema.addCompositeChild(caseNode);
108         }
109
110         return caseNodeDataWithSchema.addChild(schemas);
111     }
112
113     private AbstractNodeDataWithSchema addSimpleChild(final DataSchemaNode schema) {
114         SimpleNodeDataWithSchema newChild = null;
115         if (schema instanceof LeafSchemaNode) {
116             newChild = new LeafNodeDataWithSchema(schema);
117         } else if (schema instanceof AnyXmlSchemaNode) {
118             // YangModeledAnyXmlSchemaNode is handled by addCompositeChild method.
119             if (schema instanceof YangModeledAnyXmlSchemaNode) {
120                 return null;
121             }
122             newChild = new AnyXmlNodeDataWithSchema(schema);
123         } else {
124             return null;
125         }
126
127         AugmentationSchema augSchema = null;
128         if (schema.isAugmenting()) {
129             augSchema = findCorrespondingAugment(getSchema(), schema);
130         }
131         if (augSchema != null) {
132             augmentationsToChild.put(augSchema, newChild);
133         } else {
134             addChild(newChild);
135         }
136         return newChild;
137     }
138
139     private static CaseNodeDataWithSchema findChoice(final Collection<AbstractNodeDataWithSchema> childNodes,
140             final DataSchemaNode choiceCandidate, final DataSchemaNode caseCandidate) {
141         if (childNodes != null) {
142             for (AbstractNodeDataWithSchema nodeDataWithSchema : childNodes) {
143                 if (nodeDataWithSchema instanceof ChoiceNodeDataWithSchema
144                         && nodeDataWithSchema.getSchema().getQName().equals(choiceCandidate.getQName())) {
145                     CaseNodeDataWithSchema casePrevious = ((ChoiceNodeDataWithSchema) nodeDataWithSchema).getCase();
146
147                     Preconditions.checkArgument(casePrevious.getSchema().getQName().equals(caseCandidate.getQName()),
148                         "Data from case %s are specified but other data from case %s were specified earlier."
149                         + " Data aren't from the same case.", caseCandidate.getQName(),
150                         casePrevious.getSchema().getQName());
151
152                     return casePrevious;
153                 }
154             }
155         }
156         return null;
157     }
158
159     AbstractNodeDataWithSchema addCompositeChild(final DataSchemaNode schema) {
160         final CompositeNodeDataWithSchema newChild;
161
162         if (schema instanceof ListSchemaNode) {
163             newChild = new ListNodeDataWithSchema(schema);
164         } else if (schema instanceof LeafListSchemaNode) {
165             newChild = new LeafListNodeDataWithSchema(schema);
166         } else if (schema instanceof ContainerSchemaNode) {
167             newChild = new ContainerNodeDataWithSchema(schema);
168         } else if (schema instanceof YangModeledAnyXmlSchemaNode) {
169             newChild = new YangModeledAnyXmlNodeDataWithSchema((YangModeledAnyXmlSchemaNode)schema);
170         } else {
171             newChild = new CompositeNodeDataWithSchema(schema);
172         }
173
174         addCompositeChild(newChild);
175         return newChild;
176     }
177
178     void addCompositeChild(final CompositeNodeDataWithSchema newChild) {
179         AugmentationSchema augSchema = findCorrespondingAugment(getSchema(), newChild.getSchema());
180         if (augSchema != null) {
181             augmentationsToChild.put(augSchema, newChild);
182         } else {
183             addChild(newChild);
184         }
185     }
186
187     /**
188      * Return a hint about how may children we are going to generate.
189      * @return Size of currently-present node list.
190      */
191     protected final int childSizeHint() {
192         return children.size();
193     }
194
195     @Override
196     public void write(final NormalizedNodeStreamWriter writer) throws IOException {
197         for (AbstractNodeDataWithSchema child : children) {
198             child.write(writer);
199         }
200         for (Entry<AugmentationSchema, Collection<AbstractNodeDataWithSchema>> augmentationToChild
201                 : augmentationsToChild.asMap().entrySet()) {
202             final Collection<AbstractNodeDataWithSchema> childsFromAgumentation = augmentationToChild.getValue();
203             if (!childsFromAgumentation.isEmpty()) {
204                 // FIXME: can we get the augmentation schema?
205                 writer.startAugmentationNode(getNodeIdentifierForAugmentation(augmentationToChild.getKey()));
206
207                 for (AbstractNodeDataWithSchema nodeDataWithSchema : childsFromAgumentation) {
208                     nodeDataWithSchema.write(writer);
209                 }
210
211                 writer.endNode();
212             }
213         }
214     }
215
216     /**
217      * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
218      * node is found then it is returned, else null.
219      *
220      * @param parent parent node
221      * @param child child node
222      * @return augmentation schema
223      */
224     private static AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent,
225             final DataSchemaNode child) {
226         if (parent instanceof AugmentationTarget && !(parent instanceof ChoiceSchemaNode)) {
227             for (AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
228                 DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
229                 if (childInAugmentation != null) {
230                     return augmentation;
231                 }
232             }
233         }
234         return null;
235     }
236
237     public static YangInstanceIdentifier.AugmentationIdentifier getNodeIdentifierForAugmentation(
238             final AugmentationSchema schema) {
239         final Collection<QName> qnames = Collections2.transform(schema.getChildNodes(), DataSchemaNode::getQName);
240         return new YangInstanceIdentifier.AugmentationIdentifier(ImmutableSet.copyOf(qnames));
241     }
242 }