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