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