Clean up DataSchemaContainerTree
[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 com.google.common.collect.ImmutableSet;
11 import com.google.common.collect.Iterables;
12 import java.util.Optional;
13 import java.util.Set;
14 import java.util.stream.Collectors;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.eclipse.jdt.annotation.Nullable;
17 import org.opendaylight.yangtools.concepts.AbstractSimpleIdentifiable;
18 import org.opendaylight.yangtools.yang.common.QName;
19 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
22 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
23 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
24 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
26 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
29 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
30 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
32 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
36
37 /**
38  * Schema derived data providing necessary information for mapping between
39  * {@link org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode} and serialization format defined in RFC6020,
40  * since the mapping is not one-to-one.
41  *
42  * @param <T> Path Argument type
43  */
44 public abstract class DataSchemaContextNode<T extends PathArgument> extends AbstractSimpleIdentifiable<T> {
45     // FIXME: this can be null only for AugmentationContextNode and in that case the interior part is handled by a
46     //        separate field in DataContainerContextNode. We need to re-examine our base interface class hierarchy
47     //        so that the underlying (effective in augment's case) SchemaNode is always available.
48     private final DataSchemaNode dataSchemaNode;
49
50     DataSchemaContextNode(final T identifier, final DataSchemaNode schema) {
51         super(identifier);
52         this.dataSchemaNode = schema;
53     }
54
55     @Deprecated(forRemoval = true, since = "8.0.2")
56     protected DataSchemaContextNode(final T identifier, final SchemaNode schema) {
57         this(identifier, schema instanceof DataSchemaNode ? (DataSchemaNode) schema : null);
58     }
59
60     // FIXME: document this method
61     public boolean isMixin() {
62         return false;
63     }
64
65     // FIXME: document this method
66     public boolean isKeyedEntry() {
67         return false;
68     }
69
70     // FIXME: this is counter-intuitive: anydata/anyxml are considered non-leaf. This method needs a better name and
71     //        a proper description.
72     public abstract boolean isLeaf();
73
74     protected Set<QName> getQNameIdentifiers() {
75         return ImmutableSet.of(getIdentifier().getNodeType());
76     }
77
78     /**
79      * Find a child node identifier by its {@link PathArgument}.
80      *
81      * @param child Child path argument
82      * @return A child node, or null if not found
83      */
84     // FIXME: document PathArgument type mismatch
85     public abstract @Nullable DataSchemaContextNode<?> getChild(PathArgument child);
86
87     // FIXME: document child == null
88     public abstract @Nullable DataSchemaContextNode<?> getChild(QName child);
89
90     // FIXME: final
91     public @Nullable DataSchemaNode getDataSchemaNode() {
92         return dataSchemaNode;
93     }
94
95     /**
96      * Find a child node as identified by a {@link YangInstanceIdentifier} relative to this node.
97      *
98      * @param path Path towards the child node
99      * @return Child node if present, or empty when corresponding child is not found.
100      * @throws NullPointerException if {@code path} is null
101      */
102     public final @NonNull Optional<@NonNull DataSchemaContextNode<?>> findChild(
103             final @NonNull YangInstanceIdentifier path) {
104         DataSchemaContextNode<?> currentOp = this;
105         for (PathArgument arg : path.getPathArguments()) {
106             currentOp = currentOp.getChild(arg);
107             if (currentOp == null) {
108                 return Optional.empty();
109             }
110         }
111         return Optional.of(currentOp);
112     }
113
114     static DataSchemaNode findChildSchemaNode(final DataNodeContainer parent, final QName child) {
115         final DataSchemaNode potential = parent.dataChildByName(child);
116         return potential == null ? findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child)
117                 : potential;
118     }
119
120     static DataSchemaContextNode<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
121         final DataSchemaNode result = findChildSchemaNode(schema, child);
122         // We try to look up if this node was added by augmentation
123         if (result != null && schema instanceof DataSchemaNode && result.isAugmenting()) {
124             return fromAugmentation(schema, (AugmentationTarget) schema, result);
125         }
126         return lenientOf(result);
127     }
128
129     // FIXME: this looks like it should be a Predicate on a stream with findFirst()
130     private static ChoiceSchemaNode findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
131         for (ChoiceSchemaNode choice : choices) {
132             // FIXME: this looks weird: what are we looking for again?
133             for (CaseSchemaNode caze : choice.getCases()) {
134                 if (findChildSchemaNode(caze, child) != null) {
135                     return choice;
136                 }
137             }
138         }
139         return null;
140     }
141
142     /**
143      * Create AugmentationIdentifier from an AugmentationSchemaNode.
144      *
145      * @param schema Augmentation schema
146      * @return AugmentationIdentifier for the schema
147      * @throws NullPointerException if {@code schema} is null
148      */
149     public static AugmentationIdentifier augmentationIdentifierFrom(final AugmentationSchemaNode schema) {
150         return new AugmentationIdentifier(schema.getChildNodes().stream().map(DataSchemaNode::getQName)
151             .collect(Collectors.toSet()));
152     }
153
154     static @NonNull DataSchemaContextNode<?> of(final @NonNull DataSchemaNode schema) {
155         if (schema instanceof ContainerLike) {
156             return new ContainerContextNode((ContainerLike) schema);
157         } else if (schema instanceof ListSchemaNode) {
158             return fromListSchemaNode((ListSchemaNode) schema);
159         } else if (schema instanceof LeafSchemaNode) {
160             return new LeafContextNode((LeafSchemaNode) schema);
161         } else if (schema instanceof ChoiceSchemaNode) {
162             return new ChoiceNodeContextNode((ChoiceSchemaNode) schema);
163         } else if (schema instanceof LeafListSchemaNode) {
164             return fromLeafListSchemaNode((LeafListSchemaNode) schema);
165         } else if (schema instanceof AnydataSchemaNode) {
166             return new AnydataContextNode((AnydataSchemaNode) schema);
167         } else if (schema instanceof AnyxmlSchemaNode) {
168             return new AnyXmlContextNode((AnyxmlSchemaNode) schema);
169         } else {
170             throw new IllegalStateException("Unhandled schema " + schema);
171         }
172     }
173
174     // FIXME: do we tolerate null argument? do we tolerate unknown subclasses?
175     static @Nullable DataSchemaContextNode<?> lenientOf(final @Nullable DataSchemaNode schema) {
176         if (schema instanceof ContainerLike) {
177             return new ContainerContextNode((ContainerLike) schema);
178         } else if (schema instanceof ListSchemaNode) {
179             return fromListSchemaNode((ListSchemaNode) schema);
180         } else if (schema instanceof LeafSchemaNode) {
181             return new LeafContextNode((LeafSchemaNode) schema);
182         } else if (schema instanceof ChoiceSchemaNode) {
183             return new ChoiceNodeContextNode((ChoiceSchemaNode) schema);
184         } else if (schema instanceof LeafListSchemaNode) {
185             return fromLeafListSchemaNode((LeafListSchemaNode) schema);
186         } else if (schema instanceof AnydataSchemaNode) {
187             return new AnydataContextNode((AnydataSchemaNode) schema);
188         } else if (schema instanceof AnyxmlSchemaNode) {
189             return new AnyXmlContextNode((AnyxmlSchemaNode) schema);
190         } else {
191             return null;
192         }
193     }
194
195     /**
196      * Returns a DataContextNodeOperation for provided child node
197      *
198      * <p>
199      * If supplied child is added by Augmentation this operation returns a DataSchemaContextNode for augmentation,
200      * otherwise returns a DataSchemaContextNode for child as call for {@link #lenientOf(DataSchemaNode)}.
201      */
202     static @Nullable DataSchemaContextNode<?> fromAugmentation(final DataNodeContainer parent,
203             final AugmentationTarget parentAug, final DataSchemaNode child) {
204         for (AugmentationSchemaNode aug : parentAug.getAvailableAugmentations()) {
205             if (aug.findDataChildByName(child.getQName()).isPresent()) {
206                 return new AugmentationContextNode(aug, parent);
207             }
208         }
209         return lenientOf(child);
210     }
211
212     /**
213      * Get a {@link DataSchemaContextNode} for a particular {@link DataSchemaNode}.
214      *
215      * @param potential Backing DataSchemaNode
216      * @return A {@link DataSchemaContextNode}, or null if the input is {@code null} or of unhandled type
217      */
218     @Deprecated(forRemoval = true, since = "8.0.2")
219     public static @Nullable DataSchemaContextNode<?> fromDataSchemaNode(final DataSchemaNode potential) {
220         return lenientOf(potential);
221     }
222
223     private static @NonNull DataSchemaContextNode<?> fromListSchemaNode(final ListSchemaNode potential) {
224         var keyDefinition = potential.getKeyDefinition();
225         if (keyDefinition.isEmpty()) {
226             return new UnkeyedListMixinContextNode(potential);
227         } else if (potential.isUserOrdered()) {
228             return new OrderedMapMixinContextNode(potential);
229         } else {
230             return new UnorderedMapMixinContextNode(potential);
231         }
232     }
233
234     private static @NonNull DataSchemaContextNode<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
235         if (potential.isUserOrdered()) {
236             return new OrderedLeafListMixinContextNode(potential);
237         }
238         return new UnorderedLeafListMixinContextNode(potential);
239     }
240
241     /**
242      * Return a DataSchemaContextNode corresponding to specified {@link EffectiveModelContext}.
243      *
244      * @param ctx EffectiveModelContext
245      * @return A DataSchemaContextNode
246      * @throws NullPointerException if {@code ctx} is null
247      * @deprecated Use {@link DataSchemaContextTree#from(EffectiveModelContext)} and
248      *             {@link DataSchemaContextTree#getRoot()} instead.
249      */
250     @Deprecated(forRemoval = true, since = "8.0.2")
251     public static @NonNull DataSchemaContextNode<?> from(final EffectiveModelContext ctx) {
252         return new ContainerContextNode(ctx);
253     }
254 }