BUG-1024: reorganize code for clarity
[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 java.net.URI;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Date;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.Set;
20 import org.opendaylight.yangtools.yang.common.QName;
21 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
22 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
23 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
24 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
25 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
27 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
29 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.Module;
31 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
32 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
33 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
34 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
35 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
36 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
38 import org.opendaylight.yangtools.yang.model.api.UsesNode;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * The Schema Context Util contains support methods for searching through Schema
44  * Context modules for specified schema nodes via Schema Path or Revision Aware
45  * XPath. The Schema Context Util is designed as mixin, so it is not
46  * instantiable.
47  *
48  */
49 public final class SchemaContextUtil {
50     private static final Logger LOG = LoggerFactory.getLogger(SchemaContextUtil.class);
51
52     private SchemaContextUtil() {
53     }
54
55     /**
56      * Method attempts to find DataSchemaNode in Schema Context via specified
57      * Schema Path. The returned DataSchemaNode from method will be the node at
58      * the end of the SchemaPath. If the DataSchemaNode is not present in the
59      * Schema Context the method will return <code>null</code>. <br>
60      * In case that Schema Context or Schema Path are not specified correctly
61      * (i.e. contains <code>null</code> values) the method will return
62      * IllegalArgumentException.
63      *
64      * @throws IllegalArgumentException
65      *
66      * @param context
67      *            Schema Context
68      * @param schemaPath
69      *            Schema Path to search for
70      * @return SchemaNode from the end of the Schema Path or <code>null</code>
71      *         if the Node is not present.
72      */
73     public static SchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
74         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
75         Preconditions.checkArgument(schemaPath != null, "Schema Path reference cannot be NULL");
76
77         final List<QName> prefixedPath = (schemaPath.getPath());
78         if (prefixedPath == null) {
79             LOG.debug("Schema path {} has null path", schemaPath);
80             return null;
81         }
82
83         LOG.trace("Looking for path {} in context {}", schemaPath, context);
84         return findNodeInSchemaContext(context, prefixedPath);
85     }
86
87     /**
88      * Method attempts to find DataSchemaNode inside of provided Schema Context
89      * and Yang Module accordingly to Non-conditional Revision Aware XPath. The
90      * specified Module MUST be present in Schema Context otherwise the
91      * operation would fail and return <code>null</code>. <br>
92      * The Revision Aware XPath MUST be specified WITHOUT the conditional
93      * statement (i.e. without [cond]) in path, because in this state the Schema
94      * Context is completely unaware of data state and will be not able to
95      * properly resolve XPath. If the XPath contains condition the method will
96      * return IllegalArgumentException. <br>
97      * In case that Schema Context or Module or Revision Aware XPath contains
98      * <code>null</code> references the method will throw
99      * IllegalArgumentException <br>
100      * If the Revision Aware XPath is correct and desired Data Schema Node is
101      * present in Yang module or in depending module in Schema Context the
102      * method will return specified Data Schema Node, otherwise the operation
103      * will fail and method will return <code>null</code>.
104      *
105      * @throws IllegalArgumentException
106      *
107      * @param context
108      *            Schema Context
109      * @param module
110      *            Yang Module
111      * @param nonCondXPath
112      *            Non Conditional Revision Aware XPath
113      * @return Returns Data Schema Node for specified Schema Context for given
114      *         Non-conditional Revision Aware XPath, or <code>null</code> if the
115      *         DataSchemaNode is not present in Schema Context.
116      */
117     public static SchemaNode findDataSchemaNode(final SchemaContext context, final Module module, final RevisionAwareXPath nonCondXPath) {
118         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
119         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
120         Preconditions.checkArgument(nonCondXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
121
122         String strXPath = nonCondXPath.toString();
123         if (strXPath != null) {
124             Preconditions.checkArgument(strXPath.indexOf('[') == -1, "Revision Aware XPath may not contain a condition");
125             if (nonCondXPath.isAbsolute()) {
126                 List<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
127                 if (qnamedPath != null) {
128                     return findNodeInSchemaContext(context, qnamedPath);
129                 }
130             }
131         }
132         return null;
133     }
134
135     /**
136      * Method attempts to find DataSchemaNode inside of provided Schema Context
137      * and Yang Module accordingly to Non-conditional relative Revision Aware
138      * XPath. The specified Module MUST be present in Schema Context otherwise
139      * the operation would fail and return <code>null</code>. <br>
140      * The relative Revision Aware XPath MUST be specified WITHOUT the
141      * conditional statement (i.e. without [cond]) in path, because in this
142      * state the Schema Context is completely unaware of data state and will be
143      * not able to properly resolve XPath. If the XPath contains condition the
144      * method will return IllegalArgumentException. <br>
145      * The Actual Schema Node MUST be specified correctly because from this
146      * Schema Node will search starts. If the Actual Schema Node is not correct
147      * the operation will simply fail, because it will be unable to find desired
148      * DataSchemaNode. <br>
149      * In case that Schema Context or Module or Actual Schema Node or relative
150      * Revision Aware XPath contains <code>null</code> references the method
151      * will throw IllegalArgumentException <br>
152      * If the Revision Aware XPath doesn't have flag
153      * <code>isAbsolute == false</code> the method will throw
154      * IllegalArgumentException. <br>
155      * If the relative Revision Aware XPath is correct and desired Data Schema
156      * Node is present in Yang module or in depending module in Schema Context
157      * the method will return specified Data Schema Node, otherwise the
158      * operation will fail and method will return <code>null</code>.
159      *
160      * @throws IllegalArgumentException
161      *
162      * @param context
163      *            Schema Context
164      * @param module
165      *            Yang Module
166      * @param actualSchemaNode
167      *            Actual Schema Node
168      * @param relativeXPath
169      *            Relative Non Conditional Revision Aware XPath
170      * @return DataSchemaNode if is present in specified Schema Context for
171      *         given relative Revision Aware XPath, otherwise will return
172      *         <code>null</code>.
173      */
174     public static SchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
175             final SchemaNode actualSchemaNode, final RevisionAwareXPath relativeXPath) {
176         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
177         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
178         Preconditions.checkArgument(actualSchemaNode != null, "Actual Schema Node reference cannot be NULL");
179         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
180         Preconditions.checkState(!relativeXPath.isAbsolute(),
181                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
182                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
183
184         SchemaPath actualNodePath = actualSchemaNode.getPath();
185         if (actualNodePath != null) {
186             List<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
187
188             if (qnamePath != null) {
189                 return findNodeInSchemaContext(context, qnamePath);
190             }
191         }
192         return null;
193     }
194
195     /**
196      * Returns parent Yang Module for specified Schema Context in which Schema
197      * Node is declared. If the Schema Node is not present in Schema Context the
198      * operation will return <code>null</code>. <br>
199      * If Schema Context or Schema Node contains <code>null</code> references
200      * the method will throw IllegalArgumentException
201      *
202      * @throws IllegalArgumentException
203      *
204      * @param context
205      *            Schema Context
206      * @param schemaNode
207      *            Schema Node
208      * @return Yang Module for specified Schema Context and Schema Node, if
209      *         Schema Node is NOT present, the method will returns
210      *         <code>null</code>
211      */
212     public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
213         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL!");
214         Preconditions.checkArgument(schemaNode != null, "Schema Node cannot be NULL!");
215         Preconditions.checkState(schemaNode.getPath() != null, "Schema Path for Schema Node is not "
216                 + "set properly (Schema Path is NULL)");
217
218         List<QName> qnamedPath = schemaNode.getPath().getPath();
219         if (qnamedPath == null || qnamedPath.isEmpty()) {
220             throw new IllegalStateException("Schema Path contains invalid state of path parts."
221                     + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
222                     + "of path.");
223         }
224         QName qname = qnamedPath.get(qnamedPath.size() - 1);
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();
525             List<QName> newNames = new ArrayList<>(sp.getPath());
526             // parentPath = nodePath - lastQName
527             newNames.remove(newNames.size() - 1);
528             SchemaPath newSp = SchemaPath.create(newNames, sp.isAbsolute());
529             // find parent node by its schema path
530             parent = findDataSchemaNode(ctx, newSp);
531
532             do {
533                 tmpPath.add(currentName);
534
535                 DataSchemaNode result = null;
536                 // search parent node's used groupings for presence of wanted
537                 // node
538                 if (parent instanceof DataNodeContainer) {
539                     DataNodeContainer dataNodeParent = (DataNodeContainer) parent;
540                     for (UsesNode u : dataNodeParent.getUses()) {
541                         result = getResultFromUses(u, currentName.getLocalName(), ctx);
542                         if (result != null) {
543                             break;
544                         }
545                     }
546                 }
547
548                 // if node is not found in any of current parent's used
549                 // groupings => parent is added by grouping too, so repeat same
550                 // process for parent
551                 if (result == null) {
552                     // set current name to name of parent node
553                     currentName = ((SchemaNode) parent).getQName();
554                     Preconditions.checkArgument(parent instanceof SchemaNode,
555                             "Failed to generate code for augmend node {} at parent {}", node, parent);
556                     // create schema path for parent of current parent
557                     SchemaPath nodeSp = ((SchemaNode) parent).getPath();
558                     List<QName> nodeNewNames = new ArrayList<>(nodeSp.getPath());
559                     nodeNewNames.remove(nodeNewNames.size() - 1);
560                     // set new parent based on path
561                     if (nodeNewNames.isEmpty()) {
562                         parent = getParentModule((SchemaNode) parent, ctx);
563                     } else {
564                         SchemaPath nodeNewSp = new SchemaPath(nodeNewNames, nodeSp.isAbsolute());
565                         parent = findDataSchemaNode(ctx, nodeNewSp);
566                     }
567                 } else {
568                     // if wanted node was found in grouping, traverse this node
569                     // based on level of nesting
570                     return getTargetNode(tmpPath, result, ctx);
571                 }
572             } while (!(parent instanceof Module));
573
574             return null;
575         } else {
576             return findCorrectImmediateTargetFromGrouping(node, ctx);
577         }
578     }
579
580     private static DataSchemaNode findCorrectTargetFromAugment(final DataSchemaNode node, final SchemaContext ctx) {
581         if (!node.isAugmenting()) {
582             return null;
583         }
584
585         QName currentName = node.getQName();
586         Object currentNode = node;
587         Object parent = node;
588         List<QName> tmpPath = new ArrayList<QName>();
589         List<SchemaNode> tmpTree = new ArrayList<SchemaNode>();
590
591         AugmentationSchema augment = null;
592         do {
593             SchemaPath sp = ((SchemaNode) parent).getPath();
594             List<QName> names = sp.getPath();
595             List<QName> newNames = new ArrayList<>(names);
596             newNames.remove(newNames.size() - 1);
597             SchemaPath newSp = SchemaPath.create(newNames, sp.isAbsolute());
598             parent = findDataSchemaNode(ctx, newSp);
599             if (parent instanceof AugmentationTarget) {
600                 tmpPath.add(currentName);
601                 tmpTree.add((SchemaNode) currentNode);
602                 augment = findNodeInAugment(((AugmentationTarget) parent).getAvailableAugmentations(), currentName);
603                 if (augment == null) {
604                     currentName = ((DataSchemaNode) parent).getQName();
605                     currentNode = parent;
606                 }
607             }
608         } while (((DataSchemaNode) parent).isAugmenting() && augment == null);
609
610         if (augment == null) {
611             return null;
612         } else {
613             Collections.reverse(tmpPath);
614             Collections.reverse(tmpTree);
615             Object actualParent = augment;
616             DataSchemaNode result = null;
617             for (QName name : tmpPath) {
618                 if (actualParent instanceof DataNodeContainer) {
619                     result = ((DataNodeContainer) actualParent).getDataChildByName(name.getLocalName());
620                     actualParent = ((DataNodeContainer) actualParent).getDataChildByName(name.getLocalName());
621                 } else {
622                     if (actualParent instanceof ChoiceNode) {
623                         result = ((ChoiceNode) actualParent).getCaseNodeByName(name.getLocalName());
624                         actualParent = ((ChoiceNode) actualParent).getCaseNodeByName(name.getLocalName());
625                     }
626                 }
627             }
628
629             if (result.isAddedByUses()) {
630                 result = findCorrectTargetFromAugmentGrouping(result, augment, tmpTree, ctx);
631             }
632
633             return result;
634         }
635     }
636
637     private static DataSchemaNode getResultFromUses(final UsesNode u, final String currentName, final SchemaContext ctx) {
638         SchemaNode targetGrouping = findNodeInSchemaContext(ctx, u.getGroupingPath().getPath());
639
640         Preconditions.checkArgument(targetGrouping instanceof GroupingDefinition,
641                 "Failed to generate code for augment in %s", u);
642         GroupingDefinition gr = (GroupingDefinition) targetGrouping;
643         return gr.getDataChildByName(currentName);
644     }
645
646     private static Module getParentModule(final SchemaNode node, final SchemaContext ctx) {
647         QName qname = node.getPath().getPath().get(0);
648         URI namespace = qname.getNamespace();
649         Date revision = qname.getRevision();
650         return ctx.findModuleByNamespaceAndRevision(namespace, revision);
651     }
652
653     private static DataSchemaNode getTargetNode(final List<QName> tmpPath, final DataSchemaNode node, final SchemaContext ctx) {
654         DataSchemaNode result = node;
655         if (tmpPath.size() == 1) {
656             if (result != null && result.isAddedByUses()) {
657                 result = findOriginal(result, ctx);
658             }
659             return result;
660         } else {
661             DataSchemaNode newParent = result;
662             Collections.reverse(tmpPath);
663
664             tmpPath.remove(0);
665             for (QName name : tmpPath) {
666                 // searching by local name is must, because node has different
667                 // namespace in its original location
668                 if (newParent == null) {
669                     break;
670                 }
671                 if (newParent instanceof DataNodeContainer) {
672                     newParent = ((DataNodeContainer) newParent).getDataChildByName(name.getLocalName());
673                 } else {
674                     newParent = ((ChoiceNode) newParent).getCaseNodeByName(name.getLocalName());
675                 }
676             }
677             if (newParent != null && newParent.isAddedByUses()) {
678                 newParent = findOriginal(newParent, ctx);
679             }
680             return newParent;
681         }
682     }
683
684     private static AugmentationSchema findNodeInAugment(final Collection<AugmentationSchema> augments, final QName name) {
685         for (AugmentationSchema augment : augments) {
686             DataSchemaNode node = augment.getDataChildByName(name);
687             if (node != null) {
688                 return augment;
689             }
690         }
691         return null;
692     }
693
694     private static DataSchemaNode findCorrectTargetFromAugmentGrouping(final DataSchemaNode node,
695             final AugmentationSchema parentNode, final List<SchemaNode> dataTree, final SchemaContext ctx) {
696
697         DataSchemaNode result = null;
698         QName currentName = node.getQName();
699         List<QName> tmpPath = new ArrayList<>();
700         tmpPath.add(currentName);
701         int i = 1;
702         Object parent = null;
703
704         do {
705             if (dataTree.size() < 2 || dataTree.size() == i) {
706                 parent = parentNode;
707             } else {
708                 parent = dataTree.get(dataTree.size() - (i + 1));
709                 tmpPath.add(((SchemaNode) parent).getQName());
710             }
711
712             if (parent instanceof DataNodeContainer) {
713                 DataNodeContainer dataNodeParent = (DataNodeContainer) parent;
714                 for (UsesNode u : dataNodeParent.getUses()) {
715                     if (result == null) {
716                         result = getResultFromUses(u, currentName.getLocalName(), ctx);
717                     }
718                 }
719             }
720
721             if (result == null) {
722                 i = i + 1;
723                 currentName = ((SchemaNode) parent).getQName();
724             }
725         } while (result == null);
726
727         if (result != null) {
728             result = getTargetNode(tmpPath, result, ctx);
729         }
730         return result;
731     }
732
733     /**
734      * Transforms string representation of XPath to Queue of QNames. The XPath
735      * is split by "/" and for each part of XPath is assigned correct module in
736      * Schema Path. <br>
737      * If Schema Context, Parent Module or XPath string contains
738      * <code>null</code> values, the method will throws IllegalArgumentException
739      *
740      * @throws IllegalArgumentException
741      *
742      * @param context
743      *            Schema Context
744      * @param parentModule
745      *            Parent Module
746      * @param xpath
747      *            XPath String
748      * @return return a list of QName
749      */
750     private static List<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule, final String xpath) {
751         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
752         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
753         Preconditions.checkArgument(xpath != null, "XPath string reference cannot be NULL");
754
755         List<QName> path = new LinkedList<QName>();
756         String[] prefixedPath = xpath.split("/");
757         for (String pathComponent : prefixedPath) {
758             if (!pathComponent.isEmpty()) {
759                 path.add(stringPathPartToQName(context, parentModule, pathComponent));
760             }
761         }
762         return path;
763     }
764
765     /**
766      * Transforms part of Prefixed Path as java String to QName. <br>
767      * If the string contains module prefix separated by ":" (i.e.
768      * mod:container) this module is provided from from Parent Module list of
769      * imports. If the Prefixed module is present in Schema Context the QName
770      * can be constructed. <br>
771      * If the Prefixed Path Part does not contains prefix the Parent's Module
772      * namespace is taken for construction of QName. <br>
773      * If Schema Context, Parent Module or Prefixed Path Part refers to
774      * <code>null</code> the method will throw IllegalArgumentException
775      *
776      * @throws IllegalArgumentException
777      *
778      * @param context
779      *            Schema Context
780      * @param parentModule
781      *            Parent Module
782      * @param prefixedPathPart
783      *            Prefixed Path Part string
784      * @return QName from prefixed Path Part String.
785      */
786     private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule, final String prefixedPathPart) {
787         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
788         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
789         Preconditions.checkArgument(prefixedPathPart != null, "Prefixed Path Part cannot be NULL!");
790
791         if (prefixedPathPart.contains(":")) {
792             String[] prefixedName = prefixedPathPart.split(":");
793             Module module = resolveModuleForPrefix(context, parentModule, prefixedName[0]);
794             Preconditions.checkArgument(module != null, "Failed to resolve xpath: no module found for prefix %s in module %s",
795                     prefixedName[0], parentModule.getName());
796             return new QName(module.getNamespace(), module.getRevision(), prefixedName[1]);
797         } else {
798             return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
799         }
800     }
801
802     /**
803      * Method will attempt to resolve and provide Module reference for specified
804      * module prefix. Each Yang module could contains multiple imports which
805      * MUST be associated with corresponding module prefix. The method simply
806      * looks into module imports and returns the module that is bounded with
807      * specified prefix. If the prefix is not present in module or the prefixed
808      * module is not present in specified Schema Context, the method will return
809      * <code>null</code>. <br>
810      * If String prefix is the same as prefix of the specified Module the
811      * reference to this module is returned. <br>
812      * If Schema Context, Module or Prefix are referring to <code>null</code>
813      * the method will return IllegalArgumentException
814      *
815      * @throws IllegalArgumentException
816      *
817      * @param context
818      *            Schema Context
819      * @param module
820      *            Yang Module
821      * @param prefix
822      *            Module Prefix
823      * @return Module for given prefix in specified Schema Context if is
824      *         present, otherwise returns <code>null</code>
825      */
826     private static Module resolveModuleForPrefix(final SchemaContext context, final Module module, final String prefix) {
827         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
828         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
829         Preconditions.checkArgument(prefix != null, "Prefix string cannot be NULL");
830
831         if (prefix.equals(module.getPrefix())) {
832             return module;
833         }
834
835         Set<ModuleImport> imports = module.getImports();
836         for (ModuleImport mi : imports) {
837             if (prefix.equals(mi.getPrefix())) {
838                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
839             }
840         }
841         return null;
842     }
843
844     /**
845      * @throws IllegalArgumentException
846      *
847      * @param context
848      *            Schema Context
849      * @param module
850      *            Yang Module
851      * @param relativeXPath
852      *            Non conditional Revision Aware Relative XPath
853      * @param leafrefSchemaPath
854      *            Schema Path for Leafref
855      * @return list of QName
856      */
857     private static List<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
858             final RevisionAwareXPath relativeXPath, final SchemaNode leafrefParentNode) {
859         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
860         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
861         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
862         Preconditions.checkState(!relativeXPath.isAbsolute(),
863                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
864                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
865         Preconditions.checkState(leafrefParentNode.getPath() != null,
866                 "Schema Path reference for Leafref cannot be NULL");
867
868         List<QName> absolutePath = new LinkedList<QName>();
869         String strXPath = relativeXPath.toString();
870         String[] xpaths = strXPath.split("/");
871
872         int colCount = 0;
873         while (xpaths[colCount].contains("..")) {
874             colCount = colCount + 1;
875         }
876         List<QName> path = leafrefParentNode.getPath().getPath();
877         if (path != null) {
878             int lenght = path.size() - colCount;
879             absolutePath.addAll(path.subList(0, lenght));
880             List<String> xpathsList = Arrays.asList(xpaths);
881             List<String> sublistedXPath = xpathsList.subList(colCount, xpaths.length);
882             List<QName> sublist = new ArrayList<>();
883             for (String pathPart : sublistedXPath) {
884                 sublist.add(stringPathPartToQName(context, module, pathPart));
885             }
886             absolutePath.addAll(sublist);
887         }
888
889         return absolutePath;
890     }
891 }