Bug 7246 - Fix of SchemaTracker initialization and lookup of schema nodes
[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.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Predicate;
13 import com.google.common.collect.Collections2;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableSet;
16 import com.google.common.collect.Iterables;
17 import com.google.common.collect.Maps;
18 import com.google.common.collect.Sets;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
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.NotificationNodeContainer;
39 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
40 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
41 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
43
44 public final class SchemaUtils {
45     private SchemaUtils() {
46         throw new UnsupportedOperationException();
47     }
48
49     /**
50      * @param qname - schema node to find
51      * @param dataSchemaNode - iterable of schemaNodes to look through
52      * @return - schema node with newest revision or absent if no schema node with matching qname is found
53      */
54     public static Optional<DataSchemaNode> findFirstSchema(final QName qname, final Iterable<DataSchemaNode> dataSchemaNode) {
55         DataSchemaNode sNode = null;
56         if (dataSchemaNode != null && qname != null) {
57             for (final DataSchemaNode dsn : dataSchemaNode) {
58                 if (qname.isEqualWithoutRevision(dsn.getQName())) {
59                     if (sNode == null || sNode.getQName().getRevision().compareTo(dsn.getQName().getRevision()) < 0) {
60                         sNode = dsn;
61                     }
62                 } else if (dsn instanceof ChoiceSchemaNode) {
63                     for (final ChoiceCaseNode choiceCase : ((ChoiceSchemaNode) dsn).getCases()) {
64
65                         final DataSchemaNode dataChildByName = choiceCase.getDataChildByName(qname);
66                         if (dataChildByName != null) {
67                             return Optional.of(dataChildByName);
68                         }
69                         final Optional<DataSchemaNode> foundDsn = findFirstSchema(qname, choiceCase.getChildNodes());
70                         if (foundDsn.isPresent()) {
71                             return foundDsn;
72                         }
73                     }
74                 }
75             }
76         }
77         return Optional.fromNullable(sNode);
78     }
79
80     /**
81      *
82      * Find child schema node identified by its QName within a provided schema node
83      *
84      * @param schema schema for parent node - search root
85      * @param qname qname(with or without a revision) of a child node to be found in the parent schema
86      * @return found schema node
87      * @throws java.lang.IllegalStateException if the child was not found in parent schema node
88      */
89     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname) {
90         // Try to find child schema node directly, but use a fallback that compares QNames without revisions and auto-expands choices
91         final DataSchemaNode dataChildByName = schema.getDataChildByName(qname);
92         return dataChildByName == null ? findSchemaForChild(schema, qname, schema.getChildNodes()) : dataChildByName;
93     }
94
95     @Nullable
96     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname, final boolean strictMode) {
97         if (strictMode) {
98             return findSchemaForChild(schema, qname);
99         }
100
101         final Optional<DataSchemaNode> childSchemaOptional = findFirstSchema(qname, schema.getChildNodes());
102         if (!childSchemaOptional.isPresent()) {
103             return null;
104         }
105         return childSchemaOptional.get();
106     }
107
108     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname, final Iterable<DataSchemaNode> childNodes) {
109         final Optional<DataSchemaNode> childSchema = findFirstSchema(qname, childNodes);
110         Preconditions.checkState(childSchema.isPresent(),
111                 "Unknown child(ren) node(s) detected, identified by: %s, in: %s", qname, schema);
112         return childSchema.get();
113     }
114
115     public static AugmentationSchema findSchemaForAugment(final AugmentationTarget schema, final Set<QName> qNames) {
116         final Optional<AugmentationSchema> schemaForAugment = findAugment(schema, qNames);
117         Preconditions.checkState(schemaForAugment.isPresent(), "Unknown augmentation node detected, identified by: %s, in: %s",
118                 qNames, schema);
119         return schemaForAugment.get();
120     }
121
122     public static AugmentationSchema findSchemaForAugment(final ChoiceSchemaNode schema, final Set<QName> qNames) {
123         Optional<AugmentationSchema> schemaForAugment = Optional.absent();
124
125         for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) {
126             schemaForAugment = findAugment(choiceCaseNode, qNames);
127             if (schemaForAugment.isPresent()) {
128                 break;
129             }
130         }
131
132         Preconditions.checkState(schemaForAugment.isPresent(), "Unknown augmentation node detected, identified by: %s, in: %s",
133                 qNames, schema);
134         return schemaForAugment.get();
135     }
136
137     private static Optional<AugmentationSchema> findAugment(final AugmentationTarget schema, final Set<QName> qNames) {
138         for (final AugmentationSchema augment : schema.getAvailableAugmentations()) {
139             final HashSet<QName> qNamesFromAugment = Sets.newHashSet(Collections2.transform(augment.getChildNodes(),
140                 DataSchemaNode::getQName));
141
142             if (qNamesFromAugment.equals(qNames)) {
143                 return Optional.of(augment);
144             }
145         }
146
147         return Optional.absent();
148     }
149
150     public static DataSchemaNode findSchemaForChild(final ChoiceSchemaNode schema, final QName childPartialQName) {
151         for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) {
152             final Optional<DataSchemaNode> childSchema = findFirstSchema(childPartialQName, choiceCaseNode.getChildNodes());
153             if (childSchema.isPresent()) {
154                 return childSchema.get();
155             }
156         }
157
158
159         throw new IllegalStateException(String.format("Unknown child(ren) node(s) detected, identified by: %s, in: %s",
160                 childPartialQName, schema));
161     }
162
163     /**
164      * Recursively find all child nodes that come from choices.
165      *
166      * @param schema schema
167      * @return Map with all child nodes, to their most top augmentation
168      */
169     public static Map<QName, ChoiceSchemaNode> mapChildElementsFromChoices(final DataNodeContainer schema) {
170         return mapChildElementsFromChoices(schema, schema.getChildNodes());
171     }
172
173     private static Map<QName, ChoiceSchemaNode> mapChildElementsFromChoices(final DataNodeContainer schema, final Iterable<DataSchemaNode> childNodes) {
174         final Map<QName, ChoiceSchemaNode> mappedChoices = Maps.newLinkedHashMap();
175
176         for (final DataSchemaNode childSchema : childNodes) {
177             if (childSchema instanceof ChoiceSchemaNode) {
178
179                 if (isFromAugment(schema, childSchema)) {
180                     continue;
181                 }
182
183                 for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) {
184
185                     for (final QName qName : getChildNodesRecursive(choiceCaseNode)) {
186                         mappedChoices.put(qName, (ChoiceSchemaNode) childSchema);
187                     }
188                 }
189             }
190         }
191
192         return mappedChoices;
193     }
194
195     private static boolean isFromAugment(final DataNodeContainer schema, final DataSchemaNode childSchema) {
196         if (!(schema instanceof AugmentationTarget)) {
197             return false;
198         }
199
200         for (final AugmentationSchema augmentationSchema : ((AugmentationTarget) schema).getAvailableAugmentations()) {
201             if (augmentationSchema.getDataChildByName(childSchema.getQName()) != null) {
202                 return true;
203             }
204         }
205
206         return false;
207     }
208
209     /**
210      * Recursively find all child nodes that come from augmentations.
211      *
212      * @param schema schema
213      * @return Map with all child nodes, to their most top augmentation
214      */
215     public static Map<QName, AugmentationSchema> mapChildElementsFromAugments(final AugmentationTarget schema) {
216
217         final Map<QName, AugmentationSchema> childNodesToAugmentation = Maps.newLinkedHashMap();
218
219         // Find QNames of augmented child nodes
220         final Map<QName, AugmentationSchema> augments = Maps.newHashMap();
221         for (final AugmentationSchema augmentationSchema : schema.getAvailableAugmentations()) {
222             for (final DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) {
223                 augments.put(dataSchemaNode.getQName(), augmentationSchema);
224             }
225         }
226
227         // Augmented nodes have to be looked up directly in augmentationTarget
228         // because nodes from augment do not contain nodes from other augmentations
229         if (schema instanceof DataNodeContainer) {
230
231             for (final DataSchemaNode child : ((DataNodeContainer) schema).getChildNodes()) {
232                 // If is not augmented child, continue
233                 if (!(augments.containsKey(child.getQName()))) {
234                     continue;
235                 }
236
237                 final AugmentationSchema mostTopAugmentation = augments.get(child.getQName());
238
239                 // recursively add all child nodes in case of augment, case and choice
240                 if (child instanceof AugmentationSchema || child instanceof ChoiceCaseNode) {
241                     for (final QName qName : getChildNodesRecursive((DataNodeContainer) child)) {
242                         childNodesToAugmentation.put(qName, mostTopAugmentation);
243                     }
244                 } else if (child instanceof ChoiceSchemaNode) {
245                     for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) child).getCases()) {
246                         for (final QName qName : getChildNodesRecursive(choiceCaseNode)) {
247                             childNodesToAugmentation.put(qName, mostTopAugmentation);
248                         }
249                     }
250                 } else {
251                     childNodesToAugmentation.put(child.getQName(), mostTopAugmentation);
252                 }
253             }
254         }
255
256         // Choice Node has to map child nodes from all its cases
257         if (schema instanceof ChoiceSchemaNode) {
258             for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) schema).getCases()) {
259                 if (!(augments.containsKey(choiceCaseNode.getQName()))) {
260                     continue;
261                 }
262
263                 for (final QName qName : getChildNodesRecursive(choiceCaseNode)) {
264                     childNodesToAugmentation.put(qName, augments.get(choiceCaseNode.getQName()));
265                 }
266             }
267         }
268
269         return childNodesToAugmentation;
270     }
271
272     /**
273      * Recursively list all child nodes.
274      *
275      * In case of choice, augment and cases, step in.
276      *
277      * @param nodeContainer node container
278      * @return set of QNames
279      */
280     public static Set<QName> getChildNodesRecursive(final DataNodeContainer nodeContainer) {
281         final Set<QName> allChildNodes = Sets.newHashSet();
282
283         for (final DataSchemaNode childSchema : nodeContainer.getChildNodes()) {
284             if (childSchema instanceof ChoiceSchemaNode) {
285                 for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) {
286                     allChildNodes.addAll(getChildNodesRecursive(choiceCaseNode));
287                 }
288             } else if (childSchema instanceof AugmentationSchema || childSchema instanceof ChoiceCaseNode) {
289                 allChildNodes.addAll(getChildNodesRecursive((DataNodeContainer) childSchema));
290             }
291             else {
292                 allChildNodes.add(childSchema.getQName());
293             }
294         }
295
296         return allChildNodes;
297     }
298
299     /**
300      * Retrieves real schemas for augmented child node.
301      *
302      * Schema of the same child node from augment, and directly from target is not the same.
303      * Schema of child node from augment is incomplete, therefore its useless for XML/NormalizedNode translation.
304      *
305      * @param targetSchema target schema
306      * @param augmentSchema augment schema
307      * @return set of nodes
308      */
309     public static Set<DataSchemaNode> getRealSchemasForAugment(final AugmentationTarget targetSchema, final AugmentationSchema augmentSchema) {
310         if (!(targetSchema.getAvailableAugmentations().contains(augmentSchema))) {
311             return Collections.emptySet();
312         }
313
314         Set<DataSchemaNode> realChildNodes = Sets.newHashSet();
315
316         if (targetSchema instanceof DataNodeContainer) {
317             realChildNodes = getRealSchemasForAugment((DataNodeContainer)targetSchema, augmentSchema);
318         } else if (targetSchema instanceof ChoiceSchemaNode) {
319             for (final DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) {
320                 for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) targetSchema).getCases()) {
321                     if (getChildNodesRecursive(choiceCaseNode).contains(dataSchemaNode.getQName())) {
322                         realChildNodes.add(choiceCaseNode.getDataChildByName(dataSchemaNode.getQName()));
323                     }
324                 }
325             }
326         }
327
328         return realChildNodes;
329     }
330
331     public static Set<DataSchemaNode> getRealSchemasForAugment(final DataNodeContainer targetSchema,
332             final AugmentationSchema augmentSchema) {
333         final Set<DataSchemaNode> realChildNodes = Sets.newHashSet();
334         for (final DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) {
335             final DataSchemaNode realChild = targetSchema.getDataChildByName(dataSchemaNode.getQName());
336             realChildNodes.add(realChild);
337         }
338         return realChildNodes;
339     }
340
341     public static Optional<ChoiceCaseNode> detectCase(final ChoiceSchemaNode schema, final DataContainerChild<?, ?> child) {
342         for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) {
343             if (child instanceof AugmentationNode
344                     && belongsToCaseAugment(choiceCaseNode, (AugmentationIdentifier) child.getIdentifier())) {
345                 return Optional.of(choiceCaseNode);
346             } else if (choiceCaseNode.getDataChildByName(child.getNodeType()) != null) {
347                 return Optional.of(choiceCaseNode);
348             }
349         }
350
351         return Optional.absent();
352     }
353
354     public static boolean belongsToCaseAugment(final ChoiceCaseNode caseNode, final AugmentationIdentifier childToProcess) {
355         for (final AugmentationSchema augmentationSchema : caseNode.getAvailableAugmentations()) {
356
357             final Set<QName> currentAugmentChildNodes = Sets.newHashSet();
358             for (final DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) {
359                 currentAugmentChildNodes.add(dataSchemaNode.getQName());
360             }
361
362             if (childToProcess.getPossibleChildNames().equals(currentAugmentChildNodes)){
363                 return true;
364             }
365         }
366
367         return false;
368     }
369
370     /**
371      * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
372      * node is found then it is returned, else null.
373      *
374      * @param parent parent node
375      * @param child child node
376      * @return augmentation schema
377      */
378     public static AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent, final DataSchemaNode child) {
379         if (parent instanceof AugmentationTarget && !(parent instanceof ChoiceSchemaNode)) {
380             for (final AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
381                 final DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
382                 if (childInAugmentation != null) {
383                     return augmentation;
384                 }
385             }
386         }
387         return null;
388     }
389
390     public static AugmentationIdentifier getNodeIdentifierForAugmentation(final AugmentationSchema schema) {
391         final Collection<QName> qnames = Collections2.transform(schema.getChildNodes(), DataSchemaNode::getQName);
392         return new AugmentationIdentifier(ImmutableSet.copyOf(qnames));
393     }
394
395     /**
396      * Finds schema node for given path in schema context. This method performs
397      * lookup in the namespace of all leafs, leaf-lists, lists, containers,
398      * choices, rpcs, actions, notifications, anydatas, and anyxmls according to
399      * Rfc6050/Rfc7950 section 6.2.1.
400      *
401      * @param schemaContext
402      *            schema context
403      * @param path
404      *            path
405      * @return schema node on path
406      */
407     public static SchemaNode findDataParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) {
408         SchemaNode current = Preconditions.checkNotNull(schemaContext);
409         for (final QName qname : path.getPathFromRoot()) {
410             current = findDataChildSchemaByQName(current, qname);
411         }
412         return current;
413     }
414
415     /**
416      * Finds schema node for given path in schema context. This method performs
417      * lookup in both the namespace of groupings and the namespace of all leafs,
418      * leaf-lists, lists, containers, choices, rpcs, actions, notifications,
419      * anydatas, and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
420      *
421      * This method is deprecated, because name conflicts can occur between the
422      * namespace of groupings and namespace of data nodes and in consequence
423      * lookup could be ambiguous.
424      *
425      * @param schemaContext
426      *            schema context
427      * @param path
428      *            path
429      * @return schema node on path
430      *
431      * @deprecated use
432      *             {@link #findParentSchemaNodesOnPath(SchemaContext, SchemaPath)}
433      *             instead.
434      */
435     @Deprecated
436     public static SchemaNode findParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) {
437         SchemaNode current = Preconditions.checkNotNull(schemaContext);
438         for (final QName qname : path.getPathFromRoot()) {
439             current = findChildSchemaByQName(current, qname);
440         }
441         return current;
442     }
443
444     /**
445      * Find child data schema node identified by its QName within a provided
446      * schema node. This method performs lookup in the namespace of all leafs,
447      * leaf-lists, lists, containers, choices, rpcs, actions, notifications,
448      * anydatas, and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
449      *
450      * @param node
451      *            schema node
452      * @param qname
453      *            QName
454      * @return data child schema node
455      * @throws java.lang.IllegalArgumentException
456      *             if the schema node does not allow children
457      */
458     @Nullable
459     public static SchemaNode findDataChildSchemaByQName(final SchemaNode node, final QName qname) {
460         SchemaNode child = null;
461         if (node instanceof DataNodeContainer) {
462             child = ((DataNodeContainer) node).getDataChildByName(qname);
463             if (child == null && node instanceof SchemaContext) {
464                 child = tryFindRpc((SchemaContext) node, qname).orNull();
465             }
466             if (child == null && node instanceof NotificationNodeContainer) {
467                 child = tryFindNotification((NotificationNodeContainer) node, qname).orNull();
468             }
469             if (child == null && node instanceof ActionNodeContainer) {
470                 child = tryFindAction((ActionNodeContainer) node, qname).orNull();
471             }
472         } else if (node instanceof ChoiceSchemaNode) {
473             child = ((ChoiceSchemaNode) node).getCaseNodeByName(qname);
474         } else if (node instanceof RpcDefinition) {
475             switch (qname.getLocalName()) {
476             case "input":
477                 child = ((RpcDefinition) node).getInput();
478                 break;
479             case "output":
480                 child = ((RpcDefinition) node).getOutput();
481                 break;
482             default:
483                 child = null;
484                 break;
485             }
486         } else {
487             throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", node));
488         }
489
490         return child;
491     }
492
493     /**
494      * Find child schema node identified by its QName within a provided schema
495      * node. This method performs lookup in both the namespace of groupings and
496      * the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs,
497      * actions, notifications, anydatas, and anyxmls according to
498      * Rfc6050/Rfc7950 section 6.2.1.
499      *
500      * This method is deprecated, because name conflicts can occur between the
501      * namespace of groupings and namespace of data nodes and in consequence
502      * lookup could be ambiguous.
503      *
504      * @param node
505      *            schema node
506      * @param qname
507      *            QName
508      * @return child schema node
509      * @throws java.lang.IllegalArgumentException
510      *             if the schema node does not allow children
511      *
512      * @deprecated use
513      *             {@link #findChildSchemaNodesByQName(SchemaNode, QName)}
514      *             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).orNull();
521         }
522
523         return child;
524     }
525
526     /**
527      * Finds schema node for given path in schema context. This method performs
528      * lookup in both the namespace of groupings and the namespace of all leafs,
529      * leaf-lists, lists, containers, choices, rpcs, actions, notifications,
530      * anydatas, and anyxmls according to Rfc6050/Rfc7950 section 6.2.1.
531      *
532      * This method returns collection of SchemaNodes, because name conflicts can
533      * occur between the namespace of groupings and namespace of data nodes.
534      * This method finds and collects all schema nodes that matches supplied
535      * SchemaPath and returns them all as collection of schema nodes.
536      *
537      * @param schemaContext
538      *            schema context
539      * @param path
540      *            path
541      * @return collection of schema nodes on path
542      *
543      */
544     public static Collection<SchemaNode> findParentSchemaNodesOnPath(final SchemaContext schemaContext,
545             final SchemaPath path) {
546         final Collection<SchemaNode> currentNodes = new ArrayList<>();
547         final Collection<SchemaNode> childNodes = new ArrayList<>();
548         currentNodes.add(Preconditions.checkNotNull(schemaContext));
549         for (final QName qname : path.getPathFromRoot()) {
550             for (final SchemaNode current : currentNodes) {
551                 childNodes.addAll(findChildSchemaNodesByQName(current, qname));
552             }
553             currentNodes.clear();
554             currentNodes.addAll(childNodes);
555             childNodes.clear();
556         }
557
558         return currentNodes;
559     }
560
561     /**
562      * Find child schema node identified by its QName within a provided schema
563      * node. This method performs lookup in both the namespace of groupings and
564      * the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs,
565      * actions, notifications, anydatas, and anyxmls according to
566      * Rfc6050/Rfc7950 section 6.2.1.
567      *
568      * This method returns collection of SchemaNodes, because name conflicts can
569      * occur between the namespace of groupings and namespace of data nodes.
570      * This method finds and collects all schema nodes with supplied QName and
571      * returns them all as collection of schema nodes.
572      *
573      * @param node
574      *            schema node
575      * @param qname
576      *            QName
577      * @return collection of child schema nodes
578      * @throws java.lang.IllegalArgumentException
579      *             if the schema node does not allow children
580      *
581      */
582     public static Collection<SchemaNode> findChildSchemaNodesByQName(final SchemaNode node, final QName qname) {
583         final List<SchemaNode> childNodes = new ArrayList<>();
584         final SchemaNode dataNode = findDataChildSchemaByQName(node, qname);
585         if (dataNode != null) {
586             childNodes.add(dataNode);
587         }
588         if (node instanceof DataNodeContainer) {
589             final SchemaNode groupingNode = tryFindGroupings((DataNodeContainer) node, qname).orNull();
590             if (groupingNode != null) {
591                 childNodes.add(groupingNode);
592             }
593         }
594         return childNodes.isEmpty() ? Collections.emptyList() : ImmutableList.copyOf(childNodes);
595     }
596
597     private static Optional<SchemaNode> tryFindGroupings(final DataNodeContainer dataNodeContainer, final QName qname) {
598         return Optional
599                 .fromNullable(Iterables.find(dataNodeContainer.getGroupings(), new SchemaNodePredicate(qname), null));
600     }
601
602     private static Optional<SchemaNode> tryFindRpc(final SchemaContext ctx, final QName qname) {
603         return Optional.fromNullable(Iterables.find(ctx.getOperations(), new SchemaNodePredicate(qname), null));
604     }
605
606     private static Optional<SchemaNode> tryFindNotification(final NotificationNodeContainer notificationContanier,
607             final QName qname) {
608         return Optional.fromNullable(
609                 Iterables.find(notificationContanier.getNotifications(), new SchemaNodePredicate(qname), null));
610     }
611
612     private static Optional<SchemaNode> tryFindAction(final ActionNodeContainer actionContanier, final QName qname) {
613         return Optional.fromNullable(Iterables.find(actionContanier.getActions(), new SchemaNodePredicate(qname), null));
614     }
615
616     private static final class SchemaNodePredicate implements Predicate<SchemaNode> {
617         private final QName qname;
618
619         public SchemaNodePredicate(final QName qname) {
620             this.qname = qname;
621         }
622
623         @Override
624         public boolean apply(final SchemaNode input) {
625             return input.getQName().equals(qname);
626         }
627     }
628
629 }