Fix uses/augment linkage
[mdsal.git] / binding / mdsal-binding-generator / src / main / java / org / opendaylight / mdsal / binding / generator / impl / reactor / AugmentRequirement.java
diff --git a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AugmentRequirement.java b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AugmentRequirement.java
new file mode 100644 (file)
index 0000000..ee1b940
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.generator.impl.reactor;
+
+import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Mutable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+
+/**
+ * Class tracking state of resolution of an {@code augment} statement's target generator.
+ *
+ * <p>
+ * This is not quite straightforward. 'path' works on top of schema tree, which is instantiated view. Since we
+ * do not generate duplicate instantiations along 'uses' path, findSchemaTreeGenerator() would satisfy our
+ * request by returning a child of the source 'grouping'.
+ *
+ * <p>
+ * When that happens, our subsequent lookups need to adjust the namespace being looked up to the grouping's
+ * namespace... except for the case when the step is actually an augmentation, in which case we must not make
+ * that adjustment.
+ *
+ * <p>
+ * Hence we deal with this lookup recursively, dropping namespace hints when we cross into groupings. Note we
+ * take an initial hint -- which UsesAugmentGenerator provides, but ModuleAugmentGenerator does not -- and that
+ * accounts for the difference.
+ */
+final class AugmentRequirement implements Mutable {
+    private final @NonNull Set<QNameModule> squashNamespaces = new HashSet<>(4);
+    private final @NonNull AbstractAugmentGenerator augment;
+    private final @NonNull Iterator<QName> remaining;
+
+    private @NonNull AbstractCompositeGenerator<?> target;
+    private QNameModule localNamespace;
+    private QName qname;
+
+    private AugmentRequirement(final AbstractAugmentGenerator augment, final AbstractCompositeGenerator<?> target) {
+        this.augment = requireNonNull(augment);
+        this.target = requireNonNull(target);
+        remaining = augment.statement().argument().getNodeIdentifiers().iterator();
+        qname = remaining.next();
+    }
+
+    AugmentRequirement(final ModuleAugmentGenerator augment, final ModuleGenerator module) {
+        this((AbstractAugmentGenerator) augment, module);
+    }
+
+    AugmentRequirement(final UsesAugmentGenerator augment, final GroupingGenerator grouping) {
+        this((AbstractAugmentGenerator) augment, grouping);
+        // Starting in a grouping: squash namespace references to the grouping's namespace
+        localNamespace = grouping.getQName().getModule();
+        squashNamespaces.add(qname.getModule());
+    }
+
+    @NonNull LinkageProgress resolve() {
+        return qname == null ? resolveAsTarget() : resolveAsChild();
+    }
+
+    private @NonNull LinkageProgress resolveAsTarget() {
+        // Resolved requirement, if we also have original we end resolution here and now
+        final var original = target.tryOriginal();
+        if (original != null) {
+            augment.setTargetGenerator(original);
+            original.addAugment(augment);
+            return LinkageProgress.DONE;
+        }
+        return LinkageProgress.NONE;
+    }
+
+    private @NonNull LinkageProgress resolveAsChild() {
+        // First try local statements without adjustment
+        var gen = target.findLocalSchemaTreeGenerator(qname);
+        if (gen != null) {
+            return progressTo(gen);
+        }
+
+        // Second try local augments, as those are guaranteed to match namespace exactly
+        final var aug = target.findAugmentForGenerator(qname);
+        if (aug != null) {
+            return moveTo(aug);
+        }
+
+        // Third try local groupings, as those perform their own adjustment
+        final var grp = target.findGroupingForGenerator(qname);
+        if (grp != null) {
+            squashNamespaces.add(qname.getModule());
+            localNamespace = grp.getQName().getModule();
+            return moveTo(grp);
+        }
+
+        // Lastly try local statements adjusted with namespace, if applicable
+        gen = target.findLocalSchemaTreeGenerator(squashNamespaces.contains(qname.getModule())
+            ? qname.bindTo(verifyNotNull(localNamespace)) : qname);
+        if (gen != null) {
+            return progressTo(gen);
+        }
+        return LinkageProgress.NONE;
+    }
+
+    private @NonNull LinkageProgress moveTo(final @NonNull AbstractCompositeGenerator<?> newTarget) {
+        target = newTarget;
+        return tryProgress();
+    }
+
+    private @NonNull LinkageProgress progressTo(final @NonNull AbstractExplicitGenerator<?> newTarget) {
+        verify(newTarget instanceof AbstractCompositeGenerator, "Unexpected generator %s", newTarget);
+        target = (AbstractCompositeGenerator<?>) newTarget;
+        qname = remaining.hasNext() ? remaining.next() : null;
+        return tryProgress();
+    }
+
+    private @NonNull LinkageProgress tryProgress() {
+        final var progress = resolve();
+        return progress != LinkageProgress.NONE ? progress : LinkageProgress.SOME;
+    }
+}