BUG-4688: align Optional/nullable Date usage
[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 com.google.common.base.Preconditions;
11 import com.google.common.base.Predicate;
12 import com.google.common.collect.Collections2;
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.common.collect.Iterables;
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.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.AugmentationSchema;
33 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
34 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
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.ModuleIdentifier;
39 import org.opendaylight.yangtools.yang.model.api.NotificationNodeContainer;
40 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
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 || ModuleIdentifier.compareRevisions(schema.getQName().getRevision(),
64                         dsn.getQName().getRevision()) < 0) {
65                         schema = dsn;
66                     }
67                 } else if (dsn instanceof ChoiceSchemaNode) {
68                     for (final ChoiceCaseNode choiceCase : ((ChoiceSchemaNode) dsn).getCases()) {
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     @Nullable
101     public static 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         Preconditions.checkState(childSchema.isPresent(),
118                 "Unknown child(ren) node(s) detected, identified by: %s, in: %s", qname, schema);
119         return childSchema.get();
120     }
121
122     public static DataSchemaNode findSchemaForChild(final ChoiceSchemaNode schema, final QName childPartialQName) {
123         for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) {
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 AugmentationSchema findSchemaForAugment(final AugmentationTarget schema, final Set<QName> qnames) {
137         final Optional<AugmentationSchema> schemaForAugment = findAugment(schema, qnames);
138         Preconditions.checkState(schemaForAugment.isPresent(),
139             "Unknown augmentation node detected, identified by: %s, in: %s", qnames, schema);
140         return schemaForAugment.get();
141     }
142
143     public static AugmentationSchema findSchemaForAugment(final ChoiceSchemaNode schema, final Set<QName> qnames) {
144         Optional<AugmentationSchema> schemaForAugment = Optional.empty();
145
146         for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) {
147             schemaForAugment = findAugment(choiceCaseNode, qnames);
148             if (schemaForAugment.isPresent()) {
149                 break;
150             }
151         }
152
153         Preconditions.checkState(schemaForAugment.isPresent(),
154             "Unknown augmentation node detected, identified by: %s, in: %s", qnames, schema);
155         return schemaForAugment.get();
156     }
157
158     private static Optional<AugmentationSchema> findAugment(final AugmentationTarget schema, final Set<QName> qnames) {
159         for (final AugmentationSchema 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 ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) {
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 AugmentationSchema augmentationSchema : ((AugmentationTarget) schema).getAvailableAugmentations()) {
209             if (augmentationSchema.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, AugmentationSchema> mapChildElementsFromAugments(final AugmentationTarget schema) {
224
225         final Map<QName, AugmentationSchema> childNodesToAugmentation = new LinkedHashMap<>();
226
227         // Find QNames of augmented child nodes
228         final Map<QName, AugmentationSchema> augments = new HashMap<>();
229         for (final AugmentationSchema 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 AugmentationSchema mostTopAugmentation = augments.get(child.getQName());
246
247                 // recursively add all child nodes in case of augment, case and choice
248                 if (child instanceof AugmentationSchema || child instanceof ChoiceCaseNode) {
249                     for (final QName qname : getChildNodesRecursive((DataNodeContainer) child)) {
250                         childNodesToAugmentation.put(qname, mostTopAugmentation);
251                     }
252                 } else if (child instanceof ChoiceSchemaNode) {
253                     for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) child).getCases()) {
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 ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) schema).getCases()) {
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 ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) {
292                     allChildNodes.addAll(getChildNodesRecursive(choiceCaseNode));
293                 }
294             } else if (childSchema instanceof AugmentationSchema || childSchema instanceof ChoiceCaseNode) {
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 AugmentationSchema 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 ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) targetSchema).getCases()) {
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 AugmentationSchema 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<ChoiceCaseNode> detectCase(final ChoiceSchemaNode schema,
348             final DataContainerChild<?, ?> child) {
349         for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) {
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 ChoiceCaseNode caseNode,
362             final AugmentationIdentifier childToProcess) {
363         for (final AugmentationSchema 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 AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent, final DataSchemaNode child) {
387         if (parent instanceof AugmentationTarget && !(parent instanceof ChoiceSchemaNode)) {
388             for (final AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
389                 final DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
390                 if (childInAugmentation != null) {
391                     return augmentation;
392                 }
393             }
394         }
395         return null;
396     }
397
398     public static AugmentationIdentifier getNodeIdentifierForAugmentation(final AugmentationSchema schema) {
399         final Collection<QName> qnames = Collections2.transform(schema.getChildNodes(), DataSchemaNode::getQName);
400         return new AugmentationIdentifier(ImmutableSet.copyOf(qnames));
401     }
402
403     /**
404      * Finds schema node for given path in schema context. This method performs
405      * lookup in the namespace of all leafs, leaf-lists, lists, containers,
406      * choices, rpcs, actions, notifications, anydatas, and anyxmls according to
407      * Rfc6050/Rfc7950 section 6.2.1.
408      *
409      * @param schemaContext
410      *            schema context
411      * @param path
412      *            path
413      * @return schema node on path
414      */
415     public static SchemaNode findDataParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) {
416         SchemaNode current = Preconditions.checkNotNull(schemaContext);
417         for (final QName qname : path.getPathFromRoot()) {
418             current = findDataChildSchemaByQName(current, qname);
419         }
420         return current;
421     }
422
423     /**
424      * Finds schema node for given path in schema context. This method performs lookup in both the namespace
425      * of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions,
426      * notifications, anydatas and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
427      *
428      * <p>
429      * This method is deprecated, because name conflicts can occur between the namespace of groupings and namespace
430      * of data nodes and in consequence lookup could be ambiguous.
431      *
432      * @param schemaContext
433      *            schema context
434      * @param path
435      *            path
436      * @return schema node on path
437      *
438      * @deprecated Use {@link #findParentSchemaNodesOnPath(SchemaContext, SchemaPath)} instead.
439      */
440     @Deprecated
441     public static SchemaNode findParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) {
442         SchemaNode current = Preconditions.checkNotNull(schemaContext);
443         for (final QName qname : path.getPathFromRoot()) {
444             current = findChildSchemaByQName(current, qname);
445         }
446         return current;
447     }
448
449     /**
450      * Find child data schema node identified by its QName within a provided schema node. This method performs lookup
451      * in the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions, notifications, anydatas
452      * and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
453      *
454      * @param node
455      *            schema node
456      * @param qname
457      *            QName
458      * @return data child schema node
459      * @throws IllegalArgumentException
460      *             if the schema node does not allow children
461      */
462     @Nullable
463     public static SchemaNode findDataChildSchemaByQName(final SchemaNode node, final QName qname) {
464         SchemaNode child = null;
465         if (node instanceof DataNodeContainer) {
466             child = ((DataNodeContainer) node).getDataChildByName(qname);
467             if (child == null && node instanceof SchemaContext) {
468                 child = tryFindRpc((SchemaContext) node, qname).orElse(null);
469             }
470             if (child == null && node instanceof NotificationNodeContainer) {
471                 child = tryFindNotification((NotificationNodeContainer) node, qname).orElse(null);
472             }
473             if (child == null && node instanceof ActionNodeContainer) {
474                 child = tryFindAction((ActionNodeContainer) node, qname).orElse(null);
475             }
476         } else if (node instanceof ChoiceSchemaNode) {
477             child = ((ChoiceSchemaNode) node).getCaseNodeByName(qname);
478         } else if (node instanceof RpcDefinition) {
479             switch (qname.getLocalName()) {
480                 case "input":
481                     child = ((RpcDefinition) node).getInput();
482                     break;
483                 case "output":
484                     child = ((RpcDefinition) node).getOutput();
485                     break;
486                 default:
487                     child = null;
488                     break;
489             }
490         } else {
491             throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", node));
492         }
493
494         return child;
495     }
496
497     /**
498      * Find child schema node identified by its QName within a provided schema node. This method performs lookup
499      * in both the namespace of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs,
500      * actions, notifications, anydatas and anyxmls according to RFC6050/RFC7950 section 6.2.1.
501      *
502      * <p>
503      * This method is deprecated, because name conflicts can occur between the namespace of groupings and namespace
504      * of data nodes and in consequence lookup could be ambiguous.
505      *
506      * @param node
507      *            schema node
508      * @param qname
509      *            QName
510      * @return child schema node
511      * @throws IllegalArgumentException
512      *             if the schema node does not allow children
513      *
514      * @deprecated Use {@link #findChildSchemaNodesByQName(SchemaNode, QName)} instead.
515      */
516     @Deprecated
517     public static SchemaNode findChildSchemaByQName(final SchemaNode node, final QName qname) {
518         SchemaNode child = findDataChildSchemaByQName(node, qname);
519         if (child == null && node instanceof DataNodeContainer) {
520             child = tryFindGroupings((DataNodeContainer) node, qname).orElse(null);
521         }
522
523         return child;
524     }
525
526     /**
527      * Finds schema node for given path in schema context. This method performs lookup in both the namespace
528      * of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions,
529      * notifications, anydatas and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
530      *
531      * <p>
532      * This method returns collection of SchemaNodes, because name conflicts can occur between the namespace
533      * of groupings and namespace of data nodes. This method finds and collects all schema nodes that matches supplied
534      * SchemaPath and returns them all as collection of schema nodes.
535      *
536      * @param schemaContext
537      *            schema context
538      * @param path
539      *            path
540      * @return collection of schema nodes on path
541      */
542     public static Collection<SchemaNode> findParentSchemaNodesOnPath(final SchemaContext schemaContext,
543             final SchemaPath path) {
544         final Collection<SchemaNode> currentNodes = new ArrayList<>();
545         final Collection<SchemaNode> childNodes = new ArrayList<>();
546         currentNodes.add(Preconditions.checkNotNull(schemaContext));
547         for (final QName qname : path.getPathFromRoot()) {
548             for (final SchemaNode current : currentNodes) {
549                 childNodes.addAll(findChildSchemaNodesByQName(current, qname));
550             }
551             currentNodes.clear();
552             currentNodes.addAll(childNodes);
553             childNodes.clear();
554         }
555
556         return currentNodes;
557     }
558
559     /**
560      * Find child schema node identified by its QName within a provided schema node. This method performs lookup in both
561      * the namespace of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs,
562      * actions, notifications, anydatas and anyxmls according to RFC6050/RFC7950 section 6.2.1.
563      *
564      * <p>
565      * This method returns collection of SchemaNodes, because name conflicts can occur between the namespace
566      * of groupings and namespace of data nodes. This method finds and collects all schema nodes with supplied QName
567      * and returns them all as collection of schema nodes.
568      *
569      * @param node
570      *            schema node
571      * @param qname
572      *            QName
573      * @return collection of child schema nodes
574      * @throws IllegalArgumentException
575      *             if the schema node does not allow children
576      */
577     public static Collection<SchemaNode> findChildSchemaNodesByQName(final SchemaNode node, final QName qname) {
578         final List<SchemaNode> childNodes = new ArrayList<>();
579         final SchemaNode dataNode = findDataChildSchemaByQName(node, qname);
580         if (dataNode != null) {
581             childNodes.add(dataNode);
582         }
583         if (node instanceof DataNodeContainer) {
584             tryFindGroupings((DataNodeContainer) node, qname).ifPresent(childNodes::add);
585         }
586         return childNodes.isEmpty() ? Collections.emptyList() : ImmutableList.copyOf(childNodes);
587     }
588
589     private static Optional<SchemaNode> tryFindGroupings(final DataNodeContainer dataNodeContainer, final QName qname) {
590         return Optional
591                 .ofNullable(Iterables.find(dataNodeContainer.getGroupings(), new SchemaNodePredicate(qname), null));
592     }
593
594     private static Optional<SchemaNode> tryFindRpc(final SchemaContext ctx, final QName qname) {
595         return Optional.ofNullable(Iterables.find(ctx.getOperations(), new SchemaNodePredicate(qname), null));
596     }
597
598     private static Optional<SchemaNode> tryFindNotification(final NotificationNodeContainer notificationContanier,
599             final QName qname) {
600         return Optional.ofNullable(
601                 Iterables.find(notificationContanier.getNotifications(), new SchemaNodePredicate(qname), null));
602     }
603
604     private static Optional<SchemaNode> tryFindAction(final ActionNodeContainer actionContanier, final QName qname) {
605         return Optional.ofNullable(Iterables.find(actionContanier.getActions(), new SchemaNodePredicate(qname), null));
606     }
607
608     private static final class SchemaNodePredicate implements Predicate<SchemaNode> {
609         private final QName qname;
610
611         SchemaNodePredicate(final QName qname) {
612             this.qname = qname;
613         }
614
615         @Override
616         public boolean apply(final SchemaNode input) {
617             return input.getQName().equals(qname);
618         }
619     }
620 }