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