38dc78ff8ec5c3ec6efd7f958da9f691f3bde52e
[yangtools.git] / data / yang-data-util / src / main / java / org / opendaylight / yangtools / yang / data / util / DataSchemaContextNode.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.util;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableSet;
13 import com.google.common.collect.Iterables;
14 import java.util.Optional;
15 import java.util.Set;
16 import java.util.stream.Collectors;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.opendaylight.yangtools.concepts.AbstractSimpleIdentifiable;
20 import org.opendaylight.yangtools.yang.common.QName;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
24 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
30 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
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.ContainerLike;
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.SchemaInferenceStack;
43
44 /**
45  * Schema derived data providing necessary information for mapping between
46  * {@link org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode} and serialization format defined in RFC6020,
47  * since the mapping is not one-to-one.
48  *
49  * @param <T> Path Argument type
50  */
51 // FIXME: YANGTOOLS-1413: this really should be an interface, as there is a ton of non-trivial composition going on:
52 //        - getDataSchemaNode() cannot return AugmentationSchemaNode, which is guarded by isMixinNode() and users should
53 //          not be touching mixin details anyway
54 //        - the idea of getIdentifier() is wrong -- if does the wrong thing for items of leaf-list and keyed list
55 //          because those identifiers need a value. We also do not expect users to store the results in a Map, which
56 //          defeats the idea of Identifiable
57 //        - the generic argument is really an implementation detail and we really would like to also make dataSchemaNode
58 //          (or rather: underlying SchemaNode) an argument. Both of these are not something users can influence and
59 //          therefore we should not burden them with <?> on each reference to this class
60 public abstract class DataSchemaContextNode<T extends PathArgument> extends AbstractSimpleIdentifiable<T> {
61     // FIXME: this can be null only for AugmentationContextNode and in that case the interior part is handled by a
62     //        separate field in DataContainerContextNode. We need to re-examine our base interface class hierarchy
63     //        so that the underlying (effective in augment's case) SchemaNode is always available.
64     private final DataSchemaNode dataSchemaNode;
65
66     DataSchemaContextNode(final T identifier, final DataSchemaNode schema) {
67         super(identifier);
68         this.dataSchemaNode = schema;
69     }
70
71     /**
72      * This node is a {@link NormalizedNode} intermediate, not represented in RFC7950 XML encoding. This is typically
73      * one of
74      * <ul>
75      *   <li>{@link AugmentationNode} backed by an {@link AugmentationSchemaNode}, or</li>
76      *   <li>{@link ChoiceNode} backed by a {@link ChoiceSchemaNode}, or</li>
77      *   <li>{@link LeafSetNode} backed by a {@link LeafListSchemaNode}, or</li>
78      *   <li>{@link MapNode} backed by a {@link ListSchemaNode} with a non-empty
79      *       {@link ListSchemaNode#getKeyDefinition()}, or</li>
80      *   <li>{@link UnkeyedListNode} backed by a {@link ListSchemaNode} with an empty
81      *       {@link ListSchemaNode#getKeyDefinition()}</li>
82      * </ul>
83      *
84      * @return {@code} false if this node corresponds to an XML element, or {@code true} if it is an encapsulation node.
85      */
86     public boolean isMixin() {
87         return false;
88     }
89
90     // FIXME: document this method
91     public boolean isKeyedEntry() {
92         return false;
93     }
94
95     // FIXME: this is counter-intuitive: anydata/anyxml are considered non-leaf. This method needs a better name and
96     //        a proper description.
97     public abstract boolean isLeaf();
98
99     protected Set<QName> getQNameIdentifiers() {
100         return ImmutableSet.of(getIdentifier().getNodeType());
101     }
102
103     /**
104      * Find a child node identifier by its {@link PathArgument}.
105      *
106      * @param child Child path argument
107      * @return A child node, or null if not found
108      */
109     // FIXME: document PathArgument type mismatch
110     public abstract @Nullable DataSchemaContextNode<?> getChild(PathArgument child);
111
112     /**
113      * Find a child node identifier by its {code data tree} {@link QName}. This method returns intermediate nodes
114      * significant from {@link YangInstanceIdentifier} hierarchy of {@link PathArgument}s. If the returned node
115      * indicates {@code true} via {@link #isMixin()}, it represents a {@link NormalizedNode} encapsulation which is
116      * not visible in RFC7950 XML encoding, and a further call to this method with the same {@code child} argument will
117      * provide the next step.
118      *
119      * @param child Child data tree QName
120      * @return A child node, or null if not found
121      */
122     // FIXME: document child == null
123     public abstract @Nullable DataSchemaContextNode<?> getChild(QName child);
124
125     /**
126      * Attempt to enter a child {@link DataSchemaContextNode} towards the {@link DataSchemaNode} child identified by
127      * specified {@code data tree} {@link QName}, adjusting provided {@code stack} with inference steps corresponding to
128      * the transition to the returned node. The stack is expected to be correctly pointing at this node's schema,
129      * otherwise the results of this method are undefined.
130      *
131      * @param stack {@link SchemaInferenceStack} to update
132      * @param child Child QName
133      * @return A DataSchemaContextNode on the path towards the specified child
134      * @throws NullPointerException if any argument is {@code null}
135      */
136     public final @Nullable DataSchemaContextNode<?> enterChild(final SchemaInferenceStack stack, final QName child) {
137         return enterChild(requireNonNull(child), requireNonNull(stack));
138     }
139
140     /**
141      * Attempt to enter a child {@link DataSchemaContextNode} towards the {@link DataSchemaNode} child identified by
142      * specified {@link PathArgument}, adjusting provided {@code stack} with inference steps corresponding to
143      * the transition to the returned node. The stack is expected to be correctly pointing at this node's schema,
144      * otherwise the results of this method are undefined.
145      *
146      * @param stack {@link SchemaInferenceStack} to update
147      * @param child Child path argument
148      * @return A DataSchemaContextNode for the specified child
149      * @throws NullPointerException if any argument is {@code null}
150      */
151     public final @Nullable DataSchemaContextNode<?> enterChild(final SchemaInferenceStack stack,
152             final PathArgument child) {
153         return enterChild(requireNonNull(child), requireNonNull(stack));
154     }
155
156     abstract @Nullable DataSchemaContextNode<?> enterChild(@NonNull QName child, @NonNull SchemaInferenceStack stack);
157
158     abstract @Nullable DataSchemaContextNode<?> enterChild(@NonNull PathArgument child,
159         @NonNull SchemaInferenceStack stack);
160
161     /**
162      * Push this node into specified {@link SchemaInferenceStack}.
163      *
164      * @param stack {@link SchemaInferenceStack}
165      */
166     // FIXME: make this method package-private once the protected constructor is gone
167     protected void pushToStack(final @NonNull SchemaInferenceStack stack) {
168         // Accurate for most subclasses
169         stack.enterSchemaTree(getIdentifier().getNodeType());
170     }
171
172     // FIXME: final
173     public @Nullable DataSchemaNode getDataSchemaNode() {
174         return dataSchemaNode;
175     }
176
177     /**
178      * Find a child node as identified by a {@link YangInstanceIdentifier} relative to this node.
179      *
180      * @param path Path towards the child node
181      * @return Child node if present, or empty when corresponding child is not found.
182      * @throws NullPointerException if {@code path} is null
183      */
184     public final @NonNull Optional<@NonNull DataSchemaContextNode<?>> findChild(
185             final @NonNull YangInstanceIdentifier path) {
186         DataSchemaContextNode<?> currentOp = this;
187         for (PathArgument arg : path.getPathArguments()) {
188             currentOp = currentOp.getChild(arg);
189             if (currentOp == null) {
190                 return Optional.empty();
191             }
192         }
193         return Optional.of(currentOp);
194     }
195
196     static DataSchemaNode findChildSchemaNode(final DataNodeContainer parent, final QName child) {
197         final DataSchemaNode potential = parent.dataChildByName(child);
198         return potential == null ? findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child)
199                 : potential;
200     }
201
202     static DataSchemaContextNode<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
203         final DataSchemaNode result = findChildSchemaNode(schema, child);
204         // We try to look up if this node was added by augmentation
205         if (result != null && schema instanceof DataSchemaNode && result.isAugmenting()) {
206             return fromAugmentation(schema, (AugmentationTarget) schema, result);
207         }
208         return lenientOf(result);
209     }
210
211     // FIXME: this looks like it should be a Predicate on a stream with findFirst()
212     private static ChoiceSchemaNode findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
213         for (ChoiceSchemaNode choice : choices) {
214             // FIXME: this looks weird: what are we looking for again?
215             for (CaseSchemaNode caze : choice.getCases()) {
216                 if (findChildSchemaNode(caze, child) != null) {
217                     return choice;
218                 }
219             }
220         }
221         return null;
222     }
223
224     /**
225      * Create AugmentationIdentifier from an AugmentationSchemaNode.
226      *
227      * @param schema Augmentation schema
228      * @return AugmentationIdentifier for the schema
229      * @throws NullPointerException if {@code schema} is null
230      */
231     public static AugmentationIdentifier augmentationIdentifierFrom(final AugmentationSchemaNode schema) {
232         return new AugmentationIdentifier(schema.getChildNodes().stream().map(DataSchemaNode::getQName)
233             .collect(Collectors.toSet()));
234     }
235
236     static @NonNull DataSchemaContextNode<?> of(final @NonNull DataSchemaNode schema) {
237         if (schema instanceof ContainerLike containerLike) {
238             return new ContainerContextNode(containerLike);
239         } else if (schema instanceof ListSchemaNode list) {
240             return fromListSchemaNode(list);
241         } else if (schema instanceof LeafSchemaNode leaf) {
242             return new LeafContextNode(leaf);
243         } else if (schema instanceof ChoiceSchemaNode choice) {
244             return new ChoiceNodeContextNode(choice);
245         } else if (schema instanceof LeafListSchemaNode leafList) {
246             return fromLeafListSchemaNode(leafList);
247         } else if (schema instanceof AnydataSchemaNode anydata) {
248             return new AnydataContextNode(anydata);
249         } else if (schema instanceof AnyxmlSchemaNode anyxml) {
250             return new AnyXmlContextNode(anyxml);
251         } else {
252             throw new IllegalStateException("Unhandled schema " + schema);
253         }
254     }
255
256     // FIXME: do we tolerate null argument? do we tolerate unknown subclasses?
257     static @Nullable DataSchemaContextNode<?> lenientOf(final @Nullable DataSchemaNode schema) {
258         if (schema instanceof ContainerLike containerLike) {
259             return new ContainerContextNode(containerLike);
260         } else if (schema instanceof ListSchemaNode list) {
261             return fromListSchemaNode(list);
262         } else if (schema instanceof LeafSchemaNode leaf) {
263             return new LeafContextNode(leaf);
264         } else if (schema instanceof ChoiceSchemaNode choice) {
265             return new ChoiceNodeContextNode(choice);
266         } else if (schema instanceof LeafListSchemaNode leafList) {
267             return fromLeafListSchemaNode(leafList);
268         } else if (schema instanceof AnydataSchemaNode anydata) {
269             return new AnydataContextNode(anydata);
270         } else if (schema instanceof AnyxmlSchemaNode anyxml) {
271             return new AnyXmlContextNode(anyxml);
272         } else {
273             return null;
274         }
275     }
276
277     /**
278      * Returns a DataContextNodeOperation for provided child node
279      *
280      * <p>
281      * If supplied child is added by Augmentation this operation returns a DataSchemaContextNode for augmentation,
282      * otherwise returns a DataSchemaContextNode for child as call for {@link #lenientOf(DataSchemaNode)}.
283      */
284     static @Nullable DataSchemaContextNode<?> fromAugmentation(final DataNodeContainer parent,
285             final AugmentationTarget parentAug, final DataSchemaNode child) {
286         for (AugmentationSchemaNode aug : parentAug.getAvailableAugmentations()) {
287             if (aug.dataChildByName(child.getQName()) != null) {
288                 return new AugmentationContextNode(aug, parent);
289             }
290         }
291         return lenientOf(child);
292     }
293
294     private static @NonNull DataSchemaContextNode<?> fromListSchemaNode(final ListSchemaNode potential) {
295         var keyDefinition = potential.getKeyDefinition();
296         if (keyDefinition.isEmpty()) {
297             return new UnkeyedListMixinContextNode(potential);
298         } else if (potential.isUserOrdered()) {
299             return new OrderedMapMixinContextNode(potential);
300         } else {
301             return new UnorderedMapMixinContextNode(potential);
302         }
303     }
304
305     private static @NonNull DataSchemaContextNode<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
306         if (potential.isUserOrdered()) {
307             return new OrderedLeafListMixinContextNode(potential);
308         }
309         return new UnorderedLeafListMixinContextNode(potential);
310     }
311 }