Rework NormalizedNode type hierarchy
[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, final DataContainerChild child) {
344         for (final CaseSchemaNode choiceCaseNode : schema.getCases()) {
345             if (child instanceof AugmentationNode
346                     && belongsToCaseAugment(choiceCaseNode, (AugmentationIdentifier) child.getIdentifier())
347                     || choiceCaseNode.findDataChildByName(child.getNodeType()).isPresent()) {
348                 return Optional.of(choiceCaseNode);
349             }
350         }
351
352         return Optional.empty();
353     }
354
355     public static boolean belongsToCaseAugment(final CaseSchemaNode caseNode,
356             final AugmentationIdentifier childToProcess) {
357         for (final AugmentationSchemaNode augmentationSchema : caseNode.getAvailableAugmentations()) {
358
359             final Set<QName> currentAugmentChildNodes = new HashSet<>();
360             for (final DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) {
361                 currentAugmentChildNodes.add(dataSchemaNode.getQName());
362             }
363
364             if (childToProcess.getPossibleChildNames().equals(currentAugmentChildNodes)) {
365                 return true;
366             }
367         }
368
369         return false;
370     }
371
372     /**
373      * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
374      * node is found then it is returned, else null.
375      *
376      * @param parent parent node
377      * @param child child node
378      * @return augmentation schema
379      */
380     public static AugmentationSchemaNode findCorrespondingAugment(final DataSchemaNode parent,
381             final DataSchemaNode child) {
382         if (!(parent instanceof AugmentationTarget) || parent instanceof ChoiceSchemaNode) {
383             return null;
384         }
385
386         for (final AugmentationSchemaNode augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
387             final Optional<DataSchemaNode> childInAugmentation = augmentation.findDataChildByName(child.getQName());
388             if (childInAugmentation.isPresent()) {
389                 return augmentation;
390             }
391         }
392         return null;
393     }
394
395     /**
396      * Finds schema node for given path in schema context. This method performs
397      * lookup in the namespace of all leafs, leaf-lists, lists, containers,
398      * choices, rpcs, actions, notifications, anydatas, and anyxmls according to
399      * Rfc6050/Rfc7950 section 6.2.1.
400      *
401      * @param schemaContext
402      *            schema context
403      * @param path
404      *            path
405      * @return schema node on path
406      */
407     public static SchemaNode findDataParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) {
408         SchemaNode current = requireNonNull(schemaContext);
409         for (final QName qname : path.getPathFromRoot()) {
410             current = findDataChildSchemaByQName(current, qname);
411         }
412         return current;
413     }
414
415     /**
416      * Find child data schema node identified by its QName within a provided schema node. This method performs lookup
417      * in the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions, notifications, anydatas
418      * and anyxmls according to RFC6050/RFC7950 section 6.2.1.
419      *
420      * @param node
421      *            schema node
422      * @param qname
423      *            QName
424      * @return data child schema node
425      * @throws IllegalArgumentException
426      *             if the schema node does not allow children
427      */
428     public static @Nullable SchemaNode findDataChildSchemaByQName(final SchemaNode node, final QName qname) {
429         if (node instanceof DataNodeContainer) {
430             SchemaNode child = ((DataNodeContainer) node).getDataChildByName(qname);
431             if (child == null && node instanceof SchemaContext) {
432                 child = tryFind(((SchemaContext) node).getOperations(), qname).orElse(null);
433             }
434             if (child == null && node instanceof NotificationNodeContainer) {
435                 child = tryFind(((NotificationNodeContainer) node).getNotifications(), qname).orElse(null);
436             }
437             if (child == null && node instanceof ActionNodeContainer) {
438                 child = tryFind(((ActionNodeContainer) node).getActions(), qname).orElse(null);
439             }
440
441             return child;
442         }
443         if (node instanceof ChoiceSchemaNode) {
444             return ((ChoiceSchemaNode) node).findCase(qname).orElse(null);
445         }
446         if (node instanceof OperationDefinition) {
447             switch (qname.getLocalName()) {
448                 case "input":
449                     return ((OperationDefinition) node).getInput();
450                 case "output":
451                     return ((OperationDefinition) node).getOutput();
452                 default:
453                     return null;
454             }
455         }
456
457         throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", node));
458     }
459
460     /**
461      * Finds schema node for given path in schema context. This method performs lookup in both the namespace
462      * of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions,
463      * notifications, anydatas and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
464      *
465      * <p>
466      * This method returns collection of SchemaNodes, because name conflicts can occur between the namespace
467      * of groupings and namespace of data nodes. This method finds and collects all schema nodes that matches supplied
468      * SchemaPath and returns them all as collection of schema nodes.
469      *
470      * @param schemaContext
471      *            schema context
472      * @param path
473      *            path
474      * @return collection of schema nodes on path
475      */
476     public static Collection<SchemaNode> findParentSchemaNodesOnPath(final SchemaContext schemaContext,
477             final SchemaPath path) {
478         return findParentSchemaNodesOnPath(schemaContext, path.getPathFromRoot());
479     }
480
481     /**
482      * Finds schema node for given path in schema context. This method performs lookup in both the namespace
483      * of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions,
484      * notifications, anydatas and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
485      *
486      * <p>
487      * This method returns collection of SchemaNodes, because name conflicts can occur between the namespace
488      * of groupings and namespace of data nodes. This method finds and collects all schema nodes that matches supplied
489      * SchemaPath and returns them all as collection of schema nodes.
490      *
491      * @param schemaContext schema context
492      * @param path path
493      * @return collection of schema nodes on path
494      */
495     public static Collection<SchemaNode> findParentSchemaNodesOnPath(final SchemaContext schemaContext,
496             final Iterable<QName> path) {
497         final Collection<SchemaNode> currentNodes = new ArrayList<>();
498         final Collection<SchemaNode> childNodes = new ArrayList<>();
499         currentNodes.add(requireNonNull(schemaContext));
500         for (final QName qname : path) {
501             for (final SchemaNode current : currentNodes) {
502                 childNodes.addAll(findChildSchemaNodesByQName(current, qname));
503             }
504             currentNodes.clear();
505             currentNodes.addAll(childNodes);
506             childNodes.clear();
507         }
508
509         return currentNodes;
510     }
511
512     /**
513      * Find child schema node identified by its QName within a provided schema node. This method performs lookup in both
514      * the namespace of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs,
515      * actions, notifications, anydatas and anyxmls according to RFC6050/RFC7950 section 6.2.1.
516      *
517      * <p>
518      * This method returns collection of SchemaNodes, because name conflicts can occur between the namespace
519      * of groupings and namespace of data nodes. This method finds and collects all schema nodes with supplied QName
520      * and returns them all as collection of schema nodes.
521      *
522      * @param node
523      *            schema node
524      * @param qname
525      *            QName
526      * @return collection of child schema nodes
527      * @throws IllegalArgumentException
528      *             if the schema node does not allow children
529      */
530     public static Collection<SchemaNode> findChildSchemaNodesByQName(final SchemaNode node, final QName qname) {
531         final List<SchemaNode> childNodes = new ArrayList<>();
532         final SchemaNode dataNode = findDataChildSchemaByQName(node, qname);
533         if (dataNode != null) {
534             childNodes.add(dataNode);
535         }
536         if (node instanceof DataNodeContainer) {
537             tryFind(((DataNodeContainer) node).getGroupings(), qname).ifPresent(childNodes::add);
538         }
539         return childNodes.isEmpty() ? ImmutableList.of() : ImmutableList.copyOf(childNodes);
540     }
541
542     private static <T extends SchemaNode> Optional<T> tryFind(final Collection<T> nodes, final QName qname) {
543         return nodes.stream().filter(node -> qname.equals(node.getQName())).findFirst();
544     }
545 }