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