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