Fix uses/augment linkage 14/99414/26
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 21 Jan 2022 06:10:37 +0000 (07:10 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sat, 12 Feb 2022 23:19:01 +0000 (00:19 +0100)
Our previous fix for MDSAL-715 switched the resolution logic to a unified
approach based on linked augmentation. Unfortunately it missed the fact
that linking the groupings also had the side-effect of setting the
augment target, which in turn populates 'augments' list.

Our failure to do so ends up wrecking lookups in the case where we have
an uses-augmented node further augmented by a module-augment.

Fix this by intertwining original and augment linkage, so that a subtree
root (such as module) pays attention to augments which need to resolve
before descending to recursive linkage.

JIRA: MDSAL-718
Change-Id: I7bd6cbed636267d35113888ddd35f0c6d9411043
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
13 files changed:
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractAugmentGenerator.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractCompositeGenerator.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractExplicitGenerator.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AugmentRequirement.java [new file with mode: 0644]
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/GeneratorReactor.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/LinkageProgress.java [new file with mode: 0644]
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/ModuleAugmentGenerator.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/UsesAugmentGenerator.java
binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal718Test.java [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/resources/mdsal718/bar.yang [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/resources/mdsal718/baz.yang [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/resources/mdsal718/foo.yang [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/resources/mdsal718/xyzzy.yang [new file with mode: 0644]

index 4875bd00c4f601ece3bbd07e83fc611eea09fd7a..807a253907f80ad1a07b526b19077dba6b096fc2 100644 (file)
@@ -9,6 +9,7 @@ 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.Comparator;
 import java.util.Iterator;
@@ -143,21 +144,17 @@ abstract class AbstractAugmentGenerator extends AbstractCompositeGenerator<Augme
         // Augments are never added as getters, as they are handled via Augmentable mechanics
     }
 
-    final void setTargetGenerator(final AbstractExplicitGenerator<?> target) {
-        verify(target instanceof AbstractCompositeGenerator, "Unexpected target %s", target);
-        targetGen = (AbstractCompositeGenerator<?>) target;
-        targetGen.addAugment(this);
+    final void startLinkage(final AbstractCompositeGenerator<?> base) {
+        verify(targetGen == null, "Attempted to start linkage of %s, already have target %s", this, targetGen);
+        base.resolveAugmentTarget(this);
     }
 
-    final @NonNull AbstractCompositeGenerator<?> targetGenerator() {
-        final AbstractCompositeGenerator<?> existing = targetGen;
-        if (existing != null) {
-            return existing.getOriginal();
-        }
-
-        loadTargetGenerator();
-        return verifyNotNull(targetGen, "No target for %s", this).getOriginal();
+    final void setTargetGenerator(final AbstractCompositeGenerator<?> targetGenerator) {
+        verify(targetGen == null, "Attempted to relink %s, already have target %s", this, targetGen);
+        targetGen = requireNonNull(targetGenerator);
     }
 
-    abstract void loadTargetGenerator();
+    final @NonNull AbstractCompositeGenerator<?> targetGenerator() {
+        return verifyNotNull(targetGen, "No target for %s", this);
+    }
 }
index 6dae9ac58423207f2d426e9f13d6523fbccda3d2..f4890eb2f5306f72668e62a5cac220ecf2c792da 100644 (file)
@@ -23,7 +23,6 @@ import org.opendaylight.mdsal.binding.model.api.GeneratedType;
 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
 import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.AddedByUsesAware;
 import org.opendaylight.yangtools.yang.model.api.CopyableNode;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
@@ -43,7 +42,6 @@ import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.NotificationEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.OutputEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.UsesEffectiveStatement;
@@ -59,24 +57,85 @@ import org.slf4j.LoggerFactory;
 abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> extends AbstractExplicitGenerator<T> {
     private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeGenerator.class);
 
+    // FIXME: we want to allocate this lazily to lower memory footprint
     private final @NonNull CollisionDomain domain = new CollisionDomain(this);
     private final List<Generator> children;
 
+    // Linkage of 'grouping' statements this generator references and 'augment' statements targeting this generator
     private List<AbstractAugmentGenerator> augments = List.of();
     private List<GroupingGenerator> groupings;
 
-    // Performance optimization: if this is true, we have ascertained our original definition as well that of all our
-    // children
-    private boolean originalsResolved;
+    /*
+     * State tracking for resolution of children to their original declaration, i.e. back along the 'uses' and 'augment'
+     * axis. This is quite convoluted because we are traversing the generator tree recursively in the iteration order
+     * of children, but actual dependencies may require resolution in a different order, for example in the case of:
+     *
+     *   container foo {
+     *     uses bar {             // A
+     *       augment bar {        // B
+     *         container xyzzy;   // C
+     *       }
+     *     }
+     *
+     *     grouping bar {
+     *       container bar {      // D
+     *         uses baz;          // E
+     *       }
+     *     }
+     *
+     *     grouping baz {
+     *       leaf baz {           // F
+     *         type string;
+     *       }
+     *     }
+     *   }
+     *
+     *   augment /foo/bar/xyzzy { // G
+     *     leaf xyzzy {           // H
+     *       type string;
+     *     }
+     *   }
+     *
+     * In this case we have three manifestations of 'leaf baz' -- marked A, E and F in the child iteration order. In
+     * order to perform a resolution, we first have to determine that F is the original definition, then establish that
+     * E is using the definition made by F and finally establish that A is using the definition made by F.
+     *
+     * Dealing with augmentations is harder still, because we need to attach them to the original definition, hence we
+     * for the /foo/bar container at A, we need to understand that its original definition is at D and we need to attach
+     * the augment at B to D. Futhermore we also need to establish that the augmentation at G attaches to container
+     * defined in C, so that the 'leaf xyzzy' existing as /foo/bar/xyzzy/xyzzy under C has its original definition at H.
+     *
+     * Finally realize that the augment at G can actually exist in a different module and is shown in this example only
+     * the simplified form. That also means we could encounter G well before 'container foo' as well as we can have
+     * multiple such augments sprinkled across multiple modules having the same dependency rules as between C and G --
+     * but they still have to form a directed acyclic graph and we partially deal with those complexities by having
+     * modules sorted by their dependencies.
+     *
+     *
+     * FIXME: describe fields and general outline of what we are trying to achieve
+     */
+    private List<AugmentRequirement> augmentRequirements = List.of();
+
+    // FIXME: these two lists should really be combined and should use encapsulation. That list should also be
+    //        initialized when we first start resolving it. We therefore will need something like:
+    //
+    //          List<LinkageState> unlinkedChildren;
+    //
+    //        The state should capture the different lifecycles for original/addedByUses/augmenting.
+
+    // List of children which have not had their original linked. Set to null once all children are linked.
+    private List<Generator> unlinkedChildren;
 
     AbstractCompositeGenerator(final T statement) {
         super(statement);
         children = createChildren(statement);
+        unlinkedChildren = children;
     }
 
     AbstractCompositeGenerator(final T statement, final AbstractCompositeGenerator<?> parent) {
         super(statement, parent);
         children = createChildren(statement);
+        unlinkedChildren = children;
     }
 
     @Override
@@ -89,6 +148,46 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
         return children.isEmpty();
     }
 
+    final void resolveAugmentTarget(final AbstractAugmentGenerator augment) {
+        // 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'.
+        //
+        // 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.
+        //
+        // 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 var path = augment.statement().argument().getNodeIdentifiers().iterator();
+        addRequirement(this instanceof GroupingGenerator
+            ? new AugmentRequirement(augment, path, (GroupingGenerator) this)
+                : new AugmentRequirement(augment, path));
+    }
+
+    final void addRequirement(final AugmentRequirement req) {
+        final var qname = req.qname();
+        if (qname == null) {
+            // Resolved requirement, if we also have original we end resolution here and now
+            final var original = tryOriginal();
+            if (original != null) {
+                original.addAugment(req.augment());
+                return;
+            }
+        }
+
+        if (augmentRequirements != null) {
+            if (augmentRequirements.isEmpty()) {
+                augmentRequirements = new ArrayList<>(2);
+            }
+            augmentRequirements.add(req);
+        } else {
+            // Late-coming after we have established all linkage. Deal with it now.
+            verify(resolveChildRequirement(qname, req), "Requirement %s failed to resolve in %s", qname, this);
+        }
+    }
+
     final @Nullable AbstractExplicitGenerator<?> findGenerator(final List<EffectiveStatement<?, ?>> stmtPath) {
         return findGenerator(MatchStrategy.identity(), stmtPath, 0);
     }
@@ -146,41 +245,137 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
     }
 
     final void linkUsesDependencies(final GeneratorContext context) {
-        // We are resolving 'uses' statements to their corresponding 'grouping' definitions
+        // We are establishing two linkages here:
+        // - we are resolving 'uses' statements to their corresponding 'grouping' definitions
+        // - we propagate those groupings as anchors to any augment statements, which takes out some amount of guesswork
+        //   from augment+uses resolution case, as groupings know about their immediate augments as soon as uses linkage
+        //   is resolved
         final List<GroupingGenerator> tmp = new ArrayList<>();
         for (EffectiveStatement<?, ?> stmt : statement().effectiveSubstatements()) {
             if (stmt instanceof UsesEffectiveStatement) {
-                tmp.add(context.resolveTreeScoped(GroupingGenerator.class, ((UsesEffectiveStatement) stmt).argument()));
+                final UsesEffectiveStatement uses = (UsesEffectiveStatement) stmt;
+                final GroupingGenerator grouping = context.resolveTreeScoped(GroupingGenerator.class, uses.argument());
+                tmp.add(grouping);
+
+                // Trigger resolution of uses/augment statements. This looks like guesswork, but there may be multiple
+                // 'augment' statements in a 'uses' statement and keeping a ListMultimap here seems wasteful.
+                for (Generator gen : this) {
+                    if (gen instanceof UsesAugmentGenerator) {
+                        ((UsesAugmentGenerator) gen).resolveGrouping(uses, grouping);
+                    }
+                }
             }
         }
         groupings = List.copyOf(tmp);
     }
 
-    final void addAugment(final AbstractAugmentGenerator augment) {
+    private void addAugment(final AbstractAugmentGenerator augment) {
+        augment.setTargetGenerator(this);
         if (augments.isEmpty()) {
             augments = new ArrayList<>(2);
         }
         augments.add(requireNonNull(augment));
     }
 
-    @Override
-    long linkOriginalGenerator() {
-        if (originalsResolved) {
-            return 0;
+    /**
+     * Attempt to link the generator corresponding to the original definition for this generator's statements as well as
+     * to all child generators.
+     *
+     * @return Number of generators that were linked, or {@code -1} when linkage has been completed
+     */
+    final void linkOriginalGeneratorRecursive(final LinkageProgress progress) {
+        if (!unlinkedChildren.isEmpty()) {
+            // Attempt to make progress on child linkage
+            final var nextUnlinked = new ArrayList<Generator>();
+            for (Generator child : unlinkedChildren) {
+                if (child instanceof AbstractExplicitGenerator) {
+                    if (((AbstractExplicitGenerator<?>) child).linkOriginalGenerator()) {
+                        progress.linkedOriginal();
+                    } else {
+                        nextUnlinked.add(child);
+                    }
+                }
+            }
+
+            if (nextUnlinked.isEmpty()) {
+                // Nothing left to do, make sure any previously-allocated list can be scavenged
+                unlinkedChildren = List.of();
+            } else {
+                // We have some unlinked children, report progress and wait for the next iteration
+                unlinkedChildren = nextUnlinked;
+                progress.setRetry();
+            }
         }
 
-        long remaining = super.linkOriginalGenerator();
-        if (remaining == 0) {
-            for (Generator child : children) {
-                if (child instanceof AbstractExplicitGenerator) {
-                    remaining += ((AbstractExplicitGenerator<?>) child).linkOriginalGenerator();
+        // Deal with any augment target resolution
+        if (augmentRequirements != null) {
+            final var it = augmentRequirements.iterator();
+            while (it.hasNext()) {
+                final var req = it.next();
+                final var qname = req.qname();
+                if (qname == null) {
+                    // Finish resolution if we have the original definition
+                    final var original = tryOriginal();
+                    if (original != null) {
+                        original.addAugment(req.augment());
+                        progress.resolvedAugment();
+                        it.remove();
+                    }
+                } else if (resolveChildRequirement(qname, req)) {
+                    progress.resolvedAugment();
+                    it.remove();
                 }
             }
-            if (remaining == 0) {
-                originalsResolved = true;
+            if (augmentRequirements.isEmpty()) {
+                augmentRequirements = null;
+            } else {
+                progress.setRetry();
+            }
+        }
+
+        // Determine which composites to process
+        for (var child : children) {
+            if (child instanceof AbstractCompositeGenerator) {
+                ((AbstractCompositeGenerator<?>) child).linkOriginalGeneratorRecursive(progress);
             }
         }
-        return remaining;
+    }
+
+    private boolean resolveChildRequirement(final QName qname, final AugmentRequirement req) {
+        // First try local statements without adjustment
+        if (resolveLocalChild(qname, req)) {
+            return true;
+        }
+
+        // Second try local augments, as those are guaranteed to match namespace exactly
+        for (var augment : augments) {
+            final var gen = augment.findSchemaTreeGenerator(qname);
+            if (gen != null) {
+                augment.addRequirement(req);
+                return true;
+            }
+        }
+
+        // Third try local groupings, as those perform their own adjustment
+        for (var grouping : groupings) {
+            final var gen = grouping.findSchemaTreeGenerator(qname.bindTo(grouping.statement().argument().getModule()));
+            if (gen != null) {
+                req.inGrouping(grouping, gen);
+                return true;
+            }
+        }
+
+        // Lastly try local statements adjusted with namespace, if applicable
+        return resolveLocalChild(req.adjustedQName(), req);
+    }
+
+    private boolean resolveLocalChild(final QName qname, final AugmentRequirement req) {
+        final var gen = super.findSchemaTreeGenerator(qname);
+        if (gen != null) {
+            req.inLocal(gen);
+            return true;
+        }
+        return false;
     }
 
     @Override
@@ -188,20 +383,30 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
         return (AbstractCompositeGenerator<?>) super.getOriginal();
     }
 
-    final @NonNull OriginalLink getOriginalChild(final QName childQName) {
+    @Override
+    final AbstractCompositeGenerator<?> tryOriginal() {
+        return (AbstractCompositeGenerator<?>) super.tryOriginal();
+    }
+
+    final @Nullable OriginalLink originalChild(final QName childQName) {
         // First try groupings/augments ...
-        final AbstractExplicitGenerator<?> found = findInferredGenerator(childQName);
+        AbstractExplicitGenerator<?> found = findInferredGenerator(childQName);
         if (found != null) {
             return OriginalLink.partial(found);
         }
 
-        // ... no luck, we really need to start looking at our origin
+        // ... then we need our original first ...
+        if (!linkOriginalGenerator()) {
+            return null;
+        }
+
+        // ... to start looking at our origin
         final AbstractExplicitGenerator<?> prev = verifyNotNull(previous(),
             "Failed to find %s in scope of %s", childQName, this);
 
         final QName prevQName = childQName.bindTo(prev.getQName().getModule());
-        return verifyNotNull(prev.findSchemaTreeGenerator(prevQName),
-            "Failed to find child %s (proxy for %s) in %s", prevQName, childQName, prev).originalLink();
+        found = prev.findSchemaTreeGenerator(prevQName);
+        return found == null ? null : found.originalLink();
     }
 
     @Override
@@ -229,56 +434,6 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
         return null;
     }
 
-    final @NonNull AbstractExplicitGenerator<?> resolveSchemaNode(final SchemaNodeIdentifier path) {
-        // 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'.
-        //
-        // 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.
-        //
-        // Hence we deal with this lookup recursively, dropping namespace hints when we cross into groupings.
-        return resolveSchemaNode(path.getNodeIdentifiers().iterator(), null);
-    }
-
-    private @NonNull AbstractExplicitGenerator<?> resolveSchemaNode(final Iterator<QName> qnames,
-            final @Nullable QNameModule localNamespace) {
-        final QName qname = qnames.next();
-
-        // First try local augments, as those are guaranteed to match namespace exactly
-        for (AbstractAugmentGenerator augment : augments) {
-            final AbstractExplicitGenerator<?> gen = augment.findSchemaTreeGenerator(qname);
-            if (gen != null) {
-                return resolveNext(gen, qnames, null);
-            }
-        }
-
-        // Second try local groupings, as those perform their own adjustment
-        for (GroupingGenerator grouping : groupings) {
-            final QNameModule ns = grouping.statement().argument().getModule();
-            final AbstractExplicitGenerator<?> gen = grouping.findSchemaTreeGenerator(qname.bindTo(ns));
-            if (gen != null) {
-                return resolveNext(gen, qnames, ns);
-            }
-        }
-
-        // Lastly try local statements adjusted with namespace, if applicable
-        final QName lookup = localNamespace == null ? qname : qname.bindTo(localNamespace);
-        final AbstractExplicitGenerator<?> gen = verifyNotNull(super.findSchemaTreeGenerator(lookup),
-            "Failed to find %s as %s in %s", qname, lookup, this);
-        return resolveNext(gen, qnames, localNamespace);
-    }
-
-    private static @NonNull AbstractExplicitGenerator<?> resolveNext(final @NonNull AbstractExplicitGenerator<?> gen,
-            final Iterator<QName> qnames, final QNameModule localNamespace) {
-        if (qnames.hasNext()) {
-            verify(gen instanceof AbstractCompositeGenerator, "Unexpected generator %s", gen);
-            return ((AbstractCompositeGenerator<?>) gen).resolveSchemaNode(qnames, localNamespace);
-        }
-        return gen;
-    }
-
     /**
      * Update the specified builder to implement interfaces generated for the {@code grouping} statements this generator
      * is using.
@@ -404,7 +559,7 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
                 final UsesEffectiveStatement uses = (UsesEffectiveStatement) stmt;
                 for (EffectiveStatement<?, ?> usesSub : uses.effectiveSubstatements()) {
                     if (usesSub instanceof AugmentEffectiveStatement) {
-                        tmpAug.add(new UsesAugmentGenerator((AugmentEffectiveStatement) usesSub, this));
+                        tmpAug.add(new UsesAugmentGenerator((AugmentEffectiveStatement) usesSub, uses, this));
                     }
                 }
             } else {
@@ -414,7 +569,9 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
         }
 
         // Sort augments and add them last. This ensures child iteration order always reflects potential
-        // interdependencies, hence we do not need to worry about them.
+        // interdependencies, hence we do not need to worry about them. This is extremely important, as there are a
+        // number of places where we would have to either move the logic to parent statement and explicitly filter/sort
+        // substatements to establish this order.
         tmpAug.sort(AbstractAugmentGenerator.COMPARATOR);
         tmp.addAll(tmpAug);
 
index 69a2f217e51e242d9408940958e1f232935ec471..3927f03845ae8204a4c1ea1b52e718c6e635f677 100644 (file)
@@ -85,15 +85,14 @@ public abstract class AbstractExplicitGenerator<T extends EffectiveStatement<?,
     }
 
     /**
-     * Attempt to link the generator corresponding to the original definition for this generator's statements as well as
-     * to all child generators.
+     * Attempt to link the generator corresponding to the original definition for this generator.
      *
-     * @return Number of generators that remain unlinked.
+     * @return {@code true} if this generator is linked
      */
-    long linkOriginalGenerator() {
+    final boolean linkOriginalGenerator() {
         if (orig != null) {
             // Original already linked
-            return 0;
+            return true;
         }
 
         if (prev == null) {
@@ -102,27 +101,32 @@ public abstract class AbstractExplicitGenerator<T extends EffectiveStatement<?,
             if (!isAddedByUses() && !isAugmenting()) {
                 orig = prev = this;
                 LOG.trace("Linked {} to self", this);
-                return 0;
+                return true;
+            }
+
+            final var link = getParent().originalChild(getQName());
+            if (link == null) {
+                LOG.trace("Cannot link {} yet", this);
+                return false;
             }
 
-            final var link = getParent().getOriginalChild(getQName());
             prev = link.previous();
             orig = link.original();
             if (orig != null) {
                 LOG.trace("Linked {} to {} original {}", this, prev, orig);
-                return 0;
+                return true;
             }
 
             LOG.trace("Linked {} to intermediate {}", this, prev);
-            return 1;
+            return false;
         }
 
         orig = prev.originalLink().original();
         if (orig != null) {
             LOG.trace("Linked {} to original {}", this, orig);
-            return 0;
+            return true;
         }
-        return 1;
+        return false;
     }
 
     /**
@@ -141,7 +145,11 @@ public abstract class AbstractExplicitGenerator<T extends EffectiveStatement<?,
      * @return Original incarnation of this generator
      */
     @NonNull AbstractExplicitGenerator<?> getOriginal() {
-        return verifyNotNull(orig, "Generator %s does not have linkage to original instance resolved", this);
+        return verifyNotNull(tryOriginal(), "Generator %s does not have linkage to original instance resolved", this);
+    }
+
+    @Nullable AbstractExplicitGenerator<?> tryOriginal() {
+        return orig;
     }
 
     /**
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..89c0452
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Mutable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+
+final class AugmentRequirement implements Mutable {
+    private final @NonNull Set<QNameModule> squashNamespaces;
+    private final @NonNull AbstractAugmentGenerator augment;
+    private final @NonNull Iterator<QName> remaining;
+
+    private QNameModule localNamespace;
+    private QName qname;
+
+    private AugmentRequirement(final AbstractAugmentGenerator augment, final Iterator<QName> remaining,
+            final QName qname, final QNameModule localNamespace, final Set<QNameModule> squashNamespaces) {
+        this.augment = requireNonNull(augment);
+        this.remaining = requireNonNull(remaining);
+        this.qname = requireNonNull(qname);
+        this.squashNamespaces = requireNonNull(squashNamespaces);
+        this.localNamespace = localNamespace;
+    }
+
+    AugmentRequirement(final AbstractAugmentGenerator augment, final Iterator<QName> path) {
+        this(augment, path, path.next(), null, new HashSet<>(4));
+    }
+
+    AugmentRequirement(final AbstractAugmentGenerator augment, final Iterator<QName> path,
+            final GroupingGenerator grouping) {
+        this(augment, path, path.next(), grouping.getQName().getModule(), new HashSet<>(4));
+        squashNamespaces.add(qname.getModule());
+    }
+
+    @NonNull AbstractAugmentGenerator augment() {
+        return augment;
+    }
+
+    @Nullable QName qname() {
+        return qname;
+    }
+
+    @NonNull QName adjustedQName() {
+        final var qn = verifyNotNull(qname);
+        return squashNamespaces.contains(qn.getModule()) ? qn.bindTo(verifyNotNull(localNamespace)) : qn;
+    }
+
+    void inGrouping(final GroupingGenerator grouping, final AbstractExplicitGenerator<?> gen) {
+        squashNamespaces.add(qname.getModule());
+        localNamespace = grouping.statement().argument().getModule();
+        grouping.addRequirement(this);
+    }
+
+    void inLocal(final AbstractExplicitGenerator<?> gen) {
+        addRequirement(gen);
+    }
+
+    private void addRequirement(final AbstractExplicitGenerator<?> gen) {
+        final var composite = verifyComposite(gen);
+        qname = remaining.hasNext() ? remaining.next() : null;
+        composite.addRequirement(this);
+    }
+
+    private static AbstractCompositeGenerator<?> verifyComposite(final AbstractExplicitGenerator<?> gen) {
+        verify(gen instanceof AbstractCompositeGenerator, "Unexpected generator %s", gen);
+        return (AbstractCompositeGenerator<?>) gen;
+    }
+}
\ No newline at end of file
index 8eec089c175edcb0565bffd9954f8e03b851de5e..e404723de728e3743689848abd9cec1a8435acf0 100644 (file)
@@ -122,16 +122,18 @@ public final class GeneratorReactor extends GeneratorContext implements Mutable
         // Start measuring time...
         final Stopwatch sw = Stopwatch.createStarted();
 
-        // Step 1a: walk all composite generators and resolve 'uses' statements to the corresponding grouping node,
-        //          establishing implied inheritance ...
+        // Step 1a: Walk all composite generators and resolve 'uses' statements to the corresponding grouping generator,
+        //          establishing implied inheritance. During this walk we maintain 'stack' to aid this process.
+        //          This indirectly triggers resolution of UsesAugmentGenerators' targets by hooking a requirement
+        //          on the resolved grouping's child nodes as needed.
         linkUsesDependencies(children);
 
-        // Step 1b: ... and also link augments and their targets in a separate pass, as we need groupings fully resolved
-        //          before we attempt augmentation lookups ...
+        // Step 1b: Walk all module generators and start ModuleAugmentGenerators' target resolution by linking the first
+        //          step of each 'augment' statement to its corresponding instantiated site.
         for (ModuleGenerator module : children) {
-            for (Generator child : module) {
-                if (child instanceof ModuleAugmentGenerator) {
-                    ((ModuleAugmentGenerator) child).linkAugmentationTarget(this);
+            for (Generator gen : module) {
+                if (gen instanceof ModuleAugmentGenerator) {
+                    ((ModuleAugmentGenerator) gen).startLinkage(this);
                 }
             }
         }
@@ -139,16 +141,22 @@ public final class GeneratorReactor extends GeneratorContext implements Mutable
         // Step 1c: ... finally establish linkage along the reverse uses/augment axis. This is needed to route generated
         //          type manifestations (isAddedByUses/isAugmenting) to their type generation sites. Since generator
         //          tree iteration order does not match dependencies, we may need to perform multiple passes.
-        long unlinkedOriginals = Long.MAX_VALUE;
-        do {
-            long remaining = 0;
+        for (ModuleGenerator module : children) {
+            verify(module.linkOriginalGenerator(), "Module %s failed to link", module);
+        }
+
+        while (true) {
+            final var progress = new LinkageProgress();
             for (ModuleGenerator module : children) {
-                remaining += module.linkOriginalGenerator();
+                module.linkOriginalGeneratorRecursive(progress);
             }
-            verify(remaining < unlinkedOriginals, "Failed to make progress on linking of remaining %s originals",
-                remaining);
-            unlinkedOriginals = remaining;
-        } while (unlinkedOriginals != 0);
+
+            if (!progress.retry()) {
+                break;
+            }
+
+            verify(progress.hasProgress(), "Failed to make progress on linking of original generators");
+        }
 
         /*
          * Step 2: link typedef statements, so that typedef's 'type' axis is fully established
diff --git a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/LinkageProgress.java b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/LinkageProgress.java
new file mode 100644 (file)
index 0000000..a43ef38
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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 com.google.common.base.MoreObjects;
+
+/**
+ * Shared structure tracking progress of a linkage pass.
+ */
+final class LinkageProgress {
+    private boolean retry;
+    private int originals;
+    private int augments;
+
+    boolean hasProgress() {
+        return augments != 0 || originals != 0;
+    }
+
+    boolean retry() {
+        return retry;
+    }
+
+    void setRetry() {
+        retry = true;
+    }
+
+    void linkedOriginal() {
+        originals++;
+    }
+
+    void resolvedAugment() {
+        augments++;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("retry", retry)
+            .add("augments", augments)
+            .add("originals", originals)
+            .toString();
+    }
+}
index f4c01a3e7e0f38bab1a9135925e0eb0620033a9b..8b99048581ada5a26144f3eb302baba22af4235e 100644 (file)
@@ -8,7 +8,6 @@
 package org.opendaylight.mdsal.binding.generator.impl.reactor;
 
 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
 
 /**
  * Generator corresponding to a {@code augment} statement used as a child of a {@code module} statement.
@@ -18,14 +17,7 @@ final class ModuleAugmentGenerator extends AbstractAugmentGenerator {
         super(statement, parent);
     }
 
-    @Override
-    void loadTargetGenerator() {
-        throw new UnsupportedOperationException();
-    }
-
-    void linkAugmentationTarget(final GeneratorContext context) {
-        final SchemaNodeIdentifier path = statement().argument();
-        final ModuleGenerator module = context.resolveModule(path.firstNodeIdentifier().getModule());
-        setTargetGenerator(module.resolveSchemaNode(path));
+    void startLinkage(final GeneratorContext context) {
+        startLinkage(context.resolveModule(statement().argument().firstNodeIdentifier().getModule()));
     }
 }
index f8a9eb7fb2ca843465ae44cf1723147fdbb011ef..e306c75bc92d8d84093ef4502587d1899d96c3db 100644 (file)
@@ -7,26 +7,35 @@
  */
 package org.opendaylight.mdsal.binding.generator.impl.reactor;
 
+import static java.util.Objects.requireNonNull;
+
 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.UsesEffectiveStatement;
 
 /**
  * Generator corresponding to a {@code augment} statement used as a child of a {@code uses} statement.
  */
 final class UsesAugmentGenerator extends AbstractAugmentGenerator {
-    UsesAugmentGenerator(final AugmentEffectiveStatement statement, final AbstractCompositeGenerator<?> parent) {
+    private final UsesEffectiveStatement uses;
+
+    UsesAugmentGenerator(final AugmentEffectiveStatement statement, final UsesEffectiveStatement uses,
+            final AbstractCompositeGenerator<?> parent) {
         super(statement, parent);
+        this.uses = requireNonNull(uses);
+
     }
 
-    @Override
-    void loadTargetGenerator() {
-        // Here we are going in the opposite direction of RFC7950, section 7.13:
-        //
-        //    The effect of a "uses" reference to a grouping is that the nodes
-        //    defined by the grouping are copied into the current schema tree and
-        //    are then updated according to the "refine" and "augment" statements.
-        //
-        // Our parent here is *not* the uses statement, but rather the statement which contains uses -- and its
-        // getSchemaTreeGenerator() is well equipped to deal with the namespace hopping needed to perform the lookups
-        setTargetGenerator(getParent().resolveSchemaNode(statement().argument()));
+    void resolveGrouping(final UsesEffectiveStatement resolvedUses, final GroupingGenerator resolvedGrouping) {
+        if (resolvedUses == uses) {
+            // Here we are going in the opposite direction of RFC7950, section 7.13:
+            //
+            //    The effect of a "uses" reference to a grouping is that the nodes
+            //    defined by the grouping are copied into the current schema tree and
+            //    are then updated according to the "refine" and "augment" statements.
+            //
+            // Our parent here is *not* the 'uses' statement, but rather the statement which contains it -- and its
+            // resolveSchemaNode() is well equipped to deal with the namespace hopping needed to perform the lookups.
+            startLinkage(resolvedGrouping);
+        }
     }
 }
diff --git a/binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal718Test.java b/binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal718Test.java
new file mode 100644 (file)
index 0000000..cd71ef9
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class Mdsal718Test {
+    @Test
+    public void testModuleUsesAugmentLinking() {
+        final var generatedTypes = DefaultBindingGenerator.generateFor(
+            YangParserTestUtils.parseYangResourceDirectory("/mdsal718"));
+        assertEquals(10, generatedTypes.size());
+    }
+}
diff --git a/binding/mdsal-binding-generator/src/test/resources/mdsal718/bar.yang b/binding/mdsal-binding-generator/src/test/resources/mdsal718/bar.yang
new file mode 100644 (file)
index 0000000..5dd6013
--- /dev/null
@@ -0,0 +1,6 @@
+module bar {
+  namespace bar;
+  prefix bar;
+
+  container bar;
+}
diff --git a/binding/mdsal-binding-generator/src/test/resources/mdsal718/baz.yang b/binding/mdsal-binding-generator/src/test/resources/mdsal718/baz.yang
new file mode 100644 (file)
index 0000000..37b6990
--- /dev/null
@@ -0,0 +1,15 @@
+module baz {
+  namespace baz;
+  prefix baz;
+
+  import foo { prefix foo; }
+  import bar { prefix bar; }
+
+  augment /bar:bar {
+    uses foo:foo {
+      augment foo/nested {
+        container baz;
+      }
+    }
+  }
+}
diff --git a/binding/mdsal-binding-generator/src/test/resources/mdsal718/foo.yang b/binding/mdsal-binding-generator/src/test/resources/mdsal718/foo.yang
new file mode 100644 (file)
index 0000000..fba948f
--- /dev/null
@@ -0,0 +1,10 @@
+module foo {
+  namespace foo;
+  prefix foo;
+
+  grouping foo {
+    container foo {
+      container nested;
+    }
+  }
+}
diff --git a/binding/mdsal-binding-generator/src/test/resources/mdsal718/xyzzy.yang b/binding/mdsal-binding-generator/src/test/resources/mdsal718/xyzzy.yang
new file mode 100644 (file)
index 0000000..a8e7b3d
--- /dev/null
@@ -0,0 +1,11 @@
+module xyzzy {
+  namespace xyzzy;
+  prefix xyzzy;
+
+  import bar { prefix bar; }
+  import baz { prefix baz; }
+
+  augment /bar:bar/baz:foo/baz:nested/baz:baz {
+    container xyzzy;
+  }
+}