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