BUG-1440: improve CompositeNodeDataWithSchema
[yangtools.git] / yang / yang-data-codec-gson / src / main / java / org / opendaylight / yangtools / yang / data / codec / gson / CompositeNodeDataWithSchema.java
1 /*
2  * Copyright (c) 2014 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.codec.gson;
9
10 import com.google.common.base.Function;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.Collections2;
13 import com.google.common.collect.ImmutableSet;
14
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Deque;
19 import java.util.LinkedHashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23
24 import javax.annotation.Nonnull;
25
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
29 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
31 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
32 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
33 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
34 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
39
40 /**
41  * A node which is composed of multiple simpler nodes.
42  */
43 class CompositeNodeDataWithSchema extends AbstractNodeDataWithSchema {
44     private static final Function<DataSchemaNode, QName> QNAME_FUNCTION = new Function<DataSchemaNode, QName>() {
45         @Override
46         public QName apply(@Nonnull final DataSchemaNode input) {
47             return input.getQName();
48         }
49     };
50
51     /**
52      * nodes which were added to schema via augmentation and are present in data input
53      */
54     private final Map<AugmentationSchema, List<AbstractNodeDataWithSchema>> augmentationsToChild = new LinkedHashMap<>();
55
56     /**
57      * remaining data nodes (which aren't added via augment). Every of one them should have the same QName.
58      */
59     private final List<AbstractNodeDataWithSchema> children = new ArrayList<>();
60
61     public CompositeNodeDataWithSchema(final DataSchemaNode schema) {
62         super(schema);
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 ChoiceNode,
78             "Expected node of type ChoiceNode but was %s", choiceCandidate.getClass().getSimpleName());
79         final ChoiceNode choiceNode = (ChoiceNode) 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 List<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             addChild(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             newChild = new AnyXmlNodeDataWithSchema(schema);
115         }
116
117         if (newChild != null) {
118
119             AugmentationSchema augSchema = null;
120             if (schema.isAugmenting()) {
121                 augSchema = findCorrespondingAugment(getSchema(), schema);
122             }
123             if (augSchema != null) {
124                 addChildToAugmentation(augSchema, newChild);
125             } else {
126                 addChild(newChild);
127             }
128             return newChild;
129         }
130         return null;
131     }
132
133     private void addChildToAugmentation(final AugmentationSchema augSchema, final AbstractNodeDataWithSchema newChild) {
134         List<AbstractNodeDataWithSchema> childsInAugment = augmentationsToChild.get(augSchema);
135         if (childsInAugment == null) {
136             childsInAugment = new ArrayList<>();
137             augmentationsToChild.put(augSchema, childsInAugment);
138         }
139         childsInAugment.add(newChild);
140     }
141
142     private CaseNodeDataWithSchema findChoice(final List<AbstractNodeDataWithSchema> childNodes, final DataSchemaNode choiceCandidate,
143             final DataSchemaNode caseCandidate) {
144         if (childNodes != null) {
145             for (AbstractNodeDataWithSchema nodeDataWithSchema : childNodes) {
146                 if (nodeDataWithSchema instanceof ChoiceNodeDataWithSchema
147                         && nodeDataWithSchema.getSchema().getQName().equals(choiceCandidate.getQName())) {
148                     CaseNodeDataWithSchema casePrevious = ((ChoiceNodeDataWithSchema) nodeDataWithSchema).getCase();
149
150                     Preconditions.checkArgument(casePrevious.getSchema().getQName().equals(caseCandidate.getQName()),
151                         "Data from case %s are specified but other data from case %s were specified erlier. Data aren't from the same case.",
152                         caseCandidate.getQName(), casePrevious.getSchema().getQName());
153
154                     return casePrevious;
155                 }
156             }
157         }
158         return null;
159     }
160
161     AbstractNodeDataWithSchema addCompositeChild(final DataSchemaNode schema) {
162         CompositeNodeDataWithSchema newChild;
163         if (schema instanceof ListSchemaNode) {
164             newChild = new ListNodeDataWithSchema(schema);
165         } else if (schema instanceof LeafListSchemaNode) {
166             newChild = new LeafListNodeDataWithSchema(schema);
167         } else if (schema instanceof ContainerSchemaNode) {
168             newChild = new ContainerNodeDataWithSchema(schema);
169         } else {
170             newChild = new CompositeNodeDataWithSchema(schema);
171         }
172         addCompositeChild(newChild);
173         return newChild;
174     }
175
176     void addCompositeChild(final CompositeNodeDataWithSchema newChild) {
177         AugmentationSchema augSchema = findCorrespondingAugment(getSchema(), newChild.getSchema());
178         if (augSchema != null) {
179             addChildToAugmentation(augSchema, newChild);
180         } else {
181             addChild(newChild);
182         }
183     }
184
185     private AbstractNodeDataWithSchema addChild(final DataSchemaNode schema) {
186         AbstractNodeDataWithSchema newChild = addSimpleChild(schema);
187         return newChild == null ? addCompositeChild(schema) : newChild;
188     }
189
190     public void addChild(final AbstractNodeDataWithSchema newChild) {
191         children.add(newChild);
192     }
193
194     /**
195      * Return a hint about how may children we are going to generate.
196      * @return Size of currently-present node list.
197      */
198     protected final int childSizeHint() {
199         return children.size();
200     }
201
202     /**
203      * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
204      * node is found then it is returned, else null.
205      */
206     AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent, final DataSchemaNode child) {
207         if (parent instanceof AugmentationTarget) {
208             for (AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
209                 DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
210                 if (childInAugmentation != null) {
211                     return augmentation;
212                 }
213             }
214         }
215         return null;
216     }
217
218     @Override
219     public void write(final NormalizedNodeStreamWriter writer) throws IOException {
220         for (AbstractNodeDataWithSchema child : children) {
221             child.write(writer);
222         }
223         for (Entry<AugmentationSchema, List<AbstractNodeDataWithSchema>> augmentationToChild : augmentationsToChild.entrySet()) {
224             final List<AbstractNodeDataWithSchema> childsFromAgumentation = augmentationToChild.getValue();
225             if (!childsFromAgumentation.isEmpty()) {
226                 writer.startAugmentationNode(toAugmentationIdentifier(augmentationToChild.getKey()));
227
228                 for (AbstractNodeDataWithSchema nodeDataWithSchema : childsFromAgumentation) {
229                     nodeDataWithSchema.write(writer);
230                 }
231
232                 writer.endNode();
233             }
234         }
235     }
236
237     private static AugmentationIdentifier toAugmentationIdentifier(final AugmentationSchema schema) {
238         final Collection<QName> qnames = Collections2.transform(schema.getChildNodes(), QNAME_FUNCTION);
239         return new AugmentationIdentifier(ImmutableSet.copyOf(qnames));
240     }
241 }