Rework LeafRefValidation
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / leafref / LeafRefPathParserImpl.java
index d781984325f67faf48116dcd6278dc95441dd796..2e763958b74c047a170f2d84e8fb668ec8e8f790 100644 (file)
  */
 package org.opendaylight.yangtools.yang.data.impl.leafref;
 
-import org.antlr.v4.runtime.CharStreams;
-import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.tree.ParseTreeWalker;
-import org.opendaylight.yangtools.yang.data.impl.leafref.LeafRefPathParser.Path_argContext;
-import org.opendaylight.yangtools.yang.model.api.Module;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Set;
+import org.opendaylight.yangtools.yang.common.AbstractQName;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.UnqualifiedQName;
+import org.opendaylight.yangtools.yang.model.api.PathExpression;
+import org.opendaylight.yangtools.yang.model.api.PathExpression.DerefSteps;
+import org.opendaylight.yangtools.yang.model.api.PathExpression.LocationPathSteps;
+import org.opendaylight.yangtools.yang.model.api.PathExpression.Steps;
+import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.opendaylight.yangtools.yang.xpath.api.YangBinaryExpr;
+import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator;
+import org.opendaylight.yangtools.yang.xpath.api.YangExpr;
+import org.opendaylight.yangtools.yang.xpath.api.YangFunction;
+import org.opendaylight.yangtools.yang.xpath.api.YangFunctionCallExpr;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Relative;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
+import org.opendaylight.yangtools.yang.xpath.api.YangPathExpr;
+import org.opendaylight.yangtools.yang.xpath.api.YangQNameExpr;
 
 final class LeafRefPathParserImpl {
-    private final SchemaContext schemaContext;
-    private final Module module;
-    private final SchemaNode node;
-
-    LeafRefPathParserImpl(final SchemaContext schemaContext, final Module currentModule, final SchemaNode currentNode) {
-        this.schemaContext = schemaContext;
-        this.module = currentModule;
-        this.node = currentNode;
+    private final QNameModule leafrefModule;
+    private final QNameModule nodeModule;
+
+    LeafRefPathParserImpl(final LeafrefTypeDefinition leafrefType, final TypedDataSchemaNode currentNode) {
+        // FIXME: these two namespaces look not quite right:
+        //        - leafrefModule is used for absolute paths, irrespective of where they occur
+        //        - nodeModule is used for relative paths, irrespective of where they occur
+        //
+        // There is little in RFC7950 which would hint at such a distinction and if even if it were true, it would be
+        // the job of YANG parser to ensure that absolute paths are bound during parsing.
+        //
+        // The only distinction is relative to where the leafref is defined, namely:
+        //
+        // 1) as per section 9.9.2:
+        //     o  If the "path" statement is defined within a typedef, the context
+        //        node is the leaf or leaf-list node in the data tree that
+        //        references the typedef.
+        //
+        //     o  Otherwise, the context node is the node in the data tree for which
+        //        the "path" statement is defined.
+        //
+        // 2) as per section 6.4.1:
+        //     o  Names without a namespace prefix belong to the same namespace as
+        //        the identifier of the current node.  Inside a grouping, that
+        //        namespace is affected by where the grouping is used (see
+        //        Section 7.13).  Inside a typedef, that namespace is affected by
+        //        where the typedef is referenced.  If a typedef is defined and
+        //        referenced within a grouping, the namespace is affected by where
+        //        the grouping is used (see Section 7.13).
+        this.leafrefModule = getBaseModule(leafrefType);
+        this.nodeModule = currentNode.getQName().getModule();
     }
 
-    LeafRefPath parseLeafRefPath(final String path) throws LeafRefYangSyntaxErrorException {
-        final Path_argContext pathCtx = parseLeafRefPathSource(path);
+    LeafRefPath parseLeafRefPath(final PathExpression path) {
+        final Steps steps = path.getSteps();
+        if (steps instanceof LocationPathSteps) {
+            return parseLocationPath(((LocationPathSteps) steps).getLocationPath());
+        } else if (steps instanceof DerefSteps) {
+            throw new UnsupportedOperationException("deref() leafrefs are not implemented yet");
+        } else {
+            throw new IllegalStateException("Unsupported steps " + steps);
+        }
+    }
 
-        final ParseTreeWalker walker = new ParseTreeWalker();
-        final LeafRefPathParserListenerImpl leafRefPathParserListenerImpl = new LeafRefPathParserListenerImpl(
-            schemaContext, module, node);
-        walker.walk(leafRefPathParserListenerImpl, pathCtx);
+    private LeafRefPath parseLocationPath(final YangLocationPath locationPath) {
+        return LeafRefPath.create(
+            createPathSteps(locationPath.isAbsolute() ? leafrefModule : nodeModule, locationPath.getSteps()),
+            locationPath.isAbsolute());
+    }
 
-        return leafRefPathParserListenerImpl.getLeafRefPath();
+    private static Deque<QNameWithPredicate> createPathSteps(final QNameModule localModule,
+            final ImmutableList<Step> steps) {
+        final Deque<QNameWithPredicate> path = new ArrayDeque<>(steps.size());
+        for (Step step : steps) {
+            switch (step.getAxis()) {
+                case CHILD:
+                    checkState(step instanceof QNameStep, "Unsupported step %s", step);
+                    path.add(adaptChildStep((QNameStep) step, localModule));
+                    break;
+                case PARENT:
+                    path.add(QNameWithPredicate.UP_PARENT);
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported axis in step " + step);
+            }
+        }
+        return path;
     }
 
-    private Path_argContext parseLeafRefPathSource(final String path) throws LeafRefYangSyntaxErrorException {
-        final LeafRefPathLexer lexer = new LeafRefPathLexer(CharStreams.fromString(path));
-        final LeafRefPathParser parser = new LeafRefPathParser(new CommonTokenStream(lexer));
+    private static QNameWithPredicate adaptChildStep(final QNameStep step, final QNameModule localModule) {
+        final QName qname = resolve(step.getQName(), localModule);
+        final Set<YangExpr> predicates = step.getPredicates();
+        if (predicates.isEmpty()) {
+            return new SimpleQNameWithPredicate(qname);
+        }
+
+        final QNameWithPredicateBuilder builder = new QNameWithPredicateBuilder(qname.getModule(),
+            qname.getLocalName());
+
+        for (YangExpr pred : predicates) {
+            final QNamePredicateBuilder predBuilder = new QNamePredicateBuilder();
+
+            if (pred instanceof YangBinaryExpr) {
+                final YangBinaryExpr eqPred = (YangBinaryExpr) pred;
+                checkState(eqPred.getOperator() == YangBinaryOperator.EQUALS);
 
-        final LeafRefPathErrorListener errorListener = new LeafRefPathErrorListener(module);
-        lexer.removeErrorListeners();
-        lexer.addErrorListener(errorListener);
-        parser.removeErrorListeners();
-        parser.addErrorListener(errorListener);
+                final YangExpr left = eqPred.getLeftExpr();
+                checkState(left instanceof YangQNameExpr, "Unsupported left expression %s", left);
+                predBuilder.setIdentifier(resolve(((YangQNameExpr) left).getQName(), localModule));
+
+                final YangExpr right = eqPred.getRightExpr();
+                if (right instanceof YangPathExpr) {
+                    final YangPathExpr rightPath = (YangPathExpr) right;
+                    final YangExpr filter = rightPath.getFilterExpr();
+                    if (filter instanceof YangFunctionCallExpr) {
+                        checkState(YangFunction.CURRENT.getIdentifier().equals(
+                            ((YangFunctionCallExpr) filter).getName()));
+                    } else {
+                        throw new IllegalStateException("Unhandled filter " + filter);
+                    }
+
+                    final Relative location = rightPath.getLocationPath()
+                            .orElseThrow(() -> new IllegalStateException("Missing locationPath in " + rightPath));
+                    predBuilder.setPathKeyExpression(LeafRefPath.create(
+                        createPathSteps(localModule, location.getSteps()), false));
+                } else {
+                    throw new UnsupportedOperationException("Not implemented for " + right);
+                }
+            }
+
+            builder.addQNamePredicate(predBuilder.build());
+        }
+
+        return builder.build();
+    }
+
+    private static QName resolve(final AbstractQName qname, final QNameModule localModule) {
+        if (qname instanceof QName) {
+            return (QName) qname;
+        } else if (qname instanceof UnqualifiedQName) {
+            // Bind to namespace. Note we expect to perform frequent matching, hence we are interning the result
+            return ((UnqualifiedQName) qname).bindTo(localModule).intern();
+        } else {
+            throw new IllegalStateException("Unhandled unresolved QName " + qname);
+        }
+    }
 
-        final Path_argContext result = parser.path_arg();
-        errorListener.validate();
-        return result;
+    /**
+     * Find the first definition of supplied leafref type and return the module which contains this definition.
+     */
+    private static QNameModule getBaseModule(final LeafrefTypeDefinition leafrefType) {
+        LeafrefTypeDefinition current = leafrefType;
+        while (true) {
+            final LeafrefTypeDefinition base = current.getBaseType();
+            if (base == null) {
+                return current.getQName().getModule();
+            }
+            current = base;
+        }
     }
 }