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