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