Fix DataNormalizationOperation with nested choices
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / netconf / sal / restconf / impl / DataNormalizationOperation.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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.sal.restconf.impl;
9
10 import com.google.common.collect.ImmutableMap;
11 import com.google.common.collect.ImmutableSet;
12 import com.google.common.collect.Iterables;
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.util.HashSet;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.concurrent.ConcurrentHashMap;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.opendaylight.yangtools.concepts.Identifiable;
20 import org.opendaylight.yangtools.yang.common.Empty;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
27 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
30 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
33 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
37 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
40 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
41
42 abstract class DataNormalizationOperation<T extends PathArgument> implements Identifiable<T> {
43     private final T identifier;
44
45     @Override
46     public T getIdentifier() {
47         return identifier;
48     }
49
50     DataNormalizationOperation(final T identifier) {
51         this.identifier = identifier;
52     }
53
54     static DataNormalizationOperation<?> from(final EffectiveModelContext ctx) {
55         return new ContainerNormalization(ctx);
56     }
57
58     boolean isMixin() {
59         return false;
60     }
61
62     Set<QName> getQNameIdentifiers() {
63         return ImmutableSet.of(identifier.getNodeType());
64     }
65
66     abstract DataNormalizationOperation<?> getChild(PathArgument child) throws DataNormalizationException;
67
68     abstract DataNormalizationOperation<?> getChild(QName child) throws DataNormalizationException;
69
70     private abstract static class SimpleTypeNormalization<T extends PathArgument>
71             extends DataNormalizationOperation<T> {
72         SimpleTypeNormalization(final T identifier) {
73             super(identifier);
74         }
75
76         @Override
77         final DataNormalizationOperation<?> getChild(final PathArgument child) {
78             return null;
79         }
80
81         @Override
82         final DataNormalizationOperation<?> getChild(final QName child) {
83             return null;
84         }
85     }
86
87     private static final class LeafNormalization extends SimpleTypeNormalization<NodeIdentifier> {
88         LeafNormalization(final LeafSchemaNode potential) {
89             super(new NodeIdentifier(potential.getQName()));
90         }
91     }
92
93     private static final class LeafListEntryNormalization extends SimpleTypeNormalization<NodeWithValue> {
94         LeafListEntryNormalization(final LeafListSchemaNode potential) {
95             super(new NodeWithValue<>(potential.getQName(), Empty.value()));
96         }
97     }
98
99     private abstract static class DataContainerNormalizationOperation<T extends PathArgument>
100             extends DataNormalizationOperation<T> {
101         private final DataNodeContainer schema;
102         private final Map<QName, DataNormalizationOperation<?>> byQName = new ConcurrentHashMap<>();
103         private final Map<PathArgument, DataNormalizationOperation<?>> byArg = new ConcurrentHashMap<>();
104
105         DataContainerNormalizationOperation(final T identifier, final DataNodeContainer schema) {
106             super(identifier);
107             this.schema = schema;
108         }
109
110         @Override
111         DataNormalizationOperation<?> getChild(final PathArgument child) throws DataNormalizationException {
112             DataNormalizationOperation<?> potential = byArg.get(child);
113             if (potential != null) {
114                 return potential;
115             }
116             potential = fromLocalSchema(child);
117             return register(potential);
118         }
119
120         @Override
121         DataNormalizationOperation<?> getChild(final QName child) throws DataNormalizationException {
122             DataNormalizationOperation<?> potential = byQName.get(child);
123             if (potential != null) {
124                 return potential;
125             }
126             potential = fromLocalSchemaAndQName(schema, child);
127             return register(potential);
128         }
129
130         private DataNormalizationOperation<?> fromLocalSchema(final PathArgument child)
131                 throws DataNormalizationException {
132             if (child instanceof AugmentationIdentifier) {
133                 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
134                         .iterator().next());
135             }
136             return fromSchemaAndQNameChecked(schema, child.getNodeType());
137         }
138
139         DataNormalizationOperation<?> fromLocalSchemaAndQName(final DataNodeContainer schema2,
140                 final QName child) throws DataNormalizationException {
141             return fromSchemaAndQNameChecked(schema2, child);
142         }
143
144         private DataNormalizationOperation<?> register(final DataNormalizationOperation<?> potential) {
145             if (potential != null) {
146                 byArg.put(potential.getIdentifier(), potential);
147                 for (final QName qname : potential.getQNameIdentifiers()) {
148                     byQName.put(qname, potential);
149                 }
150             }
151             return potential;
152         }
153
154         private static DataNormalizationOperation<?> fromSchemaAndQNameChecked(final DataNodeContainer schema,
155                 final QName child) throws DataNormalizationException {
156
157             final DataSchemaNode result = findChildSchemaNode(schema, child);
158             if (result == null) {
159                 throw new DataNormalizationException(String.format(
160                         "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child,
161                         schema,schema.getChildNodes()));
162             }
163
164             // We try to look up if this node was added by augmentation
165             if (schema instanceof DataSchemaNode && result.isAugmenting()) {
166                 return fromAugmentation(schema, (AugmentationTarget) schema, result);
167             }
168             return fromDataSchemaNode(result);
169         }
170     }
171
172     private static final class ListItemNormalization extends
173             DataContainerNormalizationOperation<NodeIdentifierWithPredicates> {
174         ListItemNormalization(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
175             super(identifier, schema);
176         }
177     }
178
179     private static final class UnkeyedListItemNormalization
180             extends DataContainerNormalizationOperation<NodeIdentifier> {
181         UnkeyedListItemNormalization(final ListSchemaNode schema) {
182             super(new NodeIdentifier(schema.getQName()), schema);
183         }
184     }
185
186     private static final class ContainerNormalization extends DataContainerNormalizationOperation<NodeIdentifier> {
187         ContainerNormalization(final ContainerLike schema) {
188             super(new NodeIdentifier(schema.getQName()), schema);
189         }
190     }
191
192     private abstract static class MixinNormalizationOp<T extends PathArgument> extends DataNormalizationOperation<T> {
193         MixinNormalizationOp(final T identifier) {
194             super(identifier);
195         }
196
197         @Override
198         final boolean isMixin() {
199             return true;
200         }
201     }
202
203     private static final class LeafListMixinNormalization extends MixinNormalizationOp<NodeIdentifier> {
204         private final DataNormalizationOperation<?> innerOp;
205
206         LeafListMixinNormalization(final LeafListSchemaNode potential) {
207             super(new NodeIdentifier(potential.getQName()));
208             innerOp = new LeafListEntryNormalization(potential);
209         }
210
211         @Override
212         DataNormalizationOperation<?> getChild(final PathArgument child) {
213             if (child instanceof NodeWithValue) {
214                 return innerOp;
215             }
216             return null;
217         }
218
219         @Override
220         DataNormalizationOperation<?> getChild(final QName child) {
221             if (getIdentifier().getNodeType().equals(child)) {
222                 return innerOp;
223             }
224             return null;
225         }
226     }
227
228     private static final class AugmentationNormalization
229             extends DataContainerNormalizationOperation<AugmentationIdentifier> {
230
231         AugmentationNormalization(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
232             super(augmentationIdentifierFrom(augmentation), augmentationProxy(augmentation,schema));
233         }
234
235         private static DataNodeContainer augmentationProxy(final AugmentationSchemaNode augmentation,
236                 final DataNodeContainer schema) {
237             final Set<DataSchemaNode> children = new HashSet<>();
238             for (final DataSchemaNode augNode : augmentation.getChildNodes()) {
239                 children.add(schema.getDataChildByName(augNode.getQName()));
240             }
241             return new EffectiveAugmentationSchema(augmentation, children);
242         }
243
244         @Override
245         boolean isMixin() {
246             return true;
247         }
248
249         @Override
250         DataNormalizationOperation<?> fromLocalSchemaAndQName(final DataNodeContainer schema, final QName child) {
251             final DataSchemaNode result = findChildSchemaNode(schema, child);
252             if (result == null) {
253                 return null;
254             }
255
256             // We try to look up if this node was added by augmentation
257             if (schema instanceof DataSchemaNode && result.isAugmenting()) {
258                 return fromAugmentation(schema, (AugmentationTarget) schema, result);
259             }
260             return fromDataSchemaNode(result);
261         }
262
263         @Override
264         Set<QName> getQNameIdentifiers() {
265             return getIdentifier().getPossibleChildNames();
266         }
267
268         private static AugmentationIdentifier augmentationIdentifierFrom(final AugmentationSchemaNode augmentation) {
269             final ImmutableSet.Builder<QName> potentialChildren = ImmutableSet.builder();
270             for (final DataSchemaNode child : augmentation.getChildNodes()) {
271                 potentialChildren.add(child.getQName());
272             }
273             return new AugmentationIdentifier(potentialChildren.build());
274         }
275     }
276
277     private static final class MapMixinNormalization extends MixinNormalizationOp<NodeIdentifier> {
278         private final ListItemNormalization innerNode;
279
280         MapMixinNormalization(final ListSchemaNode list) {
281             super(new NodeIdentifier(list.getQName()));
282             innerNode = new ListItemNormalization(NodeIdentifierWithPredicates.of(list.getQName()), list);
283         }
284
285         @Override
286         DataNormalizationOperation<?> getChild(final PathArgument child) {
287             if (child.getNodeType().equals(getIdentifier().getNodeType())) {
288                 return innerNode;
289             }
290             return null;
291         }
292
293         @Override
294         DataNormalizationOperation<?> getChild(final QName child) {
295             if (getIdentifier().getNodeType().equals(child)) {
296                 return innerNode;
297             }
298             return null;
299         }
300     }
301
302     private static final class UnkeyedListMixinNormalization extends MixinNormalizationOp<NodeIdentifier> {
303         private final UnkeyedListItemNormalization innerNode;
304
305         UnkeyedListMixinNormalization(final ListSchemaNode list) {
306             super(new NodeIdentifier(list.getQName()));
307             innerNode = new UnkeyedListItemNormalization(list);
308         }
309
310         @Override
311         DataNormalizationOperation<?> getChild(final PathArgument child) {
312             if (child.getNodeType().equals(getIdentifier().getNodeType())) {
313                 return innerNode;
314             }
315             return null;
316         }
317
318         @Override
319         DataNormalizationOperation<?> getChild(final QName child) {
320             if (getIdentifier().getNodeType().equals(child)) {
321                 return innerNode;
322             }
323             return null;
324         }
325     }
326
327     private static final class ChoiceNodeNormalization extends MixinNormalizationOp<NodeIdentifier> {
328         private final ImmutableMap<QName, DataNormalizationOperation<?>> byQName;
329         private final ImmutableMap<PathArgument, DataNormalizationOperation<?>> byArg;
330
331         ChoiceNodeNormalization(final ChoiceSchemaNode schema) {
332             super(new NodeIdentifier(schema.getQName()));
333             final ImmutableMap.Builder<QName, DataNormalizationOperation<?>> byQNameBuilder = ImmutableMap.builder();
334             final ImmutableMap.Builder<PathArgument, DataNormalizationOperation<?>> byArgBuilder =
335                     ImmutableMap.builder();
336
337             for (final CaseSchemaNode caze : schema.getCases()) {
338                 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
339                     final DataNormalizationOperation<?> childOp = fromDataSchemaNode(cazeChild);
340                     byArgBuilder.put(childOp.getIdentifier(), childOp);
341                     for (final QName qname : childOp.getQNameIdentifiers()) {
342                         byQNameBuilder.put(qname, childOp);
343                     }
344                 }
345             }
346             byQName = byQNameBuilder.build();
347             byArg = byArgBuilder.build();
348         }
349
350         @Override
351         DataNormalizationOperation<?> getChild(final PathArgument child) {
352             return byArg.get(child);
353         }
354
355         @Override
356         DataNormalizationOperation<?> getChild(final QName child) {
357             return byQName.get(child);
358         }
359
360         @Override
361         Set<QName> getQNameIdentifiers() {
362             return byQName.keySet();
363         }
364     }
365
366     private static final class AnyxmlNormalization extends SimpleTypeNormalization<NodeIdentifier> {
367         AnyxmlNormalization(final AnyxmlSchemaNode schema) {
368             super(new NodeIdentifier(schema.getQName()));
369         }
370     }
371
372     private static @Nullable DataSchemaNode findChildSchemaNode(final DataNodeContainer parent, final QName child) {
373         final DataSchemaNode potential = parent.dataChildByName(child);
374         return potential != null ? potential : findChoice(parent, child);
375     }
376
377     private static @Nullable ChoiceSchemaNode findChoice(final DataNodeContainer parent, final QName child) {
378         for (final ChoiceSchemaNode choice : Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class)) {
379             for (final CaseSchemaNode caze : choice.getCases()) {
380                 if (findChildSchemaNode(caze, child) != null) {
381                     return choice;
382                 }
383             }
384         }
385         return null;
386     }
387
388     /**
389      * Returns a DataNormalizationOperation for provided child node.
390      *
391      * <p>
392      * If supplied child is added by Augmentation this operation returns
393      * a DataNormalizationOperation for augmentation,
394      * otherwise returns a DataNormalizationOperation for child as
395      * call for {@link #fromDataSchemaNode(DataSchemaNode)}.
396      */
397     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
398             justification = "https://github.com/spotbugs/spotbugs/issues/811")
399     private static DataNormalizationOperation<?> fromAugmentation(final DataNodeContainer parent,
400             final AugmentationTarget parentAug, final DataSchemaNode child) {
401         for (final AugmentationSchemaNode aug : parentAug.getAvailableAugmentations()) {
402             if (aug.dataChildByName(child.getQName()) != null) {
403                 return new AugmentationNormalization(aug, parent);
404             }
405         }
406         return fromDataSchemaNode(child);
407     }
408
409     static DataNormalizationOperation<?> fromDataSchemaNode(final DataSchemaNode potential) {
410         if (potential instanceof ContainerSchemaNode) {
411             return new ContainerNormalization((ContainerSchemaNode) potential);
412         } else if (potential instanceof ListSchemaNode) {
413             return fromListSchemaNode((ListSchemaNode) potential);
414         } else if (potential instanceof LeafSchemaNode) {
415             return new LeafNormalization((LeafSchemaNode) potential);
416         } else if (potential instanceof ChoiceSchemaNode) {
417             return new ChoiceNodeNormalization((ChoiceSchemaNode) potential);
418         } else if (potential instanceof LeafListSchemaNode) {
419             return new LeafListMixinNormalization((LeafListSchemaNode) potential);
420         } else if (potential instanceof AnyxmlSchemaNode) {
421             return new AnyxmlNormalization((AnyxmlSchemaNode) potential);
422         }
423         return null;
424     }
425
426     private static DataNormalizationOperation<?> fromListSchemaNode(final ListSchemaNode potential) {
427         if (potential.getKeyDefinition().isEmpty()) {
428             return new UnkeyedListMixinNormalization(potential);
429         }
430         return new MapMixinNormalization(potential);
431     }
432 }