Fix mandatory enforcer failure on augmented nodes
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / InstanceIdToCompositeNodes.java
1 /*
2  * Copyright (c) 2015 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.yangtools.yang.data.impl.schema;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableMap;
16 import com.google.common.collect.Iterables;
17 import com.google.common.collect.Maps;
18 import java.util.Collection;
19 import java.util.Iterator;
20 import java.util.LinkedHashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Optional;
25 import java.util.concurrent.ConcurrentHashMap;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
34 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
42 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
43 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
44 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
45 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
46 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
47 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
51 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
52 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
55 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 /**
60  * Base strategy for converting an instance identifier into a normalized node structure for container-like types.
61  */
62 abstract class InstanceIdToCompositeNodes<T extends PathArgument> extends InstanceIdToNodes<T> {
63     private static final Logger LOG = LoggerFactory.getLogger(InstanceIdToCompositeNodes.class);
64
65     InstanceIdToCompositeNodes(final T identifier) {
66         super(identifier);
67     }
68
69     @Override
70     @SuppressWarnings("unchecked")
71     final NormalizedNode<?, ?> create(final PathArgument first, final Iterator<PathArgument> others,
72             final Optional<NormalizedNode<?, ?>> lastChild) {
73         if (!isMixin()) {
74             final QName type = getIdentifier().getNodeType();
75             if (type != null) {
76                 final QName firstType = first.getNodeType();
77                 checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
78             }
79         }
80
81         @SuppressWarnings("rawtypes")
82         final NormalizedNodeContainerBuilder builder = createBuilder(first);
83
84         if (others.hasNext()) {
85             final PathArgument childPath = others.next();
86             final InstanceIdToNodes<?> childOp = getChildOperation(childPath);
87             builder.addChild(childOp.create(childPath, others, lastChild));
88         } else if (lastChild.isPresent()) {
89             builder.withValue(ImmutableList.copyOf((Collection<?>) lastChild.get().getValue()));
90         }
91
92         return builder.build();
93     }
94
95     @SuppressWarnings("checkstyle:illegalCatch")
96     private InstanceIdToNodes<?> getChildOperation(final PathArgument childPath) {
97         final InstanceIdToNodes<?> childOp;
98         try {
99             childOp = getChild(childPath);
100         } catch (final RuntimeException e) {
101             throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
102         }
103         checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
104         return childOp;
105     }
106
107     abstract NormalizedNodeContainerBuilder<?, ?, ?, ?> createBuilder(PathArgument compositeNode);
108
109     abstract static class DataContainerNormalizationOperation<T extends PathArgument, S extends DataNodeContainer>
110             extends InstanceIdToCompositeNodes<T> {
111
112         private final Map<PathArgument, InstanceIdToNodes<?>> byArg = new ConcurrentHashMap<>();
113         private final @NonNull S schema;
114
115         DataContainerNormalizationOperation(final T identifier, final S schema) {
116             super(identifier);
117             this.schema = requireNonNull(schema);
118         }
119
120         @Override
121         final InstanceIdToNodes<?> getChild(final PathArgument child) {
122             final InstanceIdToNodes<?> existing = byArg.get(child);
123             if (existing != null) {
124                 return existing;
125             }
126             return register(fromLocalSchema(child));
127         }
128
129         final @NonNull S schema() {
130             return schema;
131         }
132
133         private InstanceIdToNodes<?> fromLocalSchema(final PathArgument child) {
134             if (child instanceof AugmentationIdentifier) {
135                 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
136                         .iterator().next());
137             }
138             return fromSchemaAndQNameChecked(schema, child.getNodeType());
139         }
140
141         private InstanceIdToNodes<?> register(final InstanceIdToNodes<?> potential) {
142             if (potential != null) {
143                 byArg.put(potential.getIdentifier(), potential);
144             }
145             return potential;
146         }
147     }
148
149     static final class MapEntryNormalization
150             extends DataContainerNormalizationOperation<NodeIdentifierWithPredicates, ListSchemaNode> {
151         MapEntryNormalization(final ListSchemaNode schema) {
152             super(NodeIdentifierWithPredicates.of(schema.getQName()), schema);
153         }
154
155         @Override
156         boolean isMixin() {
157             return false;
158         }
159
160         @Override
161         DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> createBuilder(
162                 final PathArgument currentArg) {
163             final NodeIdentifierWithPredicates arg = (NodeIdentifierWithPredicates) currentArg;
164             return createBuilder(arg.size() < 2 ? arg : reorderPredicates(schema().getKeyDefinition(), arg));
165         }
166
167         private static DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> createBuilder(
168                 final NodeIdentifierWithPredicates arg) {
169             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = Builders
170                     .mapEntryBuilder().withNodeIdentifier(arg);
171             for (final Entry<QName, Object> keyValue : arg.entrySet()) {
172                 builder.addChild(Builders.leafBuilder()
173                         .withNodeIdentifier(NodeIdentifier.create(keyValue.getKey())).withValue(keyValue.getValue())
174                         .build());
175             }
176             return builder;
177         }
178
179         private static NodeIdentifierWithPredicates reorderPredicates(final List<QName> keys,
180                 final NodeIdentifierWithPredicates arg) {
181             if (Iterables.elementsEqual(keys, arg.keySet())) {
182                 // Iteration order matches key order, reuse the identifier
183                 return arg;
184             }
185
186             // We care about iteration order here!
187             final LinkedHashMap<QName, Object> map = Maps.newLinkedHashMapWithExpectedSize(arg.size());
188             for (QName qname : keys) {
189                 final Object value = arg.getValue(qname);
190                 if (value != null) {
191                     map.put(qname, value);
192                 }
193             }
194             if (map.size() < arg.size()) {
195                 // Okay, this should not happen, but let's handle that anyway
196                 LOG.debug("Extra predicates in {} while expecting {}", arg, keys);
197                 for (Entry<QName, Object> entry : arg.entrySet()) {
198                     map.putIfAbsent(entry.getKey(), entry.getValue());
199                 }
200             }
201
202             // This copy retains iteration order and since we have more than one argument, it should always be
203             // and ImmutableOffsetMap -- which is guaranteed to be taken as-is
204             final Map<QName, Object> copy = ImmutableOffsetMap.orderedCopyOf(map);
205             verify(copy instanceof ImmutableOffsetMap);
206             return NodeIdentifierWithPredicates.of(arg.getNodeType(), (ImmutableOffsetMap<QName, Object>) copy);
207         }
208     }
209
210     static final class UnkeyedListItemNormalization
211             extends DataContainerNormalizationOperation<NodeIdentifier, ListSchemaNode> {
212         UnkeyedListItemNormalization(final ListSchemaNode schema) {
213             super(NodeIdentifier.create(schema.getQName()), schema);
214         }
215
216         @Override
217         DataContainerNodeBuilder<NodeIdentifier, UnkeyedListEntryNode> createBuilder(
218                 final PathArgument compositeNode) {
219             return Builders.unkeyedListEntryBuilder().withNodeIdentifier(getIdentifier());
220         }
221
222         @Override
223         boolean isMixin() {
224             return false;
225         }
226     }
227
228     static final class ContainerTransformation
229             extends DataContainerNormalizationOperation<NodeIdentifier, ContainerLike> {
230         ContainerTransformation(final ContainerLike schema) {
231             super(NodeIdentifier.create(schema.getQName()), schema);
232         }
233
234         @Override
235         DataContainerNodeBuilder<NodeIdentifier, ContainerNode> createBuilder(final PathArgument compositeNode) {
236             return Builders.containerBuilder().withNodeIdentifier(getIdentifier());
237         }
238
239         @Override
240         boolean isMixin() {
241             return false;
242         }
243     }
244
245     static final class OrderedLeafListMixinNormalization extends UnorderedLeafListMixinNormalization {
246         OrderedLeafListMixinNormalization(final LeafListSchemaNode potential) {
247             super(potential);
248         }
249
250         @Override
251         ListNodeBuilder<?, ?> createBuilder(final PathArgument compositeNode) {
252             return Builders.orderedLeafSetBuilder().withNodeIdentifier(getIdentifier());
253         }
254     }
255
256     static class UnorderedLeafListMixinNormalization extends InstanceIdToCompositeNodes<NodeIdentifier> {
257         private final InstanceIdToNodes<?> innerOp;
258
259         UnorderedLeafListMixinNormalization(final LeafListSchemaNode potential) {
260             super(NodeIdentifier.create(potential.getQName()));
261             innerOp = new InstanceIdToSimpleNodes.LeafListEntryNormalization(potential);
262         }
263
264         @Override
265         ListNodeBuilder<?, ?> createBuilder(final PathArgument compositeNode) {
266             return Builders.leafSetBuilder().withNodeIdentifier(getIdentifier());
267         }
268
269         @Override
270         final InstanceIdToNodes<?> getChild(final PathArgument child) {
271             return child instanceof NodeWithValue ? innerOp : null;
272         }
273
274         @Override
275         final boolean isMixin() {
276             return true;
277         }
278     }
279
280     static final class AugmentationNormalization
281             extends DataContainerNormalizationOperation<AugmentationIdentifier, AugmentationSchemaNode> {
282         AugmentationNormalization(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
283             super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
284                     EffectiveAugmentationSchema.create(augmentation, schema));
285         }
286
287         @Override
288         DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> createBuilder(
289                 final PathArgument compositeNode) {
290             return Builders.augmentationBuilder().withNodeIdentifier(getIdentifier());
291         }
292
293         @Override
294         boolean isMixin() {
295             return true;
296         }
297     }
298
299     static class UnorderedMapMixinNormalization extends InstanceIdToCompositeNodes<NodeIdentifier> {
300         private final MapEntryNormalization innerNode;
301
302         UnorderedMapMixinNormalization(final ListSchemaNode list) {
303             super(NodeIdentifier.create(list.getQName()));
304             this.innerNode = new MapEntryNormalization(list);
305         }
306
307         @Override
308         CollectionNodeBuilder<MapEntryNode, ? extends MapNode> createBuilder(final PathArgument compositeNode) {
309             return Builders.mapBuilder().withNodeIdentifier(getIdentifier());
310         }
311
312         @Override
313         final InstanceIdToNodes<?> getChild(final PathArgument child) {
314             return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
315         }
316
317         @Override
318         final boolean isMixin() {
319             return true;
320         }
321     }
322
323     static final class OrderedMapMixinNormalization extends UnorderedMapMixinNormalization {
324         OrderedMapMixinNormalization(final ListSchemaNode list) {
325             super(list);
326         }
327
328         @Override
329         CollectionNodeBuilder<MapEntryNode, OrderedMapNode> createBuilder(final PathArgument compositeNode) {
330             return Builders.orderedMapBuilder().withNodeIdentifier(getIdentifier());
331         }
332     }
333
334     static final class ChoiceNodeNormalization extends InstanceIdToCompositeNodes<NodeIdentifier> {
335         private final ImmutableMap<PathArgument, InstanceIdToNodes<?>> byArg;
336
337         ChoiceNodeNormalization(final ChoiceSchemaNode schema) {
338             super(NodeIdentifier.create(schema.getQName()));
339             final ImmutableMap.Builder<PathArgument, InstanceIdToNodes<?>> byArgBuilder = ImmutableMap.builder();
340
341             for (final CaseSchemaNode caze : schema.getCases()) {
342                 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
343                     final InstanceIdToNodes<?> childOp = fromDataSchemaNode(cazeChild);
344                     byArgBuilder.put(childOp.getIdentifier(), childOp);
345                 }
346             }
347             byArg = byArgBuilder.build();
348         }
349
350         @Override
351         InstanceIdToNodes<?> getChild(final PathArgument child) {
352             return byArg.get(child);
353         }
354
355         @Override
356         DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> createBuilder(final PathArgument compositeNode) {
357             return Builders.choiceBuilder().withNodeIdentifier(getIdentifier());
358         }
359
360         @Override
361         boolean isMixin() {
362             return true;
363         }
364     }
365 }