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