BUG-994: do not use SchemaPath.getPath()
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / SchemaContextUtil.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.model.util;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.Iterables;
12
13 import java.net.URI;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Date;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.Set;
22
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
25 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
26 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
27 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
28 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
30 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
32 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.Module;
34 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
35 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
36 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
37 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
39 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
41 import org.opendaylight.yangtools.yang.model.api.UsesNode;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The Schema Context Util contains support methods for searching through Schema
47  * Context modules for specified schema nodes via Schema Path or Revision Aware
48  * XPath. The Schema Context Util is designed as mixin, so it is not
49  * instantiable.
50  *
51  */
52 public final class SchemaContextUtil {
53     private static final Logger LOG = LoggerFactory.getLogger(SchemaContextUtil.class);
54
55     private SchemaContextUtil() {
56     }
57
58     /**
59      * Method attempts to find DataSchemaNode in Schema Context via specified
60      * Schema Path. The returned DataSchemaNode from method will be the node at
61      * the end of the SchemaPath. If the DataSchemaNode is not present in the
62      * Schema Context the method will return <code>null</code>. <br>
63      * In case that Schema Context or Schema Path are not specified correctly
64      * (i.e. contains <code>null</code> values) the method will return
65      * IllegalArgumentException.
66      *
67      * @throws IllegalArgumentException
68      *
69      * @param context
70      *            Schema Context
71      * @param schemaPath
72      *            Schema Path to search for
73      * @return SchemaNode from the end of the Schema Path or <code>null</code>
74      *         if the Node is not present.
75      */
76     public static SchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
77         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
78         Preconditions.checkArgument(schemaPath != null, "Schema Path reference cannot be NULL");
79
80         final List<QName> prefixedPath = (schemaPath.getPath());
81         if (prefixedPath == null) {
82             LOG.debug("Schema path {} has null path", schemaPath);
83             return null;
84         }
85
86         LOG.trace("Looking for path {} in context {}", schemaPath, context);
87         return findNodeInSchemaContext(context, prefixedPath);
88     }
89
90     /**
91      * Method attempts to find DataSchemaNode inside of provided Schema Context
92      * and Yang Module accordingly to Non-conditional Revision Aware XPath. The
93      * specified Module MUST be present in Schema Context otherwise the
94      * operation would fail and return <code>null</code>. <br>
95      * The Revision Aware XPath MUST be specified WITHOUT the conditional
96      * statement (i.e. without [cond]) in path, because in this state the Schema
97      * Context is completely unaware of data state and will be not able to
98      * properly resolve XPath. If the XPath contains condition the method will
99      * return IllegalArgumentException. <br>
100      * In case that Schema Context or Module or Revision Aware XPath contains
101      * <code>null</code> references the method will throw
102      * IllegalArgumentException <br>
103      * If the Revision Aware XPath is correct and desired Data Schema Node is
104      * present in Yang module or in depending module in Schema Context the
105      * method will return specified Data Schema Node, otherwise the operation
106      * will fail and method will return <code>null</code>.
107      *
108      * @throws IllegalArgumentException
109      *
110      * @param context
111      *            Schema Context
112      * @param module
113      *            Yang Module
114      * @param nonCondXPath
115      *            Non Conditional Revision Aware XPath
116      * @return Returns Data Schema Node for specified Schema Context for given
117      *         Non-conditional Revision Aware XPath, or <code>null</code> if the
118      *         DataSchemaNode is not present in Schema Context.
119      */
120     public static SchemaNode findDataSchemaNode(final SchemaContext context, final Module module, final RevisionAwareXPath nonCondXPath) {
121         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
122         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
123         Preconditions.checkArgument(nonCondXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
124
125         String strXPath = nonCondXPath.toString();
126         if (strXPath != null) {
127             Preconditions.checkArgument(strXPath.indexOf('[') == -1, "Revision Aware XPath may not contain a condition");
128             if (nonCondXPath.isAbsolute()) {
129                 List<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
130                 if (qnamedPath != null) {
131                     return findNodeInSchemaContext(context, qnamedPath);
132                 }
133             }
134         }
135         return null;
136     }
137
138     /**
139      * Method attempts to find DataSchemaNode inside of provided Schema Context
140      * and Yang Module accordingly to Non-conditional relative Revision Aware
141      * XPath. The specified Module MUST be present in Schema Context otherwise
142      * the operation would fail and return <code>null</code>. <br>
143      * The relative Revision Aware XPath MUST be specified WITHOUT the
144      * conditional statement (i.e. without [cond]) in path, because in this
145      * state the Schema Context is completely unaware of data state and will be
146      * not able to properly resolve XPath. If the XPath contains condition the
147      * method will return IllegalArgumentException. <br>
148      * The Actual Schema Node MUST be specified correctly because from this
149      * Schema Node will search starts. If the Actual Schema Node is not correct
150      * the operation will simply fail, because it will be unable to find desired
151      * DataSchemaNode. <br>
152      * In case that Schema Context or Module or Actual Schema Node or relative
153      * Revision Aware XPath contains <code>null</code> references the method
154      * will throw IllegalArgumentException <br>
155      * If the Revision Aware XPath doesn't have flag
156      * <code>isAbsolute == false</code> the method will throw
157      * IllegalArgumentException. <br>
158      * If the relative Revision Aware XPath is correct and desired Data Schema
159      * Node is present in Yang module or in depending module in Schema Context
160      * the method will return specified Data Schema Node, otherwise the
161      * operation will fail and method will return <code>null</code>.
162      *
163      * @throws IllegalArgumentException
164      *
165      * @param context
166      *            Schema Context
167      * @param module
168      *            Yang Module
169      * @param actualSchemaNode
170      *            Actual Schema Node
171      * @param relativeXPath
172      *            Relative Non Conditional Revision Aware XPath
173      * @return DataSchemaNode if is present in specified Schema Context for
174      *         given relative Revision Aware XPath, otherwise will return
175      *         <code>null</code>.
176      */
177     public static SchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
178             final SchemaNode actualSchemaNode, final RevisionAwareXPath relativeXPath) {
179         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
180         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
181         Preconditions.checkArgument(actualSchemaNode != null, "Actual Schema Node reference cannot be NULL");
182         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
183         Preconditions.checkState(!relativeXPath.isAbsolute(),
184                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
185                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
186
187         SchemaPath actualNodePath = actualSchemaNode.getPath();
188         if (actualNodePath != null) {
189             List<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
190
191             if (qnamePath != null) {
192                 return findNodeInSchemaContext(context, qnamePath);
193             }
194         }
195         return null;
196     }
197
198     /**
199      * Returns parent Yang Module for specified Schema Context in which Schema
200      * Node is declared. If the Schema Node is not present in Schema Context the
201      * operation will return <code>null</code>. <br>
202      * If Schema Context or Schema Node contains <code>null</code> references
203      * the method will throw IllegalArgumentException
204      *
205      * @throws IllegalArgumentException
206      *
207      * @param context
208      *            Schema Context
209      * @param schemaNode
210      *            Schema Node
211      * @return Yang Module for specified Schema Context and Schema Node, if
212      *         Schema Node is NOT present, the method will returns
213      *         <code>null</code>
214      */
215     public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
216         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL!");
217         Preconditions.checkArgument(schemaNode != null, "Schema Node cannot be NULL!");
218         Preconditions.checkState(schemaNode.getPath() != null, "Schema Path for Schema Node is not "
219                 + "set properly (Schema Path is NULL)");
220
221         final QName qname = Iterables.getFirst(schemaNode.getPath().getPathTowardsRoot(), null);
222         Preconditions.checkState(qname != null,
223                 "Schema Path contains invalid state of path parts. " +
224                 "The Schema Path MUST contain at least ONE QName which defines namespace and Local name of path.");
225         return context.findModuleByNamespaceAndRevision(qname.getNamespace(), qname.getRevision());
226     }
227
228     public static SchemaNode findNodeInSchemaContext(final SchemaContext context, final List<QName> path) {
229         final QName current = path.get(0);
230
231         LOG.trace("Looking up module {} in context {}", current, path);
232         final Module module = context.findModuleByNamespaceAndRevision(current.getNamespace(), current.getRevision());
233         if (module == null) {
234             LOG.debug("Module {} not found", current);
235             return null;
236         }
237
238         return findNodeInModule(module, path);
239     }
240
241     public static GroupingDefinition findGrouping(final SchemaContext context, final Module module, final List<QName> path) {
242         QName first = path.get(0);
243         Module m = context.findModuleByNamespace(first.getNamespace()).iterator().next();
244         DataNodeContainer currentParent = m;
245         for (QName qname : path) {
246             boolean found = false;
247             DataNodeContainer node = (DataNodeContainer) currentParent.getDataChildByName(qname.getLocalName());
248             if (node == null) {
249                 Set<GroupingDefinition> groupings = currentParent.getGroupings();
250                 for (GroupingDefinition gr : groupings) {
251                     if (gr.getQName().getLocalName().equals(qname.getLocalName())) {
252                         currentParent = gr;
253                         found = true;
254                     }
255                 }
256             } else {
257                 found = true;
258                 currentParent = node;
259             }
260
261             Preconditions.checkArgument(found, "Failed to find referenced grouping: %s(%s)", path, qname.getLocalName());
262         }
263
264         return (GroupingDefinition) currentParent;
265     }
266
267     private static SchemaNode findNodeInModule(final Module module, final List<QName> path) {
268         final QName current = path.get(0);
269
270         LOG.trace("Looking for data container {} in module {}", current, module);
271         SchemaNode parent = module.getDataChildByName(current);
272         if (parent != null) {
273             final SchemaNode ret = findNode((DataSchemaNode) parent, nextLevel(path));
274             if (ret != null) {
275                 return ret;
276             }
277         }
278
279         LOG.trace("Looking for RPC {} in module {}", current, module);
280         parent = getRpcByName(module, current);
281         if (parent != null) {
282             final SchemaNode ret = findNodeInRpc((RpcDefinition) parent, nextLevel(path));
283             if (ret != null) {
284                 return ret;
285             }
286         }
287
288         LOG.trace("Looking for notification {} in module {}", current, module);
289         parent = getNotificationByName(module, current);
290         if (parent != null) {
291             final SchemaNode ret = findNodeInNotification((NotificationDefinition) parent, nextLevel(path));
292             if (ret != null) {
293                 return ret;
294             }
295         }
296
297         LOG.trace("Looking for grouping {} in module {}", current, module);
298         parent = getGroupingByName(module, current);
299         if (parent != null) {
300             final SchemaNode ret = findNodeInGrouping((GroupingDefinition) parent, nextLevel(path));
301             if (ret != null) {
302                 return ret;
303             }
304         }
305
306         LOG.debug("No node matching {} found in module {}", path, module);
307         return null;
308     }
309
310     private static SchemaNode findNodeInGrouping(final GroupingDefinition grouping, final List<QName> path) {
311         if (path.isEmpty()) {
312             LOG.debug("Found grouping {}", grouping);
313             return grouping;
314         }
315
316         LOG.trace("Looking for path {} in grouping {}", path, grouping);
317         final QName current = path.get(0);
318         final DataSchemaNode node = grouping.getDataChildByName(current);
319         if (node == null) {
320             LOG.debug("No node matching {} found in grouping {}", current, grouping);
321             return null;
322         }
323
324         return findNode(node, nextLevel(path));
325     }
326
327     private static SchemaNode findNodeInRpc(final RpcDefinition rpc, final List<QName> path) {
328         if (path.isEmpty()) {
329             LOG.debug("Found RPC {}", rpc);
330             return rpc;
331         }
332
333         LOG.trace("Looking for path {} in rpc {}", path, rpc);
334         final QName current = path.get(0);
335         switch (current.getLocalName()) {
336         case "input":
337             return findNode(rpc.getInput(), nextLevel(path));
338         case "output":
339             return findNode(rpc.getOutput(), nextLevel(path));
340         default:
341             LOG.debug("Invalid component {} of path {} in RPC {}", current, path, rpc);
342             return null;
343         }
344     }
345
346     private static SchemaNode findNodeInNotification(final NotificationDefinition ntf, final List<QName> path) {
347         if (path.isEmpty()) {
348             LOG.debug("Found notification {}", ntf);
349             return ntf;
350         }
351
352         LOG.trace("Looking for path {} in notification {}", path, ntf);
353         final QName current = path.get(0);
354         DataSchemaNode node = ntf.getDataChildByName(current);
355         if (node == null) {
356             LOG.debug("No node matching {} found in notification {}", current, ntf);
357             return null;
358         }
359
360         return findNode(node, nextLevel(path));
361     }
362
363     private static SchemaNode findNode(final ChoiceNode parent, final List<QName> path) {
364         if (path.isEmpty()) {
365             return parent;
366         }
367         QName current = path.get(0);
368         ChoiceCaseNode node = parent.getCaseNodeByName(current);
369         if (node != null) {
370             return findNodeInCase(node, nextLevel(path));
371         }
372         return null;
373     }
374
375     private static SchemaNode findNode(final ContainerSchemaNode parent, final List<QName> path) {
376         if (path.isEmpty()) {
377             return parent;
378         }
379
380         final QName current = path.get(0);
381         final DataSchemaNode node = parent.getDataChildByName(current);
382         if (node == null) {
383             LOG.debug("Failed to find {} in parent {}", path, parent);
384             return null;
385         }
386
387         return findNode(node, nextLevel(path));
388     }
389
390     private static SchemaNode findNode(final ListSchemaNode parent, final List<QName> path) {
391         if (path.isEmpty()) {
392             return parent;
393         }
394
395         QName current = path.get(0);
396         DataSchemaNode node = parent.getDataChildByName(current);
397         if (node == null) {
398             LOG.debug("Failed to find {} in parent {}", path, parent);
399             return null;
400         }
401         return findNode(node, nextLevel(path));
402     }
403
404     private static SchemaNode findNode(final DataSchemaNode parent, final List<QName> path) {
405         final SchemaNode node;
406         if (!path.isEmpty()) {
407             if (parent instanceof ContainerSchemaNode) {
408                 node = findNode((ContainerSchemaNode) parent, path);
409             } else if (parent instanceof ListSchemaNode) {
410                 node = findNode((ListSchemaNode) parent, path);
411             } else if (parent instanceof ChoiceNode) {
412                 node = findNode((ChoiceNode) parent, path);
413             } else {
414                 throw new IllegalArgumentException(
415                         String.format("Path nesting violation in parent %s path %s", parent, path));
416             }
417         } else {
418             node = parent;
419         }
420
421         if (node == null) {
422             LOG.debug("Failed to find {} in parent {}", path, parent);
423             return null;
424         }
425         return node;
426     }
427
428     public static SchemaNode findNodeInCase(final ChoiceCaseNode parent, final List<QName> path) {
429         if (path.isEmpty()) {
430             return parent;
431         }
432
433         QName current = path.get(0);
434         DataSchemaNode node = parent.getDataChildByName(current);
435         if (node == null) {
436             LOG.debug("Failed to find {} in parent {}", path, parent);
437             return null;
438         }
439         return findNode(node, nextLevel(path));
440     }
441
442     public static RpcDefinition getRpcByName(final Module module, final QName name) {
443         for (RpcDefinition rpc : module.getRpcs()) {
444             if (rpc.getQName().equals(name)) {
445                 return rpc;
446             }
447         }
448         return null;
449     }
450
451     private static List<QName> nextLevel(final List<QName> path) {
452         return path.subList(1, path.size());
453     }
454
455     public static NotificationDefinition getNotificationByName(final Module module, final QName name) {
456         for (NotificationDefinition notification : module.getNotifications()) {
457             if (notification.getQName().equals(name)) {
458                 return notification;
459             }
460         }
461         return null;
462     }
463
464     public static GroupingDefinition getGroupingByName(final Module module, final QName name) {
465         for (GroupingDefinition grouping : module.getGroupings()) {
466             if (grouping.getQName().equals(name)) {
467                 return grouping;
468             }
469         }
470         return null;
471     }
472
473     /**
474      * Utility method which search for original node defined in grouping.
475      *
476      * @param node
477      * @return
478      */
479     public static DataSchemaNode findOriginal(final DataSchemaNode node, final SchemaContext ctx) {
480         DataSchemaNode result = findCorrectTargetFromGrouping(node, ctx);
481         if (result == null) {
482             result = findCorrectTargetFromAugment(node, ctx);
483             if (result != null) {
484                 if (result.isAddedByUses()) {
485                     result = findOriginal(result, ctx);
486                 }
487             }
488         }
489         return result;
490     }
491
492     private static DataSchemaNode findCorrectImmediateTargetFromGrouping(final DataSchemaNode node, final SchemaContext ctx) {
493         // uses is under module statement
494         final Module m = findParentModule(ctx, node);
495         Preconditions.checkArgument(m != null, "Failed to find module for node {} in context {}", node, ctx);
496
497         for (final UsesNode u : m.getUses()) {
498             final SchemaNode targetGrouping = findNodeInSchemaContext(ctx, u.getGroupingPath().getPath());
499             Preconditions.checkArgument(targetGrouping instanceof GroupingDefinition,
500                     "Failed to generate code for augment in %s", u);
501
502             LOG.trace("Checking grouping {} for node {}", targetGrouping, node);
503             final GroupingDefinition gr = (GroupingDefinition) targetGrouping;
504             final DataSchemaNode result = gr.getDataChildByName(node.getQName().getLocalName());
505             if (result != null) {
506                 return result;
507             }
508
509             LOG.debug("Skipped grouping {}, no matching node found", gr);
510         }
511
512         throw new IllegalArgumentException(
513                 String.format("Failed to find uses node matching {} in context {}", node, ctx));
514     }
515
516     private static DataSchemaNode findCorrectTargetFromGrouping(final DataSchemaNode node, final SchemaContext ctx) {
517         if (node.getPath().getPath().size() != 1) {
518             QName currentName = node.getQName();
519             // tmpPath is used to track level of nesting
520             List<QName> tmpPath = new ArrayList<>();
521             Object parent = null;
522
523             // create schema path of parent node
524             SchemaPath sp = node.getPath().getParent();
525             parent = findDataSchemaNode(ctx, sp);
526
527             do {
528                 tmpPath.add(currentName);
529
530                 DataSchemaNode result = null;
531                 // search parent node's used groupings for presence of wanted
532                 // node
533                 if (parent instanceof DataNodeContainer) {
534                     DataNodeContainer dataNodeParent = (DataNodeContainer) parent;
535                     for (UsesNode u : dataNodeParent.getUses()) {
536                         result = getResultFromUses(u, currentName.getLocalName(), ctx);
537                         if (result != null) {
538                             break;
539                         }
540                     }
541                 }
542
543                 // if node is not found in any of current parent's used
544                 // groupings => parent is added by grouping too, so repeat same
545                 // process for parent
546                 if (result == null) {
547                     final SchemaNode sn = (SchemaNode) parent;
548
549                     // set current name to name of parent node
550                     currentName = sn.getQName();
551                     Preconditions.checkArgument(parent instanceof SchemaNode,
552                             "Failed to generate code for augmend node {} at parent {}", node, parent);
553
554                     // create schema path for parent of current parent
555                     final SchemaPath parentSp = sn.getPath().getParent();
556                     parent = parentSp.getPathFromRoot().iterator().hasNext() ? findDataSchemaNode(ctx, parentSp)
557                             : getParentModule(sn, ctx);
558                 } else {
559                     // if wanted node was found in grouping, traverse this node
560                     // based on level of nesting
561                     return getTargetNode(tmpPath, result, ctx);
562                 }
563             } while (!(parent instanceof Module));
564
565             return null;
566         } else {
567             return findCorrectImmediateTargetFromGrouping(node, ctx);
568         }
569     }
570
571     private static DataSchemaNode findCorrectTargetFromAugment(final DataSchemaNode node, final SchemaContext ctx) {
572         if (!node.isAugmenting()) {
573             return null;
574         }
575
576         QName currentName = node.getQName();
577         Object currentNode = node;
578         Object parent = node;
579         List<QName> tmpPath = new ArrayList<QName>();
580         List<SchemaNode> tmpTree = new ArrayList<SchemaNode>();
581
582         AugmentationSchema augment = null;
583         do {
584             SchemaPath sp = ((SchemaNode) parent).getPath();
585             parent = findDataSchemaNode(ctx, sp.getParent());
586             if (parent instanceof AugmentationTarget) {
587                 tmpPath.add(currentName);
588                 tmpTree.add((SchemaNode) currentNode);
589                 augment = findNodeInAugment(((AugmentationTarget) parent).getAvailableAugmentations(), currentName);
590                 if (augment == null) {
591                     currentName = ((DataSchemaNode) parent).getQName();
592                     currentNode = parent;
593                 }
594             }
595         } while (((DataSchemaNode) parent).isAugmenting() && augment == null);
596
597         if (augment == null) {
598             return null;
599         } else {
600             Collections.reverse(tmpPath);
601             Collections.reverse(tmpTree);
602             Object actualParent = augment;
603             DataSchemaNode result = null;
604             for (QName name : tmpPath) {
605                 if (actualParent instanceof DataNodeContainer) {
606                     result = ((DataNodeContainer) actualParent).getDataChildByName(name.getLocalName());
607                     actualParent = ((DataNodeContainer) actualParent).getDataChildByName(name.getLocalName());
608                 } else {
609                     if (actualParent instanceof ChoiceNode) {
610                         result = ((ChoiceNode) actualParent).getCaseNodeByName(name.getLocalName());
611                         actualParent = ((ChoiceNode) actualParent).getCaseNodeByName(name.getLocalName());
612                     }
613                 }
614             }
615
616             if (result.isAddedByUses()) {
617                 result = findCorrectTargetFromAugmentGrouping(result, augment, tmpTree, ctx);
618             }
619
620             return result;
621         }
622     }
623
624     private static DataSchemaNode getResultFromUses(final UsesNode u, final String currentName, final SchemaContext ctx) {
625         SchemaNode targetGrouping = findNodeInSchemaContext(ctx, u.getGroupingPath().getPath());
626
627         Preconditions.checkArgument(targetGrouping instanceof GroupingDefinition,
628                 "Failed to generate code for augment in %s", u);
629         GroupingDefinition gr = (GroupingDefinition) targetGrouping;
630         return gr.getDataChildByName(currentName);
631     }
632
633     private static Module getParentModule(final SchemaNode node, final SchemaContext ctx) {
634         QName qname = node.getPath().getPathFromRoot().iterator().next();
635         URI namespace = qname.getNamespace();
636         Date revision = qname.getRevision();
637         return ctx.findModuleByNamespaceAndRevision(namespace, revision);
638     }
639
640     private static DataSchemaNode getTargetNode(final List<QName> tmpPath, final DataSchemaNode node, final SchemaContext ctx) {
641         DataSchemaNode result = node;
642         if (tmpPath.size() == 1) {
643             if (result != null && result.isAddedByUses()) {
644                 result = findOriginal(result, ctx);
645             }
646             return result;
647         } else {
648             DataSchemaNode newParent = result;
649             Collections.reverse(tmpPath);
650
651             tmpPath.remove(0);
652             for (QName name : tmpPath) {
653                 // searching by local name is must, because node has different
654                 // namespace in its original location
655                 if (newParent == null) {
656                     break;
657                 }
658                 if (newParent instanceof DataNodeContainer) {
659                     newParent = ((DataNodeContainer) newParent).getDataChildByName(name.getLocalName());
660                 } else {
661                     newParent = ((ChoiceNode) newParent).getCaseNodeByName(name.getLocalName());
662                 }
663             }
664             if (newParent != null && newParent.isAddedByUses()) {
665                 newParent = findOriginal(newParent, ctx);
666             }
667             return newParent;
668         }
669     }
670
671     private static AugmentationSchema findNodeInAugment(final Collection<AugmentationSchema> augments, final QName name) {
672         for (AugmentationSchema augment : augments) {
673             DataSchemaNode node = augment.getDataChildByName(name);
674             if (node != null) {
675                 return augment;
676             }
677         }
678         return null;
679     }
680
681     private static DataSchemaNode findCorrectTargetFromAugmentGrouping(final DataSchemaNode node,
682             final AugmentationSchema parentNode, final List<SchemaNode> dataTree, final SchemaContext ctx) {
683
684         DataSchemaNode result = null;
685         QName currentName = node.getQName();
686         List<QName> tmpPath = new ArrayList<>();
687         tmpPath.add(currentName);
688         int i = 1;
689         Object parent = null;
690
691         do {
692             if (dataTree.size() < 2 || dataTree.size() == i) {
693                 parent = parentNode;
694             } else {
695                 parent = dataTree.get(dataTree.size() - (i + 1));
696                 tmpPath.add(((SchemaNode) parent).getQName());
697             }
698
699             if (parent instanceof DataNodeContainer) {
700                 DataNodeContainer dataNodeParent = (DataNodeContainer) parent;
701                 for (UsesNode u : dataNodeParent.getUses()) {
702                     if (result == null) {
703                         result = getResultFromUses(u, currentName.getLocalName(), ctx);
704                     }
705                 }
706             }
707
708             if (result == null) {
709                 i = i + 1;
710                 currentName = ((SchemaNode) parent).getQName();
711             }
712         } while (result == null);
713
714         if (result != null) {
715             result = getTargetNode(tmpPath, result, ctx);
716         }
717         return result;
718     }
719
720     /**
721      * Transforms string representation of XPath to Queue of QNames. The XPath
722      * is split by "/" and for each part of XPath is assigned correct module in
723      * Schema Path. <br>
724      * If Schema Context, Parent Module or XPath string contains
725      * <code>null</code> values, the method will throws IllegalArgumentException
726      *
727      * @throws IllegalArgumentException
728      *
729      * @param context
730      *            Schema Context
731      * @param parentModule
732      *            Parent Module
733      * @param xpath
734      *            XPath String
735      * @return return a list of QName
736      */
737     private static List<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule, final String xpath) {
738         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
739         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
740         Preconditions.checkArgument(xpath != null, "XPath string reference cannot be NULL");
741
742         List<QName> path = new LinkedList<QName>();
743         String[] prefixedPath = xpath.split("/");
744         for (String pathComponent : prefixedPath) {
745             if (!pathComponent.isEmpty()) {
746                 path.add(stringPathPartToQName(context, parentModule, pathComponent));
747             }
748         }
749         return path;
750     }
751
752     /**
753      * Transforms part of Prefixed Path as java String to QName. <br>
754      * If the string contains module prefix separated by ":" (i.e.
755      * mod:container) this module is provided from from Parent Module list of
756      * imports. If the Prefixed module is present in Schema Context the QName
757      * can be constructed. <br>
758      * If the Prefixed Path Part does not contains prefix the Parent's Module
759      * namespace is taken for construction of QName. <br>
760      * If Schema Context, Parent Module or Prefixed Path Part refers to
761      * <code>null</code> the method will throw IllegalArgumentException
762      *
763      * @throws IllegalArgumentException
764      *
765      * @param context
766      *            Schema Context
767      * @param parentModule
768      *            Parent Module
769      * @param prefixedPathPart
770      *            Prefixed Path Part string
771      * @return QName from prefixed Path Part String.
772      */
773     private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule, final String prefixedPathPart) {
774         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
775         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
776         Preconditions.checkArgument(prefixedPathPart != null, "Prefixed Path Part cannot be NULL!");
777
778         if (prefixedPathPart.contains(":")) {
779             String[] prefixedName = prefixedPathPart.split(":");
780             Module module = resolveModuleForPrefix(context, parentModule, prefixedName[0]);
781             Preconditions.checkArgument(module != null, "Failed to resolve xpath: no module found for prefix %s in module %s",
782                     prefixedName[0], parentModule.getName());
783             return new QName(module.getNamespace(), module.getRevision(), prefixedName[1]);
784         } else {
785             return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
786         }
787     }
788
789     /**
790      * Method will attempt to resolve and provide Module reference for specified
791      * module prefix. Each Yang module could contains multiple imports which
792      * MUST be associated with corresponding module prefix. The method simply
793      * looks into module imports and returns the module that is bounded with
794      * specified prefix. If the prefix is not present in module or the prefixed
795      * module is not present in specified Schema Context, the method will return
796      * <code>null</code>. <br>
797      * If String prefix is the same as prefix of the specified Module the
798      * reference to this module is returned. <br>
799      * If Schema Context, Module or Prefix are referring to <code>null</code>
800      * the method will return IllegalArgumentException
801      *
802      * @throws IllegalArgumentException
803      *
804      * @param context
805      *            Schema Context
806      * @param module
807      *            Yang Module
808      * @param prefix
809      *            Module Prefix
810      * @return Module for given prefix in specified Schema Context if is
811      *         present, otherwise returns <code>null</code>
812      */
813     private static Module resolveModuleForPrefix(final SchemaContext context, final Module module, final String prefix) {
814         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
815         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
816         Preconditions.checkArgument(prefix != null, "Prefix string cannot be NULL");
817
818         if (prefix.equals(module.getPrefix())) {
819             return module;
820         }
821
822         Set<ModuleImport> imports = module.getImports();
823         for (ModuleImport mi : imports) {
824             if (prefix.equals(mi.getPrefix())) {
825                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
826             }
827         }
828         return null;
829     }
830
831     /**
832      * @throws IllegalArgumentException
833      *
834      * @param context
835      *            Schema Context
836      * @param module
837      *            Yang Module
838      * @param relativeXPath
839      *            Non conditional Revision Aware Relative XPath
840      * @param leafrefSchemaPath
841      *            Schema Path for Leafref
842      * @return list of QName
843      */
844     private static List<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
845             final RevisionAwareXPath relativeXPath, final SchemaNode leafrefParentNode) {
846         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
847         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
848         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
849         Preconditions.checkState(!relativeXPath.isAbsolute(),
850                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
851                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
852         Preconditions.checkState(leafrefParentNode.getPath() != null,
853                 "Schema Path reference for Leafref cannot be NULL");
854
855         List<QName> absolutePath = new LinkedList<QName>();
856         String strXPath = relativeXPath.toString();
857         String[] xpaths = strXPath.split("/");
858
859         int colCount = 0;
860         while (xpaths[colCount].contains("..")) {
861             colCount = colCount + 1;
862         }
863         List<QName> path = leafrefParentNode.getPath().getPath();
864         if (path != null) {
865             int lenght = path.size() - colCount;
866             absolutePath.addAll(path.subList(0, lenght));
867             List<String> xpathsList = Arrays.asList(xpaths);
868             List<String> sublistedXPath = xpathsList.subList(colCount, xpaths.length);
869             List<QName> sublist = new ArrayList<>();
870             for (String pathPart : sublistedXPath) {
871                 sublist.add(stringPathPartToQName(context, module, pathPart));
872             }
873             absolutePath.addAll(sublist);
874         }
875
876         return absolutePath;
877     }
878 }