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 java.util.Map.Entry;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyxmlSchemaNode;
23 import org.opendaylight.yangtools.rfc7952.data.api.StreamWriterMetadataExtension;
24 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
25 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
29 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
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;
38 * Utility class used for tracking parser state as needed by a StAX-like parser.
39 * This class is to be used only by respective XML and JSON parsers in yang-data-codec-xml and yang-data-codec-gson.
42 * Represents a node which is composed of multiple simpler nodes.
44 public class CompositeNodeDataWithSchema<T extends DataSchemaNode> extends AbstractNodeDataWithSchema<T> {
46 * Policy on how child nodes should be treated when an attempt is made to add them multiple times.
49 public enum ChildReusePolicy {
51 * Do not consider any existing nodes at all, just perform a straight append. Multiple occurrences of a child
52 * will result in multiple children being emitted. This is almost certainly the wrong policy unless the caller
53 * prevents such a situation from arising via some different mechanism.
57 * Do not allow duplicate definition of a child node. This would typically be used when a child cannot be
58 * encountered multiple times, but the caller does not make any provision to detect such a conflict. If a child
59 * node would end up being defined a second time, {@link DuplicateChildNodeRejectedException} is reported.
63 AbstractNodeDataWithSchema<?> appendChild(final Collection<AbstractNodeDataWithSchema<?>> view,
64 final AbstractNodeDataWithSchema<?> newChild) {
65 final DataSchemaNode childSchema = newChild.getSchema();
66 final AbstractNodeDataWithSchema<?> existing = findExistingChild(view, childSchema);
67 if (existing != null) {
68 throw new DuplicateChildNodeRejectedException("Duplicate child " + childSchema.getQName());
70 return super.appendChild(view, newChild);
74 * Reuse previously-defined child node. This is most appropriate when a child may be visited multiple times
75 * and the intent is to append content of each visit. A typical usage is list elements with RFC7950 XML
76 * encoding, where there is no encapsulating element and hence list entries may be interleaved with other
81 AbstractNodeDataWithSchema<?> appendChild(final Collection<AbstractNodeDataWithSchema<?>> view,
82 final AbstractNodeDataWithSchema<?> newChild) {
83 final AbstractNodeDataWithSchema<?> existing = findExistingChild(view, newChild.getSchema());
84 return existing != null ? existing : super.appendChild(view, newChild);
88 AbstractNodeDataWithSchema<?> appendChild(final Collection<AbstractNodeDataWithSchema<?>> view,
89 final AbstractNodeDataWithSchema<?> newChild) {
94 static @Nullable AbstractNodeDataWithSchema<?> findExistingChild(
95 final Collection<AbstractNodeDataWithSchema<?>> view, final DataSchemaNode childSchema) {
96 for (AbstractNodeDataWithSchema<?> existing : view) {
97 if (childSchema.equals(existing.getSchema())) {
106 * nodes which were added to schema via augmentation and are present in data input.
108 private final Multimap<AugmentationSchemaNode, AbstractNodeDataWithSchema<?>> augmentationsToChild =
109 ArrayListMultimap.create();
112 * remaining data nodes (which aren't added via augment). Every of one them should have the same QName.
114 private final List<AbstractNodeDataWithSchema<?>> children = new ArrayList<>();
116 public CompositeNodeDataWithSchema(final T schema) {
121 public void addChild(final AbstractNodeDataWithSchema<?> newChild) {
122 children.add(newChild);
126 public AbstractNodeDataWithSchema<?> addChild(final Deque<DataSchemaNode> schemas) {
127 return addChild(schemas, ChildReusePolicy.NOOP);
130 public AbstractNodeDataWithSchema<?> addChild(final Deque<DataSchemaNode> schemas, final ChildReusePolicy policy) {
131 checkArgument(!schemas.isEmpty(), "Expecting at least one schema");
133 // Pop the first node...
134 final DataSchemaNode schema = schemas.pop();
135 if (schemas.isEmpty()) {
136 // Simple, direct node
137 return addChild(schema, policy);
140 // The choice/case mess, reuse what we already popped
141 final DataSchemaNode choiceCandidate = schema;
142 checkArgument(choiceCandidate instanceof ChoiceSchemaNode, "Expected node of type ChoiceNode but was %s",
143 choiceCandidate.getClass());
144 final ChoiceSchemaNode choiceNode = (ChoiceSchemaNode) choiceCandidate;
146 final DataSchemaNode caseCandidate = schemas.pop();
147 checkArgument(caseCandidate instanceof CaseSchemaNode, "Expected node of type ChoiceCaseNode but was %s",
148 caseCandidate.getClass());
149 final CaseSchemaNode caseNode = (CaseSchemaNode) caseCandidate;
151 final AugmentationSchemaNode augSchema;
152 if (choiceCandidate.isAugmenting()) {
153 augSchema = findCorrespondingAugment(getSchema(), choiceCandidate);
158 // looking for existing choice
159 final Collection<AbstractNodeDataWithSchema<?>> childNodes;
160 if (augSchema != null) {
161 childNodes = augmentationsToChild.get(augSchema);
163 childNodes = children;
166 CompositeNodeDataWithSchema<?> caseNodeDataWithSchema = findChoice(childNodes, choiceCandidate, caseCandidate);
167 if (caseNodeDataWithSchema == null) {
168 ChoiceNodeDataWithSchema choiceNodeDataWithSchema = new ChoiceNodeDataWithSchema(choiceNode);
169 childNodes.add(choiceNodeDataWithSchema);
170 caseNodeDataWithSchema = choiceNodeDataWithSchema.addCompositeChild(caseNode, ChildReusePolicy.NOOP);
173 return caseNodeDataWithSchema.addChild(schemas, policy);
176 private AbstractNodeDataWithSchema<?> addChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
177 AbstractNodeDataWithSchema<?> newChild = addSimpleChild(schema, policy);
178 return newChild == null ? addCompositeChild(schema, policy) : newChild;
181 private AbstractNodeDataWithSchema<?> addSimpleChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
182 final SimpleNodeDataWithSchema<?> newChild;
183 if (schema instanceof LeafSchemaNode) {
184 newChild = new LeafNodeDataWithSchema((LeafSchemaNode) schema);
185 } else if (schema instanceof AnyxmlSchemaNode) {
186 // YangModeledAnyxmlSchemaNode is handled by addCompositeChild method.
187 if (schema instanceof YangModeledAnyxmlSchemaNode) {
190 newChild = new AnyXmlNodeDataWithSchema((AnyxmlSchemaNode) schema);
191 } else if (schema instanceof AnydataSchemaNode) {
192 newChild = new AnydataNodeDataWithSchema((AnydataSchemaNode) schema);
197 final AugmentationSchemaNode augSchema;
198 if (schema.isAugmenting()) {
199 augSchema = findCorrespondingAugment(getSchema(), schema);
204 // FIXME: 6.0.0: use policy once we have removed addChild() visibility
206 if (augSchema != null) {
207 augmentationsToChild.put(augSchema, newChild);
214 private static CaseNodeDataWithSchema findChoice(final Collection<AbstractNodeDataWithSchema<?>> childNodes,
215 final DataSchemaNode choiceCandidate, final DataSchemaNode caseCandidate) {
216 if (childNodes != null) {
217 for (AbstractNodeDataWithSchema<?> nodeDataWithSchema : childNodes) {
218 if (nodeDataWithSchema instanceof ChoiceNodeDataWithSchema
219 && nodeDataWithSchema.getSchema().getQName().equals(choiceCandidate.getQName())) {
220 CaseNodeDataWithSchema casePrevious = ((ChoiceNodeDataWithSchema) nodeDataWithSchema).getCase();
222 checkArgument(casePrevious.getSchema().getQName().equals(caseCandidate.getQName()),
223 "Data from case %s are specified but other data from case %s were specified earlier."
224 + " Data aren't from the same case.", caseCandidate.getQName(),
225 casePrevious.getSchema().getQName());
234 AbstractNodeDataWithSchema<?> addCompositeChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
235 final CompositeNodeDataWithSchema<?> newChild;
237 if (schema instanceof ListSchemaNode) {
238 newChild = new ListNodeDataWithSchema((ListSchemaNode) schema);
239 } else if (schema instanceof LeafListSchemaNode) {
240 newChild = new LeafListNodeDataWithSchema((LeafListSchemaNode) schema);
241 } else if (schema instanceof ContainerSchemaNode) {
242 newChild = new ContainerNodeDataWithSchema((ContainerSchemaNode) schema);
243 } else if (schema instanceof YangModeledAnyxmlSchemaNode) {
244 newChild = new YangModeledAnyXmlNodeDataWithSchema((YangModeledAnyxmlSchemaNode)schema);
246 newChild = new CompositeNodeDataWithSchema<>(schema);
249 return addCompositeChild(newChild, policy);
252 final AbstractNodeDataWithSchema<?> addCompositeChild(final CompositeNodeDataWithSchema<?> newChild,
253 final ChildReusePolicy policy) {
254 final AugmentationSchemaNode augSchema = findCorrespondingAugment(getSchema(), newChild.getSchema());
255 final Collection<AbstractNodeDataWithSchema<?>> view = augSchema == null ? children
256 : augmentationsToChild.get(augSchema);
258 return policy.appendChild(view, newChild);
262 * Return a hint about how may children we are going to generate.
263 * @return Size of currently-present node list.
265 protected final int childSizeHint() {
266 return children.size();
270 public void write(final NormalizedNodeStreamWriter writer, final StreamWriterMetadataExtension metaWriter)
272 for (AbstractNodeDataWithSchema<?> child : children) {
273 child.write(writer, metaWriter);
275 for (Entry<AugmentationSchemaNode, Collection<AbstractNodeDataWithSchema<?>>> augmentationToChild
276 : augmentationsToChild.asMap().entrySet()) {
277 final Collection<AbstractNodeDataWithSchema<?>> childsFromAgumentation = augmentationToChild.getValue();
278 if (!childsFromAgumentation.isEmpty()) {
279 // FIXME: can we get the augmentation schema?
280 writer.startAugmentationNode(DataSchemaContextNode.augmentationIdentifierFrom(
281 augmentationToChild.getKey()));
283 for (AbstractNodeDataWithSchema<?> nodeDataWithSchema : childsFromAgumentation) {
284 nodeDataWithSchema.write(writer, metaWriter);
293 * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
294 * node is found then it is returned, else null.
296 * @param parent parent node
297 * @param child child node
298 * @return augmentation schema
300 private static AugmentationSchemaNode findCorrespondingAugment(final DataSchemaNode parent,
301 final DataSchemaNode child) {
302 if (parent instanceof AugmentationTarget && !(parent instanceof ChoiceSchemaNode)) {
303 for (AugmentationSchemaNode augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
304 DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
305 if (childInAugmentation != null) {