Enforce explicit generator linkage 21/99521/4
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 28 Jan 2022 23:06:59 +0000 (00:06 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sat, 29 Jan 2022 16:48:46 +0000 (17:48 +0100)
We are keeping the original pointer as a simple nullable field. This can
result in us confusing an unresolved generator with an original, leading
to potential badness.

Refactor the AbstractExplicitGenerator to track incremental resolution,
catching invalid accesses.

JIRA: MDSAL-718
Change-Id: Ie67fa4d08d0887f301948e3d03d846ed9ee1d628
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
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/AbstractTypeObjectGenerator.java
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/OriginalLink.java [new file with mode: 0644]

index 13776a52841b537cfc5ff941fea15f3398139a12..1828410a19d55086ba40b3a86ca9d31ddf549a1a 100644 (file)
@@ -17,6 +17,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.generator.impl.reactor.OriginalLink.Partial;
 import org.opendaylight.mdsal.binding.model.api.Enumeration;
 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject;
 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
@@ -65,6 +66,10 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
     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;
+
     AbstractCompositeGenerator(final T statement) {
         super(statement);
         children = createChildren(statement);
@@ -159,16 +164,36 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
         augments.add(requireNonNull(augment));
     }
 
+    @Override
+    long linkOriginalGenerator() {
+        if (originalsResolved) {
+            return 0;
+        }
+
+        long remaining = super.linkOriginalGenerator();
+        if (remaining == 0) {
+            for (Generator child : children) {
+                if (child instanceof AbstractExplicitGenerator) {
+                    remaining += ((AbstractExplicitGenerator<?>) child).linkOriginalGenerator();
+                }
+            }
+            if (remaining == 0) {
+                originalsResolved = true;
+            }
+        }
+        return remaining;
+    }
+
     @Override
     final AbstractCompositeGenerator<?> getOriginal() {
         return (AbstractCompositeGenerator<?>) super.getOriginal();
     }
 
-    final @NonNull AbstractExplicitGenerator<?> getOriginalChild(final QName childQName) {
+    final @NonNull OriginalLink getOriginalChild(final QName childQName) {
         // First try groupings/augments ...
         final AbstractExplicitGenerator<?> found = findInferredGenerator(childQName);
         if (found != null) {
-            return found;
+            return new Partial(found);
         }
 
         // ... no luck, we really need to start looking at our origin
@@ -177,7 +202,7 @@ abstract class AbstractCompositeGenerator<T extends EffectiveStatement<?, ?>> ex
 
         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).getOriginal();
+            "Failed to find child %s (proxy for %s) in %s", prevQName, childQName, prev).originalLink();
     }
 
     @Override
index 3e282c827c21caa40532b94501a87788df48fc79..b3e542f6ee7aea511ac80d4e5b54638bf1c0d9e2 100644 (file)
@@ -8,12 +8,15 @@
 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 com.google.common.base.MoreObjects.ToStringHelper;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.binding.generator.impl.reactor.CollisionDomain.Member;
+import org.opendaylight.mdsal.binding.generator.impl.reactor.OriginalLink.Complete;
+import org.opendaylight.mdsal.binding.generator.impl.reactor.OriginalLink.Partial;
 import org.opendaylight.mdsal.binding.model.api.MethodSignature.ValueMechanics;
 import org.opendaylight.mdsal.binding.model.api.Type;
 import org.opendaylight.mdsal.binding.model.api.TypeMemberComment;
@@ -40,12 +43,17 @@ public abstract class AbstractExplicitGenerator<T extends EffectiveStatement<?,
 
     private final @NonNull T statement;
 
-    // FIXME: this, along with AbstractTypeObjectGenerator's (and TypedefGenerator's) fields should be better-controlled
-    //        with explicit sequencing guards. It it currently stands, we are expending two (or more) additional fields
-    //        to express downstream linking. If we had the concept of resolution step (an enum), we could just get by
-    //        with a simple queue of Step/Callback pairs, which would trigger as needed. For an example see how
-    //        AbstractTypeObjectGenerator manages baseGen/inferred fields.
-    private AbstractExplicitGenerator<?> prev;
+    /**
+     * Field tracking previous incarnation (along reverse of 'uses' and 'augment' axis) of this statement. This field
+     * can either be one of:
+     * <ul>
+     *   <li>{@code null} when not resolved, i.e. access is not legal, or</li>
+     *   <li>{@code this} object if this is the original definition, or</li>
+     *   <li>an {@link AbstractExplicitGenerator} pointing to the original definition, or</li>
+     *   <li>a {@link Partial} link pointing to a generator closer to the original definition</li>
+     * </ul>
+     */
+    private Object prev;
 
     AbstractExplicitGenerator(final T statement) {
         this.statement = requireNonNull(statement);
@@ -75,20 +83,90 @@ public abstract class AbstractExplicitGenerator<T extends EffectiveStatement<?,
         return statement instanceof CopyableNode && ((CopyableNode) statement).isAugmenting();
     }
 
-    final void linkOriginalGenerator() {
-        if (isAddedByUses() || isAugmenting()) {
-            LOG.trace("Linking {}", this);
-            prev = getParent().getOriginalChild(getQName());
+    /**
+     * 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 remain unlinked.
+     */
+    long linkOriginalGenerator() {
+        final var local = prev;
+        if (local instanceof AbstractExplicitGenerator) {
+            return 0;
+        } else if (local instanceof Partial) {
+            return ((Partial) local).original() != null ? 0 : 1;
+        }
+        verify(local == null, "Unexpected link %s", local);
+
+        if (!isAddedByUses() && !isAugmenting()) {
+            prev = this;
+            LOG.trace("Linked {} to self", this);
+            return 0;
+        }
+
+        LOG.trace("Linking {}", this);
+        final var link = getParent().getOriginalChild(getQName());
+        if (link instanceof Complete) {
+            prev = ((Complete) link).original();
             LOG.trace("Linked {} to {}", this, prev);
+            return 0;
         }
+        prev = link;
+        LOG.trace("Linked {} to intermediate {}", this, prev);
+        return link.original() != null ? 0 : 1;
     }
 
+    /**
+     * Return the previous incarnation of this generator, or {@code null} if this is the original generator.
+     *
+     * @return Previous incarnation or {@code null}
+     */
     final @Nullable AbstractExplicitGenerator<?> previous() {
-        return prev;
+        final var local = prev();
+        if (local == this) {
+            return null;
+        } else if (local instanceof Partial) {
+            return ((Partial) local).previous();
+        } else {
+            return verifyExplicit(local);
+        }
     }
 
+    /**
+     * Return the original incarnation of this generator, or self if this is the original generator.
+     *
+     * @return Original incarnation of this generator
+     */
     @NonNull AbstractExplicitGenerator<?> getOriginal() {
-        return prev == null ? this : prev.getOriginal();
+        final var local = prev();
+        return local == this ? this : verifyExplicit(local).getOriginal();
+    }
+
+    /**
+     * Return the link towards the original generator.
+     *
+     * @return Link towards the original generator.
+     */
+    final @NonNull OriginalLink originalLink() {
+        final var local = prev;
+        if (local == null) {
+            return new Partial(this);
+        } else if (local == this) {
+            return new Complete(this);
+        } else if (local instanceof Partial) {
+            return (Partial) local;
+        } else {
+            return verifyExplicit(local).originalLink();
+        }
+    }
+
+    private static @NonNull AbstractExplicitGenerator<?> verifyExplicit(final Object prev) {
+        verify(prev instanceof AbstractExplicitGenerator, "Unexpected previous %s", prev);
+        return (AbstractExplicitGenerator<?>) prev;
+    }
+
+    private @NonNull Object prev() {
+        return verifyNotNull(prev, "Generator %s does not have linkage to previous instance resolved", this);
     }
 
     @Nullable AbstractExplicitGenerator<?> findSchemaTreeGenerator(final QName qname) {
index 9d8526aad09ae0c636820aa63e59998111b5d282..002ab608e07772f74399619146b9230e2d753b89 100644 (file)
@@ -281,6 +281,11 @@ abstract class AbstractTypeObjectGenerator<T extends EffectiveStatement<?, ?>> e
 
     private final TypeEffectiveStatement<?> type;
 
+    // FIXME: these fields should be better-controlled with explicit sequencing guards. It it currently stands, we are
+    //        expending two (or more) additional fields to express downstream linking. If we had the concept of
+    //        resolution step (an enum), we could just get by with a simple queue of Step/Callback pairs, which would
+    //        trigger as needed. See how we manage baseGen/inferred fields.
+
     /**
      * The generator corresponding to our YANG base type. It produces the superclass of our encapsulated type. If it is
      * {@code null}, this generator is the root of the hierarchy.
index db6080c3122468814576fbdcba8d649caf0058a2..8eec089c175edcb0565bffd9954f8e03b851de5e 100644 (file)
@@ -137,8 +137,18 @@ 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.
-        linkOriginalGenerator(children);
+        //          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) {
+                remaining += module.linkOriginalGenerator();
+            }
+            verify(remaining < unlinkedOriginals, "Failed to make progress on linking of remaining %s originals",
+                remaining);
+            unlinkedOriginals = remaining;
+        } while (unlinkedOriginals != 0);
 
         /*
          * Step 2: link typedef statements, so that typedef's 'type' axis is fully established
@@ -333,17 +343,6 @@ public final class GeneratorReactor extends GeneratorContext implements Mutable
         }
     }
 
-    private static void linkOriginalGenerator(final Iterable<? extends Generator> parent) {
-        for (Generator child : parent) {
-            if (child instanceof AbstractExplicitGenerator) {
-                ((AbstractExplicitGenerator<?>) child).linkOriginalGenerator();
-            }
-            if (child instanceof AbstractCompositeGenerator) {
-                linkOriginalGenerator(child);
-            }
-        }
-    }
-
     private void linkDependencies(final Iterable<? extends Generator> parent) {
         for (Generator child : parent) {
             if (child instanceof AbstractDependentGenerator) {
diff --git a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/OriginalLink.java b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/OriginalLink.java
new file mode 100644 (file)
index 0000000..e51fe4b
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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 java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Link to the original definition of an {@link AbstractExplicitGenerator}.
+ */
+// FIXME: sealed when we have JDK17+
+abstract class OriginalLink {
+    static final class Complete extends OriginalLink {
+        private final @NonNull AbstractExplicitGenerator<?> original;
+
+        Complete(final AbstractExplicitGenerator<?> original) {
+            this.original = requireNonNull(original);
+        }
+
+        @Override
+        AbstractExplicitGenerator<?> previous() {
+            return original;
+        }
+
+        @Override
+        @NonNull AbstractExplicitGenerator<?> original() {
+            return original;
+        }
+
+        @Override
+        ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+            return helper.add("original", original);
+        }
+    }
+
+    static final class Partial extends OriginalLink {
+        private final @NonNull AbstractExplicitGenerator<?> previous;
+        private AbstractExplicitGenerator<?> original;
+
+        Partial(final AbstractExplicitGenerator<?> previous) {
+            this.previous = requireNonNull(previous);
+        }
+
+        @Override
+        AbstractExplicitGenerator<?> previous() {
+            return previous;
+        }
+
+        @Override
+        AbstractExplicitGenerator<?> original() {
+            if (original == null) {
+                final var link = previous.originalLink();
+                if (link instanceof Complete || link.previous() != previous) {
+                    original = link.original();
+                }
+            }
+            return original;
+        }
+
+        @Override
+        ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+            return helper.add("previous", previous).add("original", original);
+        }
+    }
+
+    private OriginalLink() {
+        // Hidden on purpose
+    }
+
+    abstract @NonNull AbstractExplicitGenerator<?> previous();
+
+    abstract @Nullable AbstractExplicitGenerator<?> original();
+
+    abstract ToStringHelper addToStringAttributes(ToStringHelper helper);
+
+    @Override
+    public final String toString() {
+        return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
+    }
+}