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