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.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
20 import java.util.Map.Entry;
21 import java.util.Optional;
22 import javax.xml.transform.dom.DOMSource;
23 import org.opendaylight.yangtools.concepts.Identifiable;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
30 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
31 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
32 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
35 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
39 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
45 abstract class StreamingContext<T extends PathArgument> implements Identifiable<T> {
46 private final T identifier;
48 StreamingContext(final T identifier) {
49 this.identifier = identifier;
52 static StreamingContext<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
53 final Optional<DataSchemaNode> potential = findChildSchemaNode(schema, child);
54 checkArgument(potential.isPresent(),
55 "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child, schema,
56 schema.getChildNodes());
58 final DataSchemaNode result = potential.get();
59 // We try to look up if this node was added by augmentation
60 if (schema instanceof DataSchemaNode && result.isAugmenting()) {
61 for (final AugmentationSchemaNode aug : ((AugmentationTarget)schema).getAvailableAugmentations()) {
62 if (aug.findDataChildByName(result.getQName()).isPresent()) {
63 return new Augmentation(aug, schema);
67 return fromDataSchemaNode(result);
70 static StreamingContext<?> fromDataSchemaNode(final DataSchemaNode potential) {
71 if (potential instanceof ContainerSchemaNode) {
72 return new Container((ContainerSchemaNode) potential);
73 } else if (potential instanceof ListSchemaNode) {
74 return fromListSchemaNode((ListSchemaNode) potential);
75 } else if (potential instanceof LeafSchemaNode) {
76 return new Leaf((LeafSchemaNode) potential);
77 } else if (potential instanceof ChoiceSchemaNode) {
78 return new Choice((ChoiceSchemaNode) potential);
79 } else if (potential instanceof LeafListSchemaNode) {
80 return fromLeafListSchemaNode((LeafListSchemaNode) potential);
81 } else if (potential instanceof AnyxmlSchemaNode) {
82 return new AnyXml((AnyxmlSchemaNode) potential);
88 public final T getIdentifier() {
92 abstract StreamingContext<?> getChild(PathArgument child);
94 abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, Iterator<PathArgument> others)
97 abstract boolean isMixin();
99 private static Optional<DataSchemaNode> findChildSchemaNode(final DataNodeContainer parent, final QName child) {
100 final Optional<DataSchemaNode> potential = parent.findDataChildByName(child);
101 return potential.isPresent() ? potential
102 : findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child);
105 private static Optional<DataSchemaNode> findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
106 for (final ChoiceSchemaNode choice : choices) {
107 for (final CaseSchemaNode caze : choice.getCases()) {
108 if (findChildSchemaNode(caze, child).isPresent()) {
109 return Optional.of(choice);
113 return Optional.empty();
116 private static StreamingContext<?> fromListSchemaNode(final ListSchemaNode potential) {
117 final List<QName> keyDefinition = potential.getKeyDefinition();
118 if (keyDefinition == null || keyDefinition.isEmpty()) {
119 return new UnkeyedListMixin(potential);
121 return potential.isUserOrdered() ? new OrderedMapMixin(potential)
122 : new UnorderedMapMixin(potential);
125 private static StreamingContext<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
126 return potential.isUserOrdered() ? new OrderedLeafListMixin(potential)
127 : new UnorderedLeafListMixin(potential);
130 private abstract static class AbstractComposite<T extends PathArgument> extends StreamingContext<T> {
131 AbstractComposite(final T identifier) {
136 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
137 final Iterator<PathArgument> others) throws IOException {
139 final QName type = getIdentifier().getNodeType();
141 final QName firstType = first.getNodeType();
142 checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
146 emitElementStart(writer, first);
147 if (others.hasNext()) {
148 final PathArgument childPath = others.next();
149 final StreamingContext<?> childOp = getChildOperation(childPath);
150 childOp.streamToWriter(writer, childPath, others);
155 abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
157 @SuppressWarnings("checkstyle:illegalCatch")
158 private StreamingContext<?> getChildOperation(final PathArgument childPath) {
159 final StreamingContext<?> childOp;
161 childOp = getChild(childPath);
162 } catch (final RuntimeException e) {
163 throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
165 checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
170 private abstract static class AbstractDataContainer<T extends PathArgument> extends AbstractComposite<T> {
171 private final Map<PathArgument, StreamingContext<?>> byArg = new HashMap<>();
172 private final DataNodeContainer schema;
174 AbstractDataContainer(final T identifier, final DataNodeContainer schema) {
176 this.schema = schema;
180 final StreamingContext<?> getChild(final PathArgument child) {
181 StreamingContext<?> potential = byArg.get(child);
182 if (potential != null) {
185 potential = fromLocalSchema(child);
186 if (potential != null) {
187 byArg.put(potential.getIdentifier(), potential);
192 private StreamingContext<?> fromLocalSchema(final PathArgument child) {
193 if (child instanceof AugmentationIdentifier) {
194 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
197 return fromSchemaAndQNameChecked(schema, child.getNodeType());
201 private abstract static class AbstractMapMixin extends AbstractComposite<NodeIdentifier> {
202 private final ListEntry innerNode;
204 AbstractMapMixin(final ListSchemaNode list) {
205 super(NodeIdentifier.create(list.getQName()));
206 this.innerNode = new ListEntry(NodeIdentifierWithPredicates.of(list.getQName()), list);
210 final StreamingContext<?> getChild(final PathArgument child) {
211 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
215 final boolean isMixin() {
220 private abstract static class AbstractSimple<T extends PathArgument> extends StreamingContext<T> {
221 AbstractSimple(final T identifier) {
226 final StreamingContext<?> getChild(final PathArgument child) {
231 final boolean isMixin() {
236 private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
237 AnyXml(final AnyxmlSchemaNode schema) {
238 super(NodeIdentifier.create(schema.getQName()));
242 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
243 final Iterator<PathArgument> others) throws IOException {
244 writer.startAnyxmlNode(getIdentifier(), DOMSource.class);
245 // FIXME: why are we not emitting a value?
250 private static final class Choice extends AbstractComposite<NodeIdentifier> {
251 private final ImmutableMap<PathArgument, StreamingContext<?>> byArg;
253 Choice(final ChoiceSchemaNode schema) {
254 super(NodeIdentifier.create(schema.getQName()));
255 final ImmutableMap.Builder<PathArgument, StreamingContext<?>> byArgBuilder = ImmutableMap.builder();
257 for (final CaseSchemaNode caze : schema.getCases()) {
258 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
259 final StreamingContext<?> childOp = fromDataSchemaNode(cazeChild);
260 byArgBuilder.put(childOp.getIdentifier(), childOp);
263 byArg = byArgBuilder.build();
267 StreamingContext<?> getChild(final PathArgument child) {
268 return byArg.get(child);
277 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
278 writer.startChoiceNode(getIdentifier(), UNKNOWN_SIZE);
282 private static final class Leaf extends AbstractSimple<NodeIdentifier> {
283 Leaf(final LeafSchemaNode potential) {
284 super(new NodeIdentifier(potential.getQName()));
288 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
289 final Iterator<PathArgument> others) throws IOException {
290 writer.startLeafNode(getIdentifier());
291 // FIXME: why are we not emitting a value?
296 private static final class LeafListEntry extends AbstractSimple<NodeWithValue<?>> {
297 LeafListEntry(final LeafListSchemaNode potential) {
298 super(new NodeWithValue<>(potential.getQName(), null));
302 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
303 final Iterator<PathArgument> others) throws IOException {
304 checkArgument(first instanceof NodeWithValue);
305 final NodeWithValue<?> identifier = (NodeWithValue<?>) first;
306 writer.startLeafSetEntryNode(identifier);
307 writer.scalarValue(identifier.getValue());
312 private static final class ListEntry extends AbstractDataContainer<NodeIdentifierWithPredicates> {
313 ListEntry(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
314 super(identifier, schema);
323 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
324 final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
325 writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
327 for (Entry<QName, Object> entry : identifier.entrySet()) {
328 writer.startLeafNode(new NodeIdentifier(entry.getKey()));
329 writer.scalarValue(entry.getValue());
335 private static final class UnkeyedListItem extends AbstractDataContainer<NodeIdentifier> {
336 UnkeyedListItem(final ListSchemaNode schema) {
337 super(NodeIdentifier.create(schema.getQName()), schema);
346 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
347 writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE);
351 private static final class Container extends AbstractDataContainer<NodeIdentifier> {
352 Container(final ContainerSchemaNode schema) {
353 super(NodeIdentifier.create(schema.getQName()), schema);
362 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
363 writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE);
367 private abstract static class LeafListMixin extends AbstractComposite<NodeIdentifier> {
368 private final StreamingContext<?> innerOp;
370 LeafListMixin(final LeafListSchemaNode potential) {
371 super(NodeIdentifier.create(potential.getQName()));
372 innerOp = new LeafListEntry(potential);
376 final StreamingContext<?> getChild(final PathArgument child) {
377 return child instanceof NodeWithValue ? innerOp : null;
381 final boolean isMixin() {
386 private static final class OrderedLeafListMixin extends LeafListMixin {
387 OrderedLeafListMixin(final LeafListSchemaNode potential) {
392 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
393 writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE);
397 private static class UnorderedLeafListMixin extends LeafListMixin {
398 UnorderedLeafListMixin(final LeafListSchemaNode potential) {
403 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
404 writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE);
408 private static final class Augmentation extends AbstractDataContainer<AugmentationIdentifier> {
409 Augmentation(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
410 super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
411 EffectiveAugmentationSchema.create(augmentation, schema));
420 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
421 writer.startAugmentationNode(getIdentifier());
425 private static final class UnorderedMapMixin extends AbstractMapMixin {
426 UnorderedMapMixin(final ListSchemaNode list) {
431 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
432 writer.startMapNode(getIdentifier(), UNKNOWN_SIZE);
436 private static final class OrderedMapMixin extends AbstractMapMixin {
437 OrderedMapMixin(final ListSchemaNode list) {
442 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
443 writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE);
447 private static final class UnkeyedListMixin extends AbstractComposite<NodeIdentifier> {
448 private final UnkeyedListItem innerNode;
450 UnkeyedListMixin(final ListSchemaNode list) {
451 super(NodeIdentifier.create(list.getQName()));
452 this.innerNode = new UnkeyedListItem(list);
456 StreamingContext<?> getChild(final PathArgument child) {
457 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
466 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
467 writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE);