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