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