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;
// 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 setTargetGenerator(final AbstractCompositeGenerator<?> targetGenerator) {
+ verify(targetGen == null, "Attempted to relink %s, already have target %s", this, targetGen);
+ targetGen = requireNonNull(targetGenerator);
}
final @NonNull AbstractCompositeGenerator<?> targetGenerator() {
- final AbstractCompositeGenerator<?> existing = targetGen;
- if (existing != null) {
- return existing.getOriginal();
- }
-
- loadTargetGenerator();
- return verifyNotNull(targetGen, "No target for %s", this).getOriginal();
+ return verifyNotNull(targetGen, "No target for %s", this);
}
-
- abstract void loadTargetGenerator();
}
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.ArrayList;
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;
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;
* A composite generator. Composite generators may contain additional children, which end up being mapped into
* the naming hierarchy 'under' the composite generator. To support this use case, each composite has a Java package
* name assigned.
+ *
+ * <p>
+ * 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:
+ * <pre>
+ * 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;
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * 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.
+ *
+ * <p>
+ * Dealing with augmentations is harder still, because we need to attach them to the original definition, hence 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.
+ *
+ * <p>
+ * 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.
+ *
+ * <p>
+ * For further details see {@link #linkOriginalGenerator()} and {@link #linkOriginalGeneratorRecursive()}, which deal
+ * with linking original instances in the tree iteration order. The part dealing with augment attachment lives mostly
+ * in {@link AugmentRequirement}.
*/
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;
+ /**
+ * List of {@code augment} statements targeting this generator. This list is maintained only for the primary
+ * incarnation. This list is an evolving entity until after we have finished linkage of original statements. It is
+ * expected to be stable at the start of {@code step 2} in {@link GeneratorReactor#execute(TypeBuilderFactory)}.
+ */
private List<AbstractAugmentGenerator> augments = List.of();
+
+ /**
+ * List of {@code grouping} statements this statement references. This field is set once by
+ * {@link #linkUsesDependencies(GeneratorContext)}.
+ */
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;
+ /**
+ * List of composite children which have not been recursively processed. This may become a mutable list when we
+ * have some children which have not completed linking. Once we have completed linking of all children, including
+ * {@link #unlinkedChildren}, this will be set to {@code null}.
+ */
+ private List<AbstractCompositeGenerator<?>> unlinkedComposites = List.of();
+ /**
+ * List of children which have not had their original linked. This list starts of as null. When we first attempt
+ * linkage, it becomes non-null.
+ */
+ private List<Generator> unlinkedChildren;
AbstractCompositeGenerator(final T statement) {
super(statement);
}
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 startUsesAugmentLinkage(final List<AugmentRequirement> requirements) {
+ for (Generator child : children) {
+ if (child instanceof UsesAugmentGenerator) {
+ requirements.add(((UsesAugmentGenerator) child).startLinkage());
+ }
+ if (child instanceof AbstractCompositeGenerator) {
+ ((AbstractCompositeGenerator<?>) child).startUsesAugmentLinkage(requirements);
+ }
+ }
+ }
+
final void addAugment(final AbstractAugmentGenerator augment) {
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 Progress indication
+ */
+ final @NonNull LinkageProgress linkOriginalGeneratorRecursive() {
+ if (unlinkedComposites == null) {
+ // We have unset this list (see below), and there is nothing left to do
+ return LinkageProgress.DONE;
}
- long remaining = super.linkOriginalGenerator();
- if (remaining == 0) {
- for (Generator child : children) {
+ if (unlinkedChildren == null) {
+ unlinkedChildren = children.stream()
+ .filter(AbstractExplicitGenerator.class::isInstance)
+ .map(child -> (AbstractExplicitGenerator<?>) child)
+ .collect(Collectors.toList());
+ }
+
+ var progress = LinkageProgress.NONE;
+ if (!unlinkedChildren.isEmpty()) {
+ // Attempt to make progress on child linkage
+ final var it = unlinkedChildren.iterator();
+ while (it.hasNext()) {
+ final var child = it.next();
if (child instanceof AbstractExplicitGenerator) {
- remaining += ((AbstractExplicitGenerator<?>) child).linkOriginalGenerator();
+ if (((AbstractExplicitGenerator<?>) child).linkOriginalGenerator()) {
+ progress = LinkageProgress.SOME;
+ it.remove();
+
+ // If this is a composite generator we need to process is further
+ if (child instanceof AbstractCompositeGenerator) {
+ if (unlinkedComposites.isEmpty()) {
+ unlinkedComposites = new ArrayList<>();
+ }
+ unlinkedComposites.add((AbstractCompositeGenerator<?>) child);
+ }
+ }
}
}
- if (remaining == 0) {
- originalsResolved = true;
+
+ if (unlinkedChildren.isEmpty()) {
+ // Nothing left to do, make sure any previously-allocated list can be scavenged
+ unlinkedChildren = List.of();
}
}
- return remaining;
+
+ // Process children of any composite children we have.
+ final var it = unlinkedComposites.iterator();
+ while (it.hasNext()) {
+ final var tmp = it.next().linkOriginalGeneratorRecursive();
+ if (tmp != LinkageProgress.NONE) {
+ progress = LinkageProgress.SOME;
+ }
+ if (tmp == LinkageProgress.DONE) {
+ it.remove();
+ }
+ }
+
+ if (unlinkedChildren.isEmpty() && unlinkedComposites.isEmpty()) {
+ // All done, set the list to null to indicate there is nothing left to do in this generator or any of our
+ // children.
+ unlinkedComposites = null;
+ return LinkageProgress.DONE;
+ }
+
+ return progress;
}
@Override
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);
+ var found = findInferredGenerator(childQName);
if (found != null) {
return OriginalLink.partial(found);
}
// ... no luck, we really need to start looking at our origin
- final AbstractExplicitGenerator<?> prev = verifyNotNull(previous(),
- "Failed to find %s in scope of %s", childQName, this);
+ final var prev = previous();
+ if (prev != null) {
+ final QName prevQName = childQName.bindTo(prev.getQName().getModule());
+ found = prev.findSchemaTreeGenerator(prevQName);
+ if (found != null) {
+ return found.originalLink();
+ }
+ }
- 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();
+ return null;
}
@Override
return found != null ? found : findInferredGenerator(qname);
}
- private @Nullable AbstractExplicitGenerator<?> findInferredGenerator(final QName qname) {
- // First search our local groupings ...
- for (GroupingGenerator grouping : groupings) {
- final AbstractExplicitGenerator<?> gen = grouping.findSchemaTreeGenerator(
- qname.bindTo(grouping.statement().argument().getModule()));
+ final @Nullable AbstractAugmentGenerator findAugmentForGenerator(final QName qname) {
+ for (var augment : augments) {
+ final var gen = augment.findSchemaTreeGenerator(qname);
if (gen != null) {
- return gen;
+ return augment;
}
}
- // ... next try local augments, which may have groupings themselves
- for (AbstractAugmentGenerator augment : augments) {
- final AbstractExplicitGenerator<?> gen = augment.findSchemaTreeGenerator(qname);
+ return null;
+ }
+
+ final @Nullable GroupingGenerator findGroupingForGenerator(final QName qname) {
+ for (GroupingGenerator grouping : groupings) {
+ final var gen = grouping.findSchemaTreeGenerator(qname.bindTo(grouping.statement().argument().getModule()));
if (gen != null) {
- return gen;
+ return grouping;
}
}
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);
+ private @Nullable AbstractExplicitGenerator<?> findInferredGenerator(final QName qname) {
+ // First search our local groupings ...
+ for (var grouping : groupings) {
+ final var gen = grouping.findSchemaTreeGenerator(qname.bindTo(grouping.statement().argument().getModule()));
if (gen != null) {
- return resolveNext(gen, qnames, null);
+ return gen;
}
}
-
- // 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));
+ // ... next try local augments, which may have groupings themselves
+ for (var augment : augments) {
+ final var gen = augment.findSchemaTreeGenerator(qname);
if (gen != null) {
- return resolveNext(gen, qnames, ns);
+ return gen;
}
}
-
- // 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;
+ return null;
}
/**
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 {
}
// 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);
}
/**
- * 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) {
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;
}
/**
return verifyNotNull(orig, "Generator %s does not have linkage to original instance resolved", this);
}
+ @Nullable AbstractExplicitGenerator<?> tryOriginal() {
+ return orig;
+ }
+
/**
* Return the link towards the original generator.
*
}
@Nullable AbstractExplicitGenerator<?> findSchemaTreeGenerator(final QName qname) {
+ return findLocalSchemaTreeGenerator(qname);
+ }
+
+ final @Nullable AbstractExplicitGenerator<?> findLocalSchemaTreeGenerator(final QName qname) {
for (Generator child : this) {
if (child instanceof AbstractExplicitGenerator) {
final AbstractExplicitGenerator<?> gen = (AbstractExplicitGenerator<?>) child;
--- /dev/null
+/*
+ * 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;
+ }
+}
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
// 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.
+ // Then start all UsesAugmentGenerators' target resolution.
+ final var augments = new ArrayList<AugmentRequirement>();
for (ModuleGenerator module : children) {
- for (Generator child : module) {
- if (child instanceof ModuleAugmentGenerator) {
- ((ModuleAugmentGenerator) child).linkAugmentationTarget(this);
+ for (Generator gen : module) {
+ if (gen instanceof ModuleAugmentGenerator) {
+ augments.add(((ModuleAugmentGenerator) gen).startLinkage(this));
}
}
}
+ for (ModuleGenerator module : children) {
+ module.startUsesAugmentLinkage(augments);
+ }
+ LOG.trace("Processing linkage of {} augment generators", augments.size());
// 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) {
- remaining += module.linkOriginalGenerator();
+ for (ModuleGenerator module : children) {
+ verify(module.linkOriginalGenerator(), "Module %s failed to link", module);
+ }
+
+ final var unlinkedModules = new ArrayList<>(children);
+ while (true) {
+ final boolean progress =
+ progressAndClean(unlinkedModules, ModuleGenerator::linkOriginalGeneratorRecursive)
+ // not '||' because we need the side-effects, which would get short-circuited
+ | progressAndClean(augments, AugmentRequirement::resolve);
+
+ if (augments.isEmpty() && unlinkedModules.isEmpty()) {
+ break;
}
- verify(remaining < unlinkedOriginals, "Failed to make progress on linking of remaining %s originals",
- remaining);
- unlinkedOriginals = remaining;
- } while (unlinkedOriginals != 0);
+
+ verify(progress, "Failed to make progress on linking of original generators");
+ }
/*
* Step 2: link typedef statements, so that typedef's 'type' axis is fully established
}
}
+ private static <T> boolean progressAndClean(final List<T> items, final Function<T, LinkageProgress> function) {
+ boolean progress = false;
+
+ final var it = items.iterator();
+ while (it.hasNext()) {
+ final var tmp = function.apply(it.next());
+ if (tmp != LinkageProgress.NONE) {
+ progress = true;
+ }
+ if (tmp == LinkageProgress.DONE) {
+ it.remove();
+ }
+ }
+
+ return progress;
+ }
+
private void linkDependencies(final Iterable<? extends Generator> parent) {
for (Generator child : parent) {
if (child instanceof AbstractDependentGenerator) {
--- /dev/null
+/*
+ * 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;
+
+/**
+ * Enumeration of progress indicators.
+ */
+enum LinkageProgress {
+ /**
+ * No progress made and there is work to do.
+ */
+ NONE,
+ /**
+ * Some progress made, but there is more work to do.
+ */
+ SOME,
+ /**
+ * There is no more work to do.
+ */
+ DONE;
+}
*/
package org.opendaylight.mdsal.binding.generator.impl.reactor;
+import org.eclipse.jdt.annotation.NonNull;
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.
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));
+ @NonNull AugmentRequirement startLinkage(final GeneratorContext context) {
+ return new AugmentRequirement(this,
+ context.resolveModule(statement().argument().firstNodeIdentifier().getModule()));
}
}
*/
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 org.eclipse.jdt.annotation.NonNull;
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;
+
+ private GroupingGenerator grouping;
+
+ UsesAugmentGenerator(final AugmentEffectiveStatement statement, final UsesEffectiveStatement uses,
+ final AbstractCompositeGenerator<?> parent) {
super(statement, parent);
+ this.uses = requireNonNull(uses);
+ }
+
+ void resolveGrouping(final UsesEffectiveStatement resolvedUses, final GroupingGenerator resolvedGrouping) {
+ if (resolvedUses == uses) {
+ verify(grouping == null, "Attempted to re-resolve grouping of %s", this);
+ grouping = requireNonNull(resolvedGrouping);
+ }
}
- @Override
- void loadTargetGenerator() {
+ @NonNull AugmentRequirement startLinkage() {
// 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()));
+ // Our parent here is *not* the 'uses' statement, but rather the statement which contains it.
+ return new AugmentRequirement(this, verifyNotNull(grouping, "Unresolved grouping in %s", this));
}
}
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+module bar {
+ namespace bar;
+ prefix bar;
+
+ container bar;
+}
--- /dev/null
+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;
+ }
+ }
+ }
+}
--- /dev/null
+module foo {
+ namespace foo;
+ prefix foo;
+
+ grouping foo {
+ container foo {
+ container nested;
+ }
+ }
+}
--- /dev/null
+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;
+ }
+}