From 9f3139fa4ade5c83a9b6ae435610fdf190837cdd Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Wed, 3 Jan 2024 21:54:34 +0100 Subject: [PATCH] Eliminate YangModelDependencyInfo ModuleImport is a completely different concept to what we want to express. We already have SourceIdentifier, which matches the fields, but unfortunately it has slightly different co-notations -- for a dependency a missing revision says it is a wildcard. We therefore create a dedicated construct, SourceDependency, which really is a model.api.source contract. This clarifies a lot of details about SourceInfo and DependencyResolver, leading to complete elimination of YangModelDependencyInfo, making DependencyResolve work on SourceInfo. The extraction logic is hosted in YangIRSourceInfoExtractor, but we expect it to mvoe to YangIRSchemaSource, once we have dealt with the code structors within yang-parser-rfc7950. JIRA: YANGTOOLS-1150 Change-Id: I913a03d2b2486e566d06f11b7f5f0d496f30c98f Signed-off-by: Robert Varga --- .../model/api/source/SourceDependency.java | 96 ++++ .../model/api/stmt/RootDeclaredStatement.java | 3 + .../model/spi/source/ModuleSourceInfo.java | 40 -- .../yang/model/spi/source/SourceInfo.java | 214 ++++++++- .../model/spi/source/SubmoduleSourceInfo.java | 37 -- .../yang/parser/repo/AssembleSources.java | 6 +- .../yang/parser/repo/DependencyResolver.java | 175 +++---- .../repo/RevisionDependencyResolver.java | 40 +- .../parser/repo/DependencyResolverTest.java | 78 ++-- .../rfc7950/repo/TextToIRTransformer.java | 11 +- .../repo/YangIRSourceInfoExtractor.java | 192 ++++++++ .../rfc7950/repo/YangModelDependencyInfo.java | 432 ------------------ .../repo/YangIRSourceInfoExtractorTest.java | 105 +++++ .../repo/YangModelDependencyInfoTest.java | 97 ---- yang/yang-repo-api/pom.xml | 6 + .../repo/api/SchemaResolutionException.java | 13 +- 16 files changed, 727 insertions(+), 818 deletions(-) create mode 100644 model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/source/SourceDependency.java delete mode 100644 model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/ModuleSourceInfo.java delete mode 100644 model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SubmoduleSourceInfo.java create mode 100644 parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangIRSourceInfoExtractor.java delete mode 100644 parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfo.java create mode 100644 parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangIRSourceInfoExtractorTest.java delete mode 100644 parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfoTest.java diff --git a/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/source/SourceDependency.java b/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/source/SourceDependency.java new file mode 100644 index 0000000000..9537e6498d --- /dev/null +++ b/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/source/SourceDependency.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 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.yangtools.yang.model.api.source; + +import static java.util.Objects.requireNonNull; + +import java.io.Serializable; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified; +import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.ImportStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.IncludeStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement; + +/** + * Common interface expressing a dependency on a source, be it a {@link ModuleStatement} + * or a {@link SubmoduleStatement}. + */ +@NonNullByDefault +public sealed interface SourceDependency extends Serializable + permits SourceDependency.Import, SourceDependency.Include, SourceDependency.BelongsTo { + /** + * The name of the required source. + * + * @return name of the required source + */ + Unqualified name(); + + /** + * Returns required source revision. If specified, this dependency can be satisfied only by the specified revision + * or its semantic equivalent (think semantic version of imports). If unspecified, this dependency can be satisfied + * by any source with a matching {@link #name()}. + * + * @return required source revision, {@code null} if unspecified + */ + @Nullable Revision revision(); + + /** + * A dependency created by a {@link BelongsToStatement}. + */ + record BelongsTo(Unqualified name, Unqualified prefix) implements SourceDependency { + @java.io.Serial + private static final long serialVersionUID = 0L; + + public BelongsTo { + requireNonNull(name); + requireNonNull(prefix); + } + + @Override + public @Nullable Revision revision() { + return null; + } + } + + /** + * A dependency created by an {@link ImportStatement}. + */ + record Import(Unqualified name, Unqualified prefix, @Nullable Revision revision) implements SourceDependency { + @java.io.Serial + private static final long serialVersionUID = 0L; + + public Import { + requireNonNull(name); + requireNonNull(prefix); + } + + public Import(final Unqualified name, final Unqualified prefix) { + this(name, prefix, null); + } + } + + /** + * A dependency created by an {@link IncludeStatement}. + */ + record Include(Unqualified name, @Nullable Revision revision) implements SourceDependency { + @java.io.Serial + private static final long serialVersionUID = 0L; + + public Include { + requireNonNull(name); + } + + public Include(final Unqualified name) { + this(name, null); + } + } +} diff --git a/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/RootDeclaredStatement.java b/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/RootDeclaredStatement.java index e53567abd0..92b1b77d54 100644 --- a/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/RootDeclaredStatement.java +++ b/model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/RootDeclaredStatement.java @@ -22,6 +22,9 @@ public sealed interface RootDeclaredStatement extends DocumentedDeclaredStatement, NotificationStatementAwareDeclaredStatement, DataDefinitionAwareDeclaredStatement.WithReusableDefinitions permits ModuleStatement, SubmoduleStatement { + @Override + Unqualified argument(); + default Optional getOrganization() { return findFirstDeclaredSubstatement(OrganizationStatement.class); } diff --git a/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/ModuleSourceInfo.java b/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/ModuleSourceInfo.java deleted file mode 100644 index 23e9d53445..0000000000 --- a/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/ModuleSourceInfo.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2024 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.yangtools.yang.model.spi.source; - -import static java.util.Objects.requireNonNull; - -import com.google.common.collect.ImmutableSet; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.opendaylight.yangtools.yang.common.Revision; -import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified; -import org.opendaylight.yangtools.yang.common.XMLNamespace; -import org.opendaylight.yangtools.yang.common.YangVersion; - -/** - * A {@link SourceInfo} about a {@code module}. - */ -@NonNullByDefault -public record ModuleSourceInfo( - Unqualified name, - YangVersion yangVersion, - XMLNamespace namespace, - String prefix, - ImmutableSet revisions, - ImmutableSet imports, - ImmutableSet includes) implements SourceInfo { - public ModuleSourceInfo { - requireNonNull(name); - requireNonNull(yangVersion); - requireNonNull(namespace); - requireNonNull(prefix); - requireNonNull(revisions); - requireNonNull(imports); - requireNonNull(includes); - } -} diff --git a/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SourceInfo.java b/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SourceInfo.java index e159819457..6e83046517 100644 --- a/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SourceInfo.java +++ b/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SourceInfo.java @@ -10,20 +10,33 @@ package org.opendaylight.yangtools.yang.model.spi.source; import static java.util.Objects.requireNonNull; import com.google.common.collect.ImmutableSet; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.ArrayList; +import java.util.Comparator; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.yang.common.Revision; import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified; +import org.opendaylight.yangtools.yang.common.XMLNamespace; import org.opendaylight.yangtools.yang.common.YangVersion; +import org.opendaylight.yangtools.yang.model.api.source.SourceDependency.BelongsTo; +import org.opendaylight.yangtools.yang.model.api.source.SourceDependency.Import; +import org.opendaylight.yangtools.yang.model.api.source.SourceDependency.Include; +import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation; +import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.RevisionDateStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.RootDeclaredStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.YangVersionStatement; /** * Linkage information about a particular {@link SourceRepresentation}. It has two specializations *
    - *
  1. {@link ModuleSourceInfo} pertaining to {@link SourceRepresentation} which have {@code module} as its root - * statement
  2. - *
  3. {@link SubmoduleSourceInfo} pertaining to {@link SourceRepresentation} which have {@code submodule} as its root + *
  4. {@link SourceInfo.Module} pertaining to {@link SourceRepresentation} which have {@code module} as its root * statement
  5. + *
  6. {@link SourceInfo.Submodule} pertaining to {@link SourceRepresentation} which have {@code submodule} as its + * root statement
  7. *
* *

@@ -36,38 +49,199 @@ import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation; * */ @NonNullByDefault -public sealed interface SourceInfo permits ModuleSourceInfo, SubmoduleSourceInfo { - record Import(Unqualified name, String prefix, @Nullable Revision revision) { - public Import { - requireNonNull(name); - requireNonNull(prefix); - } - } - - record Include(Unqualified name, @Nullable Revision revision) { - public Include { - requireNonNull(name); - } - } - +public sealed interface SourceInfo permits SourceInfo.Module, SourceInfo.Submodule { /** - * The name of this source, as expressed by the argument of {@code module} or {@code submodule} statement. + * Return the {@link SourceIdentifier} of this source, as expressed by {@link RootDeclaredStatement#argument()} + * contract coupled with the first entry in {@link #revisions()}. * * @return name of this source. */ - Unqualified name(); + SourceIdentifier sourceId(); /** - * {@link YangVersion} of the source. If no {@code yang-version} is present, this method will return + * Return {@link YangVersion} of the source. If no {@link YangVersionStatement} is present, this method will return * {@link YangVersion#VERSION_1}. * * @return {@link YangVersion} of the source */ YangVersion yangVersion(); + /** + * The set of all {@link RevisionDateStatement} mentioned in {@link RevisionStatement}s. The returned set is ordered + * in reverse order, i.e. newest revision is encountered first. + * + * @return all revisions known by this source + */ ImmutableSet revisions(); + /** + * Return all {@link Import} dependencies. + * + * @return all import dependencies + */ ImmutableSet imports(); + /** + * Return all {@link Include} dependencies. + * + * @return all include dependencies + */ ImmutableSet includes(); + + /** + * A {@link SourceInfo} about a {@link ModuleStatement}-backed source. + */ + record Module( + SourceIdentifier sourceId, + YangVersion yangVersion, + XMLNamespace namespace, + Unqualified prefix, + ImmutableSet revisions, + ImmutableSet imports, + ImmutableSet includes) implements SourceInfo { + public Module { + requireNonNull(sourceId); + requireNonNull(yangVersion); + requireNonNull(namespace); + requireNonNull(prefix); + requireNonNull(revisions); + requireNonNull(imports); + requireNonNull(includes); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder extends SourceInfo.Builder { + @SuppressFBWarnings(value = "NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", + justification = "https://github.com/spotbugs/spotbugs/issues/743") + private @Nullable XMLNamespace namespace; + @SuppressFBWarnings(value = "NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", + justification = "https://github.com/spotbugs/spotbugs/issues/743") + private @Nullable Unqualified prefix; + + Builder() { + // Hidden on purpose + } + + public Builder setNamespace(final XMLNamespace namespace) { + this.namespace = requireNonNull(namespace); + return this; + } + + public Builder setPrefix(final Unqualified prefix) { + this.prefix = requireNonNull(prefix); + return this; + } + + @Override + Module buildInstance(final SourceIdentifier sourceId, final YangVersion yangVersion, + final ImmutableSet revisions, final ImmutableSet imports, + final ImmutableSet includes) { + return new Module(sourceId, yangVersion, requireNonNull(namespace), requireNonNull(prefix), revisions, + imports, includes); + } + } + } + + /** + * A {@link SourceInfo} about a {@code submodule}. + */ + record Submodule( + SourceIdentifier sourceId, + YangVersion yangVersion, + BelongsTo belongsTo, + ImmutableSet revisions, + ImmutableSet imports, + ImmutableSet includes) implements SourceInfo { + public Submodule { + requireNonNull(sourceId); + requireNonNull(yangVersion); + requireNonNull(belongsTo); + requireNonNull(revisions); + requireNonNull(imports); + requireNonNull(imports); + requireNonNull(includes); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder extends SourceInfo.Builder { + @SuppressFBWarnings(value = "NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", + justification = "https://github.com/spotbugs/spotbugs/issues/743") + private @Nullable BelongsTo belongsTo; + + Builder() { + // Hidden on purpose + } + + public Builder setBelongsTo(final BelongsTo belongsTo) { + this.belongsTo = requireNonNull(belongsTo); + return this; + } + + @Override + Submodule buildInstance(final SourceIdentifier sourceId, final YangVersion yangVersion, + final ImmutableSet revisions, final ImmutableSet imports, + final ImmutableSet includes) { + return new Submodule(sourceId, yangVersion, requireNonNull(belongsTo), revisions, imports, includes); + } + } + } + + abstract sealed class Builder, I extends SourceInfo> { + private final ImmutableSet.Builder imports = ImmutableSet.builder(); + private final ImmutableSet.Builder includes = ImmutableSet.builder(); + private final ArrayList revisions = new ArrayList<>(); + private YangVersion yangVersion = YangVersion.VERSION_1; + @SuppressFBWarnings(value = "NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", + justification = "https://github.com/spotbugs/spotbugs/issues/743") + private @Nullable Unqualified name; + + public final B setName(final Unqualified newName) { + name = requireNonNull(newName); + return thisInstance(); + } + + public final B setYangVersion(final YangVersion newYangVersion) { + yangVersion = requireNonNull(newYangVersion); + return thisInstance(); + } + + public final B addImport(final Import importDep) { + imports.add(importDep); + return thisInstance(); + } + + public final B addInclude(final Include includeDep) { + includes.add(includeDep); + return thisInstance(); + } + + public final B addRevision(final Revision revision) { + revisions.add(revision); + return thisInstance(); + } + + public final I build() { + final var sorted = revisions.stream() + .sorted(Comparator.reverseOrder()) + .collect(ImmutableSet.toImmutableSet()); + + return buildInstance( + new SourceIdentifier(requireNonNull(name), sorted.isEmpty() ? null : sorted.iterator().next()), + yangVersion, sorted, imports.build(), includes.build()); + } + + abstract I buildInstance(SourceIdentifier sourceId, YangVersion yangVersion, ImmutableSet revisions, + ImmutableSet imports, ImmutableSet includes); + + @SuppressWarnings("unchecked") + private B thisInstance() { + return (B) this; + } + } } diff --git a/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SubmoduleSourceInfo.java b/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SubmoduleSourceInfo.java deleted file mode 100644 index 2ad7a925a2..0000000000 --- a/model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SubmoduleSourceInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2024 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.yangtools.yang.model.spi.source; - -import static java.util.Objects.requireNonNull; - -import com.google.common.collect.ImmutableSet; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.opendaylight.yangtools.yang.common.Revision; -import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified; -import org.opendaylight.yangtools.yang.common.YangVersion; - -/** - * A {@link SourceInfo} about a {@code submodule}. - */ -@NonNullByDefault -public record SubmoduleSourceInfo( - Unqualified name, - YangVersion yangVersion, - Unqualified belongsTo, - ImmutableSet revisions, - ImmutableSet imports, - ImmutableSet includes) implements SourceInfo { - public SubmoduleSourceInfo { - requireNonNull(name); - requireNonNull(yangVersion); - requireNonNull(belongsTo); - requireNonNull(revisions); - requireNonNull(imports); - requireNonNull(includes); - } -} diff --git a/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java b/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java index fc40a2f63a..9b236eebe7 100644 --- a/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java +++ b/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java @@ -23,7 +23,7 @@ import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException; import org.opendaylight.yangtools.yang.parser.api.YangParserException; import org.opendaylight.yangtools.yang.parser.api.YangParserFactory; import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException; -import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo; +import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangIRSourceInfoExtractor; import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,11 +47,11 @@ final class AssembleSources implements AsyncFunction, E @Override public FluentFuture apply(final List sources) { final var srcs = Maps.uniqueIndex(sources, getIdentifier); - final var deps = Maps.transformValues(srcs, YangModelDependencyInfo::forIR); + final var deps = Maps.transformValues(srcs, YangIRSourceInfoExtractor::forIR); LOG.debug("Resolving dependency reactor {}", deps); final var res = switch (config.getStatementParserMode()) { - case DEFAULT_MODE -> RevisionDependencyResolver.create(deps); + case DEFAULT_MODE -> new RevisionDependencyResolver(deps); }; final var unresolved = res.unresolvedSources(); diff --git a/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java b/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java index ba06ab4d64..52afb83080 100644 --- a/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java +++ b/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java @@ -7,23 +7,17 @@ */ package org.opendaylight.yangtools.yang.parser.repo; -import com.google.common.base.MoreObjects; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; -import java.util.ArrayList; +import com.google.common.collect.Sets; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.Optional; -import org.opendaylight.yangtools.yang.common.Revision; -import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified; -import org.opendaylight.yangtools.yang.model.api.ModuleImport; +import org.opendaylight.yangtools.yang.model.api.source.SourceDependency; import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; -import org.opendaylight.yangtools.yang.model.api.stmt.ImportEffectiveStatement; +import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo; +import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo.Submodule; import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration; -import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo; -import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo.SubmoduleDependencyInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,38 +35,21 @@ abstract class DependencyResolver { private final ImmutableList resolvedSources; private final ImmutableList unresolvedSources; - private final ImmutableMultimap unsatisfiedImports; + private final ImmutableMultimap unsatisfiedImports; - protected DependencyResolver(final Map depInfo) { - final var resolved = new ArrayList(depInfo.size()); - final var pending = new ArrayList<>(depInfo.keySet()); - final var submodules = new HashMap(); + protected DependencyResolver(final Map depInfo) { + final var resolved = Sets.newHashSetWithExpectedSize(depInfo.size()); + final var pending = new HashMap<>(depInfo); boolean progress; do { progress = false; - final var it = pending.iterator(); + final var it = pending.values().iterator(); while (it.hasNext()) { - final var sourceId = it.next(); - final var dep = depInfo.get(sourceId); - - // in case of submodule, remember belongs to - if (dep instanceof SubmoduleDependencyInfo submodule) { - final var parent = submodule.getParentModule(); - submodules.put(sourceId, new BelongsToDependency(parent)); - } - - boolean okay = true; - for (var dependency : dep.getDependencies()) { - if (!isKnown(resolved, dependency)) { - LOG.debug("Source {} is missing import {}", sourceId, dependency); - okay = false; - break; - } - } - - if (okay) { + final var dep = it.next(); + if (tryResolve(resolved, dep)) { + final var sourceId = dep.sourceId(); LOG.debug("Resolved source {}", sourceId); resolved.add(sourceId); it.remove(); @@ -81,112 +58,84 @@ abstract class DependencyResolver { } } while (progress); - /// Additional check only for belongs-to statement - for (var submodule : submodules.entrySet()) { - final var sourceId = submodule.getKey(); - final var belongs = submodule.getValue(); - if (!isKnown(resolved, belongs)) { - LOG.debug("Source {} is missing parent {}", sourceId, belongs); - pending.add(sourceId); - resolved.remove(sourceId); - } - } + resolvedSources = ImmutableList.copyOf(resolved); + unresolvedSources = ImmutableList.copyOf(pending.keySet()); - final var imports = ArrayListMultimap.create(); - for (var sourceId : pending) { - for (var dependency : depInfo.get(sourceId).getDependencies()) { - if (!isKnown(pending, dependency) && !isKnown(resolved, dependency)) { - imports.put(sourceId, dependency); + final var unstatisfied = ImmutableMultimap.builder(); + for (var info : pending.values()) { + for (var dep : info.imports()) { + if (!isKnown(depInfo.keySet(), dep)) { + unstatisfied.put(info.sourceId(), dep); + } + } + for (var dep : info.includes()) { + if (!isKnown(depInfo.keySet(), dep)) { + unstatisfied.put(info.sourceId(), dep); + } + } + if (info instanceof Submodule submodule) { + final var dep = submodule.belongsTo(); + if (!isKnown(depInfo.keySet(), dep)) { + unstatisfied.put(info.sourceId(), dep); } } } - - resolvedSources = ImmutableList.copyOf(resolved); - unresolvedSources = ImmutableList.copyOf(pending); - unsatisfiedImports = ImmutableMultimap.copyOf(imports); + unsatisfiedImports = unstatisfied.build(); } - protected abstract boolean isKnown(Collection haystack, ModuleImport mi); - - abstract YangParserConfiguration parserConfig(); - /** * Collection of sources which have been resolved. */ - ImmutableList resolvedSources() { + final ImmutableList resolvedSources() { return resolvedSources; } /** * Collection of sources which have not been resolved due to missing dependencies. */ - ImmutableList unresolvedSources() { + final ImmutableList unresolvedSources() { return unresolvedSources; } /** - * Detailed information about which imports were missing. The key in the map - * is the source identifier of module which was issuing an import, the values - * are imports which were unsatisfied. - * - *

- * Note that this map contains only imports which are missing from the reactor, - * not transitive failures. + * Detailed information about which imports were missing. The key in the map is the source identifier of module + * which was issuing an import, the values are imports which were unsatisfied. * *

- * Examples: - *

  • - * If A imports B, B imports C, and both A and B are in the reactor, only B->C - * will be reported. - *
  • - * If A imports B and C, B imports C, and both A and B are in the reactor, - * A->C and B->C will be reported. - *
+ * Note that this map contains only imports which are missing from the reactor, not transitive failures. Examples: + *
    + *
  • if A imports B, B imports C, and both A and B are in the reactor, only B->C will be reported
  • + *
  • if A imports B and C, B imports C, and both A and B are in the reactor, A->C and B->C will be reported
  • + *
*/ - ImmutableMultimap unsatisfiedImports() { + final ImmutableMultimap unsatisfiedImports() { return unsatisfiedImports; } - private static class BelongsToDependency implements ModuleImport { - private final Unqualified parent; - - BelongsToDependency(final Unqualified parent) { - this.parent = parent; - } - - @Override - public Unqualified getModuleName() { - return parent; - } - - @Override - public Optional getRevision() { - return Optional.empty(); - } - - @Override - public Optional getDescription() { - return Optional.empty(); + private boolean tryResolve(final Collection resolved, final SourceInfo info) { + for (var dep : info.imports()) { + if (!isKnown(resolved, dep)) { + LOG.debug("Source {} is missing import {}", info.sourceId(), dep); + return false; + } } - - @Override - public Optional getReference() { - return Optional.empty(); + for (var dep : info.includes()) { + if (!isKnown(resolved, dep)) { + LOG.debug("Source {} is missing include {}", info.sourceId(), dep); + return false; + } } - - @Override - public String getPrefix() { - throw new UnsupportedOperationException(); + if (info instanceof Submodule submodule) { + final var dep = submodule.belongsTo(); + if (!isKnown(resolved, dep)) { + LOG.debug("Source {} is missing belongs-to {}", info.sourceId(), dep); + return false; + } } + return true; + } - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("parent", parent).toString(); - } + abstract boolean isKnown(Collection haystack, SourceDependency dependency); - @Override - public ImportEffectiveStatement asEffectiveStatement() { - throw new UnsupportedOperationException(); - } - } + abstract YangParserConfiguration parserConfig(); } diff --git a/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/RevisionDependencyResolver.java b/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/RevisionDependencyResolver.java index 312ddfe4f9..8ad4f0298d 100644 --- a/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/RevisionDependencyResolver.java +++ b/parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/RevisionDependencyResolver.java @@ -10,46 +10,36 @@ package org.opendaylight.yangtools.yang.parser.repo; import java.util.Collection; import java.util.Map; import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified; -import org.opendaylight.yangtools.yang.model.api.ModuleImport; +import org.opendaylight.yangtools.yang.model.api.source.SourceDependency; import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo; import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration; -import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo; final class RevisionDependencyResolver extends DependencyResolver { - RevisionDependencyResolver(final Map depInfo) { + RevisionDependencyResolver(final Map depInfo) { super(depInfo); } - protected static SourceIdentifier findWildcard(final Iterable haystack, - final Unqualified needle) { - for (final SourceIdentifier r : haystack) { - if (needle.equals(r.name())) { - return r; - } - } - - return null; - } - @Override YangParserConfiguration parserConfig() { return YangParserConfiguration.DEFAULT; } @Override - protected boolean isKnown(final Collection haystack, final ModuleImport mi) { - final SourceIdentifier msi = new SourceIdentifier(mi.getModuleName(), mi.getRevision().orElse(null)); - + boolean isKnown(final Collection haystack, final SourceDependency dependency) { // Quick lookup - if (haystack.contains(msi)) { - return true; - } - - // Slow revision-less walk - return mi.getRevision().isEmpty() && findWildcard(haystack, mi.getModuleName()) != null; + return haystack.contains(new SourceIdentifier(dependency.name(), dependency.revision())) + // Slow revision-less walk + || dependency.revision() == null && findWildcard(haystack, dependency.name()) != null; } - public static RevisionDependencyResolver create(final Map depInfo) { - return new RevisionDependencyResolver(depInfo); + private static SourceIdentifier findWildcard(final Collection haystack, + final Unqualified needle) { + for (var sourceId : haystack) { + if (needle.equals(sourceId.name())) { + return sourceId; + } + } + return null; } } \ No newline at end of file diff --git a/parser/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolverTest.java b/parser/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolverTest.java index b931ea40ad..3e34069c7d 100644 --- a/parser/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolverTest.java +++ b/parser/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolverTest.java @@ -7,62 +7,68 @@ */ package org.opendaylight.yangtools.yang.parser.repo; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import com.google.common.collect.ImmutableMultimap; import java.util.HashMap; -import java.util.Map; +import java.util.List; import org.junit.jupiter.api.Test; +import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified; +import org.opendaylight.yangtools.yang.model.api.source.SourceDependency.BelongsTo; import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo; import org.opendaylight.yangtools.yang.model.spi.source.YangTextSource; -import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo; -import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo.ModuleDependencyInfo; +import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangIRSourceInfoExtractor; -@Deprecated -public class DependencyResolverTest { +class DependencyResolverTest { @Test public void testModulesWithoutRevisionAndImport() throws Exception { - final var map = new HashMap(); - addToMap(map, "/no-revision/imported.yang"); - addToMap(map, "/no-revision/imported@2012-12-12.yang"); - addToMap(map, "/no-revision/top@2012-10-10.yang"); - - final var resolved = RevisionDependencyResolver.create(map); - assertEquals(0, resolved.unresolvedSources().size()); - assertEquals(0, resolved.unsatisfiedImports().size()); + final var resolved = resolveResources("/no-revision/imported.yang", "/no-revision/imported@2012-12-12.yang", + "/no-revision/top@2012-10-10.yang"); + assertThat(resolved.resolvedSources()).containsExactlyInAnyOrder( + new SourceIdentifier("top", "2012-10-10"), + new SourceIdentifier("imported"), + new SourceIdentifier("imported", "2012-12-12")); + assertEquals(List.of(), resolved.unresolvedSources()); + assertEquals(ImmutableMultimap.of(), resolved.unsatisfiedImports()); } @Test public void testSubmoduleNoModule() throws Exception { - final var map = new HashMap(); // Subfoo does not have parent in reactor - addToMap(map, "/model/subfoo.yang"); - addToMap(map, "/model/bar.yang"); - addToMap(map, "/model/baz.yang"); + final var resolved = resolveResources("/model/subfoo.yang", "/model/bar.yang", "/model/baz.yang"); + assertThat(resolved.resolvedSources()).containsExactlyInAnyOrder( + new SourceIdentifier("bar", "2013-07-03"), + new SourceIdentifier("baz", "2013-02-27")); + assertThat(resolved.unresolvedSources()).containsExactlyInAnyOrder( + new SourceIdentifier("subfoo", "2013-02-27")); - final var resolved = RevisionDependencyResolver.create(map); - assertEquals(2, resolved.resolvedSources().size()); - assertEquals(1, resolved.unresolvedSources().size()); - assertEquals(0, resolved.unsatisfiedImports().size()); + assertEquals(ImmutableMultimap.of( + new SourceIdentifier("subfoo", "2013-02-27"), new BelongsTo(Unqualified.of("foo"), Unqualified.of("f"))), + resolved.unsatisfiedImports()); } @Test public void testSubmodule() throws Exception { - final var map = new HashMap(); - addToMap(map, "/model/subfoo.yang"); - addToMap(map, "/model/foo.yang"); - addToMap(map, "/model/bar.yang"); - addToMap(map, "/model/baz.yang"); - - final var resolved = RevisionDependencyResolver.create(map); - assertEquals(0, resolved.unresolvedSources().size()); - assertEquals(0, resolved.unsatisfiedImports().size()); - assertEquals(4, resolved.resolvedSources().size()); + final var resolved = resolveResources("/model/subfoo.yang", "/model/foo.yang", "/model/bar.yang", + "/model/baz.yang"); + assertThat(resolved.resolvedSources()).containsExactlyInAnyOrder( + new SourceIdentifier("bar", "2013-07-03"), + new SourceIdentifier("baz", "2013-02-27")); + assertThat(resolved.unresolvedSources()).containsExactlyInAnyOrder( + new SourceIdentifier("foo", "2013-02-27"), + new SourceIdentifier("subfoo", "2013-02-27")); + assertEquals(ImmutableMultimap.of(), resolved.unsatisfiedImports()); } - private static void addToMap(final Map map, final String yangFileName) - throws Exception { - final var info = ModuleDependencyInfo.forYangText(YangTextSource.forResource(DependencyResolverTest.class, - yangFileName)); - map.put(new SourceIdentifier(info.getName(), info.getFormattedRevision()), info); + private static RevisionDependencyResolver resolveResources(final String... resourceNames) throws Exception { + final var map = new HashMap(); + for (var resourceName : resourceNames) { + final var info = YangIRSourceInfoExtractor.forYangText( + YangTextSource.forResource(DependencyResolverTest.class, resourceName)); + map.put(info.sourceId(), info); + } + return new RevisionDependencyResolver(map); } } diff --git a/parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/TextToIRTransformer.java b/parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/TextToIRTransformer.java index d9292c8080..e988eb560f 100644 --- a/parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/TextToIRTransformer.java +++ b/parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/TextToIRTransformer.java @@ -11,9 +11,7 @@ import com.google.common.annotations.Beta; import com.google.common.util.concurrent.Futures; import java.io.IOException; import org.eclipse.jdt.annotation.NonNull; -import org.opendaylight.yangtools.yang.ir.IRStatement; import org.opendaylight.yangtools.yang.ir.YangIRSchemaSource; -import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceTransformer; @@ -35,11 +33,8 @@ public final class TextToIRTransformer extends SchemaSourceTransformer