Populate data/ hierarchy
[yangtools.git] / data / 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.UnkeyedListEntryNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
43 import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
44 import org.opendaylight.yangtools.yang.data.api.schema.builder.ListNodeBuilder;
45 import org.opendaylight.yangtools.yang.data.api.schema.builder.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().body()));
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     private abstract static class LeafListMixinNormalization extends InstanceIdToCompositeNodes<NodeIdentifier> {
246         private final InstanceIdToNodes<?> innerOp;
247
248         LeafListMixinNormalization(final LeafListSchemaNode potential) {
249             super(NodeIdentifier.create(potential.getQName()));
250             innerOp = new InstanceIdToSimpleNodes.LeafListEntryNormalization(potential);
251         }
252
253         @Override
254         final InstanceIdToNodes<?> getChild(final PathArgument child) {
255             return child instanceof NodeWithValue ? innerOp : null;
256         }
257
258         @Override
259         final boolean isMixin() {
260             return true;
261         }
262     }
263
264     static final class OrderedLeafListMixinNormalization extends LeafListMixinNormalization {
265         OrderedLeafListMixinNormalization(final LeafListSchemaNode potential) {
266             super(potential);
267         }
268
269         @Override
270         ListNodeBuilder<?, ?> createBuilder(final PathArgument compositeNode) {
271             return Builders.orderedLeafSetBuilder().withNodeIdentifier(getIdentifier());
272         }
273     }
274
275     static class UnorderedLeafListMixinNormalization extends LeafListMixinNormalization {
276         UnorderedLeafListMixinNormalization(final LeafListSchemaNode potential) {
277             super(potential);
278         }
279
280         @Override
281         ListNodeBuilder<?, ?> createBuilder(final PathArgument compositeNode) {
282             return Builders.leafSetBuilder().withNodeIdentifier(getIdentifier());
283         }
284     }
285
286     static final class AugmentationNormalization
287             extends DataContainerNormalizationOperation<AugmentationIdentifier, AugmentationSchemaNode> {
288         AugmentationNormalization(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
289             super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
290                     EffectiveAugmentationSchema.create(augmentation, schema));
291         }
292
293         @Override
294         DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> createBuilder(
295                 final PathArgument compositeNode) {
296             return Builders.augmentationBuilder().withNodeIdentifier(getIdentifier());
297         }
298
299         @Override
300         boolean isMixin() {
301             return true;
302         }
303     }
304
305     static class UnorderedMapMixinNormalization extends InstanceIdToCompositeNodes<NodeIdentifier> {
306         private final MapEntryNormalization innerNode;
307
308         UnorderedMapMixinNormalization(final ListSchemaNode list) {
309             super(NodeIdentifier.create(list.getQName()));
310             this.innerNode = new MapEntryNormalization(list);
311         }
312
313         @Override
314         CollectionNodeBuilder<MapEntryNode, ? extends MapNode> createBuilder(final PathArgument compositeNode) {
315             return Builders.mapBuilder().withNodeIdentifier(getIdentifier());
316         }
317
318         @Override
319         final InstanceIdToNodes<?> getChild(final PathArgument child) {
320             return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
321         }
322
323         @Override
324         final boolean isMixin() {
325             return true;
326         }
327     }
328
329     static final class OrderedMapMixinNormalization extends UnorderedMapMixinNormalization {
330         OrderedMapMixinNormalization(final ListSchemaNode list) {
331             super(list);
332         }
333
334         @Override
335         CollectionNodeBuilder<MapEntryNode, UserMapNode> createBuilder(final PathArgument compositeNode) {
336             return Builders.orderedMapBuilder().withNodeIdentifier(getIdentifier());
337         }
338     }
339
340     static final class ChoiceNodeNormalization extends InstanceIdToCompositeNodes<NodeIdentifier> {
341         private final ImmutableMap<PathArgument, InstanceIdToNodes<?>> byArg;
342
343         ChoiceNodeNormalization(final ChoiceSchemaNode schema) {
344             super(NodeIdentifier.create(schema.getQName()));
345             final ImmutableMap.Builder<PathArgument, InstanceIdToNodes<?>> byArgBuilder = ImmutableMap.builder();
346
347             for (final CaseSchemaNode caze : schema.getCases()) {
348                 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
349                     final InstanceIdToNodes<?> childOp = fromDataSchemaNode(cazeChild);
350                     byArgBuilder.put(childOp.getIdentifier(), childOp);
351                 }
352             }
353             byArg = byArgBuilder.build();
354         }
355
356         @Override
357         InstanceIdToNodes<?> getChild(final PathArgument child) {
358             return byArg.get(child);
359         }
360
361         @Override
362         DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> createBuilder(final PathArgument compositeNode) {
363             return Builders.choiceBuilder().withNodeIdentifier(getIdentifier());
364         }
365
366         @Override
367         boolean isMixin() {
368             return true;
369         }
370     }
371 }