Fix AbstractValueNodeModificationStrategy message typos
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / SchemaUtils.java
1 /*
2  * Copyright (c) 2013 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.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.collect.Collections2;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableSet;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.LinkedHashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Set;
26 import javax.annotation.Nullable;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.common.Revision;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
32 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
33 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
35 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
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.NotificationNodeContainer;
40 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
41 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
42 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
44
45 public final class SchemaUtils {
46     private SchemaUtils() {
47         throw new UnsupportedOperationException();
48     }
49
50     /**
51      * Find the first schema with specified QName.
52      *
53      * @param qname schema node to find
54      * @param dataSchemaNode Iterable of schemaNodes to look through
55      * @return schema node with newest revision or absent if no schema node with matching qname is found
56      */
57     public static Optional<DataSchemaNode> findFirstSchema(final QName qname,
58             final Iterable<DataSchemaNode> dataSchemaNode) {
59         DataSchemaNode schema = null;
60         if (dataSchemaNode != null && qname != null) {
61             for (final DataSchemaNode dsn : dataSchemaNode) {
62                 if (qname.isEqualWithoutRevision(dsn.getQName())) {
63                     if (schema == null || Revision.compare(schema.getQName().getRevision(),
64                         dsn.getQName().getRevision()) < 0) {
65                         schema = dsn;
66                     }
67                 } else if (dsn instanceof ChoiceSchemaNode) {
68                     for (final CaseSchemaNode choiceCase : ((ChoiceSchemaNode) dsn).getCases().values()) {
69
70                         final DataSchemaNode dataChildByName = choiceCase.getDataChildByName(qname);
71                         if (dataChildByName != null) {
72                             return Optional.of(dataChildByName);
73                         }
74                         final Optional<DataSchemaNode> foundDsn = findFirstSchema(qname, choiceCase.getChildNodes());
75                         if (foundDsn.isPresent()) {
76                             return foundDsn;
77                         }
78                     }
79                 }
80             }
81         }
82         return Optional.ofNullable(schema);
83     }
84
85     /**
86      * Find child schema node identified by its QName within a provided schema node.
87      *
88      * @param schema schema for parent node - search root
89      * @param qname qname(with or without a revision) of a child node to be found in the parent schema
90      * @return found schema node
91      * @throws java.lang.IllegalStateException if the child was not found in parent schema node
92      */
93     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname) {
94         // Try to find child schema node directly, but use a fallback that compares QNames without revisions
95         // and auto-expands choices
96         final DataSchemaNode dataChildByName = schema.getDataChildByName(qname);
97         return dataChildByName == null ? findSchemaForChild(schema, qname, schema.getChildNodes()) : dataChildByName;
98     }
99
100     @Nullable
101     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname,
102             final boolean strictMode) {
103         if (strictMode) {
104             return findSchemaForChild(schema, qname);
105         }
106
107         final Optional<DataSchemaNode> childSchemaOptional = findFirstSchema(qname, schema.getChildNodes());
108         if (!childSchemaOptional.isPresent()) {
109             return null;
110         }
111         return childSchemaOptional.get();
112     }
113
114     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname,
115             final Iterable<DataSchemaNode> childNodes) {
116         final Optional<DataSchemaNode> childSchema = findFirstSchema(qname, childNodes);
117         checkState(childSchema.isPresent(), "Unknown child(ren) node(s) detected, identified by: %s, in: %s", qname,
118             schema);
119         return childSchema.get();
120     }
121
122     public static DataSchemaNode findSchemaForChild(final ChoiceSchemaNode schema, final QName childPartialQName) {
123         for (final CaseSchemaNode choiceCaseNode : schema.getCases().values()) {
124             final Optional<DataSchemaNode> childSchema = findFirstSchema(childPartialQName,
125                 choiceCaseNode.getChildNodes());
126             if (childSchema.isPresent()) {
127                 return childSchema.get();
128             }
129         }
130
131
132         throw new IllegalStateException(String.format("Unknown child(ren) node(s) detected, identified by: %s, in: %s",
133                 childPartialQName, schema));
134     }
135
136     public static AugmentationSchemaNode findSchemaForAugment(final AugmentationTarget schema,
137             final Set<QName> qnames) {
138         final Optional<AugmentationSchemaNode> schemaForAugment = findAugment(schema, qnames);
139         checkState(schemaForAugment.isPresent(), "Unknown augmentation node detected, identified by: %s, in: %s",
140             qnames, schema);
141         return schemaForAugment.get();
142     }
143
144     public static AugmentationSchemaNode findSchemaForAugment(final ChoiceSchemaNode schema, final Set<QName> qnames) {
145         for (final CaseSchemaNode choiceCaseNode : schema.getCases().values()) {
146             final Optional<AugmentationSchemaNode> schemaForAugment = findAugment(choiceCaseNode, qnames);
147             if (schemaForAugment.isPresent()) {
148                 return schemaForAugment.get();
149             }
150         }
151
152         throw new IllegalStateException(String.format("Unknown augmentation node detected, identified by: %s, in: %s",
153             qnames, schema));
154     }
155
156     private static Optional<AugmentationSchemaNode> findAugment(final AugmentationTarget schema,
157             final Set<QName> qnames) {
158         for (final AugmentationSchemaNode augment : schema.getAvailableAugmentations()) {
159             final Set<QName> qNamesFromAugment = ImmutableSet.copyOf(Collections2.transform(augment.getChildNodes(),
160                 DataSchemaNode::getQName));
161
162             if (qNamesFromAugment.equals(qnames)) {
163                 return Optional.of(augment);
164             }
165         }
166
167         return Optional.empty();
168     }
169
170     /**
171      * Recursively find all child nodes that come from choices.
172      *
173      * @param schema schema
174      * @return Map with all child nodes, to their most top augmentation
175      */
176     public static Map<QName, ChoiceSchemaNode> mapChildElementsFromChoices(final DataNodeContainer schema) {
177         return mapChildElementsFromChoices(schema, schema.getChildNodes());
178     }
179
180     private static Map<QName, ChoiceSchemaNode> mapChildElementsFromChoices(final DataNodeContainer schema,
181             final Iterable<DataSchemaNode> childNodes) {
182         final Map<QName, ChoiceSchemaNode> mappedChoices = new LinkedHashMap<>();
183
184         for (final DataSchemaNode childSchema : childNodes) {
185             if (childSchema instanceof ChoiceSchemaNode) {
186
187                 if (isFromAugment(schema, childSchema)) {
188                     continue;
189                 }
190
191                 for (final CaseSchemaNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases().values()) {
192                     for (final QName qname : getChildNodesRecursive(choiceCaseNode)) {
193                         mappedChoices.put(qname, (ChoiceSchemaNode) childSchema);
194                     }
195                 }
196             }
197         }
198
199         return mappedChoices;
200     }
201
202     private static boolean isFromAugment(final DataNodeContainer schema, final DataSchemaNode childSchema) {
203         if (!(schema instanceof AugmentationTarget)) {
204             return false;
205         }
206
207         for (final AugmentationSchemaNode augmentation : ((AugmentationTarget) schema).getAvailableAugmentations()) {
208             if (augmentation.getDataChildByName(childSchema.getQName()) != null) {
209                 return true;
210             }
211         }
212
213         return false;
214     }
215
216     /**
217      * Recursively find all child nodes that come from augmentations.
218      *
219      * @param schema schema
220      * @return Map with all child nodes, to their most top augmentation
221      */
222     public static Map<QName, AugmentationSchemaNode> mapChildElementsFromAugments(final AugmentationTarget schema) {
223
224         final Map<QName, AugmentationSchemaNode> childNodesToAugmentation = new LinkedHashMap<>();
225
226         // Find QNames of augmented child nodes
227         final Map<QName, AugmentationSchemaNode> augments = new HashMap<>();
228         for (final AugmentationSchemaNode augmentationSchema : schema.getAvailableAugmentations()) {
229             for (final DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) {
230                 augments.put(dataSchemaNode.getQName(), augmentationSchema);
231             }
232         }
233
234         // Augmented nodes have to be looked up directly in augmentationTarget
235         // because nodes from augment do not contain nodes from other augmentations
236         if (schema instanceof DataNodeContainer) {
237
238             for (final DataSchemaNode child : ((DataNodeContainer) schema).getChildNodes()) {
239                 // If is not augmented child, continue
240                 if (!augments.containsKey(child.getQName())) {
241                     continue;
242                 }
243
244                 final AugmentationSchemaNode mostTopAugmentation = augments.get(child.getQName());
245
246                 // recursively add all child nodes in case of augment, case and choice
247                 if (child instanceof AugmentationSchemaNode || child instanceof CaseSchemaNode) {
248                     for (final QName qname : getChildNodesRecursive((DataNodeContainer) child)) {
249                         childNodesToAugmentation.put(qname, mostTopAugmentation);
250                     }
251                 } else if (child instanceof ChoiceSchemaNode) {
252                     for (final CaseSchemaNode choiceCaseNode : ((ChoiceSchemaNode) child).getCases().values()) {
253                         for (final QName qname : getChildNodesRecursive(choiceCaseNode)) {
254                             childNodesToAugmentation.put(qname, mostTopAugmentation);
255                         }
256                     }
257                 } else {
258                     childNodesToAugmentation.put(child.getQName(), mostTopAugmentation);
259                 }
260             }
261         }
262
263         // Choice Node has to map child nodes from all its cases
264         if (schema instanceof ChoiceSchemaNode) {
265             for (final CaseSchemaNode choiceCaseNode : ((ChoiceSchemaNode) schema).getCases().values()) {
266                 if (!augments.containsKey(choiceCaseNode.getQName())) {
267                     continue;
268                 }
269
270                 for (final QName qname : getChildNodesRecursive(choiceCaseNode)) {
271                     childNodesToAugmentation.put(qname, augments.get(choiceCaseNode.getQName()));
272                 }
273             }
274         }
275
276         return childNodesToAugmentation;
277     }
278
279     /**
280      * Recursively list all child nodes. In case of choice, augment and cases, step in.
281      *
282      * @param nodeContainer node container
283      * @return set of QNames
284      */
285     public static Set<QName> getChildNodesRecursive(final DataNodeContainer nodeContainer) {
286         final Set<QName> allChildNodes = new HashSet<>();
287
288         for (final DataSchemaNode childSchema : nodeContainer.getChildNodes()) {
289             if (childSchema instanceof ChoiceSchemaNode) {
290                 for (final CaseSchemaNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases().values()) {
291                     allChildNodes.addAll(getChildNodesRecursive(choiceCaseNode));
292                 }
293             } else if (childSchema instanceof AugmentationSchemaNode || childSchema instanceof CaseSchemaNode) {
294                 allChildNodes.addAll(getChildNodesRecursive((DataNodeContainer) childSchema));
295             } else {
296                 allChildNodes.add(childSchema.getQName());
297             }
298         }
299
300         return allChildNodes;
301     }
302
303     /**
304      * Retrieves real schemas for augmented child node.
305      *
306      * <p>
307      * Schema of the same child node from augment, and directly from target is not the same.
308      * Schema of child node from augment is incomplete, therefore its useless for XML/NormalizedNode translation.
309      *
310      * @param targetSchema target schema
311      * @param augmentSchema augment schema
312      * @return set of nodes
313      */
314     public static Set<DataSchemaNode> getRealSchemasForAugment(final AugmentationTarget targetSchema,
315             final AugmentationSchemaNode augmentSchema) {
316         if (!targetSchema.getAvailableAugmentations().contains(augmentSchema)) {
317             return Collections.emptySet();
318         }
319         if (targetSchema instanceof DataNodeContainer) {
320             return getRealSchemasForAugment((DataNodeContainer)targetSchema, augmentSchema);
321         }
322         final Set<DataSchemaNode> realChildNodes = new HashSet<>();
323         if (targetSchema instanceof ChoiceSchemaNode) {
324             for (final DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) {
325                 for (final CaseSchemaNode choiceCaseNode : ((ChoiceSchemaNode) targetSchema).getCases().values()) {
326                     if (getChildNodesRecursive(choiceCaseNode).contains(dataSchemaNode.getQName())) {
327                         realChildNodes.add(choiceCaseNode.getDataChildByName(dataSchemaNode.getQName()));
328                     }
329                 }
330             }
331         }
332
333         return realChildNodes;
334     }
335
336     public static Set<DataSchemaNode> getRealSchemasForAugment(final DataNodeContainer targetSchema,
337             final AugmentationSchemaNode augmentSchema) {
338         final Set<DataSchemaNode> realChildNodes = new HashSet<>();
339         for (final DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) {
340             final DataSchemaNode realChild = targetSchema.getDataChildByName(dataSchemaNode.getQName());
341             realChildNodes.add(realChild);
342         }
343         return realChildNodes;
344     }
345
346     public static Optional<CaseSchemaNode> detectCase(final ChoiceSchemaNode schema,
347             final DataContainerChild<?, ?> child) {
348         for (final CaseSchemaNode choiceCaseNode : schema.getCases().values()) {
349             if (child instanceof AugmentationNode
350                     && belongsToCaseAugment(choiceCaseNode, (AugmentationIdentifier) child.getIdentifier())) {
351                 return Optional.of(choiceCaseNode);
352             } else if (choiceCaseNode.getDataChildByName(child.getNodeType()) != null) {
353                 return Optional.of(choiceCaseNode);
354             }
355         }
356
357         return Optional.empty();
358     }
359
360     public static boolean belongsToCaseAugment(final CaseSchemaNode caseNode,
361             final AugmentationIdentifier childToProcess) {
362         for (final AugmentationSchemaNode augmentationSchema : caseNode.getAvailableAugmentations()) {
363
364             final Set<QName> currentAugmentChildNodes = new HashSet<>();
365             for (final DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) {
366                 currentAugmentChildNodes.add(dataSchemaNode.getQName());
367             }
368
369             if (childToProcess.getPossibleChildNames().equals(currentAugmentChildNodes)) {
370                 return true;
371             }
372         }
373
374         return false;
375     }
376
377     /**
378      * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
379      * node is found then it is returned, else null.
380      *
381      * @param parent parent node
382      * @param child child node
383      * @return augmentation schema
384      */
385     public static AugmentationSchemaNode findCorrespondingAugment(final DataSchemaNode parent,
386             final DataSchemaNode child) {
387         if (!(parent instanceof AugmentationTarget) || parent instanceof ChoiceSchemaNode) {
388             return null;
389         }
390
391         for (final AugmentationSchemaNode augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
392             final DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
393             if (childInAugmentation != null) {
394                 return augmentation;
395             }
396         }
397         return null;
398     }
399
400     public static AugmentationIdentifier getNodeIdentifierForAugmentation(final AugmentationSchemaNode schema) {
401         return new AugmentationIdentifier(ImmutableSet.copyOf(Collections2.transform(schema.getChildNodes(),
402             DataSchemaNode::getQName)));
403     }
404
405     /**
406      * Finds schema node for given path in schema context. This method performs
407      * lookup in the namespace of all leafs, leaf-lists, lists, containers,
408      * choices, rpcs, actions, notifications, anydatas, and anyxmls according to
409      * Rfc6050/Rfc7950 section 6.2.1.
410      *
411      * @param schemaContext
412      *            schema context
413      * @param path
414      *            path
415      * @return schema node on path
416      */
417     public static SchemaNode findDataParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) {
418         SchemaNode current = requireNonNull(schemaContext);
419         for (final QName qname : path.getPathFromRoot()) {
420             current = findDataChildSchemaByQName(current, qname);
421         }
422         return current;
423     }
424
425     /**
426      * Find child data schema node identified by its QName within a provided schema node. This method performs lookup
427      * in the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions, notifications, anydatas
428      * and anyxmls according to RFC6050/RFC7950 section 6.2.1.
429      *
430      * @param node
431      *            schema node
432      * @param qname
433      *            QName
434      * @return data child schema node
435      * @throws IllegalArgumentException
436      *             if the schema node does not allow children
437      */
438     @Nullable
439     public static SchemaNode findDataChildSchemaByQName(final SchemaNode node, final QName qname) {
440         if (node instanceof DataNodeContainer) {
441             SchemaNode child = ((DataNodeContainer) node).getDataChildByName(qname);
442             if (child == null && node instanceof SchemaContext) {
443                 child = tryFind(((SchemaContext) node).getOperations(), qname).orElse(null);
444             }
445             if (child == null && node instanceof NotificationNodeContainer) {
446                 child = tryFind(((NotificationNodeContainer) node).getNotifications(), qname).orElse(null);
447             }
448             if (child == null && node instanceof ActionNodeContainer) {
449                 child = tryFind(((ActionNodeContainer) node).getActions(), qname).orElse(null);
450             }
451
452             return child;
453         }
454         if (node instanceof ChoiceSchemaNode) {
455             return ((ChoiceSchemaNode) node).getCaseNodeByName(qname);
456         }
457         if (node instanceof RpcDefinition) {
458             switch (qname.getLocalName()) {
459                 case "input":
460                     return ((RpcDefinition) node).getInput();
461                 case "output":
462                     return ((RpcDefinition) node).getOutput();
463                 default:
464                     return null;
465             }
466         }
467
468         throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", node));
469     }
470
471     /**
472      * Finds schema node for given path in schema context. This method performs lookup in both the namespace
473      * of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions,
474      * notifications, anydatas and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
475      *
476      * <p>
477      * This method returns collection of SchemaNodes, because name conflicts can occur between the namespace
478      * of groupings and namespace of data nodes. This method finds and collects all schema nodes that matches supplied
479      * SchemaPath and returns them all as collection of schema nodes.
480      *
481      * @param schemaContext
482      *            schema context
483      * @param path
484      *            path
485      * @return collection of schema nodes on path
486      */
487     public static Collection<SchemaNode> findParentSchemaNodesOnPath(final SchemaContext schemaContext,
488             final SchemaPath path) {
489         final Collection<SchemaNode> currentNodes = new ArrayList<>();
490         final Collection<SchemaNode> childNodes = new ArrayList<>();
491         currentNodes.add(requireNonNull(schemaContext));
492         for (final QName qname : path.getPathFromRoot()) {
493             for (final SchemaNode current : currentNodes) {
494                 childNodes.addAll(findChildSchemaNodesByQName(current, qname));
495             }
496             currentNodes.clear();
497             currentNodes.addAll(childNodes);
498             childNodes.clear();
499         }
500
501         return currentNodes;
502     }
503
504     /**
505      * Find child schema node identified by its QName within a provided schema node. This method performs lookup in both
506      * the namespace of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs,
507      * actions, notifications, anydatas and anyxmls according to RFC6050/RFC7950 section 6.2.1.
508      *
509      * <p>
510      * This method returns collection of SchemaNodes, because name conflicts can occur between the namespace
511      * of groupings and namespace of data nodes. This method finds and collects all schema nodes with supplied QName
512      * and returns them all as collection of schema nodes.
513      *
514      * @param node
515      *            schema node
516      * @param qname
517      *            QName
518      * @return collection of child schema nodes
519      * @throws IllegalArgumentException
520      *             if the schema node does not allow children
521      */
522     public static Collection<SchemaNode> findChildSchemaNodesByQName(final SchemaNode node, final QName qname) {
523         final List<SchemaNode> childNodes = new ArrayList<>();
524         final SchemaNode dataNode = findDataChildSchemaByQName(node, qname);
525         if (dataNode != null) {
526             childNodes.add(dataNode);
527         }
528         if (node instanceof DataNodeContainer) {
529             tryFind(((DataNodeContainer) node).getGroupings(), qname).ifPresent(childNodes::add);
530         }
531         return childNodes.isEmpty() ? ImmutableList.of() : ImmutableList.copyOf(childNodes);
532     }
533
534     private static <T extends SchemaNode> Optional<T> tryFind(final Collection<T> nodes, final QName qname) {
535         return nodes.stream().filter(node -> qname.equals(node.getQName())).findFirst();
536     }
537 }