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