Update NodeIdentifierWithPredicates construction
[netconf.git] / netconf / netconf-util / src / main / java / org / opendaylight / netconf / util / StreamingContext.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
12
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;
19 import java.util.Map;
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;
43
44 abstract class StreamingContext<T extends PathArgument> implements Identifiable<T> {
45     private final T identifier;
46
47     StreamingContext(final T identifier) {
48         this.identifier = identifier;
49     }
50
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());
56
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());
62                 if (found != null) {
63                     return new Augmentation(aug, schema);
64                 }
65             }
66         }
67         return fromDataSchemaNode(result);
68     }
69
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);
83         }
84         return null;
85     }
86
87     @Override
88     public final T getIdentifier() {
89         return identifier;
90     }
91
92     abstract StreamingContext<?> getChild(PathArgument child);
93
94     abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, Iterator<PathArgument> others)
95             throws IOException;
96
97     abstract boolean isMixin();
98
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);
103         }
104         return Optional.ofNullable(potential);
105     }
106
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()) {
111                     return choice;
112                 }
113             }
114         }
115         return null;
116     }
117
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);
122         }
123         return potential.isUserOrdered() ? new OrderedMapMixin(potential)
124                 : new UnorderedMapMixin(potential);
125     }
126
127     private static StreamingContext<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
128         return potential.isUserOrdered() ? new OrderedLeafListMixin(potential)
129                 : new UnorderedLeafListMixin(potential);
130     }
131
132     private abstract static class AbstractComposite<T extends PathArgument> extends StreamingContext<T> {
133         AbstractComposite(final T identifier) {
134             super(identifier);
135         }
136
137         @Override
138         final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
139                 final Iterator<PathArgument> others) throws IOException {
140             if (!isMixin()) {
141                 final QName type = getIdentifier().getNodeType();
142                 if (type != null) {
143                     final QName firstType = first.getNodeType();
144                     checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
145                 }
146             }
147
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);
153             }
154             writer.endNode();
155         }
156
157         abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
158
159         @SuppressWarnings("checkstyle:illegalCatch")
160         private StreamingContext<?> getChildOperation(final PathArgument childPath) {
161             final StreamingContext<?> childOp;
162             try {
163                 childOp = getChild(childPath);
164             } catch (final RuntimeException e) {
165                 throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
166             }
167             checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
168             return childOp;
169         }
170     }
171
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;
175
176         AbstractDataContainer(final T identifier, final DataNodeContainer schema) {
177             super(identifier);
178             this.schema = schema;
179         }
180
181         @Override
182         final StreamingContext<?> getChild(final PathArgument child) {
183             StreamingContext<?> potential = byArg.get(child);
184             if (potential != null) {
185                 return potential;
186             }
187             potential = fromLocalSchema(child);
188             if (potential != null) {
189                 byArg.put(potential.getIdentifier(), potential);
190             }
191             return potential;
192         }
193
194         private StreamingContext<?> fromLocalSchema(final PathArgument child) {
195             if (child instanceof AugmentationIdentifier) {
196                 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
197                         .iterator().next());
198             }
199             return fromSchemaAndQNameChecked(schema, child.getNodeType());
200         }
201     }
202
203     private abstract static class AbstractMapMixin extends AbstractComposite<NodeIdentifier> {
204         private final ListEntry innerNode;
205
206         AbstractMapMixin(final ListSchemaNode list) {
207             super(NodeIdentifier.create(list.getQName()));
208             this.innerNode = new ListEntry(NodeIdentifierWithPredicates.of(list.getQName()), list);
209         }
210
211         @Override
212         final StreamingContext<?> getChild(final PathArgument child) {
213             return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
214         }
215
216         @Override
217         final boolean isMixin() {
218             return true;
219         }
220     }
221
222     private abstract static class AbstractSimple<T extends PathArgument> extends StreamingContext<T> {
223         AbstractSimple(final T identifier) {
224             super(identifier);
225         }
226
227         @Override
228         final StreamingContext<?> getChild(final PathArgument child) {
229             return null;
230         }
231
232         @Override
233         final boolean isMixin() {
234             return false;
235         }
236     }
237
238     private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
239         AnyXml(final AnyXmlSchemaNode schema) {
240             super(NodeIdentifier.create(schema.getQName()));
241         }
242
243         @Override
244         void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
245                 final Iterator<PathArgument> others) throws IOException {
246             writer.startAnyxmlNode(getIdentifier());
247             // FIXME: why are we not emitting a value?
248             writer.endNode();
249         }
250     }
251
252     private static final class Choice extends AbstractComposite<NodeIdentifier> {
253         private final ImmutableMap<PathArgument, StreamingContext<?>> byArg;
254
255         Choice(final ChoiceSchemaNode schema) {
256             super(NodeIdentifier.create(schema.getQName()));
257             final ImmutableMap.Builder<PathArgument, StreamingContext<?>> byArgBuilder = ImmutableMap.builder();
258
259             for (final CaseSchemaNode caze : schema.getCases().values()) {
260                 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
261                     final StreamingContext<?> childOp = fromDataSchemaNode(cazeChild);
262                     byArgBuilder.put(childOp.getIdentifier(), childOp);
263                 }
264             }
265             byArg = byArgBuilder.build();
266         }
267
268         @Override
269         StreamingContext<?> getChild(final PathArgument child) {
270             return byArg.get(child);
271         }
272
273         @Override
274         boolean isMixin() {
275             return true;
276         }
277
278         @Override
279         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
280             writer.startChoiceNode(getIdentifier(), UNKNOWN_SIZE);
281         }
282     }
283
284     private static final class Leaf extends AbstractSimple<NodeIdentifier> {
285         Leaf(final LeafSchemaNode potential) {
286             super(new NodeIdentifier(potential.getQName()));
287         }
288
289         @Override
290         void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
291                 final Iterator<PathArgument> others) throws IOException {
292             writer.startLeafNode(getIdentifier());
293             // FIXME: why are we not emitting a value?
294             writer.endNode();
295         }
296     }
297
298     private static final class LeafListEntry extends AbstractSimple<NodeWithValue<?>> {
299         LeafListEntry(final LeafListSchemaNode potential) {
300             super(new NodeWithValue<>(potential.getQName(), null));
301         }
302
303         @Override
304         void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
305                 final Iterator<PathArgument> others) throws IOException {
306             checkArgument(first instanceof NodeWithValue);
307             final NodeWithValue<?> identifier = (NodeWithValue<?>) first;
308             writer.startLeafSetEntryNode(identifier);
309             writer.scalarValue(identifier.getValue());
310             writer.endNode();
311         }
312     }
313
314     private static final class ListEntry extends AbstractDataContainer<NodeIdentifierWithPredicates> {
315         ListEntry(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
316             super(identifier, schema);
317         }
318
319         @Override
320         boolean isMixin() {
321             return false;
322         }
323
324         @Override
325         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
326             final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
327             writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
328
329             for (Entry<QName, Object> entry : identifier.getKeyValues().entrySet()) {
330                 writer.startLeafNode(new NodeIdentifier(entry.getKey()));
331                 writer.scalarValue(entry.getValue());
332                 writer.endNode();
333             }
334         }
335     }
336
337     private static final class UnkeyedListItem extends AbstractDataContainer<NodeIdentifier> {
338         UnkeyedListItem(final ListSchemaNode schema) {
339             super(NodeIdentifier.create(schema.getQName()), schema);
340         }
341
342         @Override
343         boolean isMixin() {
344             return false;
345         }
346
347         @Override
348         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
349             writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE);
350         }
351     }
352
353     private static final class Container extends AbstractDataContainer<NodeIdentifier> {
354         Container(final ContainerSchemaNode schema) {
355             super(NodeIdentifier.create(schema.getQName()), schema);
356         }
357
358         @Override
359         boolean isMixin() {
360             return false;
361         }
362
363         @Override
364         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
365             writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE);
366         }
367     }
368
369     private abstract static class LeafListMixin extends AbstractComposite<NodeIdentifier> {
370         private final StreamingContext<?> innerOp;
371
372         LeafListMixin(final LeafListSchemaNode potential) {
373             super(NodeIdentifier.create(potential.getQName()));
374             innerOp = new LeafListEntry(potential);
375         }
376
377         @Override
378         final StreamingContext<?> getChild(final PathArgument child) {
379             return child instanceof NodeWithValue ? innerOp : null;
380         }
381
382         @Override
383         final boolean isMixin() {
384             return true;
385         }
386     }
387
388     private static final class OrderedLeafListMixin extends LeafListMixin {
389         OrderedLeafListMixin(final LeafListSchemaNode potential) {
390             super(potential);
391         }
392
393         @Override
394         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
395             writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE);
396         }
397     }
398
399     private static class UnorderedLeafListMixin extends LeafListMixin {
400         UnorderedLeafListMixin(final LeafListSchemaNode potential) {
401             super(potential);
402         }
403
404         @Override
405         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
406             writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE);
407         }
408     }
409
410     private static final class Augmentation extends AbstractDataContainer<AugmentationIdentifier> {
411         Augmentation(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
412             super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
413                     EffectiveAugmentationSchema.create(augmentation, schema));
414         }
415
416         @Override
417         boolean isMixin() {
418             return true;
419         }
420
421         @Override
422         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
423             writer.startAugmentationNode(getIdentifier());
424         }
425     }
426
427     private static final class UnorderedMapMixin extends AbstractMapMixin {
428         UnorderedMapMixin(final ListSchemaNode list) {
429             super(list);
430         }
431
432         @Override
433         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
434             writer.startMapNode(getIdentifier(), UNKNOWN_SIZE);
435         }
436     }
437
438     private static final class OrderedMapMixin extends AbstractMapMixin {
439         OrderedMapMixin(final ListSchemaNode list) {
440             super(list);
441         }
442
443         @Override
444         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
445             writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE);
446         }
447     }
448
449     private static final class UnkeyedListMixin extends AbstractComposite<NodeIdentifier> {
450         private final UnkeyedListItem innerNode;
451
452         UnkeyedListMixin(final ListSchemaNode list) {
453             super(NodeIdentifier.create(list.getQName()));
454             this.innerNode = new UnkeyedListItem(list);
455         }
456
457         @Override
458         StreamingContext<?> getChild(final PathArgument child) {
459             return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
460         }
461
462         @Override
463         boolean isMixin() {
464             return true;
465         }
466
467         @Override
468         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
469             writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE);
470         }
471     }
472 }