2 * Copyright (c) 2018 Pantheon Technologies, s.r.o. 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.netconf.util;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.Iterables;
15 import java.io.IOException;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.List;
21 import java.util.Map.Entry;
22 import java.util.Optional;
23 import javax.xml.transform.dom.DOMSource;
24 import org.opendaylight.yangtools.concepts.Identifiable;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
33 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
34 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
37 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
41 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
47 abstract class StreamingContext<T extends PathArgument> implements Identifiable<T> {
48 private final T identifier;
50 StreamingContext(final T identifier) {
51 this.identifier = identifier;
54 static StreamingContext<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
55 final Optional<DataSchemaNode> potential = findChildSchemaNode(schema, child);
56 checkArgument(potential.isPresent(),
57 "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child, schema,
58 schema.getChildNodes());
60 final DataSchemaNode result = potential.get();
61 // We try to look up if this node was added by augmentation
62 if (schema instanceof DataSchemaNode && result.isAugmenting()) {
63 for (final AugmentationSchemaNode aug : ((AugmentationTarget)schema).getAvailableAugmentations()) {
64 if (aug.findDataChildByName(result.getQName()).isPresent()) {
65 return new Augmentation(aug, schema);
69 return fromDataSchemaNode(result);
72 static StreamingContext<?> fromDataSchemaNode(final DataSchemaNode potential) {
73 if (potential instanceof ContainerSchemaNode) {
74 return new Container((ContainerSchemaNode) potential);
75 } else if (potential instanceof ListSchemaNode) {
76 return fromListSchemaNode((ListSchemaNode) potential);
77 } else if (potential instanceof LeafSchemaNode) {
78 return new Leaf((LeafSchemaNode) potential);
79 } else if (potential instanceof ChoiceSchemaNode) {
80 return new Choice((ChoiceSchemaNode) potential);
81 } else if (potential instanceof LeafListSchemaNode) {
82 return fromLeafListSchemaNode((LeafListSchemaNode) potential);
83 } else if (potential instanceof AnyxmlSchemaNode) {
84 return new AnyXml((AnyxmlSchemaNode) potential);
90 public final T getIdentifier() {
94 abstract StreamingContext<?> getChild(PathArgument child);
97 * Writing node structure that is described by series of {@link PathArgument}
98 * into {@link NormalizedNodeStreamWriter}.
100 * @param writer output {@link NormalizedNode} writer
101 * @param first the first {@link PathArgument}
102 * @param others iterator that points to next path arguments
103 * @throws IOException failed to write a stream of path arguments into {@link NormalizedNodeStreamWriter}
105 abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, Iterator<PathArgument> others)
109 * Writing node structure that is described by provided {@link PathNode} into {@link NormalizedNodeStreamWriter}.
111 * @param writer output {@link NormalizedNode} writer
112 * @param first the first {@link PathArgument}
113 * @param tree subtree of path arguments that starts with the first path argument
114 * @throws IOException failed to write a stream of path arguments into {@link NormalizedNodeStreamWriter}
116 abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, PathNode tree)
119 abstract boolean isMixin();
121 private static Optional<DataSchemaNode> findChildSchemaNode(final DataNodeContainer parent, final QName child) {
122 final Optional<DataSchemaNode> potential = parent.findDataChildByName(child);
123 return potential.isPresent() ? potential
124 : findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child);
127 private static Optional<DataSchemaNode> findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
128 for (final ChoiceSchemaNode choice : choices) {
129 for (final CaseSchemaNode caze : choice.getCases()) {
130 if (findChildSchemaNode(caze, child).isPresent()) {
131 return Optional.of(choice);
135 return Optional.empty();
138 private static StreamingContext<?> fromListSchemaNode(final ListSchemaNode potential) {
139 final List<QName> keyDefinition = potential.getKeyDefinition();
140 if (keyDefinition == null || keyDefinition.isEmpty()) {
141 return new UnkeyedListMixin(potential);
143 return potential.isUserOrdered() ? new OrderedMapMixin(potential)
144 : new UnorderedMapMixin(potential);
147 private static StreamingContext<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
148 return potential.isUserOrdered() ? new OrderedLeafListMixin(potential)
149 : new UnorderedLeafListMixin(potential);
152 private abstract static class AbstractComposite<T extends PathArgument> extends StreamingContext<T> {
153 AbstractComposite(final T identifier) {
158 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
159 final Iterator<PathArgument> others) throws IOException {
160 verifyActualPathArgument(first);
162 emitElementStart(writer, first);
163 if (others.hasNext()) {
164 final PathArgument childPath = others.next();
165 final StreamingContext<?> childOp = getChildOperation(childPath);
166 childOp.streamToWriter(writer, childPath, others);
172 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
173 final PathNode subtree) throws IOException {
174 verifyActualPathArgument(first);
176 emitElementStart(writer, first);
177 for (final PathNode node : subtree.children()) {
178 final PathArgument childPath = node.element();
179 final StreamingContext<?> childOp = getChildOperation(childPath);
180 childOp.streamToWriter(writer, childPath, node);
185 private void verifyActualPathArgument(final PathArgument first) {
187 final QName type = getIdentifier().getNodeType();
188 final QName firstType = first.getNodeType();
189 checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
193 abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
195 @SuppressWarnings("checkstyle:illegalCatch")
196 private StreamingContext<?> getChildOperation(final PathArgument childPath) {
197 final StreamingContext<?> childOp;
199 childOp = getChild(childPath);
200 } catch (final RuntimeException e) {
201 throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
203 checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
208 private abstract static class AbstractDataContainer<T extends PathArgument> extends AbstractComposite<T> {
209 private final Map<PathArgument, StreamingContext<?>> byArg = new HashMap<>();
210 private final DataNodeContainer schema;
212 AbstractDataContainer(final T identifier, final DataNodeContainer schema) {
214 this.schema = schema;
218 final StreamingContext<?> getChild(final PathArgument child) {
219 StreamingContext<?> potential = byArg.get(child);
220 if (potential != null) {
223 potential = fromLocalSchema(child);
224 if (potential != null) {
225 byArg.put(potential.getIdentifier(), potential);
230 private StreamingContext<?> fromLocalSchema(final PathArgument child) {
231 if (child instanceof AugmentationIdentifier) {
232 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
235 return fromSchemaAndQNameChecked(schema, child.getNodeType());
239 private abstract static class AbstractMapMixin extends AbstractComposite<NodeIdentifier> {
240 private final ListEntry innerNode;
242 AbstractMapMixin(final ListSchemaNode list) {
243 super(NodeIdentifier.create(list.getQName()));
244 this.innerNode = new ListEntry(NodeIdentifierWithPredicates.of(list.getQName()), list);
248 final StreamingContext<?> getChild(final PathArgument child) {
249 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
253 final boolean isMixin() {
258 private abstract static class AbstractSimple<T extends PathArgument> extends StreamingContext<T> {
259 AbstractSimple(final T identifier) {
264 final StreamingContext<?> getChild(final PathArgument child) {
269 final boolean isMixin() {
274 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
275 final PathNode tree) throws IOException {
276 streamToWriter(writer, first, Collections.emptyIterator());
280 private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
281 AnyXml(final AnyxmlSchemaNode schema) {
282 super(NodeIdentifier.create(schema.getQName()));
286 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
287 final Iterator<PathArgument> others) throws IOException {
288 writer.startAnyxmlNode(getIdentifier(), DOMSource.class);
289 // FIXME: why are we not emitting a value?
294 private static final class Choice extends AbstractComposite<NodeIdentifier> {
295 private final ImmutableMap<PathArgument, StreamingContext<?>> byArg;
297 Choice(final ChoiceSchemaNode schema) {
298 super(NodeIdentifier.create(schema.getQName()));
299 final ImmutableMap.Builder<PathArgument, StreamingContext<?>> byArgBuilder = ImmutableMap.builder();
301 for (final CaseSchemaNode caze : schema.getCases()) {
302 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
303 final StreamingContext<?> childOp = fromDataSchemaNode(cazeChild);
304 byArgBuilder.put(childOp.getIdentifier(), childOp);
307 byArg = byArgBuilder.build();
311 StreamingContext<?> getChild(final PathArgument child) {
312 return byArg.get(child);
321 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
322 writer.startChoiceNode(getIdentifier(), UNKNOWN_SIZE);
326 private static final class Leaf extends AbstractSimple<NodeIdentifier> {
327 Leaf(final LeafSchemaNode potential) {
328 super(new NodeIdentifier(potential.getQName()));
332 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
333 final Iterator<PathArgument> others) throws IOException {
334 writer.startLeafNode(getIdentifier());
335 // FIXME: why are we not emitting a value?
340 private static final class LeafListEntry extends AbstractSimple<NodeWithValue<?>> {
341 LeafListEntry(final LeafListSchemaNode potential) {
342 super(new NodeWithValue<>(potential.getQName(), null));
346 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
347 final Iterator<PathArgument> others) throws IOException {
348 checkArgument(first instanceof NodeWithValue);
349 final NodeWithValue<?> identifier = (NodeWithValue<?>) first;
350 writer.startLeafSetEntryNode(identifier);
351 writer.scalarValue(identifier.getValue());
356 private static final class ListEntry extends AbstractDataContainer<NodeIdentifierWithPredicates> {
357 ListEntry(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
358 super(identifier, schema);
367 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
368 final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
369 writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
371 for (Entry<QName, Object> entry : identifier.entrySet()) {
372 writer.startLeafNode(new NodeIdentifier(entry.getKey()));
373 writer.scalarValue(entry.getValue());
379 private static final class UnkeyedListItem extends AbstractDataContainer<NodeIdentifier> {
380 UnkeyedListItem(final ListSchemaNode schema) {
381 super(NodeIdentifier.create(schema.getQName()), schema);
390 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
391 writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE);
395 private static final class Container extends AbstractDataContainer<NodeIdentifier> {
396 Container(final ContainerSchemaNode schema) {
397 super(NodeIdentifier.create(schema.getQName()), schema);
406 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
407 writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE);
411 private abstract static class LeafListMixin extends AbstractComposite<NodeIdentifier> {
412 private final StreamingContext<?> innerOp;
414 LeafListMixin(final LeafListSchemaNode potential) {
415 super(NodeIdentifier.create(potential.getQName()));
416 innerOp = new LeafListEntry(potential);
420 final StreamingContext<?> getChild(final PathArgument child) {
421 return child instanceof NodeWithValue ? innerOp : null;
425 final boolean isMixin() {
430 private static final class OrderedLeafListMixin extends LeafListMixin {
431 OrderedLeafListMixin(final LeafListSchemaNode potential) {
436 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
437 writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE);
441 private static class UnorderedLeafListMixin extends LeafListMixin {
442 UnorderedLeafListMixin(final LeafListSchemaNode potential) {
447 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
448 writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE);
452 private static final class Augmentation extends AbstractDataContainer<AugmentationIdentifier> {
453 Augmentation(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
454 super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
455 EffectiveAugmentationSchema.create(augmentation, schema));
464 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
465 writer.startAugmentationNode(getIdentifier());
469 private static final class UnorderedMapMixin extends AbstractMapMixin {
470 UnorderedMapMixin(final ListSchemaNode list) {
475 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
476 writer.startMapNode(getIdentifier(), UNKNOWN_SIZE);
480 private static final class OrderedMapMixin extends AbstractMapMixin {
481 OrderedMapMixin(final ListSchemaNode list) {
486 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
487 writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE);
491 private static final class UnkeyedListMixin extends AbstractComposite<NodeIdentifier> {
492 private final UnkeyedListItem innerNode;
494 UnkeyedListMixin(final ListSchemaNode list) {
495 super(NodeIdentifier.create(list.getQName()));
496 this.innerNode = new UnkeyedListItem(list);
500 StreamingContext<?> getChild(final PathArgument child) {
501 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
510 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
511 writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE);