Merge "BUG-994: Use SchemaPath.getParent()"
[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                     final SchemaNode sn = (SchemaNode) parent;
553
554                     // set current name to name of parent node
555                     currentName = sn.getQName();
556                     Preconditions.checkArgument(parent instanceof SchemaNode,
557                             "Failed to generate code for augmend node {} at parent {}", node, parent);
558
559                     // create schema path for parent of current parent
560                     final SchemaPath parentSp = sn.getPath().getParent();
561                     parent = parentSp.getPathFromRoot().iterator().hasNext() ? findDataSchemaNode(ctx, parentSp)
562                             : getParentModule(sn, ctx);
563                 } else {
564                     // if wanted node was found in grouping, traverse this node
565                     // based on level of nesting
566                     return getTargetNode(tmpPath, result, ctx);
567                 }
568             } while (!(parent instanceof Module));
569
570             return null;
571         } else {
572             return findCorrectImmediateTargetFromGrouping(node, ctx);
573         }
574     }
575
576     private static DataSchemaNode findCorrectTargetFromAugment(final DataSchemaNode node, final SchemaContext ctx) {
577         if (!node.isAugmenting()) {
578             return null;
579         }
580
581         QName currentName = node.getQName();
582         Object currentNode = node;
583         Object parent = node;
584         List<QName> tmpPath = new ArrayList<QName>();
585         List<SchemaNode> tmpTree = new ArrayList<SchemaNode>();
586
587         AugmentationSchema augment = null;
588         do {
589             SchemaPath sp = ((SchemaNode) parent).getPath();
590             List<QName> names = sp.getPath();
591             List<QName> newNames = new ArrayList<>(names);
592             newNames.remove(newNames.size() - 1);
593             SchemaPath newSp = SchemaPath.create(newNames, sp.isAbsolute());
594             parent = findDataSchemaNode(ctx, newSp);
595             if (parent instanceof AugmentationTarget) {
596                 tmpPath.add(currentName);
597                 tmpTree.add((SchemaNode) currentNode);
598                 augment = findNodeInAugment(((AugmentationTarget) parent).getAvailableAugmentations(), currentName);
599                 if (augment == null) {
600                     currentName = ((DataSchemaNode) parent).getQName();
601                     currentNode = parent;
602                 }
603             }
604         } while (((DataSchemaNode) parent).isAugmenting() && augment == null);
605
606         if (augment == null) {
607             return null;
608         } else {
609             Collections.reverse(tmpPath);
610             Collections.reverse(tmpTree);
611             Object actualParent = augment;
612             DataSchemaNode result = null;
613             for (QName name : tmpPath) {
614                 if (actualParent instanceof DataNodeContainer) {
615                     result = ((DataNodeContainer) actualParent).getDataChildByName(name.getLocalName());
616                     actualParent = ((DataNodeContainer) actualParent).getDataChildByName(name.getLocalName());
617                 } else {
618                     if (actualParent instanceof ChoiceNode) {
619                         result = ((ChoiceNode) actualParent).getCaseNodeByName(name.getLocalName());
620                         actualParent = ((ChoiceNode) actualParent).getCaseNodeByName(name.getLocalName());
621                     }
622                 }
623             }
624
625             if (result.isAddedByUses()) {
626                 result = findCorrectTargetFromAugmentGrouping(result, augment, tmpTree, ctx);
627             }
628
629             return result;
630         }
631     }
632
633     private static DataSchemaNode getResultFromUses(final UsesNode u, final String currentName, final SchemaContext ctx) {
634         SchemaNode targetGrouping = findNodeInSchemaContext(ctx, u.getGroupingPath().getPath());
635
636         Preconditions.checkArgument(targetGrouping instanceof GroupingDefinition,
637                 "Failed to generate code for augment in %s", u);
638         GroupingDefinition gr = (GroupingDefinition) targetGrouping;
639         return gr.getDataChildByName(currentName);
640     }
641
642     private static Module getParentModule(final SchemaNode node, final SchemaContext ctx) {
643         QName qname = node.getPath().getPath().get(0);
644         URI namespace = qname.getNamespace();
645         Date revision = qname.getRevision();
646         return ctx.findModuleByNamespaceAndRevision(namespace, revision);
647     }
648
649     private static DataSchemaNode getTargetNode(final List<QName> tmpPath, final DataSchemaNode node, final SchemaContext ctx) {
650         DataSchemaNode result = node;
651         if (tmpPath.size() == 1) {
652             if (result != null && result.isAddedByUses()) {
653                 result = findOriginal(result, ctx);
654             }
655             return result;
656         } else {
657             DataSchemaNode newParent = result;
658             Collections.reverse(tmpPath);
659
660             tmpPath.remove(0);
661             for (QName name : tmpPath) {
662                 // searching by local name is must, because node has different
663                 // namespace in its original location
664                 if (newParent == null) {
665                     break;
666                 }
667                 if (newParent instanceof DataNodeContainer) {
668                     newParent = ((DataNodeContainer) newParent).getDataChildByName(name.getLocalName());
669                 } else {
670                     newParent = ((ChoiceNode) newParent).getCaseNodeByName(name.getLocalName());
671                 }
672             }
673             if (newParent != null && newParent.isAddedByUses()) {
674                 newParent = findOriginal(newParent, ctx);
675             }
676             return newParent;
677         }
678     }
679
680     private static AugmentationSchema findNodeInAugment(final Collection<AugmentationSchema> augments, final QName name) {
681         for (AugmentationSchema augment : augments) {
682             DataSchemaNode node = augment.getDataChildByName(name);
683             if (node != null) {
684                 return augment;
685             }
686         }
687         return null;
688     }
689
690     private static DataSchemaNode findCorrectTargetFromAugmentGrouping(final DataSchemaNode node,
691             final AugmentationSchema parentNode, final List<SchemaNode> dataTree, final SchemaContext ctx) {
692
693         DataSchemaNode result = null;
694         QName currentName = node.getQName();
695         List<QName> tmpPath = new ArrayList<>();
696         tmpPath.add(currentName);
697         int i = 1;
698         Object parent = null;
699
700         do {
701             if (dataTree.size() < 2 || dataTree.size() == i) {
702                 parent = parentNode;
703             } else {
704                 parent = dataTree.get(dataTree.size() - (i + 1));
705                 tmpPath.add(((SchemaNode) parent).getQName());
706             }
707
708             if (parent instanceof DataNodeContainer) {
709                 DataNodeContainer dataNodeParent = (DataNodeContainer) parent;
710                 for (UsesNode u : dataNodeParent.getUses()) {
711                     if (result == null) {
712                         result = getResultFromUses(u, currentName.getLocalName(), ctx);
713                     }
714                 }
715             }
716
717             if (result == null) {
718                 i = i + 1;
719                 currentName = ((SchemaNode) parent).getQName();
720             }
721         } while (result == null);
722
723         if (result != null) {
724             result = getTargetNode(tmpPath, result, ctx);
725         }
726         return result;
727     }
728
729     /**
730      * Transforms string representation of XPath to Queue of QNames. The XPath
731      * is split by "/" and for each part of XPath is assigned correct module in
732      * Schema Path. <br>
733      * If Schema Context, Parent Module or XPath string contains
734      * <code>null</code> values, the method will throws IllegalArgumentException
735      *
736      * @throws IllegalArgumentException
737      *
738      * @param context
739      *            Schema Context
740      * @param parentModule
741      *            Parent Module
742      * @param xpath
743      *            XPath String
744      * @return return a list of QName
745      */
746     private static List<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule, final String xpath) {
747         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
748         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
749         Preconditions.checkArgument(xpath != null, "XPath string reference cannot be NULL");
750
751         List<QName> path = new LinkedList<QName>();
752         String[] prefixedPath = xpath.split("/");
753         for (String pathComponent : prefixedPath) {
754             if (!pathComponent.isEmpty()) {
755                 path.add(stringPathPartToQName(context, parentModule, pathComponent));
756             }
757         }
758         return path;
759     }
760
761     /**
762      * Transforms part of Prefixed Path as java String to QName. <br>
763      * If the string contains module prefix separated by ":" (i.e.
764      * mod:container) this module is provided from from Parent Module list of
765      * imports. If the Prefixed module is present in Schema Context the QName
766      * can be constructed. <br>
767      * If the Prefixed Path Part does not contains prefix the Parent's Module
768      * namespace is taken for construction of QName. <br>
769      * If Schema Context, Parent Module or Prefixed Path Part refers to
770      * <code>null</code> the method will throw IllegalArgumentException
771      *
772      * @throws IllegalArgumentException
773      *
774      * @param context
775      *            Schema Context
776      * @param parentModule
777      *            Parent Module
778      * @param prefixedPathPart
779      *            Prefixed Path Part string
780      * @return QName from prefixed Path Part String.
781      */
782     private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule, final String prefixedPathPart) {
783         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
784         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
785         Preconditions.checkArgument(prefixedPathPart != null, "Prefixed Path Part cannot be NULL!");
786
787         if (prefixedPathPart.contains(":")) {
788             String[] prefixedName = prefixedPathPart.split(":");
789             Module module = resolveModuleForPrefix(context, parentModule, prefixedName[0]);
790             Preconditions.checkArgument(module != null, "Failed to resolve xpath: no module found for prefix %s in module %s",
791                     prefixedName[0], parentModule.getName());
792             return new QName(module.getNamespace(), module.getRevision(), prefixedName[1]);
793         } else {
794             return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
795         }
796     }
797
798     /**
799      * Method will attempt to resolve and provide Module reference for specified
800      * module prefix. Each Yang module could contains multiple imports which
801      * MUST be associated with corresponding module prefix. The method simply
802      * looks into module imports and returns the module that is bounded with
803      * specified prefix. If the prefix is not present in module or the prefixed
804      * module is not present in specified Schema Context, the method will return
805      * <code>null</code>. <br>
806      * If String prefix is the same as prefix of the specified Module the
807      * reference to this module is returned. <br>
808      * If Schema Context, Module or Prefix are referring to <code>null</code>
809      * the method will return IllegalArgumentException
810      *
811      * @throws IllegalArgumentException
812      *
813      * @param context
814      *            Schema Context
815      * @param module
816      *            Yang Module
817      * @param prefix
818      *            Module Prefix
819      * @return Module for given prefix in specified Schema Context if is
820      *         present, otherwise returns <code>null</code>
821      */
822     private static Module resolveModuleForPrefix(final SchemaContext context, final Module module, final String prefix) {
823         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
824         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
825         Preconditions.checkArgument(prefix != null, "Prefix string cannot be NULL");
826
827         if (prefix.equals(module.getPrefix())) {
828             return module;
829         }
830
831         Set<ModuleImport> imports = module.getImports();
832         for (ModuleImport mi : imports) {
833             if (prefix.equals(mi.getPrefix())) {
834                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
835             }
836         }
837         return null;
838     }
839
840     /**
841      * @throws IllegalArgumentException
842      *
843      * @param context
844      *            Schema Context
845      * @param module
846      *            Yang Module
847      * @param relativeXPath
848      *            Non conditional Revision Aware Relative XPath
849      * @param leafrefSchemaPath
850      *            Schema Path for Leafref
851      * @return list of QName
852      */
853     private static List<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
854             final RevisionAwareXPath relativeXPath, final SchemaNode leafrefParentNode) {
855         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
856         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
857         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
858         Preconditions.checkState(!relativeXPath.isAbsolute(),
859                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
860                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
861         Preconditions.checkState(leafrefParentNode.getPath() != null,
862                 "Schema Path reference for Leafref cannot be NULL");
863
864         List<QName> absolutePath = new LinkedList<QName>();
865         String strXPath = relativeXPath.toString();
866         String[] xpaths = strXPath.split("/");
867
868         int colCount = 0;
869         while (xpaths[colCount].contains("..")) {
870             colCount = colCount + 1;
871         }
872         List<QName> path = leafrefParentNode.getPath().getPath();
873         if (path != null) {
874             int lenght = path.size() - colCount;
875             absolutePath.addAll(path.subList(0, lenght));
876             List<String> xpathsList = Arrays.asList(xpaths);
877             List<String> sublistedXPath = xpathsList.subList(colCount, xpaths.length);
878             List<QName> sublist = new ArrayList<>();
879             for (String pathPart : sublistedXPath) {
880                 sublist.add(stringPathPartToQName(context, module, pathPart));
881             }
882             absolutePath.addAll(sublist);
883         }
884
885         return absolutePath;
886     }
887 }