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;
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
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);
}
}
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
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
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.
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);
--- /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.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