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