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