BUG-2304 Fix SchemaContextUtil for augmented nodes.
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / SchemaContextUtil.java
index ed8fbd15e1f9d7b23f91454458663b5029415554..132edfc347bdb7dd2405aefd755cfc832a118bd9 100644 (file)
@@ -7,45 +7,60 @@
  */
 package org.opendaylight.yangtools.yang.model.util;
 
-import java.net.URI;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import java.util.Arrays;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Queue;
 import java.util.Set;
-
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
- * The Schema Context Util contains support methods for searching through Schema Context modules for specified schema
- * nodes via Schema Path or Revision Aware XPath. The Schema Context Util is designed as mixin,
- * so it is not instantiable.
+ * The Schema Context Util contains support methods for searching through Schema
+ * Context modules for specified schema nodes via Schema Path or Revision Aware
+ * XPath. The Schema Context Util is designed as mixin, so it is not
+ * instantiable.
  *
- * @author Lukas Sedlak <lsedlak@cisco.com>
  */
 public final class SchemaContextUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(SchemaContextUtil.class);
+    private static final Splitter COLON_SPLITTER = Splitter.on(':');
+    private static final Splitter SLASH_SPLITTER = Splitter.on('/');
 
     private SchemaContextUtil() {
     }
 
     /**
-     * Method attempts to find DataSchemaNode in Schema Context via specified Schema Path. The returned
-     * DataSchemaNode from method will be the node at the end of the SchemaPath. If the DataSchemaNode is not present
-     * in the Schema Context the method will return <code>null</code>.
-     * <br>
-     * In case that Schema Context or Schema Path are not specified correctly (i.e. contains <code>null</code>
-     * values) the method will return IllegalArgumentException.
+     * Method attempts to find DataSchemaNode in Schema Context via specified
+     * Schema Path. The returned DataSchemaNode from method will be the node at
+     * the end of the SchemaPath. If the DataSchemaNode is not present in the
+     * Schema Context the method will return <code>null</code>. <br>
+     * In case that Schema Context or Schema Path are not specified correctly
+     * (i.e. contains <code>null</code> values) the method will return
+     * IllegalArgumentException.
      *
      * @throws IllegalArgumentException
      *
@@ -53,72 +68,65 @@ public final class SchemaContextUtil {
      *            Schema Context
      * @param schemaPath
      *            Schema Path to search for
-     * @return DataSchemaNode from the end of the Schema Path or
-     *         <code>null</code> if the Node is not present.
+     * @return SchemaNode from the end of the Schema Path or <code>null</code>
+     *         if the Node is not present.
      */
-    public static DataSchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
-        }
-        if (schemaPath == null) {
-            throw new IllegalArgumentException("Schema Path reference cannot be NULL");
-        }
+    public static SchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
+        Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
+        Preconditions.checkArgument(schemaPath != null, "Schema Path reference cannot be NULL");
 
-        final Module module = resolveModuleFromSchemaPath(context, schemaPath);
-        final Queue<QName> prefixedPath = new LinkedList<>(schemaPath.getPath());
-
-        if ((module != null) && (prefixedPath != null)) {
-            return findSchemaNodeForGivenPath(context, module, prefixedPath);
+        final Iterable<QName> prefixedPath = schemaPath.getPathFromRoot();
+        if (prefixedPath == null) {
+            LOG.debug("Schema path {} has null path", schemaPath);
+            return null;
         }
-        return null;
+
+        LOG.trace("Looking for path {} in context {}", schemaPath, context);
+        return findNodeInSchemaContext(context, prefixedPath);
     }
 
     /**
-     * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
-     * Non-conditional Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise the
-     * operation would fail and return <code>null</code>.
-     * <br>
-     * The Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in path,
-     * because in this state the Schema Context is completely unaware of data state and will be not able to properly
-     * resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
-     * <br>
-     * In case that Schema Context or Module or Revision Aware XPath contains <code>null</code> references the method
-     * will throw IllegalArgumentException
-     * <br>
-     * If the Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in depending
-     * module in Schema Context the method will return specified Data Schema Node, otherwise the operation will fail
-     * and method will return <code>null</code>.
+     * Method attempts to find DataSchemaNode inside of provided Schema Context
+     * and Yang Module accordingly to Non-conditional Revision Aware XPath. The
+     * specified Module MUST be present in Schema Context otherwise the
+     * operation would fail and return <code>null</code>. <br>
+     * The Revision Aware XPath MUST be specified WITHOUT the conditional
+     * statement (i.e. without [cond]) in path, because in this state the Schema
+     * Context is completely unaware of data state and will be not able to
+     * properly resolve XPath. If the XPath contains condition the method will
+     * return IllegalArgumentException. <br>
+     * In case that Schema Context or Module or Revision Aware XPath contains
+     * <code>null</code> references the method will throw
+     * IllegalArgumentException <br>
+     * If the Revision Aware XPath is correct and desired Data Schema Node is
+     * present in Yang module or in depending module in Schema Context the
+     * method will return specified Data Schema Node, otherwise the operation
+     * will fail and method will return <code>null</code>.
      *
      * @throws IllegalArgumentException
      *
-     * @param context Schema Context
-     * @param module Yang Module
-     * @param nonCondXPath Non Conditional Revision Aware XPath
-     * @return Returns Data Schema Node for specified Schema Context for given Non-conditional Revision Aware XPath,
-     * or <code>null</code> if the DataSchemaNode is not present in Schema Context.
+     * @param context
+     *            Schema Context
+     * @param module
+     *            Yang Module
+     * @param nonCondXPath
+     *            Non Conditional Revision Aware XPath
+     * @return Returns Data Schema Node for specified Schema Context for given
+     *         Non-conditional Revision Aware XPath, or <code>null</code> if the
+     *         DataSchemaNode is not present in Schema Context.
      */
-    public static DataSchemaNode findDataSchemaNode(final SchemaContext context, final Module module,
-            final RevisionAwareXPath nonCondXPath) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
-        }
-        if (module == null) {
-            throw new IllegalArgumentException("Module reference cannot be NULL!");
-        }
-        if (nonCondXPath == null) {
-            throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
-        }
+    public static SchemaNode findDataSchemaNode(final SchemaContext context, final Module module, final RevisionAwareXPath nonCondXPath) {
+        Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
+        Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
+        Preconditions.checkArgument(nonCondXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
 
-        final String strXPath = nonCondXPath.toString();
+        String strXPath = nonCondXPath.toString();
         if (strXPath != null) {
-            if (strXPath.contains("[")) {
-                throw new IllegalArgumentException("Revision Aware XPath cannot contains condition!");
-            }
+            Preconditions.checkArgument(strXPath.indexOf('[') == -1, "Revision Aware XPath may not contain a condition");
             if (nonCondXPath.isAbsolute()) {
-                final Queue<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
+                List<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
                 if (qnamedPath != null) {
-                    final DataSchemaNode dataNode = findSchemaNodeForGivenPath(context, module, qnamedPath);
-                    return dataNode;
+                    return findNodeInSchemaContext(context, qnamedPath);
                 }
             }
         }
@@ -126,366 +134,426 @@ public final class SchemaContextUtil {
     }
 
     /**
-     * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
-     * Non-conditional relative Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise
-     * the operation would fail and return <code>null</code>.
-     * <br>
-     * The relative Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in
-     * path, because in this state the Schema Context is completely unaware of data state and will be not able to
-     * properly resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
-     * <br>
-     * The Actual Schema Node MUST be specified correctly because from this Schema Node will search starts. If the
-     * Actual Schema Node is not correct the operation will simply fail, because it will be unable to find desired
-     * DataSchemaNode.
-     * <br>
-     * In case that Schema Context or Module or Actual Schema Node or relative Revision Aware XPath contains
-     * <code>null</code> references the method will throw IllegalArgumentException
-     * <br>
-     * If the Revision Aware XPath doesn't have flag <code>isAbsolute == false</code> the method will
-     * throw IllegalArgumentException.
-     * <br>
-     * If the relative Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in
-     * depending module in Schema Context the method will return specified Data Schema Node,
-     * otherwise the operation will fail
-     * and method will return <code>null</code>.
+     * Method attempts to find DataSchemaNode inside of provided Schema Context
+     * and Yang Module accordingly to Non-conditional relative Revision Aware
+     * XPath. The specified Module MUST be present in Schema Context otherwise
+     * the operation would fail and return <code>null</code>. <br>
+     * The relative Revision Aware XPath MUST be specified WITHOUT the
+     * conditional statement (i.e. without [cond]) in path, because in this
+     * state the Schema Context is completely unaware of data state and will be
+     * not able to properly resolve XPath. If the XPath contains condition the
+     * method will return IllegalArgumentException. <br>
+     * The Actual Schema Node MUST be specified correctly because from this
+     * Schema Node will search starts. If the Actual Schema Node is not correct
+     * the operation will simply fail, because it will be unable to find desired
+     * DataSchemaNode. <br>
+     * In case that Schema Context or Module or Actual Schema Node or relative
+     * Revision Aware XPath contains <code>null</code> references the method
+     * will throw IllegalArgumentException <br>
+     * If the Revision Aware XPath doesn't have flag
+     * <code>isAbsolute == false</code> the method will throw
+     * IllegalArgumentException. <br>
+     * If the relative Revision Aware XPath is correct and desired Data Schema
+     * Node is present in Yang module or in depending module in Schema Context
+     * the method will return specified Data Schema Node, otherwise the
+     * operation will fail and method will return <code>null</code>.
      *
      * @throws IllegalArgumentException
      *
-     * @param context Schema Context
-     * @param module Yang Module
-     * @param actualSchemaNode Actual Schema Node
-     * @param relativeXPath Relative Non Conditional Revision Aware XPath
-     * @return DataSchemaNode if is present in specified Schema Context for given relative Revision Aware XPath,
-     * otherwise will return <code>null</code>.
+     * @param context
+     *            Schema Context
+     * @param module
+     *            Yang Module
+     * @param actualSchemaNode
+     *            Actual Schema Node
+     * @param relativeXPath
+     *            Relative Non Conditional Revision Aware XPath
+     * @return DataSchemaNode if is present in specified Schema Context for
+     *         given relative Revision Aware XPath, otherwise will return
+     *         <code>null</code>.
      */
-    public static DataSchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
+    public static SchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
             final SchemaNode actualSchemaNode, final RevisionAwareXPath relativeXPath) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
-        }
-        if (module == null) {
-            throw new IllegalArgumentException("Module reference cannot be NULL!");
-        }
-        if (actualSchemaNode == null) {
-            throw new IllegalArgumentException("Actual Schema Node reference cannot be NULL!");
-        }
-        if (relativeXPath == null) {
-            throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
-        }
-        if (relativeXPath.isAbsolute()) {
-            throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
-                    + "for non relative Revision Aware XPath use findDataSchemaNode method!");
-        }
-
-        final SchemaPath actualNodePath = actualSchemaNode.getPath();
+        Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
+        Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
+        Preconditions.checkArgument(actualSchemaNode != null, "Actual Schema Node reference cannot be NULL");
+        Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
+        Preconditions.checkState(!relativeXPath.isAbsolute(),
+                "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
+                        + "for non relative Revision Aware XPath use findDataSchemaNode method");
+
+        SchemaPath actualNodePath = actualSchemaNode.getPath();
         if (actualNodePath != null) {
-            final Queue<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualNodePath);
+            Iterable<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
 
             if (qnamePath != null) {
-                final DataSchemaNode dataNode = findSchemaNodeForGivenPath(context, module, qnamePath);
-                return dataNode;
+                return findNodeInSchemaContext(context, qnamePath);
             }
         }
         return null;
     }
 
     /**
-     * Retrieve information from Schema Path and returns the module reference to which Schema Node belongs. The
-     * search for correct Module is based on namespace within the last item in Schema Path. If schema context
-     * contains module with namespace specified in last item of Schema Path, then operation will returns Module
-     * reference, otherwise returns <code>null</code>
-     * <br>
-     * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
+     * Returns parent Yang Module for specified Schema Context in which Schema
+     * Node is declared. If the Schema Node is not present in Schema Context the
+     * operation will return <code>null</code>. <br>
+     * If Schema Context or Schema Node contains <code>null</code> references
+     * the method will throw IllegalArgumentException
      *
      * @throws IllegalArgumentException
      *
-     * @param context Schema Context
-     * @param schemaPath Schema Path
-     * @return Module reference for given Schema Path if module is present in Schema Context,
-     * otherwise returns <code>null</code>
+     * @param context
+     *            Schema Context
+     * @param schemaNode
+     *            Schema Node
+     * @return Yang Module for specified Schema Context and Schema Node, if
+     *         Schema Node is NOT present, the method will returns
+     *         <code>null</code>
      */
-    private static Module resolveModuleFromSchemaPath(final SchemaContext context, final SchemaPath schemaPath) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
+    public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
+        Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL!");
+        Preconditions.checkArgument(schemaNode != null, "Schema Node cannot be NULL!");
+        Preconditions.checkState(schemaNode.getPath() != null, "Schema Path for Schema Node is not "
+                + "set properly (Schema Path is NULL)");
+
+        final QName qname = Iterables.getFirst(schemaNode.getPath().getPathTowardsRoot(), null);
+        Preconditions.checkState(qname != null,
+                "Schema Path contains invalid state of path parts. " +
+                "The Schema Path MUST contain at least ONE QName which defines namespace and Local name of path.");
+        return context.findModuleByNamespaceAndRevision(qname.getNamespace(), qname.getRevision());
+    }
+
+    public static SchemaNode findNodeInSchemaContext(final SchemaContext context, final Iterable<QName> path) {
+        final QName current = path.iterator().next();
+
+        LOG.trace("Looking up module {} in context {}", current, path);
+        final Module module = context.findModuleByNamespaceAndRevision(current.getNamespace(), current.getRevision());
+        if (module == null) {
+            LOG.debug("Module {} not found", current);
+            return null;
+        }
+
+        return findNodeInModule(module, path);
+    }
+
+    private static SchemaNode findNodeInModule(final Module module, final Iterable<QName> path) {
+        final QName current = path.iterator().next();
+
+        LOG.trace("Looking for data container {} in module {}", current, module);
+        SchemaNode parent = module.getDataChildByName(current);
+        if (parent != null) {
+            final SchemaNode ret = findNode((DataSchemaNode) parent, nextLevel(path));
+            if (ret != null) {
+                return ret;
+            }
         }
-        if (schemaPath == null) {
-            throw new IllegalArgumentException("Schema Path reference cannot be NULL");
+
+        LOG.trace("Looking for RPC {} in module {}", current, module);
+        parent = getRpcByName(module, current);
+        if (parent != null) {
+            final SchemaNode ret = findNodeInRpc((RpcDefinition) parent, nextLevel(path));
+            if (ret != null) {
+                return ret;
+            }
         }
 
-        final List<QName> path = schemaPath.getPath();
-        if (!path.isEmpty()) {
-            final QName qname = path.get(path.size() - 1);
+        LOG.trace("Looking for notification {} in module {}", current, module);
+        parent = getNotificationByName(module, current);
+        if (parent != null) {
+            final SchemaNode ret = findNodeInNotification((NotificationDefinition) parent, nextLevel(path));
+            if (ret != null) {
+                return ret;
+            }
+        }
 
-            if ((qname != null) && (qname.getNamespace() != null)) {
-                return context.findModuleByNamespace(qname.getNamespace());
+        LOG.trace("Looking for grouping {} in module {}", current, module);
+        parent = getGroupingByName(module, current);
+        if (parent != null) {
+            final SchemaNode ret = findNodeInGrouping((GroupingDefinition) parent, nextLevel(path));
+            if (ret != null) {
+                return ret;
             }
         }
 
+        LOG.debug("No node matching {} found in module {}", path, module);
         return null;
     }
 
-    /**
-     * Returns the Yang Module from specified Schema Context in which the TypeDefinition is declared. If the
-     * TypeDefinition si not present in Schema Context then the method will return <code>null</code>
-     *
-     * If Schema Context or TypeDefinition contains <code>null</code> references the method will throw IllegalArgumentException
-     *
-     * @throws IllegalArgumentException
-     *
-     * @param context Schema Context
-     * @param type Type Definition
-     * @return Yang Module in which the TypeDefinition is declared, if is not present, returns <code>null</code>.
-     */
-    public static Module findParentModuleForTypeDefinition(final SchemaContext context, final TypeDefinition<?> type) {
-        final SchemaPath schemaPath = type.getPath();
-        if (schemaPath == null) {
-            throw new IllegalArgumentException("Schema Path reference cannot be NULL");
-        }
-        final List<QName> qnamedPath = schemaPath.getPath();
-        if (qnamedPath == null || qnamedPath.isEmpty()) {
-            throw new IllegalStateException("Schema Path contains invalid state of path parts."
-                    + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
-                    + "of path.");
-        }
-
-        if (type instanceof ExtendedType) {
-            final QName qname = qnamedPath.get(qnamedPath.size() - 1);
-            if ((qname != null) && (qname.getNamespace() != null)) {
-                return context.findModuleByNamespace(qname.getNamespace());
-            }
-        } else {
-            final QName qname = qnamedPath.get(qnamedPath.size() - 2);
-            if ((qname != null) && (qname.getNamespace() != null)) {
-                return context.findModuleByNamespace(qname.getNamespace());
-            }
+    private static SchemaNode findNodeInGrouping(
+            final GroupingDefinition grouping, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
+            LOG.debug("Found grouping {}", grouping);
+            return grouping;
+        }
+
+        LOG.trace("Looking for path {} in grouping {}", path, grouping);
+        final DataSchemaNode node = grouping.getDataChildByName(current);
+
+        if (node != null)
+            return findNode(node, nextLevel(path));
+
+        for (GroupingDefinition groupingDefinition : grouping.getGroupings()) {
+            if (groupingDefinition.getQName().equals(current))
+                return findNodeInGrouping(groupingDefinition, nextLevel(path));
         }
+
+        LOG.debug("No node matching {} found in grouping {}", current, grouping);
         return null;
     }
 
-    /**
-     * Returns parent Yang Module for specified Schema Context in which Schema Node is declared. If the Schema Node
-     * is not present in Schema Context the operation will return <code>null</code>.
-     * <br>
-     * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
-     *
-     * @throws IllegalArgumentException
-     *
-     * @param context Schema Context
-     * @param schemaNode Schema Node
-     * @return Yang Module for specified Schema Context and Schema Node, if Schema Node is NOT present,
-     * the method will returns <code>null</code>
-     */
-    public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
+    private static SchemaNode findNodeInRpc(final RpcDefinition rpc, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
+            LOG.debug("Found RPC {}", rpc);
+            return rpc;
         }
-        if (schemaNode == null) {
-            throw new IllegalArgumentException("Schema Node cannot be NULL!");
+
+        LOG.trace("Looking for path {} in rpc {}", path, rpc);
+        switch (current.getLocalName()) {
+        case "input":
+            return findNode(rpc.getInput(), nextLevel(path));
+        case "output":
+            return findNode(rpc.getOutput(), nextLevel(path));
+        default:
+            LOG.debug("Invalid component {} of path {} in RPC {}", current, path, rpc);
+            return null;
         }
+    }
 
-        final SchemaPath schemaPath = schemaNode.getPath();
-        if (schemaPath == null) {
-            throw new IllegalStateException("Schema Path for Schema Node is not "
-                    + "set properly (Schema Path is NULL)");
+    private static SchemaNode findNodeInNotification(final NotificationDefinition ntf, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
+            LOG.debug("Found notification {}", ntf);
+            return ntf;
         }
-        final List<QName> qnamedPath = schemaPath.getPath();
-        if (qnamedPath == null || qnamedPath.isEmpty()) {
-            throw new IllegalStateException("Schema Path contains invalid state of path parts."
-                    + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
-                    + "of path.");
+
+        LOG.trace("Looking for path {} in notification {}", path, ntf);
+        DataSchemaNode node = ntf.getDataChildByName(current);
+        if (node == null) {
+            LOG.debug("No node matching {} found in notification {}", current, ntf);
+            return null;
         }
-        final QName qname = qnamedPath.get(qnamedPath.size() - 1);
-        return context.findModuleByNamespace(qname.getNamespace());
+
+        return findNode(node, nextLevel(path));
     }
 
-    /**
-     * Method will attempt to find DataSchemaNode from specified Module and Queue of QNames through the Schema
-     * Context. The QNamed path could be defined across multiple modules in Schema Context so the method is called
-     * recursively. If the QNamed path contains QNames that are not part of any Module or Schema Context Path the
-     * operation will fail and returns <code>null</code>
-     * <br>
-     * If Schema Context, Module or Queue of QNames refers to <code>null</code> values,
-     * the method will throws IllegalArgumentException
-     *
-     * @throws IllegalArgumentException
-     *
-     * @param context Schema Context
-     * @param module Yang Module
-     * @param qnamedPath Queue of QNames
-     * @return DataSchemaNode if is present in Module(s) for specified Schema Context and given QNamed Path,
-     * otherwise will return <code>null</code>.
-     */
-    private static DataSchemaNode findSchemaNodeForGivenPath(final SchemaContext context, final Module module,
-            final Queue<QName> qnamedPath) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
+    private static SchemaNode findNode(final ChoiceNode parent, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
+            return parent;
         }
-        if (module == null) {
-            throw new IllegalArgumentException("Module reference cannot be NULL!");
-        }
-        if (module.getNamespace() == null) {
-            throw new IllegalArgumentException("Namespace for Module cannot contains NULL reference!");
-        }
-        if (qnamedPath == null || qnamedPath.isEmpty()) {
-            throw new IllegalStateException("Schema Path contains invalid state of path parts."
-                    + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
-                    + "of path.");
-        }
-
-        DataNodeContainer nextNode = module;
-        final URI moduleNamespace = module.getNamespace();
-
-        QName childNodeQName;
-        DataSchemaNode schemaNode = null;
-        while ((nextNode != null) && !qnamedPath.isEmpty()) {
-            childNodeQName = qnamedPath.peek();
-            if (childNodeQName != null) {
-                final URI childNodeNamespace = childNodeQName.getNamespace();
-
-                schemaNode = nextNode.getDataChildByName(childNodeQName);
-                if (schemaNode != null) {
-                    if (schemaNode instanceof ContainerSchemaNode) {
-                        nextNode = (ContainerSchemaNode) schemaNode;
-                    } else if (schemaNode instanceof ListSchemaNode) {
-                        nextNode = (ListSchemaNode) schemaNode;
-                    } else if (schemaNode instanceof ChoiceNode) {
-                        final ChoiceNode choice = (ChoiceNode) schemaNode;
-                        qnamedPath.poll();
-                        if (!qnamedPath.isEmpty()) {
-                            childNodeQName = qnamedPath.peek();
-                            nextNode = choice.getCaseNodeByName(childNodeQName);
-                            schemaNode = (DataSchemaNode) nextNode;
-                        }
-                    } else {
-                        nextNode = null;
-                    }
-                } else if (!childNodeNamespace.equals(moduleNamespace)) {
-                    final Module nextModule = context.findModuleByNamespace(childNodeNamespace);
-                    schemaNode = findSchemaNodeForGivenPath(context, nextModule, qnamedPath);
-                    return schemaNode;
-                }
-                qnamedPath.poll();
+        ChoiceCaseNode node = parent.getCaseNodeByName(current);
+        if (node != null) {
+            return findNodeInCase(node, nextLevel(path));
+        }
+        return null;
+    }
+
+    private static SchemaNode findNode(final ContainerSchemaNode parent, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
+            return parent;
+        }
+
+        final DataSchemaNode node = parent.getDataChildByName(current);
+        if (node == null) {
+            LOG.debug("Failed to find {} in parent {}", path, parent);
+            return null;
+        }
+
+        return findNode(node, nextLevel(path));
+    }
+
+    private static SchemaNode findNode(final ListSchemaNode parent, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
+            return parent;
+        }
+
+        DataSchemaNode node = parent.getDataChildByName(current);
+        if (node == null) {
+            LOG.debug("Failed to find {} in parent {}", path, parent);
+            return null;
+        }
+        return findNode(node, nextLevel(path));
+    }
+
+    private static SchemaNode findNode(final DataSchemaNode parent, final Iterable<QName> path) {
+        final SchemaNode node;
+        if (!Iterables.isEmpty(path)) {
+            if (parent instanceof ContainerSchemaNode) {
+                node = findNode((ContainerSchemaNode) parent, path);
+            } else if (parent instanceof ListSchemaNode) {
+                node = findNode((ListSchemaNode) parent, path);
+            } else if (parent instanceof ChoiceNode) {
+                node = findNode((ChoiceNode) parent, path);
+            } else {
+                throw new IllegalArgumentException(
+                        String.format("Path nesting violation in parent %s path %s", parent, path));
             }
+        } else {
+            node = parent;
+        }
+
+        if (node == null) {
+            LOG.debug("Failed to find {} in parent {}", path, parent);
+            return null;
         }
-        return schemaNode;
+        return node;
+    }
+
+    private static SchemaNode findNodeInCase(final ChoiceCaseNode parent, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
+            return parent;
+        }
+
+        DataSchemaNode node = parent.getDataChildByName(current);
+        if (node == null) {
+            LOG.debug("Failed to find {} in parent {}", path, parent);
+            return null;
+        }
+        return findNode(node, nextLevel(path));
+    }
+
+    private static RpcDefinition getRpcByName(final Module module, final QName name) {
+        for (RpcDefinition rpc : module.getRpcs()) {
+            if (rpc.getQName().equals(name)) {
+                return rpc;
+            }
+        }
+        return null;
+    }
+
+    private static Iterable<QName> nextLevel(final Iterable<QName> path) {
+        return Iterables.skip(path, 1);
+    }
+
+    private static NotificationDefinition getNotificationByName(final Module module, final QName name) {
+        for (NotificationDefinition notification : module.getNotifications()) {
+            if (notification.getQName().equals(name)) {
+                return notification;
+            }
+        }
+        return null;
+    }
+
+    private static GroupingDefinition getGroupingByName(final Module module, final QName name) {
+        for (GroupingDefinition grouping : module.getGroupings()) {
+            if (grouping.getQName().equals(name)) {
+                return grouping;
+            }
+        }
+        return null;
     }
 
     /**
-     * Transforms string representation of XPath to Queue of QNames. The XPath is split by "/" and for each part of
-     * XPath is assigned correct module in Schema Path.
-     * <br>
-     * If Schema Context, Parent Module or XPath string contains <code>null</code> values,
-     * the method will throws IllegalArgumentException
+     * Transforms string representation of XPath to Queue of QNames. The XPath
+     * is split by "/" and for each part of XPath is assigned correct module in
+     * Schema Path. <br>
+     * If Schema Context, Parent Module or XPath string contains
+     * <code>null</code> values, the method will throws IllegalArgumentException
      *
      * @throws IllegalArgumentException
      *
-     * @param context Schema Context
-     * @param parentModule Parent Module
-     * @param xpath XPath String
-     * @return
+     * @param context
+     *            Schema Context
+     * @param parentModule
+     *            Parent Module
+     * @param xpath
+     *            XPath String
+     * @return return a list of QName
      */
-    private static Queue<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule,
-            final String xpath) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
-        }
-        if (parentModule == null) {
-            throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
-        }
-        if (xpath == null) {
-            throw new IllegalArgumentException("XPath string reference cannot be NULL!");
-        }
-
-        final Queue<QName> path = new LinkedList<>();
-        final String[] prefixedPath = xpath.split("/");
-        for (int i = 0; i < prefixedPath.length; ++i) {
-            if (!prefixedPath[i].isEmpty()) {
-                path.add(stringPathPartToQName(context, parentModule, prefixedPath[i]));
+    private static List<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule, final String xpath) {
+        Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
+        Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
+        Preconditions.checkArgument(xpath != null, "XPath string reference cannot be NULL");
+
+        List<QName> path = new LinkedList<QName>();
+        for (String pathComponent : SLASH_SPLITTER.split(xpath)) {
+            if (!pathComponent.isEmpty()) {
+                path.add(stringPathPartToQName(context, parentModule, pathComponent));
             }
         }
         return path;
     }
 
     /**
-     * Transforms part of Prefixed Path as java String to QName.
-     * <br>
-     * If the string contains module prefix separated by ":" (i.e. mod:container) this module is provided from from
-     * Parent Module list of imports. If the Prefixed module is present in Schema Context the QName can be
-     * constructed.
-     * <br>
-     * If the Prefixed Path Part does not contains prefix the Parent's Module namespace is taken for construction of
-     * QName.
-     * <br>
-     * If Schema Context, Parent Module or Prefixed Path Part refers to <code>null</code> the method will throw
-     * IllegalArgumentException
+     * Transforms part of Prefixed Path as java String to QName. <br>
+     * If the string contains module prefix separated by ":" (i.e.
+     * mod:container) this module is provided from from Parent Module list of
+     * imports. If the Prefixed module is present in Schema Context the QName
+     * can be constructed. <br>
+     * If the Prefixed Path Part does not contains prefix the Parent's Module
+     * namespace is taken for construction of QName. <br>
+     * If Schema Context, Parent Module or Prefixed Path Part refers to
+     * <code>null</code> the method will throw IllegalArgumentException
      *
      * @throws IllegalArgumentException
      *
-     * @param context Schema Context
-     * @param parentModule Parent Module
-     * @param prefixedPathPart Prefixed Path Part string
+     * @param context
+     *            Schema Context
+     * @param parentModule
+     *            Parent Module
+     * @param prefixedPathPart
+     *            Prefixed Path Part string
      * @return QName from prefixed Path Part String.
      */
-    private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule,
-            final String prefixedPathPart) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
-        }
-        if (parentModule == null) {
-            throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
-        }
-        if (prefixedPathPart == null) {
-            throw new IllegalArgumentException("Prefixed Path Part cannot be NULL!");
-        }
+    private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule, final String prefixedPathPart) {
+        Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
+        Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
+        Preconditions.checkArgument(prefixedPathPart != null, "Prefixed Path Part cannot be NULL!");
 
-        if (prefixedPathPart.contains(":")) {
-            final String[] prefixedName = prefixedPathPart.split(":");
-            final Module module = resolveModuleForPrefix(context, parentModule, prefixedName[0]);
-            if (module != null) {
-                return new QName(module.getNamespace(), module.getRevision(), prefixedName[1]);
-            }
+        if (prefixedPathPart.indexOf(':') != -1) {
+            final Iterator<String> prefixedName = COLON_SPLITTER.split(prefixedPathPart).iterator();
+            final String modulePrefix = prefixedName.next();
+
+            Module module = resolveModuleForPrefix(context, parentModule, modulePrefix);
+            Preconditions.checkArgument(module != null, "Failed to resolve xpath: no module found for prefix %s in module %s",
+                    modulePrefix, parentModule.getName());
+
+            return QName.create(module.getQNameModule(), prefixedName.next());
         } else {
-            return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
+            return QName.create(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
         }
-        return null;
     }
 
     /**
-     * Method will attempt to resolve and provide Module reference for specified module prefix. Each Yang module
-     * could contains multiple imports which MUST be associated with corresponding module prefix. The method simply
-     * looks into module imports and returns the module that is bounded with specified prefix. If the prefix is not
-     * present in module or the prefixed module is not present in specified Schema Context,
-     * the method will return <code>null</code>.
-     * <br>
-     * If String prefix is the same as prefix of the specified Module the reference to this module is returned.
-     * <br>
-     * If Schema Context, Module or Prefix are referring to <code>null</code> the method will return
-     * IllegalArgumentException
+     * Method will attempt to resolve and provide Module reference for specified
+     * module prefix. Each Yang module could contains multiple imports which
+     * MUST be associated with corresponding module prefix. The method simply
+     * looks into module imports and returns the module that is bounded with
+     * specified prefix. If the prefix is not present in module or the prefixed
+     * module is not present in specified Schema Context, the method will return
+     * <code>null</code>. <br>
+     * If String prefix is the same as prefix of the specified Module the
+     * reference to this module is returned. <br>
+     * If Schema Context, Module or Prefix are referring to <code>null</code>
+     * the method will return IllegalArgumentException
      *
      * @throws IllegalArgumentException
      *
-     * @param context Schema Context
-     * @param module Yang Module
-     * @param prefix Module Prefix
-     * @return Module for given prefix in specified Schema Context if is present, otherwise returns <code>null</code>
+     * @param context
+     *            Schema Context
+     * @param module
+     *            Yang Module
+     * @param prefix
+     *            Module Prefix
+     * @return Module for given prefix in specified Schema Context if is
+     *         present, otherwise returns <code>null</code>
      */
     private static Module resolveModuleForPrefix(final SchemaContext context, final Module module, final String prefix) {
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
-        }
-        if (module == null) {
-            throw new IllegalArgumentException("Module reference cannot be NULL!");
-        }
-        if (prefix == null) {
-            throw new IllegalArgumentException("Prefix string cannot be NULL!");
-        }
+        Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
+        Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
+        Preconditions.checkArgument(prefix != null, "Prefix string cannot be NULL");
 
         if (prefix.equals(module.getPrefix())) {
             return module;
         }
 
-        final Set<ModuleImport> imports = module.getImports();
-        for (final ModuleImport mi : imports) {
+        Set<ModuleImport> imports = module.getImports();
+        for (ModuleImport mi : imports) {
             if (prefix.equals(mi.getPrefix())) {
                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
             }
@@ -496,52 +564,154 @@ public final class SchemaContextUtil {
     /**
      * @throws IllegalArgumentException
      *
-     * @param context Schema Context
-     * @param module Yang Module
-     * @param relativeXPath Non conditional Revision Aware Relative XPath
-     * @param leafrefSchemaPath Schema Path for Leafref
-     * @return
+     * @param context
+     *            Schema Context
+     * @param module
+     *            Yang Module
+     * @param relativeXPath
+     *            Non conditional Revision Aware Relative XPath
+     * @param actualSchemaNode
+     *            actual schema node
+     * @return list of QName
      */
-    private static Queue<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
-            final RevisionAwareXPath relativeXPath, final SchemaPath leafrefSchemaPath) {
-        final Queue<QName> absolutePath = new LinkedList<>();
-        if (context == null) {
-            throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
+    private static Iterable<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
+            final RevisionAwareXPath relativeXPath, final SchemaNode actualSchemaNode) {
+        Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
+        Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
+        Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
+        Preconditions.checkState(!relativeXPath.isAbsolute(),
+                "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
+                        + "for non relative Revision Aware XPath use findDataSchemaNode method");
+        Preconditions.checkState(actualSchemaNode.getPath() != null,
+                "Schema Path reference for Leafref cannot be NULL");
+
+        final Iterable<String> xpaths = SLASH_SPLITTER.split(relativeXPath.toString());
+
+        // Find out how many "parent" components there are
+        // FIXME: is .contains() the right check here?
+        // FIXME: case ../../node1/node2/../node3/../node4
+        int colCount = 0;
+        for (Iterator<String> it = xpaths.iterator(); it.hasNext() && it.next().contains(".."); ) {
+            ++colCount;
+        }
+
+        final Iterable<QName> schemaNodePath = actualSchemaNode.getPath().getPathFromRoot();
+
+        if (Iterables.size(schemaNodePath) - colCount >= 0) {
+            return Iterables.concat(Iterables.limit(schemaNodePath, Iterables.size(schemaNodePath) - colCount),
+                    Iterables.transform(Iterables.skip(xpaths, colCount), new Function<String, QName>() {
+                        @Override
+                        public QName apply(final String input) {
+                            return stringPathPartToQName(context, module, input);
+                        }
+                    }));
         }
-        if (module == null) {
-            throw new IllegalArgumentException("Module reference cannot be NULL!");
+        return Iterables.concat(schemaNodePath,
+                Iterables.transform(Iterables.skip(xpaths, colCount), new Function<String, QName>() {
+                    @Override
+                    public QName apply(final String input) {
+                        return stringPathPartToQName(context, module, input);
+                    }
+                }));
+    }
+
+    /**
+     * 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
+     * type definition.
+     *
+     * @param typeDefinition
+     *            type of node which will be extracted
+     * @param schemaContext
+     *            Schema Context
+     * @param schema
+     *            Schema Node
+     * @return recursively found type definition this leafref is pointing to or null if the xpath is incorrect (null is there to preserve backwards compatibility)
+     */
+    public static TypeDefinition<?> getBaseTypeForLeafRef(final LeafrefTypeDefinition typeDefinition, final SchemaContext schemaContext, final SchemaNode schema) {
+        RevisionAwareXPath pathStatement = typeDefinition.getPathStatement();
+        pathStatement = new RevisionAwareXPathImpl(stripConditionsFromXPathString(pathStatement), pathStatement.isAbsolute());
+
+        final Module parentModule = SchemaContextUtil.findParentModule(schemaContext, schema);
+
+        final DataSchemaNode dataSchemaNode;
+        if(pathStatement.isAbsolute()) {
+            dataSchemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(schemaContext, parentModule, pathStatement);
+        } else {
+            dataSchemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNodeForRelativeXPath(schemaContext, parentModule, schema, pathStatement);
+        }
+
+        // FIXME this is just to preserve backwards compatibility since yangtools do not mind wrong leafref xpaths
+        // and current expected behaviour for such cases is to just use pure string
+        // This should throw an exception about incorrect XPath in leafref
+        if(dataSchemaNode == null) {
+            return null;
         }
-        if (relativeXPath == null) {
-            throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
+
+        final TypeDefinition<?> targetTypeDefinition = typeDefinition(dataSchemaNode);
+
+        if(targetTypeDefinition instanceof LeafrefTypeDefinition) {
+            return getBaseTypeForLeafRef(((LeafrefTypeDefinition) targetTypeDefinition), schemaContext, dataSchemaNode);
+        } else {
+            return targetTypeDefinition;
         }
-        if (relativeXPath.isAbsolute()) {
-            throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
-                    + "for non relative Revision Aware XPath use findDataSchemaNode method!");
+    }
+
+    /**
+     * Removes conditions from xPath pointed to target node.
+     *
+     * @param pathStatement
+     *            xPath to target node
+     * @return string representation of xPath without conditions
+     *
+     */
+    private static String stripConditionsFromXPathString(final RevisionAwareXPath pathStatement) {
+        return pathStatement.toString().replaceAll("\\[.*\\]", "");
+    }
+
+    /**
+     * Extracts the base type of leaf schema node until it reach concrete type of TypeDefinition.
+     *
+     * @param node
+     *            a node representing LeafSchemaNode
+     * @return concrete type definition of node value
+     */
+    private static TypeDefinition<? extends Object> typeDefinition(final LeafSchemaNode node) {
+        TypeDefinition<?> baseType = node.getType();
+        while (baseType.getBaseType() != null) {
+            baseType = baseType.getBaseType();
         }
-        if (leafrefSchemaPath == null) {
-            throw new IllegalArgumentException("Schema Path reference for Leafref cannot be NULL!");
+        return baseType;
+    }
+
+    /**
+     * Extracts the base type of leaf schema node until it reach concrete type of TypeDefinition.
+     *
+     * @param node
+     *            a node representing LeafListSchemaNode
+     * @return concrete type definition of node value
+     */
+    private static TypeDefinition<? extends Object> typeDefinition(final LeafListSchemaNode node) {
+        TypeDefinition<?> baseType = node.getType();
+        while (baseType.getBaseType() != null) {
+            baseType = baseType.getBaseType();
         }
+        return baseType;
+    }
 
-        final String strXPath = relativeXPath.toString();
-        if (strXPath != null) {
-            final String[] xpaths = strXPath.split("/");
-            if (xpaths != null) {
-                int colCount = 0;
-                while (xpaths[colCount].contains("..")) {
-                    ++colCount;
-                }
-                final List<QName> path = leafrefSchemaPath.getPath();
-                if (path != null) {
-                    int lenght = path.size() - colCount - 1;
-                    for (int i = 0; i < lenght; ++i) {
-                        absolutePath.add(path.get(i));
-                    }
-                    for (int i = colCount; i < xpaths.length; ++i) {
-                        absolutePath.add(stringPathPartToQName(context, module, xpaths[i]));
-                    }
-                }
-            }
+    /**
+     * Gets the base type of DataSchemaNode value.
+     *
+     * @param node
+     *            a node representing DataSchemaNode
+     * @return concrete type definition of node value
+     */
+    private static TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
+        if (node instanceof LeafListSchemaNode) {
+            return typeDefinition((LeafListSchemaNode) node);
+        } else if (node instanceof LeafSchemaNode) {
+            return typeDefinition((LeafSchemaNode) node);
+        } else {
+            throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(node).toString());
         }
-        return absolutePath;
     }
 }