Revert "Revert "BUG-994: reorganize SchemaPath into a tree""
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / SchemaContextUtil.java
index 0dc0c19e6b19d2b7e539052d2c15bab45848a6b7..fc98c1daf0aef88d6e202743d0db8e3fcb56576d 100644 (file)
@@ -7,12 +7,17 @@
  */
 package org.opendaylight.yangtools.yang.model.util;
 
+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.net.URI;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -39,8 +44,6 @@ import org.opendaylight.yangtools.yang.model.api.UsesNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Preconditions;
-
 /**
  * The Schema Context Util contains support methods for searching through Schema
  * Context modules for specified schema nodes via Schema Path or Revision Aware
@@ -50,6 +53,8 @@ import com.google.common.base.Preconditions;
  */
 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() {
     }
@@ -185,7 +190,7 @@ public final class SchemaContextUtil {
 
         SchemaPath actualNodePath = actualSchemaNode.getPath();
         if (actualNodePath != null) {
-            List<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
+            Iterable<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
 
             if (qnamePath != null) {
                 return findNodeInSchemaContext(context, qnamePath);
@@ -217,18 +222,15 @@ public final class SchemaContextUtil {
         Preconditions.checkState(schemaNode.getPath() != null, "Schema Path for Schema Node is not "
                 + "set properly (Schema Path is NULL)");
 
-        List<QName> qnamedPath = schemaNode.getPath().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.");
-        }
-        QName qname = qnamedPath.get(qnamedPath.size() - 1);
+        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 List<QName> path) {
-        final QName current = path.get(0);
+    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());
@@ -266,8 +268,8 @@ public final class SchemaContextUtil {
         return (GroupingDefinition) currentParent;
     }
 
-    private static SchemaNode findNodeInModule(final Module module, final List<QName> path) {
-        final QName current = path.get(0);
+    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);
@@ -309,14 +311,14 @@ public final class SchemaContextUtil {
         return null;
     }
 
-    private static SchemaNode findNodeInGrouping(final GroupingDefinition grouping, final List<QName> path) {
-        if (path.isEmpty()) {
+    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 QName current = path.get(0);
         final DataSchemaNode node = grouping.getDataChildByName(current);
         if (node == null) {
             LOG.debug("No node matching {} found in grouping {}", current, grouping);
@@ -326,14 +328,14 @@ public final class SchemaContextUtil {
         return findNode(node, nextLevel(path));
     }
 
-    private static SchemaNode findNodeInRpc(final RpcDefinition rpc, final List<QName> path) {
-        if (path.isEmpty()) {
+    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;
         }
 
         LOG.trace("Looking for path {} in rpc {}", path, rpc);
-        final QName current = path.get(0);
         switch (current.getLocalName()) {
         case "input":
             return findNode(rpc.getInput(), nextLevel(path));
@@ -345,14 +347,14 @@ public final class SchemaContextUtil {
         }
     }
 
-    private static SchemaNode findNodeInNotification(final NotificationDefinition ntf, final List<QName> path) {
-        if (path.isEmpty()) {
+    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;
         }
 
         LOG.trace("Looking for path {} in notification {}", path, ntf);
-        final QName current = path.get(0);
         DataSchemaNode node = ntf.getDataChildByName(current);
         if (node == null) {
             LOG.debug("No node matching {} found in notification {}", current, ntf);
@@ -362,11 +364,11 @@ public final class SchemaContextUtil {
         return findNode(node, nextLevel(path));
     }
 
-    private static SchemaNode findNode(final ChoiceNode parent, final List<QName> path) {
-        if (path.isEmpty()) {
+    private static SchemaNode findNode(final ChoiceNode parent, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
             return parent;
         }
-        QName current = path.get(0);
         ChoiceCaseNode node = parent.getCaseNodeByName(current);
         if (node != null) {
             return findNodeInCase(node, nextLevel(path));
@@ -374,12 +376,12 @@ public final class SchemaContextUtil {
         return null;
     }
 
-    private static SchemaNode findNode(final ContainerSchemaNode parent, final List<QName> path) {
-        if (path.isEmpty()) {
+    private static SchemaNode findNode(final ContainerSchemaNode parent, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
             return parent;
         }
 
-        final QName current = path.get(0);
         final DataSchemaNode node = parent.getDataChildByName(current);
         if (node == null) {
             LOG.debug("Failed to find {} in parent {}", path, parent);
@@ -389,12 +391,12 @@ public final class SchemaContextUtil {
         return findNode(node, nextLevel(path));
     }
 
-    private static SchemaNode findNode(final ListSchemaNode parent, final List<QName> path) {
-        if (path.isEmpty()) {
+    private static SchemaNode findNode(final ListSchemaNode parent, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
             return parent;
         }
 
-        QName current = path.get(0);
         DataSchemaNode node = parent.getDataChildByName(current);
         if (node == null) {
             LOG.debug("Failed to find {} in parent {}", path, parent);
@@ -403,9 +405,9 @@ public final class SchemaContextUtil {
         return findNode(node, nextLevel(path));
     }
 
-    private static SchemaNode findNode(final DataSchemaNode parent, final List<QName> path) {
+    private static SchemaNode findNode(final DataSchemaNode parent, final Iterable<QName> path) {
         final SchemaNode node;
-        if (!path.isEmpty()) {
+        if (!Iterables.isEmpty(path)) {
             if (parent instanceof ContainerSchemaNode) {
                 node = findNode((ContainerSchemaNode) parent, path);
             } else if (parent instanceof ListSchemaNode) {
@@ -427,12 +429,12 @@ public final class SchemaContextUtil {
         return node;
     }
 
-    public static SchemaNode findNodeInCase(final ChoiceCaseNode parent, final List<QName> path) {
-        if (path.isEmpty()) {
+    public static SchemaNode findNodeInCase(final ChoiceCaseNode parent, final Iterable<QName> path) {
+        final QName current = Iterables.getFirst(path, null);
+        if (current == null) {
             return parent;
         }
 
-        QName current = path.get(0);
         DataSchemaNode node = parent.getDataChildByName(current);
         if (node == null) {
             LOG.debug("Failed to find {} in parent {}", path, parent);
@@ -450,8 +452,8 @@ public final class SchemaContextUtil {
         return null;
     }
 
-    private static List<QName> nextLevel(final List<QName> path) {
-        return path.subList(1, path.size());
+    private static Iterable<QName> nextLevel(final Iterable<QName> path) {
+        return Iterables.skip(path, 1);
     }
 
     public static NotificationDefinition getNotificationByName(final Module module, final QName name) {
@@ -491,69 +493,82 @@ public final class SchemaContextUtil {
         return result;
     }
 
-    private static DataSchemaNode findCorrectTargetFromGrouping(final DataSchemaNode node, final SchemaContext ctx) {
-        if (node.getPath().getPath().size() == 1) {
-            // uses is under module statement
-            Module m = findParentModule(ctx, node);
-            DataSchemaNode result = null;
-            for (UsesNode u : m.getUses()) {
-                SchemaNode targetGrouping = findNodeInSchemaContext(ctx, u.getGroupingPath().getPath());
-                if (!(targetGrouping instanceof GroupingDefinition)) {
-                    throw new IllegalArgumentException(String.format("Failed to generate code for augment in %s", u));
-                }
-                GroupingDefinition gr = (GroupingDefinition) targetGrouping;
-                result = gr.getDataChildByName(node.getQName().getLocalName());
-            }
-            if (result == null) {
-                throw new IllegalArgumentException("Failed to generate code for augment");
+    private static DataSchemaNode findCorrectImmediateTargetFromGrouping(final DataSchemaNode node, final SchemaContext ctx) {
+        // uses is under module statement
+        final Module m = findParentModule(ctx, node);
+        Preconditions.checkArgument(m != null, "Failed to find module for node {} in context {}", node, ctx);
+
+        for (final UsesNode u : m.getUses()) {
+            final SchemaNode targetGrouping = findNodeInSchemaContext(ctx, u.getGroupingPath().getPathFromRoot());
+            Preconditions.checkArgument(targetGrouping instanceof GroupingDefinition,
+                    "Failed to generate code for augment in %s", u);
+
+            LOG.trace("Checking grouping {} for node {}", targetGrouping, node);
+            final GroupingDefinition gr = (GroupingDefinition) targetGrouping;
+            final DataSchemaNode result = gr.getDataChildByName(node.getQName().getLocalName());
+            if (result != null) {
+                return result;
             }
-            return result;
-        } else {
-            DataSchemaNode result = null;
+
+            LOG.debug("Skipped grouping {}, no matching node found", gr);
+        }
+
+        throw new IllegalArgumentException(
+                String.format("Failed to find uses node matching {} in context {}", node, ctx));
+    }
+
+    private static DataSchemaNode findCorrectTargetFromGrouping(final DataSchemaNode node, final SchemaContext ctx) {
+        if (node.getPath().getPath().size() != 1) {
             QName currentName = node.getQName();
+            // tmpPath is used to track level of nesting
             List<QName> tmpPath = new ArrayList<>();
             Object parent = null;
 
-            SchemaPath sp = node.getPath();
-            List<QName> names = sp.getPath();
-            List<QName> newNames = new ArrayList<>(names);
-            newNames.remove(newNames.size() - 1);
-            SchemaPath newSp = new SchemaPath(newNames, sp.isAbsolute());
-            parent = findDataSchemaNode(ctx, newSp);
+            // create schema path of parent node
+            SchemaPath sp = node.getPath().getParent();
+            parent = findDataSchemaNode(ctx, sp);
 
             do {
                 tmpPath.add(currentName);
+
+                DataSchemaNode result = null;
+                // search parent node's used groupings for presence of wanted
+                // node
                 if (parent instanceof DataNodeContainer) {
                     DataNodeContainer dataNodeParent = (DataNodeContainer) parent;
                     for (UsesNode u : dataNodeParent.getUses()) {
-                        if (result == null) {
-                            result = getResultFromUses(u, currentName.getLocalName(), ctx);
+                        result = getResultFromUses(u, currentName.getLocalName(), ctx);
+                        if (result != null) {
+                            break;
                         }
                     }
                 }
+
+                // if node is not found in any of current parent's used
+                // groupings => parent is added by grouping too, so repeat same
+                // process for parent
                 if (result == null) {
-                    currentName = ((SchemaNode) parent).getQName();
-                    if (parent instanceof SchemaNode) {
-                        SchemaPath nodeSp = ((SchemaNode) parent).getPath();
-                        List<QName> nodeNames = nodeSp.getPath();
-                        List<QName> nodeNewNames = new ArrayList<>(nodeNames);
-                        nodeNewNames.remove(nodeNewNames.size() - 1);
-                        if (nodeNewNames.isEmpty()) {
-                            parent = getParentModule((SchemaNode) parent, ctx);
-                        } else {
-                            SchemaPath nodeNewSp = new SchemaPath(nodeNewNames, nodeSp.isAbsolute());
-                            parent = findDataSchemaNode(ctx, nodeNewSp);
-                        }
-                    } else {
-                        throw new IllegalArgumentException("Failed to generate code for augment");
-                    }
+                    final SchemaNode sn = (SchemaNode) parent;
+
+                    // set current name to name of parent node
+                    currentName = sn.getQName();
+                    Preconditions.checkArgument(parent instanceof SchemaNode,
+                            "Failed to generate code for augmend node {} at parent {}", node, parent);
+
+                    // create schema path for parent of current parent
+                    final SchemaPath parentSp = sn.getPath().getParent();
+                    parent = parentSp.getPathFromRoot().iterator().hasNext() ? findDataSchemaNode(ctx, parentSp)
+                            : getParentModule(sn, ctx);
+                } else {
+                    // if wanted node was found in grouping, traverse this node
+                    // based on level of nesting
+                    return getTargetNode(tmpPath, result, ctx);
                 }
-            } while (result == null && !(parent instanceof Module));
+            } while (!(parent instanceof Module));
 
-            if (result != null) {
-                result = getTargetNode(tmpPath, result, ctx);
-            }
-            return result;
+            return null;
+        } else {
+            return findCorrectImmediateTargetFromGrouping(node, ctx);
         }
     }
 
@@ -571,11 +586,7 @@ public final class SchemaContextUtil {
         AugmentationSchema augment = null;
         do {
             SchemaPath sp = ((SchemaNode) parent).getPath();
-            List<QName> names = sp.getPath();
-            List<QName> newNames = new ArrayList<>(names);
-            newNames.remove(newNames.size() - 1);
-            SchemaPath newSp = new SchemaPath(newNames, sp.isAbsolute());
-            parent = findDataSchemaNode(ctx, newSp);
+            parent = findDataSchemaNode(ctx, sp.getParent());
             if (parent instanceof AugmentationTarget) {
                 tmpPath.add(currentName);
                 tmpTree.add((SchemaNode) currentNode);
@@ -615,7 +626,7 @@ public final class SchemaContextUtil {
     }
 
     private static DataSchemaNode getResultFromUses(final UsesNode u, final String currentName, final SchemaContext ctx) {
-        SchemaNode targetGrouping = findNodeInSchemaContext(ctx, u.getGroupingPath().getPath());
+        SchemaNode targetGrouping = findNodeInSchemaContext(ctx, u.getGroupingPath().getPathFromRoot());
 
         Preconditions.checkArgument(targetGrouping instanceof GroupingDefinition,
                 "Failed to generate code for augment in %s", u);
@@ -624,7 +635,7 @@ public final class SchemaContextUtil {
     }
 
     private static Module getParentModule(final SchemaNode node, final SchemaContext ctx) {
-        QName qname = node.getPath().getPath().get(0);
+        QName qname = node.getPath().getPathFromRoot().iterator().next();
         URI namespace = qname.getNamespace();
         Date revision = qname.getRevision();
         return ctx.findModuleByNamespaceAndRevision(namespace, revision);
@@ -733,8 +744,7 @@ public final class SchemaContextUtil {
         Preconditions.checkArgument(xpath != null, "XPath string reference cannot be NULL");
 
         List<QName> path = new LinkedList<QName>();
-        String[] prefixedPath = xpath.split("/");
-        for (String pathComponent : prefixedPath) {
+        for (String pathComponent : SLASH_SPLITTER.split(xpath)) {
             if (!pathComponent.isEmpty()) {
                 path.add(stringPathPartToQName(context, parentModule, pathComponent));
             }
@@ -768,14 +778,18 @@ public final class SchemaContextUtil {
         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
         Preconditions.checkArgument(prefixedPathPart != null, "Prefixed Path Part cannot be NULL!");
 
-        if (prefixedPathPart.contains(":")) {
-            String[] prefixedName = prefixedPathPart.split(":");
-            Module module = resolveModuleForPrefix(context, parentModule, prefixedName[0]);
+        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",
-                    prefixedName[0], parentModule.getName());
-            return new QName(module.getNamespace(), module.getRevision(), prefixedName[1]);
+                    modulePrefix, parentModule.getName());
+
+            // FIXME: Module should have a QNameModule handle
+            return QName.create(module.getNamespace(), module.getRevision(), prefixedName.next());
         } else {
-            return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
+            return QName.create(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
         }
     }
 
@@ -834,7 +848,7 @@ public final class SchemaContextUtil {
      *            Schema Path for Leafref
      * @return list of QName
      */
-    private static List<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
+    private static Iterable<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
             final RevisionAwareXPath relativeXPath, final SchemaNode leafrefParentNode) {
         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
@@ -845,27 +859,22 @@ public final class SchemaContextUtil {
         Preconditions.checkState(leafrefParentNode.getPath() != null,
                 "Schema Path reference for Leafref cannot be NULL");
 
-        List<QName> absolutePath = new LinkedList<QName>();
-        String strXPath = relativeXPath.toString();
-        String[] xpaths = strXPath.split("/");
+        final Iterable<String> xpaths = SLASH_SPLITTER.split(relativeXPath.toString());
 
+        // Find out how many "parent" components there are
+        // FIXME: is .contains() the right check here?
         int colCount = 0;
-        while (xpaths[colCount].contains("..")) {
-            colCount = colCount + 1;
-        }
-        List<QName> path = leafrefParentNode.getPath().getPath();
-        if (path != null) {
-            int lenght = path.size() - colCount;
-            absolutePath.addAll(path.subList(0, lenght));
-            List<String> xpathsList = Arrays.asList(xpaths);
-            List<String> sublistedXPath = xpathsList.subList(colCount, xpaths.length);
-            List<QName> sublist = new ArrayList<>();
-            for (String pathPart : sublistedXPath) {
-                sublist.add(stringPathPartToQName(context, module, pathPart));
-            }
-            absolutePath.addAll(sublist);
+        for (Iterator<String> it = xpaths.iterator(); it.hasNext() && it.next().contains(".."); ) {
+            ++colCount;
         }
 
-        return absolutePath;
+        final Iterable<QName> parent = leafrefParentNode.getPath().getPathFromRoot();
+        return Iterables.concat(Iterables.limit(parent, Iterables.size(parent) - colCount),
+                Iterables.transform(Iterables.skip(xpaths, colCount), new Function<String, QName>() {
+                    @Override
+                    public QName apply(final String input) {
+                        return stringPathPartToQName(context, module, input);
+                    }
+                }));
     }
 }