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