2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.util;
10 import static com.google.common.base.Preconditions.checkArgument;
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;
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.
39 * Represents a node which is composed of multiple simpler nodes.
41 public class CompositeNodeDataWithSchema<T extends DataSchemaNode> extends AbstractNodeDataWithSchema<T> {
43 * Policy on how child nodes should be treated when an attempt is made to add them multiple times.
46 public enum ChildReusePolicy {
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.
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.
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());
67 return super.appendChild(view, newChild);
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
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);
85 AbstractNodeDataWithSchema<?> appendChild(final Collection<AbstractNodeDataWithSchema<?>> view,
86 final AbstractNodeDataWithSchema<?> newChild) {
91 static @Nullable AbstractNodeDataWithSchema<?> findExistingChild(
92 final Collection<AbstractNodeDataWithSchema<?>> view, final DataSchemaNode childSchema) {
93 for (AbstractNodeDataWithSchema<?> existing : view) {
94 if (childSchema.equals(existing.getSchema())) {
103 * nodes which were added to schema via augmentation and are present in data input.
105 private final Multimap<AugmentationSchemaNode, AbstractNodeDataWithSchema<?>> augmentationsToChild =
106 ArrayListMultimap.create();
109 * remaining data nodes (which aren't added via augment). Every of one them should have the same QName.
111 private final List<AbstractNodeDataWithSchema<?>> children = new ArrayList<>();
113 public CompositeNodeDataWithSchema(final T schema) {
117 void addChild(final AbstractNodeDataWithSchema<?> newChild) {
118 children.add(newChild);
121 public final AbstractNodeDataWithSchema<?> addChild(final Deque<DataSchemaNode> schemas,
122 final ChildReusePolicy policy) {
123 checkArgument(!schemas.isEmpty(), "Expecting at least one schema");
125 // Pop the first node...
126 final DataSchemaNode schema = schemas.pop();
127 if (schemas.isEmpty()) {
128 // Simple, direct node
129 return addChild(schema, policy);
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;
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;
143 final AugmentationSchemaNode augSchema;
144 if (choiceCandidate.isAugmenting()) {
145 augSchema = NormalizedNodeSchemaUtils.findCorrespondingAugment(getSchema(), choiceCandidate);
150 // looking for existing choice
151 final Collection<AbstractNodeDataWithSchema<?>> childNodes;
152 if (augSchema != null) {
153 childNodes = augmentationsToChild.get(augSchema);
155 childNodes = children;
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);
165 return caseNodeDataWithSchema.addChild(schemas, policy);
168 private AbstractNodeDataWithSchema<?> addChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
169 AbstractNodeDataWithSchema<?> newChild = addSimpleChild(schema, policy);
170 return newChild == null ? addCompositeChild(schema, policy) : newChild;
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);
185 final AugmentationSchemaNode augSchema;
186 if (schema.isAugmenting()) {
187 augSchema = NormalizedNodeSchemaUtils.findCorrespondingAugment(getSchema(), schema);
192 // FIXME: 7.0.0: use policy to determine if we should reuse or replace the child
194 if (augSchema != null) {
195 augmentationsToChild.put(augSchema, newChild);
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();
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());
222 AbstractNodeDataWithSchema<?> addCompositeChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
223 final CompositeNodeDataWithSchema<?> newChild;
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);
232 newChild = new CompositeNodeDataWithSchema<>(schema);
235 return addCompositeChild(newChild, policy);
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);
243 return policy.appendChild(view, newChild);
247 * Return a hint about how may children we are going to generate.
248 * @return Size of currently-present node list.
250 protected final int childSizeHint() {
251 return children.size();
255 public void write(final NormalizedNodeStreamWriter writer, final MetadataExtension metaWriter) throws IOException {
256 for (AbstractNodeDataWithSchema<?> child : children) {
257 child.write(writer, metaWriter);
259 for (var childsFromAgumentation : augmentationsToChild.asMap().values()) {
260 for (var nodeDataWithSchema : childsFromAgumentation) {
261 nodeDataWithSchema.write(writer, metaWriter);