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