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 org.opendaylight.yangtools.concepts.Identifiable;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
29 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
30 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
31 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
34 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
38 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
42 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
44 abstract class StreamingContext<T extends PathArgument> implements Identifiable<T> {
45 private final T identifier;
47 StreamingContext(final T identifier) {
48 this.identifier = identifier;
51 static StreamingContext<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
52 final Optional<DataSchemaNode> potential = findChildSchemaNode(schema, child);
53 checkArgument(potential.isPresent(),
54 "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child, schema,
55 schema.getChildNodes());
57 final DataSchemaNode result = potential.get();
58 // We try to look up if this node was added by augmentation
59 if (schema instanceof DataSchemaNode && result.isAugmenting()) {
60 for (final AugmentationSchemaNode aug : ((AugmentationTarget)schema).getAvailableAugmentations()) {
61 final DataSchemaNode found = aug.getDataChildByName(result.getQName());
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 DataSchemaNode potential = parent.getDataChildByName(child);
101 if (potential == null) {
102 potential = findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child);
104 return Optional.ofNullable(potential);
107 private static ChoiceSchemaNode findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
108 for (final ChoiceSchemaNode choice : choices) {
109 for (final CaseSchemaNode caze : choice.getCases().values()) {
110 if (findChildSchemaNode(caze, child).isPresent()) {
118 private static StreamingContext<?> fromListSchemaNode(final ListSchemaNode potential) {
119 final List<QName> keyDefinition = potential.getKeyDefinition();
120 if (keyDefinition == null || keyDefinition.isEmpty()) {
121 return new UnkeyedListMixin(potential);
123 return potential.isUserOrdered() ? new OrderedMapMixin(potential)
124 : new UnorderedMapMixin(potential);
127 private static StreamingContext<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
128 return potential.isUserOrdered() ? new OrderedLeafListMixin(potential)
129 : new UnorderedLeafListMixin(potential);
132 private abstract static class AbstractComposite<T extends PathArgument> extends StreamingContext<T> {
133 AbstractComposite(final T identifier) {
138 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
139 final Iterator<PathArgument> others) throws IOException {
141 final QName type = getIdentifier().getNodeType();
143 final QName firstType = first.getNodeType();
144 checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
148 emitElementStart(writer, first);
149 if (others.hasNext()) {
150 final PathArgument childPath = others.next();
151 final StreamingContext<?> childOp = getChildOperation(childPath);
152 childOp.streamToWriter(writer, childPath, others);
157 abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
159 @SuppressWarnings("checkstyle:illegalCatch")
160 private StreamingContext<?> getChildOperation(final PathArgument childPath) {
161 final StreamingContext<?> childOp;
163 childOp = getChild(childPath);
164 } catch (final RuntimeException e) {
165 throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
167 checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
172 private abstract static class AbstractDataContainer<T extends PathArgument> extends AbstractComposite<T> {
173 private final Map<PathArgument, StreamingContext<?>> byArg = new HashMap<>();
174 private final DataNodeContainer schema;
176 AbstractDataContainer(final T identifier, final DataNodeContainer schema) {
178 this.schema = schema;
182 final StreamingContext<?> getChild(final PathArgument child) {
183 StreamingContext<?> potential = byArg.get(child);
184 if (potential != null) {
187 potential = fromLocalSchema(child);
188 if (potential != null) {
189 byArg.put(potential.getIdentifier(), potential);
194 private StreamingContext<?> fromLocalSchema(final PathArgument child) {
195 if (child instanceof AugmentationIdentifier) {
196 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
199 return fromSchemaAndQNameChecked(schema, child.getNodeType());
203 private abstract static class AbstractMapMixin extends AbstractComposite<NodeIdentifier> {
204 private final ListEntry innerNode;
206 AbstractMapMixin(final ListSchemaNode list) {
207 super(NodeIdentifier.create(list.getQName()));
208 this.innerNode = new ListEntry(new NodeIdentifierWithPredicates(list.getQName(), ImmutableMap.of()), list);
212 final StreamingContext<?> getChild(final PathArgument child) {
213 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
217 final boolean isMixin() {
222 private abstract static class AbstractSimple<T extends PathArgument> extends StreamingContext<T> {
223 AbstractSimple(final T identifier) {
228 final StreamingContext<?> getChild(final PathArgument child) {
233 final boolean isMixin() {
238 private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
239 AnyXml(final AnyXmlSchemaNode schema) {
240 super(NodeIdentifier.create(schema.getQName()));
244 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
245 final Iterator<PathArgument> others) throws IOException {
246 writer.anyxmlNode(getIdentifier(), null);
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().values()) {
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.leafNode(getIdentifier(), null);
294 private static final class LeafListEntry extends AbstractSimple<NodeWithValue<?>> {
295 LeafListEntry(final LeafListSchemaNode potential) {
296 super(new NodeWithValue<>(potential.getQName(), null));
300 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
301 final Iterator<PathArgument> others) throws IOException {
302 checkArgument(first instanceof NodeWithValue);
303 final NodeWithValue<?> identifier = (NodeWithValue<?>) first;
304 writer.leafSetEntryNode(identifier.getNodeType(), identifier.getValue());
308 private static final class ListEntry extends AbstractDataContainer<NodeIdentifierWithPredicates> {
309 ListEntry(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
310 super(identifier, schema);
319 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
320 final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
321 writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
323 for (Entry<QName, Object> entry : identifier.getKeyValues().entrySet()) {
324 writer.leafNode(new NodeIdentifier(entry.getKey()), entry.getValue());
329 private static final class UnkeyedListItem extends AbstractDataContainer<NodeIdentifier> {
330 UnkeyedListItem(final ListSchemaNode schema) {
331 super(NodeIdentifier.create(schema.getQName()), schema);
340 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
341 writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE);
345 private static final class Container extends AbstractDataContainer<NodeIdentifier> {
346 Container(final ContainerSchemaNode schema) {
347 super(NodeIdentifier.create(schema.getQName()), schema);
356 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
357 writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE);
361 private abstract static class LeafListMixin extends AbstractComposite<NodeIdentifier> {
362 private final StreamingContext<?> innerOp;
364 LeafListMixin(final LeafListSchemaNode potential) {
365 super(NodeIdentifier.create(potential.getQName()));
366 innerOp = new LeafListEntry(potential);
370 final StreamingContext<?> getChild(final PathArgument child) {
371 return child instanceof NodeWithValue ? innerOp : null;
375 final boolean isMixin() {
380 private static final class OrderedLeafListMixin extends LeafListMixin {
381 OrderedLeafListMixin(final LeafListSchemaNode potential) {
386 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
387 writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE);
391 private static class UnorderedLeafListMixin extends LeafListMixin {
392 UnorderedLeafListMixin(final LeafListSchemaNode potential) {
397 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
398 writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE);
402 private static final class Augmentation extends AbstractDataContainer<AugmentationIdentifier> {
403 Augmentation(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
404 super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
405 EffectiveAugmentationSchema.create(augmentation, schema));
414 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
415 writer.startAugmentationNode(getIdentifier());
419 private static final class UnorderedMapMixin extends AbstractMapMixin {
420 UnorderedMapMixin(final ListSchemaNode list) {
425 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
426 writer.startMapNode(getIdentifier(), UNKNOWN_SIZE);
430 private static final class OrderedMapMixin extends AbstractMapMixin {
431 OrderedMapMixin(final ListSchemaNode list) {
436 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
437 writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE);
441 private static final class UnkeyedListMixin extends AbstractComposite<NodeIdentifier> {
442 private final UnkeyedListItem innerNode;
444 UnkeyedListMixin(final ListSchemaNode list) {
445 super(NodeIdentifier.create(list.getQName()));
446 this.innerNode = new UnkeyedListItem(list);
450 StreamingContext<?> getChild(final PathArgument child) {
451 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
460 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
461 writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE);