Bug 3325 - RPC returns 500.
[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.annotations.Beta;
11 import com.google.common.base.Function;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Splitter;
15 import com.google.common.collect.Iterables;
16 import java.util.Arrays;
17 import java.util.Iterator;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Set;
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.common.QNameModule;
25 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
28 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.DerivableSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
31 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.Module;
34 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
35 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
36 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
37 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
39 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
41 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
42 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 /**
47  * The Schema Context Util contains support methods for searching through Schema
48  * Context modules for specified schema nodes via Schema Path or Revision Aware
49  * XPath. The Schema Context Util is designed as mixin, so it is not
50  * instantiable.
51  *
52  */
53 public final class SchemaContextUtil {
54     private static final Logger LOG = LoggerFactory.getLogger(SchemaContextUtil.class);
55     private static final Splitter COLON_SPLITTER = Splitter.on(':');
56     private static final Splitter SLASH_SPLITTER = Splitter.on('/');
57
58     private SchemaContextUtil() {
59     }
60
61     /**
62      * Method attempts to find DataSchemaNode in Schema Context via specified
63      * Schema Path. The returned DataSchemaNode from method will be the node at
64      * the end of the SchemaPath. If the DataSchemaNode is not present in the
65      * Schema Context the method will return <code>null</code>. <br>
66      * In case that Schema Context or Schema Path are not specified correctly
67      * (i.e. contains <code>null</code> values) the method will return
68      * IllegalArgumentException.
69      *
70      * @throws IllegalArgumentException
71      *
72      * @param context
73      *            Schema Context
74      * @param schemaPath
75      *            Schema Path to search for
76      * @return SchemaNode from the end of the Schema Path or <code>null</code>
77      *         if the Node is not present.
78      */
79     public static SchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
80         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
81         Preconditions.checkArgument(schemaPath != null, "Schema Path reference cannot be NULL");
82
83         final Iterable<QName> prefixedPath = schemaPath.getPathFromRoot();
84         if (prefixedPath == null) {
85             LOG.debug("Schema path {} has null path", schemaPath);
86             return null;
87         }
88
89         LOG.trace("Looking for path {} in context {}", schemaPath, context);
90         return findNodeInSchemaContext(context, prefixedPath);
91     }
92
93     /**
94      * Method attempts to find DataSchemaNode inside of provided Schema Context
95      * and Yang Module accordingly to Non-conditional Revision Aware XPath. The
96      * specified Module MUST be present in Schema Context otherwise the
97      * operation would fail and return <code>null</code>. <br>
98      * The Revision Aware XPath MUST be specified WITHOUT the conditional
99      * statement (i.e. without [cond]) in path, because in this state the Schema
100      * Context is completely unaware of data state and will be not able to
101      * properly resolve XPath. If the XPath contains condition the method will
102      * return IllegalArgumentException. <br>
103      * In case that Schema Context or Module or Revision Aware XPath contains
104      * <code>null</code> references the method will throw
105      * IllegalArgumentException <br>
106      * If the Revision Aware XPath is correct and desired Data Schema Node is
107      * present in Yang module or in depending module in Schema Context the
108      * method will return specified Data Schema Node, otherwise the operation
109      * will fail and method will return <code>null</code>.
110      *
111      * @throws IllegalArgumentException
112      *
113      * @param context
114      *            Schema Context
115      * @param module
116      *            Yang Module
117      * @param nonCondXPath
118      *            Non Conditional Revision Aware XPath
119      * @return Returns Data Schema Node for specified Schema Context for given
120      *         Non-conditional Revision Aware XPath, or <code>null</code> if the
121      *         DataSchemaNode is not present in Schema Context.
122      */
123     public static SchemaNode findDataSchemaNode(final SchemaContext context, final Module module, final RevisionAwareXPath nonCondXPath) {
124         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
125         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
126         Preconditions.checkArgument(nonCondXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
127
128         final String strXPath = nonCondXPath.toString();
129         if (strXPath != null) {
130             Preconditions.checkArgument(strXPath.indexOf('[') == -1, "Revision Aware XPath may not contain a condition");
131             if (nonCondXPath.isAbsolute()) {
132                 final List<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
133                 if (qnamedPath != null) {
134                     return findNodeInSchemaContext(context, qnamedPath);
135                 }
136             }
137         }
138         return null;
139     }
140
141     /**
142      * Method attempts to find DataSchemaNode inside of provided Schema Context
143      * and Yang Module accordingly to Non-conditional relative Revision Aware
144      * XPath. The specified Module MUST be present in Schema Context otherwise
145      * the operation would fail and return <code>null</code>. <br>
146      * The relative Revision Aware XPath MUST be specified WITHOUT the
147      * conditional statement (i.e. without [cond]) in path, because in this
148      * state the Schema Context is completely unaware of data state and will be
149      * not able to properly resolve XPath. If the XPath contains condition the
150      * method will return IllegalArgumentException. <br>
151      * The Actual Schema Node MUST be specified correctly because from this
152      * Schema Node will search starts. If the Actual Schema Node is not correct
153      * the operation will simply fail, because it will be unable to find desired
154      * DataSchemaNode. <br>
155      * In case that Schema Context or Module or Actual Schema Node or relative
156      * Revision Aware XPath contains <code>null</code> references the method
157      * will throw IllegalArgumentException <br>
158      * If the Revision Aware XPath doesn't have flag
159      * <code>isAbsolute == false</code> the method will throw
160      * IllegalArgumentException. <br>
161      * If the relative Revision Aware XPath is correct and desired Data Schema
162      * Node is present in Yang module or in depending module in Schema Context
163      * the method will return specified Data Schema Node, otherwise the
164      * operation will fail and method will return <code>null</code>.
165      *
166      * @throws IllegalArgumentException
167      *
168      * @param context
169      *            Schema Context
170      * @param module
171      *            Yang Module
172      * @param actualSchemaNode
173      *            Actual Schema Node
174      * @param relativeXPath
175      *            Relative Non Conditional Revision Aware XPath
176      * @return DataSchemaNode if is present in specified Schema Context for
177      *         given relative Revision Aware XPath, otherwise will return
178      *         <code>null</code>.
179      */
180     public static SchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
181             final SchemaNode actualSchemaNode, final RevisionAwareXPath relativeXPath) {
182         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
183         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
184         Preconditions.checkArgument(actualSchemaNode != null, "Actual Schema Node reference cannot be NULL");
185         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
186         Preconditions.checkState(!relativeXPath.isAbsolute(),
187                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
188                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
189
190         final SchemaPath actualNodePath = actualSchemaNode.getPath();
191         if (actualNodePath != null) {
192             final Iterable<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
193
194             if (qnamePath != null) {
195                 return findNodeInSchemaContext(context, qnamePath);
196             }
197         }
198         return null;
199     }
200
201     /**
202      * Returns parent Yang Module for specified Schema Context in which Schema
203      * Node is declared. If the Schema Node is not present in Schema Context the
204      * operation will return <code>null</code>. <br>
205      * If Schema Context or Schema Node contains <code>null</code> references
206      * the method will throw IllegalArgumentException
207      *
208      * @throws IllegalArgumentException
209      *
210      * @param context
211      *            Schema Context
212      * @param schemaNode
213      *            Schema Node
214      * @return Yang Module for specified Schema Context and Schema Node, if
215      *         Schema Node is NOT present, the method will returns
216      *         <code>null</code>
217      */
218     public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
219         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL!");
220         Preconditions.checkArgument(schemaNode != null, "Schema Node cannot be NULL!");
221         Preconditions.checkState(schemaNode.getPath() != null, "Schema Path for Schema Node is not "
222                 + "set properly (Schema Path is NULL)");
223
224         final QName qname = Iterables.getFirst(schemaNode.getPath().getPathTowardsRoot(), null);
225         Preconditions.checkState(qname != null,
226                 "Schema Path contains invalid state of path parts. " +
227                         "The Schema Path MUST contain at least ONE QName which defines namespace and Local name of path.");
228         return context.findModuleByNamespaceAndRevision(qname.getNamespace(), qname.getRevision());
229     }
230
231     public static SchemaNode findNodeInSchemaContext(final SchemaContext context, final Iterable<QName> path) {
232         final QName current = path.iterator().next();
233
234         LOG.trace("Looking up module {} in context {}", current, path);
235         final Module module = context.findModuleByNamespaceAndRevision(current.getNamespace(), current.getRevision());
236         if (module == null) {
237             LOG.debug("Module {} not found", current);
238             return null;
239         }
240
241         return findNodeInModule(module, path);
242     }
243
244     /**
245      * Returns NotificationDefinition from Schema Context
246      *
247      * @param schema SchemaContext in which lookup should be performed.
248      * @param path Schema Path of notification
249      * @return Notification schema or null, if notification is not present in schema context.
250      */
251     @Beta
252     public static @Nullable NotificationDefinition getNotificationSchema(@Nonnull final SchemaContext schema,@Nonnull  final SchemaPath path) {
253         Preconditions.checkNotNull(schema, "Schema context must not be null.");
254         Preconditions.checkNotNull(path, "Schema path must not be null.");
255         for (final NotificationDefinition potential : schema.getNotifications()) {
256             if (path.equals(potential.getPath())) {
257                return potential;
258             }
259         }
260         return null;
261     }
262
263     /**
264      * Returns RPC Input or Output Data container from RPC definition.
265      *
266      * @param schema SchemaContext in which lookup should be performed.
267      * @param path Schema path of RPC input/output data container
268      * @return Notification schema or null, if notification is not present in schema context.
269      */
270     @Beta
271     public static @Nullable ContainerSchemaNode getRpcDataSchema(@Nonnull final SchemaContext schema,@Nonnull  final SchemaPath path) {
272         Preconditions.checkNotNull(schema, "Schema context must not be null.");
273         Preconditions.checkNotNull(path, "Schema path must not be null.");
274         final Iterator<QName> it = path.getPathFromRoot().iterator();
275         Preconditions.checkArgument(it.hasNext(), "Rpc must have QName.");
276         final QName rpcName = it.next();
277         Preconditions.checkArgument(it.hasNext(), "input or output must be part of path.");
278         final QName inOrOut = it.next();
279         for (final RpcDefinition potential : schema.getOperations()) {
280             if (rpcName.equals(potential.getQName())) {
281                return SchemaNodeUtils.getRpcDataSchema(potential, inOrOut);
282             }
283         }
284         return null;
285     }
286
287     private static SchemaNode findNodeInModule(final Module module, final Iterable<QName> path) {
288
289         Preconditions.checkArgument(module != null, "Parent reference cannot be NULL");
290         Preconditions.checkArgument(path != null, "Path reference cannot be NULL");
291
292         if (!path.iterator().hasNext()) {
293             LOG.debug("No node matching {} found in node {}", path, module);
294             return null;
295         }
296
297         final QName current = path.iterator().next();
298         LOG.trace("Looking for node {} in module {}", current, module);
299
300         SchemaNode foundNode = null;
301         final Iterable<QName> nextPath = nextLevel(path);
302
303         foundNode = module.getDataChildByName(current);
304         if (foundNode != null && nextPath.iterator().hasNext()) {
305             foundNode = findNodeIn(foundNode, nextPath);
306         }
307
308         if (foundNode == null) {
309             foundNode = getGroupingByName(module, current);
310             if (foundNode != null && nextPath.iterator().hasNext()) {
311                 foundNode = findNodeIn(foundNode, nextPath);
312             }
313         }
314
315         if (foundNode == null) {
316             foundNode = getRpcByName(module, current);
317             if (foundNode != null && nextPath.iterator().hasNext()) {
318                 foundNode = findNodeIn(foundNode, nextPath);
319             }
320         }
321
322         if (foundNode == null) {
323             foundNode = getNotificationByName(module, current);
324             if (foundNode != null && nextPath.iterator().hasNext()) {
325                 foundNode = findNodeIn(foundNode, nextPath);
326             }
327         }
328
329         if (foundNode == null) {
330             LOG.debug("No node matching {} found in node {}", path, module);
331         }
332
333         return foundNode;
334
335     }
336
337     private static SchemaNode findNodeIn(final SchemaNode parent, final Iterable<QName> path) {
338
339         Preconditions.checkArgument(parent != null, "Parent reference cannot be NULL");
340         Preconditions.checkArgument(path != null, "Path reference cannot be NULL");
341
342         if (!path.iterator().hasNext()) {
343             LOG.debug("No node matching {} found in node {}", path, parent);
344             return null;
345         }
346
347         final QName current = path.iterator().next();
348         LOG.trace("Looking for node {} in node {}", current, parent);
349
350         SchemaNode foundNode = null;
351         final Iterable<QName> nextPath = nextLevel(path);
352
353         if (parent instanceof DataNodeContainer) {
354             final DataNodeContainer parentDataNodeContainer = (DataNodeContainer) parent;
355
356             foundNode = parentDataNodeContainer.getDataChildByName(current);
357             if (foundNode != null && nextPath.iterator().hasNext()) {
358                 foundNode = findNodeIn(foundNode, nextPath);
359             }
360
361             if (foundNode == null) {
362                 foundNode = getGroupingByName(parentDataNodeContainer, current);
363                 if (foundNode != null && nextPath.iterator().hasNext()) {
364                     foundNode = findNodeIn(foundNode, nextPath);
365                 }
366             }
367         }
368
369         if (foundNode == null && parent instanceof RpcDefinition) {
370             final RpcDefinition parentRpcDefinition = (RpcDefinition) parent;
371
372             if (current.getLocalName().equals("input")) {
373                 foundNode = parentRpcDefinition.getInput();
374                 if (foundNode != null && nextPath.iterator().hasNext()) {
375                     foundNode = findNodeIn(foundNode, nextPath);
376                 }
377             }
378
379             if (current.getLocalName().equals("output")) {
380                 foundNode = parentRpcDefinition.getOutput();
381                 if (foundNode != null && nextPath.iterator().hasNext()) {
382                     foundNode = findNodeIn(foundNode, nextPath);
383                 }
384             }
385
386             if (foundNode == null) {
387                 foundNode = getGroupingByName(parentRpcDefinition, current);
388                 if (foundNode != null && nextPath.iterator().hasNext()) {
389                     foundNode = findNodeIn(foundNode, nextPath);
390                 }
391             }
392         }
393
394         if (foundNode == null && parent instanceof ChoiceSchemaNode) {
395             foundNode = ((ChoiceSchemaNode) parent).getCaseNodeByName(current);
396             if (foundNode != null && nextPath.iterator().hasNext()) {
397                 foundNode = findNodeIn(foundNode, nextPath);
398             }
399         }
400
401         if (foundNode == null) {
402             LOG.debug("No node matching {} found in node {}", path, parent);
403         }
404
405         return foundNode;
406
407     }
408
409     private static Iterable<QName> nextLevel(final Iterable<QName> path) {
410         return Iterables.skip(path, 1);
411     }
412
413     private static RpcDefinition getRpcByName(final Module module, final QName name) {
414         for (final RpcDefinition rpc : module.getRpcs()) {
415             if (rpc.getQName().equals(name)) {
416                 return rpc;
417             }
418         }
419         return null;
420     }
421
422     private static NotificationDefinition getNotificationByName(final Module module, final QName name) {
423         for (final NotificationDefinition notification : module.getNotifications()) {
424             if (notification.getQName().equals(name)) {
425                 return notification;
426             }
427         }
428         return null;
429     }
430
431     private static GroupingDefinition getGroupingByName(final DataNodeContainer dataNodeContainer, final QName name) {
432         for (final GroupingDefinition grouping : dataNodeContainer.getGroupings()) {
433             if (grouping.getQName().equals(name)) {
434                 return grouping;
435             }
436         }
437         return null;
438     }
439
440     private static GroupingDefinition getGroupingByName(final RpcDefinition rpc, final QName name) {
441         for (final GroupingDefinition grouping : rpc.getGroupings()) {
442             if (grouping.getQName().equals(name)) {
443                 return grouping;
444             }
445         }
446         return null;
447     }
448
449     /**
450      * Transforms string representation of XPath to Queue of QNames. The XPath
451      * is split by "/" and for each part of XPath is assigned correct module in
452      * Schema Path. <br>
453      * If Schema Context, Parent Module or XPath string contains
454      * <code>null</code> values, the method will throws IllegalArgumentException
455      *
456      * @throws IllegalArgumentException
457      *
458      * @param context
459      *            Schema Context
460      * @param parentModule
461      *            Parent Module
462      * @param xpath
463      *            XPath String
464      * @return return a list of QName
465      */
466     private static List<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule, final String xpath) {
467         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
468         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
469         Preconditions.checkArgument(xpath != null, "XPath string reference cannot be NULL");
470
471         final List<QName> path = new LinkedList<QName>();
472         for (final String pathComponent : SLASH_SPLITTER.split(xpath)) {
473             if (!pathComponent.isEmpty()) {
474                 path.add(stringPathPartToQName(context, parentModule, pathComponent));
475             }
476         }
477         return path;
478     }
479
480     /**
481      * Transforms part of Prefixed Path as java String to QName. <br>
482      * If the string contains module prefix separated by ":" (i.e.
483      * mod:container) this module is provided from from Parent Module list of
484      * imports. If the Prefixed module is present in Schema Context the QName
485      * can be constructed. <br>
486      * If the Prefixed Path Part does not contains prefix the Parent's Module
487      * namespace is taken for construction of QName. <br>
488      * If Schema Context, Parent Module or Prefixed Path Part refers to
489      * <code>null</code> the method will throw IllegalArgumentException
490      *
491      * @throws IllegalArgumentException
492      *
493      * @param context
494      *            Schema Context
495      * @param parentModule
496      *            Parent Module
497      * @param prefixedPathPart
498      *            Prefixed Path Part string
499      * @return QName from prefixed Path Part String.
500      */
501     private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule, final String prefixedPathPart) {
502         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
503         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
504         Preconditions.checkArgument(prefixedPathPart != null, "Prefixed Path Part cannot be NULL!");
505
506         if (prefixedPathPart.indexOf(':') != -1) {
507             final Iterator<String> prefixedName = COLON_SPLITTER.split(prefixedPathPart).iterator();
508             final String modulePrefix = prefixedName.next();
509
510             final Module module = resolveModuleForPrefix(context, parentModule, modulePrefix);
511             Preconditions.checkArgument(module != null, "Failed to resolve xpath: no module found for prefix %s in module %s",
512                     modulePrefix, parentModule.getName());
513
514             return QName.create(module.getQNameModule(), prefixedName.next());
515         } else {
516             return QName.create(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
517         }
518     }
519
520     /**
521      * Method will attempt to resolve and provide Module reference for specified
522      * module prefix. Each Yang module could contains multiple imports which
523      * MUST be associated with corresponding module prefix. The method simply
524      * looks into module imports and returns the module that is bounded with
525      * specified prefix. If the prefix is not present in module or the prefixed
526      * module is not present in specified Schema Context, the method will return
527      * <code>null</code>. <br>
528      * If String prefix is the same as prefix of the specified Module the
529      * reference to this module is returned. <br>
530      * If Schema Context, Module or Prefix are referring to <code>null</code>
531      * the method will return IllegalArgumentException
532      *
533      * @throws IllegalArgumentException
534      *
535      * @param context
536      *            Schema Context
537      * @param module
538      *            Yang Module
539      * @param prefix
540      *            Module Prefix
541      * @return Module for given prefix in specified Schema Context if is
542      *         present, otherwise returns <code>null</code>
543      */
544     private static Module resolveModuleForPrefix(final SchemaContext context, final Module module, final String prefix) {
545         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
546         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
547         Preconditions.checkArgument(prefix != null, "Prefix string cannot be NULL");
548
549         if (prefix.equals(module.getPrefix())) {
550             return module;
551         }
552
553         final Set<ModuleImport> imports = module.getImports();
554         for (final ModuleImport mi : imports) {
555             if (prefix.equals(mi.getPrefix())) {
556                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
557             }
558         }
559         return null;
560     }
561
562     /**
563      * @throws IllegalArgumentException
564      *
565      * @param context
566      *            Schema Context
567      * @param module
568      *            Yang Module
569      * @param relativeXPath
570      *            Non conditional Revision Aware Relative XPath
571      * @param actualSchemaNode
572      *            actual schema node
573      * @return list of QName
574      */
575     private static Iterable<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
576             final RevisionAwareXPath relativeXPath, final SchemaNode actualSchemaNode) {
577         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
578         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
579         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
580         Preconditions.checkState(!relativeXPath.isAbsolute(),
581                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
582                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
583         Preconditions.checkState(actualSchemaNode.getPath() != null,
584                 "Schema Path reference for Leafref cannot be NULL");
585
586         final Iterable<String> xpaths = SLASH_SPLITTER.split(relativeXPath.toString());
587
588         // Find out how many "parent" components there are
589         // FIXME: is .contains() the right check here?
590         // FIXME: case ../../node1/node2/../node3/../node4
591         int colCount = 0;
592         for (final Iterator<String> it = xpaths.iterator(); it.hasNext() && it.next().contains(".."); ) {
593             ++colCount;
594         }
595
596         final Iterable<QName> schemaNodePath = actualSchemaNode.getPath().getPathFromRoot();
597
598         if (Iterables.size(schemaNodePath) - colCount >= 0) {
599             return Iterables.concat(Iterables.limit(schemaNodePath, Iterables.size(schemaNodePath) - colCount),
600                     Iterables.transform(Iterables.skip(xpaths, colCount), new Function<String, QName>() {
601                         @Override
602                         public QName apply(final String input) {
603                             return stringPathPartToQName(context, module, input);
604                         }
605                     }));
606         }
607         return Iterables.concat(schemaNodePath,
608                 Iterables.transform(Iterables.skip(xpaths, colCount), new Function<String, QName>() {
609                     @Override
610                     public QName apply(final String input) {
611                         return stringPathPartToQName(context, module, input);
612                     }
613                 }));
614     }
615
616     /**
617      * Extracts the base type of node on which schema node points to. If target node is again of type LeafrefTypeDefinition, methods will be call recursively until it reach concrete
618      * type definition.
619      *
620      * @param typeDefinition
621      *            type of node which will be extracted
622      * @param schemaContext
623      *            Schema Context
624      * @param schema
625      *            Schema Node
626      * @return recursively found type definition this leafref is pointing to or null if the xpath is incorrect (null is there to preserve backwards compatibility)
627      */
628     public static TypeDefinition<?> getBaseTypeForLeafRef(final LeafrefTypeDefinition typeDefinition, final SchemaContext schemaContext, final SchemaNode schema) {
629         RevisionAwareXPath pathStatement = typeDefinition.getPathStatement();
630         pathStatement = new RevisionAwareXPathImpl(stripConditionsFromXPathString(pathStatement), pathStatement.isAbsolute());
631
632         SchemaNode baseSchema = schema;
633         while (baseSchema instanceof DerivableSchemaNode) {
634             final Optional<? extends SchemaNode> basePotential = ((DerivableSchemaNode) baseSchema).getOriginal();
635             if (basePotential.isPresent()) {
636                 baseSchema = basePotential.get();
637             } else {
638                 break;
639             }
640         }
641
642         Module parentModule = findParentModuleByType(schemaContext, baseSchema);
643
644         final DataSchemaNode dataSchemaNode;
645         if(pathStatement.isAbsolute()) {
646             dataSchemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(schemaContext, parentModule, pathStatement);
647         } else {
648             dataSchemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNodeForRelativeXPath(schemaContext, parentModule, baseSchema, pathStatement);
649         }
650
651         // FIXME this is just to preserve backwards compatibility since yangtools do not mind wrong leafref xpaths
652         // and current expected behaviour for such cases is to just use pure string
653         // This should throw an exception about incorrect XPath in leafref
654         if(dataSchemaNode == null) {
655             return null;
656         }
657
658         final TypeDefinition<?> targetTypeDefinition = typeDefinition(dataSchemaNode);
659
660         if(targetTypeDefinition instanceof LeafrefTypeDefinition) {
661             return getBaseTypeForLeafRef(((LeafrefTypeDefinition) targetTypeDefinition), schemaContext, dataSchemaNode);
662         } else {
663             return targetTypeDefinition;
664         }
665     }
666
667     /**
668      * Returns parent Yang Module for specified Schema Context in which Schema
669      * Node is declared. If Schema Node is of type 'ExtendedType' it tries to find parent module
670      * in which the type was originally declared (needed for correct leafref path resolution). <br>
671      * If the Schema Node is not present in Schema Context the
672      * operation will return <code>null</code>. <br>
673      * If Schema Context or Schema Node contains <code>null</code> references
674      * the method will throw IllegalArgumentException
675      *
676      * @throws IllegalArgumentException
677      *
678      * @param schemaContext
679      *            Schema Context
680      * @param schemaNode
681      *            Schema Node
682      * @return Yang Module for specified Schema Context and Schema Node, if
683      *         Schema Node is NOT present, the method will returns
684      *         <code>null</code>
685      */
686     public static Module findParentModuleByType(final SchemaContext schemaContext, final SchemaNode schemaNode) {
687         Preconditions.checkArgument(schemaContext != null, "Schema Context reference cannot be NULL!");
688         Preconditions.checkArgument(schemaNode != null, "Schema Node cannot be NULL!");
689         TypeDefinition<?> nodeType = null;
690
691         if (schemaNode instanceof LeafSchemaNode) {
692             nodeType = ((LeafSchemaNode) schemaNode).getType();
693         } else if (schemaNode instanceof LeafListSchemaNode) {
694             nodeType = ((LeafListSchemaNode) schemaNode).getType();
695         }
696
697         if (nodeType instanceof ExtendedType) {
698             while (nodeType.getBaseType() instanceof ExtendedType) {
699                 nodeType = nodeType.getBaseType();
700             }
701
702             QNameModule typeDefModuleQname = nodeType.getQName().getModule();
703
704             return schemaContext.findModuleByNamespaceAndRevision(typeDefModuleQname.getNamespace(),
705                     typeDefModuleQname.getRevision());
706         }
707
708         return SchemaContextUtil.findParentModule(schemaContext, schemaNode);
709     }
710
711     /**
712      * Returns base type for {@code typeDefinition} which belongs to module specified via {@code qName}. This handle case
713      * when leafref type isn't specified as type substatement of leaf or leaf-list but is defined in other module as typedef
714      * which is then imported to referenced module.
715      *
716      * Because {@code typeDefinition} is definied via typedef statement, only absolute path is meaningful.
717      *
718      * @param typeDefinition
719      * @param schemaContext
720      * @param qName
721      * @return
722      */
723     public static TypeDefinition<?> getBaseTypeForLeafRef(final LeafrefTypeDefinition typeDefinition,
724             final SchemaContext schemaContext, final QName qName) {
725         final RevisionAwareXPath pathStatement = typeDefinition.getPathStatement();
726         final RevisionAwareXPath strippedPathStatement = new RevisionAwareXPathImpl(stripConditionsFromXPathString(pathStatement), pathStatement.isAbsolute());
727         if (!strippedPathStatement.isAbsolute()) {
728             return null;
729         }
730
731         final Module parentModule = schemaContext.findModuleByNamespaceAndRevision(qName.getNamespace(),qName.getRevision());
732         final DataSchemaNode dataSchemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(schemaContext, parentModule, strippedPathStatement);
733         final TypeDefinition<?> targetTypeDefinition = typeDefinition(dataSchemaNode);
734         if (targetTypeDefinition instanceof LeafrefTypeDefinition) {
735             return getBaseTypeForLeafRef(((LeafrefTypeDefinition) targetTypeDefinition), schemaContext, dataSchemaNode);
736         } else {
737             return targetTypeDefinition;
738         }
739     }
740
741     /**
742      * Removes conditions from xPath pointed to target node.
743      *
744      * @param pathStatement
745      *            xPath to target node
746      * @return string representation of xPath without conditions
747      *
748      */
749     private static String stripConditionsFromXPathString(final RevisionAwareXPath pathStatement) {
750         return pathStatement.toString().replaceAll("\\[.*\\]", "");
751     }
752
753     /**
754      * Extracts the base type of leaf schema node until it reach concrete type of TypeDefinition.
755      *
756      * @param node
757      *            a node representing LeafSchemaNode
758      * @return concrete type definition of node value
759      */
760     private static TypeDefinition<? extends Object> typeDefinition(final LeafSchemaNode node) {
761         TypeDefinition<?> baseType = node.getType();
762         while (baseType.getBaseType() != null) {
763             baseType = baseType.getBaseType();
764         }
765         return baseType;
766     }
767
768     /**
769      * Extracts the base type of leaf schema node until it reach concrete type of TypeDefinition.
770      *
771      * @param node
772      *            a node representing LeafListSchemaNode
773      * @return concrete type definition of node value
774      */
775     private static TypeDefinition<? extends Object> typeDefinition(final LeafListSchemaNode node) {
776         TypeDefinition<?> baseType = node.getType();
777         while (baseType.getBaseType() != null) {
778             baseType = baseType.getBaseType();
779         }
780         return baseType;
781     }
782
783     /**
784      * Gets the base type of DataSchemaNode value.
785      *
786      * @param node
787      *            a node representing DataSchemaNode
788      * @return concrete type definition of node value
789      */
790     private static TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
791         if (node instanceof LeafListSchemaNode) {
792             return typeDefinition((LeafListSchemaNode) node);
793         } else if (node instanceof LeafSchemaNode) {
794             return typeDefinition((LeafSchemaNode) node);
795         } else {
796             throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(node).toString());
797         }
798     }
799 }