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