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