Do not pretty-print body class
[yangtools.git] / data / 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 static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.collect.ArrayListMultimap;
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.eclipse.jdt.annotation.Nullable;
22 import org.opendaylight.yangtools.rfc7952.data.api.StreamWriterMetadataExtension;
23 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
24 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
28 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
31 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
35
36 /**
37  * Utility class used for tracking parser state as needed by a StAX-like parser.
38  * This class is to be used only by respective XML and JSON parsers in yang-data-codec-xml and yang-data-codec-gson.
39  *
40  * <p>
41  * Represents a node which is composed of multiple simpler nodes.
42  */
43 public class CompositeNodeDataWithSchema<T extends DataSchemaNode> extends AbstractNodeDataWithSchema<T> {
44     /**
45      * Policy on how child nodes should be treated when an attempt is made to add them multiple times.
46      */
47     @Beta
48     public enum ChildReusePolicy {
49         /**
50          * Do not consider any existing nodes at all, just perform a straight append. Multiple occurrences of a child
51          * will result in multiple children being emitted. This is almost certainly the wrong policy unless the caller
52          * prevents such a situation from arising via some different mechanism.
53          */
54         NOOP,
55         /**
56          * Do not allow duplicate definition of a child node. This would typically be used when a child cannot be
57          * encountered multiple times, but the caller does not make any provision to detect such a conflict. If a child
58          * node would end up being defined a second time, {@link DuplicateChildNodeRejectedException} is reported.
59          */
60         REJECT {
61             @Override
62             AbstractNodeDataWithSchema<?> appendChild(final Collection<AbstractNodeDataWithSchema<?>> view,
63                     final AbstractNodeDataWithSchema<?> newChild) {
64                 final DataSchemaNode childSchema = newChild.getSchema();
65                 final AbstractNodeDataWithSchema<?> existing = findExistingChild(view, childSchema);
66                 if (existing != null) {
67                     throw new DuplicateChildNodeRejectedException("Duplicate child " + childSchema.getQName());
68                 }
69                 return super.appendChild(view, newChild);
70             }
71         },
72         /**
73          * Reuse previously-defined child node. This is most appropriate when a child may be visited multiple times
74          * and the intent is to append content of each visit. A typical usage is list elements with RFC7950 XML
75          * encoding, where there is no encapsulating element and hence list entries may be interleaved with other
76          * children.
77          */
78         REUSE {
79             @Override
80             AbstractNodeDataWithSchema<?> appendChild(final Collection<AbstractNodeDataWithSchema<?>> view,
81                     final AbstractNodeDataWithSchema<?> newChild) {
82                 final AbstractNodeDataWithSchema<?> existing = findExistingChild(view, newChild.getSchema());
83                 return existing != null ? existing : super.appendChild(view, newChild);
84             }
85         };
86
87         AbstractNodeDataWithSchema<?> appendChild(final Collection<AbstractNodeDataWithSchema<?>> view,
88                 final AbstractNodeDataWithSchema<?> newChild) {
89             view.add(newChild);
90             return newChild;
91         }
92
93         static @Nullable AbstractNodeDataWithSchema<?> findExistingChild(
94                 final Collection<AbstractNodeDataWithSchema<?>> view, final DataSchemaNode childSchema) {
95             for (AbstractNodeDataWithSchema<?> existing : view) {
96                 if (childSchema.equals(existing.getSchema())) {
97                     return existing;
98                 }
99             }
100             return null;
101         }
102     }
103
104     /**
105      * nodes which were added to schema via augmentation and are present in data input.
106      */
107     private final Multimap<AugmentationSchemaNode, AbstractNodeDataWithSchema<?>> augmentationsToChild =
108         ArrayListMultimap.create();
109
110     /**
111      * remaining data nodes (which aren't added via augment). Every of one them should have the same QName.
112      */
113     private final List<AbstractNodeDataWithSchema<?>> children = new ArrayList<>();
114
115     public CompositeNodeDataWithSchema(final T schema) {
116         super(schema);
117     }
118
119     void addChild(final AbstractNodeDataWithSchema<?> newChild) {
120         children.add(newChild);
121     }
122
123     public final AbstractNodeDataWithSchema<?> addChild(final Deque<DataSchemaNode> schemas,
124             final ChildReusePolicy policy) {
125         checkArgument(!schemas.isEmpty(), "Expecting at least one schema");
126
127         // Pop the first node...
128         final DataSchemaNode schema = schemas.pop();
129         if (schemas.isEmpty()) {
130             // Simple, direct node
131             return addChild(schema, policy);
132         }
133
134         // The choice/case mess, reuse what we already popped
135         final DataSchemaNode choiceCandidate = schema;
136         checkArgument(choiceCandidate instanceof ChoiceSchemaNode, "Expected node of type ChoiceNode but was %s",
137             choiceCandidate.getClass());
138         final ChoiceSchemaNode choiceNode = (ChoiceSchemaNode) choiceCandidate;
139
140         final DataSchemaNode caseCandidate = schemas.pop();
141         checkArgument(caseCandidate instanceof CaseSchemaNode, "Expected node of type ChoiceCaseNode but was %s",
142             caseCandidate.getClass());
143         final CaseSchemaNode caseNode = (CaseSchemaNode) caseCandidate;
144
145         final AugmentationSchemaNode augSchema;
146         if (choiceCandidate.isAugmenting()) {
147             augSchema = findCorrespondingAugment(getSchema(), choiceCandidate);
148         } else {
149             augSchema = null;
150         }
151
152         // looking for existing choice
153         final Collection<AbstractNodeDataWithSchema<?>> childNodes;
154         if (augSchema != null) {
155             childNodes = augmentationsToChild.get(augSchema);
156         } else {
157             childNodes = children;
158         }
159
160         CompositeNodeDataWithSchema<?> caseNodeDataWithSchema = findChoice(childNodes, choiceCandidate, caseCandidate);
161         if (caseNodeDataWithSchema == null) {
162             ChoiceNodeDataWithSchema choiceNodeDataWithSchema = new ChoiceNodeDataWithSchema(choiceNode);
163             childNodes.add(choiceNodeDataWithSchema);
164             caseNodeDataWithSchema = choiceNodeDataWithSchema.addCompositeChild(caseNode, ChildReusePolicy.NOOP);
165         }
166
167         return caseNodeDataWithSchema.addChild(schemas, policy);
168     }
169
170     private AbstractNodeDataWithSchema<?> addChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
171         AbstractNodeDataWithSchema<?> newChild = addSimpleChild(schema, policy);
172         return newChild == null ? addCompositeChild(schema, policy) : newChild;
173     }
174
175     private AbstractNodeDataWithSchema<?> addSimpleChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
176         final SimpleNodeDataWithSchema<?> newChild;
177         if (schema instanceof LeafSchemaNode) {
178             newChild = new LeafNodeDataWithSchema((LeafSchemaNode) schema);
179         } else if (schema instanceof AnyxmlSchemaNode) {
180             newChild = new AnyXmlNodeDataWithSchema((AnyxmlSchemaNode) schema);
181         } else if (schema instanceof AnydataSchemaNode) {
182             newChild = new AnydataNodeDataWithSchema((AnydataSchemaNode) schema);
183         } else {
184             return null;
185         }
186
187         final AugmentationSchemaNode augSchema;
188         if (schema.isAugmenting()) {
189             augSchema = findCorrespondingAugment(getSchema(), schema);
190         } else {
191             augSchema = null;
192         }
193
194         // FIXME: 7.0.0: use policy to determine if we should reuse or replace the child
195
196         if (augSchema != null) {
197             augmentationsToChild.put(augSchema, newChild);
198         } else {
199             addChild(newChild);
200         }
201         return newChild;
202     }
203
204     private static CaseNodeDataWithSchema findChoice(final Collection<AbstractNodeDataWithSchema<?>> childNodes,
205             final DataSchemaNode choiceCandidate, final DataSchemaNode caseCandidate) {
206         if (childNodes != null) {
207             for (AbstractNodeDataWithSchema<?> nodeDataWithSchema : childNodes) {
208                 if (nodeDataWithSchema instanceof ChoiceNodeDataWithSchema
209                         && nodeDataWithSchema.getSchema().getQName().equals(choiceCandidate.getQName())) {
210                     CaseNodeDataWithSchema casePrevious = ((ChoiceNodeDataWithSchema) nodeDataWithSchema).getCase();
211
212                     checkArgument(casePrevious.getSchema().getQName().equals(caseCandidate.getQName()),
213                         "Data from case %s are specified but other data from case %s were specified earlier."
214                         + " Data aren't from the same case.", caseCandidate.getQName(),
215                         casePrevious.getSchema().getQName());
216
217                     return casePrevious;
218                 }
219             }
220         }
221         return null;
222     }
223
224     AbstractNodeDataWithSchema<?> addCompositeChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
225         final CompositeNodeDataWithSchema<?> newChild;
226
227         if (schema instanceof ListSchemaNode) {
228             newChild = new ListNodeDataWithSchema((ListSchemaNode) schema);
229         } else if (schema instanceof LeafListSchemaNode) {
230             newChild = new LeafListNodeDataWithSchema((LeafListSchemaNode) schema);
231         } else if (schema instanceof ContainerLike) {
232             newChild = new ContainerNodeDataWithSchema((ContainerLike) schema);
233         } else {
234             newChild = new CompositeNodeDataWithSchema<>(schema);
235         }
236
237         return addCompositeChild(newChild, policy);
238     }
239
240     final AbstractNodeDataWithSchema<?> addCompositeChild(final CompositeNodeDataWithSchema<?> newChild,
241             final ChildReusePolicy policy) {
242         final AugmentationSchemaNode augSchema = findCorrespondingAugment(getSchema(), newChild.getSchema());
243         final Collection<AbstractNodeDataWithSchema<?>> view = augSchema == null ? children
244                 : augmentationsToChild.get(augSchema);
245
246         return policy.appendChild(view, newChild);
247     }
248
249     /**
250      * Return a hint about how may children we are going to generate.
251      * @return Size of currently-present node list.
252      */
253     protected final int childSizeHint() {
254         return children.size();
255     }
256
257     @Override
258     public void write(final NormalizedNodeStreamWriter writer, final StreamWriterMetadataExtension metaWriter)
259             throws IOException {
260         for (AbstractNodeDataWithSchema<?> child : children) {
261             child.write(writer, metaWriter);
262         }
263         for (Entry<AugmentationSchemaNode, Collection<AbstractNodeDataWithSchema<?>>> augmentationToChild
264                 : augmentationsToChild.asMap().entrySet()) {
265             final Collection<AbstractNodeDataWithSchema<?>> childsFromAgumentation = augmentationToChild.getValue();
266             if (!childsFromAgumentation.isEmpty()) {
267                 // FIXME: can we get the augmentation schema?
268                 writer.startAugmentationNode(DataSchemaContextNode.augmentationIdentifierFrom(
269                     augmentationToChild.getKey()));
270
271                 for (AbstractNodeDataWithSchema<?> nodeDataWithSchema : childsFromAgumentation) {
272                     nodeDataWithSchema.write(writer, metaWriter);
273                 }
274
275                 writer.endNode();
276             }
277         }
278     }
279
280     /**
281      * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
282      * node is found then it is returned, else null.
283      *
284      * @param parent parent node
285      * @param child child node
286      * @return augmentation schema
287      */
288     private static AugmentationSchemaNode findCorrespondingAugment(final DataSchemaNode parent,
289             final DataSchemaNode child) {
290         if (parent instanceof AugmentationTarget && !(parent instanceof ChoiceSchemaNode)) {
291             for (AugmentationSchemaNode augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
292                 DataSchemaNode childInAugmentation = augmentation.dataChildByName(child.getQName());
293                 if (childInAugmentation != null) {
294                     return augmentation;
295                 }
296             }
297         }
298         return null;
299     }
300 }