Eliminate YangModelDependencyInfo 17/109617/6
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 3 Jan 2024 20:54:34 +0000 (21:54 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 4 Jan 2024 02:40:41 +0000 (03:40 +0100)
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 <robert.varga@pantheon.tech>
16 files changed:
model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/source/SourceDependency.java [new file with mode: 0644]
model/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/stmt/RootDeclaredStatement.java
model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/ModuleSourceInfo.java [deleted file]
model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SourceInfo.java
model/yang-model-spi/src/main/java/org/opendaylight/yangtools/yang/model/spi/source/SubmoduleSourceInfo.java [deleted file]
parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/AssembleSources.java
parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolver.java
parser/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/RevisionDependencyResolver.java
parser/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/repo/DependencyResolverTest.java
parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/TextToIRTransformer.java
parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangIRSourceInfoExtractor.java [new file with mode: 0644]
parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfo.java [deleted file]
parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangIRSourceInfoExtractorTest.java [new file with mode: 0644]
parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfoTest.java [deleted file]
yang/yang-repo-api/pom.xml
yang/yang-repo-api/src/main/java/org/opendaylight/yangtools/yang/model/repo/api/SchemaResolutionException.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 (file)
index 0000000..9537e64
--- /dev/null
@@ -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);
+        }
+    }
+}
index e53567abd0b06e5124779f8a8c3a8ef6b2b9e5ca..92b1b77d544bb70440bbfdaeb5c87f3fd17308c1 100644 (file)
@@ -22,6 +22,9 @@ public sealed interface RootDeclaredStatement
         extends DocumentedDeclaredStatement<Unqualified>, NotificationStatementAwareDeclaredStatement<Unqualified>,
                 DataDefinitionAwareDeclaredStatement.WithReusableDefinitions<Unqualified>
         permits ModuleStatement, SubmoduleStatement {
+    @Override
+    Unqualified argument();
+
     default Optional<OrganizationStatement> 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 (file)
index 23e9d53..0000000
+++ /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<Revision> revisions,
-        ImmutableSet<Import> imports,
-        ImmutableSet<Include> includes) implements SourceInfo {
-    public ModuleSourceInfo {
-        requireNonNull(name);
-        requireNonNull(yangVersion);
-        requireNonNull(namespace);
-        requireNonNull(prefix);
-        requireNonNull(revisions);
-        requireNonNull(imports);
-        requireNonNull(includes);
-    }
-}
index e159819457f5d4c406ed1cf4255fdc79dc0b4064..6e830465176ae6496b8c86a5cc0e692cb82ae20d 100644 (file)
@@ -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
  * <ol>
- *   <li>{@link ModuleSourceInfo} pertaining to {@link SourceRepresentation} which have {@code module} as its root
- *       statement</li>
- *   <li>{@link SubmoduleSourceInfo} pertaining to {@link SourceRepresentation} which have {@code submodule} as its root
+ *   <li>{@link SourceInfo.Module} pertaining to {@link SourceRepresentation} which have {@code module} as its root
  *       statement</li>
+ *   <li>{@link SourceInfo.Submodule} pertaining to {@link SourceRepresentation} which have {@code submodule} as its
+ *       root statement</li>
  * </ol>
  *
  * <p>
@@ -36,38 +49,199 @@ import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation;
  * </ul>
  */
 @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<Revision> revisions();
 
+    /**
+     * Return all {@link Import} dependencies.
+     *
+     * @return all import dependencies
+     */
     ImmutableSet<Import> imports();
 
+    /**
+     * Return all {@link Include} dependencies.
+     *
+     * @return all include dependencies
+     */
     ImmutableSet<Include> includes();
+
+    /**
+     * A {@link SourceInfo} about a {@link ModuleStatement}-backed source.
+     */
+    record Module(
+            SourceIdentifier sourceId,
+            YangVersion yangVersion,
+            XMLNamespace namespace,
+            Unqualified prefix,
+            ImmutableSet<Revision> revisions,
+            ImmutableSet<Import> imports,
+            ImmutableSet<Include> 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<Builder, Module> {
+            @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<Revision> revisions, final ImmutableSet<Import> imports,
+                    final ImmutableSet<Include> 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<Revision> revisions,
+            ImmutableSet<Import> imports,
+            ImmutableSet<Include> 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<Builder, Submodule> {
+            @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<Revision> revisions, final ImmutableSet<Import> imports,
+                    final ImmutableSet<Include> includes) {
+                return new Submodule(sourceId, yangVersion, requireNonNull(belongsTo), revisions, imports, includes);
+            }
+        }
+    }
+
+    abstract sealed class Builder<B extends Builder<B, I>, I extends SourceInfo> {
+        private final ImmutableSet.Builder<Import> imports = ImmutableSet.builder();
+        private final ImmutableSet.Builder<Include> includes = ImmutableSet.builder();
+        private final ArrayList<Revision> 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<Revision> revisions,
+            ImmutableSet<Import> imports, ImmutableSet<Include> 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 (file)
index 2ad7a92..0000000
+++ /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<Revision> revisions,
-        ImmutableSet<Import> imports,
-        ImmutableSet<Include> includes) implements SourceInfo {
-    public SubmoduleSourceInfo {
-        requireNonNull(name);
-        requireNonNull(yangVersion);
-        requireNonNull(belongsTo);
-        requireNonNull(revisions);
-        requireNonNull(imports);
-        requireNonNull(includes);
-    }
-}
index fc40a2f63a5ec0bcec30bc975a29c090bc62bcc4..9b236eebe709e37f7e70685a4bfef21e133cbb82 100644 (file)
@@ -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<List<YangIRSchemaSource>, E
     @Override
     public FluentFuture<EffectiveModelContext> apply(final List<YangIRSchemaSource> 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();
index ba06ab4d64646bbeea3950b99c54dbac98185114..52afb83080799833d20b72a998cc2a784469251c 100644 (file)
@@ -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<SourceIdentifier> resolvedSources;
     private final ImmutableList<SourceIdentifier> unresolvedSources;
-    private final ImmutableMultimap<SourceIdentifier, ModuleImport> unsatisfiedImports;
+    private final ImmutableMultimap<SourceIdentifier, SourceDependency> unsatisfiedImports;
 
-    protected DependencyResolver(final Map<SourceIdentifier, YangModelDependencyInfo> depInfo) {
-        final var resolved = new ArrayList<SourceIdentifier>(depInfo.size());
-        final var pending = new ArrayList<>(depInfo.keySet());
-        final var submodules = new HashMap<SourceIdentifier, BelongsToDependency>();
+    protected DependencyResolver(final Map<SourceIdentifier, SourceInfo> depInfo) {
+        final var resolved = Sets.<SourceIdentifier>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.<SourceIdentifier, ModuleImport>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.<SourceIdentifier, SourceDependency>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<SourceIdentifier> haystack, ModuleImport mi);
-
-    abstract YangParserConfiguration parserConfig();
-
     /**
      * Collection of sources which have been resolved.
      */
-    ImmutableList<SourceIdentifier> resolvedSources() {
+    final ImmutableList<SourceIdentifier> resolvedSources() {
         return resolvedSources;
     }
 
     /**
      * Collection of sources which have not been resolved due to missing dependencies.
      */
-    ImmutableList<SourceIdentifier> unresolvedSources() {
+    final ImmutableList<SourceIdentifier> 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.
-     *
-     * <p>
-     * 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.
      *
      * <p>
-     * Examples:
-     * <ul><li>
-     * If A imports B, B imports C, and both A and B are in the reactor, only B->C
-     * will be reported.
-     * </li><li>
-     * 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.
-     * </li></ul>
+     * Note that this map contains only imports which are missing from the reactor, not transitive failures. Examples:
+     * <ul>
+     *   <li>if A imports B, B imports C, and both A and B are in the reactor, only B->C will be reported</li>
+     *   <li>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</li>
+     * </ul>
      */
-    ImmutableMultimap<SourceIdentifier, ModuleImport> unsatisfiedImports() {
+    final ImmutableMultimap<SourceIdentifier, SourceDependency> 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<Revision> getRevision() {
-            return Optional.empty();
-        }
-
-        @Override
-        public Optional<String> getDescription() {
-            return Optional.empty();
+    private boolean tryResolve(final Collection<SourceIdentifier> 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<String> 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<SourceIdentifier> haystack, SourceDependency dependency);
 
-        @Override
-        public ImportEffectiveStatement asEffectiveStatement() {
-            throw new UnsupportedOperationException();
-        }
-    }
+    abstract YangParserConfiguration parserConfig();
 }
index 312ddfe4f96ffc59e989dd0beb8c9ca8b17a26f1..8ad4f0298df67e80a7fa2233bb938b82d6f22c4f 100644 (file)
@@ -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<SourceIdentifier, YangModelDependencyInfo> depInfo) {
+    RevisionDependencyResolver(final Map<SourceIdentifier, SourceInfo> depInfo) {
         super(depInfo);
     }
 
-    protected static SourceIdentifier findWildcard(final Iterable<SourceIdentifier> 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<SourceIdentifier> haystack, final ModuleImport mi) {
-        final SourceIdentifier msi = new SourceIdentifier(mi.getModuleName(), mi.getRevision().orElse(null));
-
+    boolean isKnown(final Collection<SourceIdentifier> 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<SourceIdentifier, YangModelDependencyInfo> depInfo) {
-        return new RevisionDependencyResolver(depInfo);
+    private static SourceIdentifier findWildcard(final Collection<SourceIdentifier> haystack,
+            final Unqualified needle) {
+        for (var sourceId : haystack) {
+            if (needle.equals(sourceId.name())) {
+                return sourceId;
+            }
+        }
+        return null;
     }
 }
\ No newline at end of file
index b931ea40ad929725c660be3ac8ca0043a7ca6e37..3e34069c7da01b88dfd0dba31d6a1edcb933248a 100644 (file)
@@ -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<SourceIdentifier, YangModelDependencyInfo>();
-        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<SourceIdentifier, YangModelDependencyInfo>();
         // 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<SourceIdentifier, YangModelDependencyInfo>();
-        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<SourceIdentifier, YangModelDependencyInfo> 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<SourceIdentifier, SourceInfo>();
+        for (var resourceName : resourceNames) {
+            final var info = YangIRSourceInfoExtractor.forYangText(
+                YangTextSource.forResource(DependencyResolverTest.class, resourceName));
+            map.put(info.sourceId(), info);
+        }
+        return new RevisionDependencyResolver(map);
     }
 }
index d9292c8080405222ddf26fb065ad4248a2f91654..e988eb560f415a7b1596865997ad930696042621 100644 (file)
@@ -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<YangTextS
 
     public static @NonNull YangIRSchemaSource transformText(final YangTextSource text)
             throws YangSyntaxErrorException, IOException {
-        final IRStatement rootStatement = IRSupport.createStatement(YangStatementStreamSource.parseYangSource(text));
-        final String name = YangModelDependencyInfo.safeStringArgument(text.sourceId(), rootStatement, "name");
-        final String latestRevision = YangModelDependencyInfo.getLatestRevision(rootStatement, text.sourceId());
-        final SourceIdentifier sourceId = new SourceIdentifier(name, latestRevision);
-
-        return new YangIRSchemaSource(sourceId, rootStatement, text.symbolicName());
+        final var rootStatement = IRSupport.createStatement(YangStatementStreamSource.parseYangSource(text));
+        final var info = YangIRSourceInfoExtractor.forIR(rootStatement, text.sourceId());
+        return new YangIRSchemaSource(info.sourceId(), rootStatement, text.symbolicName());
     }
 }
diff --git a/parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangIRSourceInfoExtractor.java b/parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangIRSourceInfoExtractor.java
new file mode 100644 (file)
index 0000000..c038ed7
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. 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.parser.rfc7950.repo;
+
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+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.ir.IRKeyword;
+import org.opendaylight.yangtools.yang.ir.IRStatement;
+import org.opendaylight.yangtools.yang.ir.YangIRSchemaSource;
+import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementSourceReference;
+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.spi.source.SourceInfo;
+import org.opendaylight.yangtools.yang.model.spi.source.YangTextSource;
+import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
+import org.opendaylight.yangtools.yang.parser.spi.source.ExplicitStatement;
+
+/**
+ * Utility class for extract {@link SourceInfo} from a {@link YangIRSchemaSource}.
+ */
+public final class YangIRSourceInfoExtractor {
+    private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
+    private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
+    private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
+    private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
+    private static final String NAMESPACE = YangStmtMapping.NAMESPACE.getStatementName().getLocalName();
+    private static final String PREFIX = YangStmtMapping.PREFIX.getStatementName().getLocalName();
+    private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
+    private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
+    private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
+    private static final String YANG_VERSION = YangStmtMapping.YANG_VERSION.getStatementName().getLocalName();
+
+    private YangIRSourceInfoExtractor() {
+        // Hidden on purpose
+    }
+
+    /**
+     * Extracts {@link SourceInfo} from an intermediate representation root statement of a YANG model.
+     *
+     * @param source Schema source
+     * @return {@link SourceInfo}
+     * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
+     */
+    public static @NonNull SourceInfo forIR(final YangIRSchemaSource source) {
+        return forIR(source.getRootStatement(), source.sourceId());
+    }
+
+    /**
+     * Extracts {@link SourceInfo} from an intermediate representation root statement of a YANG model.
+     *
+     * @param sourceId Source identifier, perhaps guessed from input name
+     * @param rootStatement root statement
+     * @return {@link SourceInfo}
+     * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
+     */
+    public static @NonNull SourceInfo forIR(final IRStatement rootStatement, final SourceIdentifier sourceId) {
+        final var keyword = rootStatement.keyword();
+        if (!(keyword instanceof IRKeyword.Unqualified)) {
+            throw new IllegalArgumentException("Invalid root statement " + keyword);
+        }
+
+        final String arg = keyword.identifier();
+        if (MODULE.equals(arg)) {
+            return moduleForIR(rootStatement, sourceId);
+        }
+        if (SUBMODULE.equals(arg)) {
+            return submmoduleForIR(rootStatement, sourceId);
+        }
+        throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
+    }
+
+    /**
+     * Extracts {@link SourceInfo} from a {@link YangTextSource}. This parsing does not validate full YANG module, only
+     * parses header up to the revisions and imports.
+     *
+     * @param yangText {@link YangTextSource}
+     * @return {@link SourceInfo}
+     * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
+     * @throws IOException When the resource cannot be read
+     */
+    public static SourceInfo forYangText(final YangTextSource yangText)
+            throws IOException, YangSyntaxErrorException {
+        final var source = YangStatementStreamSource.create(yangText);
+        return forIR(source.rootStatement(), source.getIdentifier());
+    }
+
+    private static SourceInfo.@NonNull Module moduleForIR(final IRStatement root, final SourceIdentifier sourceId) {
+        final var builder = SourceInfo.Module.builder();
+        fill(builder, root, sourceId);
+        return builder
+            .setNamespace(root.statements().stream()
+                .filter(stmt -> isStatement(stmt, NAMESPACE))
+                .findFirst()
+                .map(stmt -> safeStringArgument(sourceId, stmt, "namespace argument"))
+                .map(XMLNamespace::of)
+                .orElseThrow(() -> new IllegalArgumentException("No namespace statement in " + refOf(sourceId, root))))
+            .setPrefix(extractPrefix(root, sourceId))
+            .build();
+    }
+
+    private static SourceInfo.@NonNull Submodule submmoduleForIR(final IRStatement root,
+            final SourceIdentifier sourceId) {
+        final var builder = SourceInfo.Submodule.builder();
+        fill(builder, root, sourceId);
+        return builder
+            .setBelongsTo(root.statements().stream()
+                .filter(stmt -> isStatement(stmt, BELONGS_TO))
+                .findFirst()
+                .map(stmt -> new BelongsTo(Unqualified.of(safeStringArgument(sourceId, stmt, "belongs-to module name")),
+                    extractPrefix(stmt, sourceId)))
+                .orElseThrow(() -> new IllegalArgumentException("No belongs-to statement in " + refOf(sourceId, root))))
+            .build();
+    }
+
+    private static void fill(final SourceInfo.Builder<?, ?> builder, final IRStatement root,
+            final SourceIdentifier sourceId) {
+        builder.setName(Unqualified.of(safeStringArgument(sourceId, root, "module/submodule argument")));
+
+        root.statements().stream()
+            .filter(stmt -> isStatement(stmt, YANG_VERSION))
+            .findFirst()
+            .map(stmt -> safeStringArgument(sourceId, stmt, "yang-version argument"))
+            .map(YangVersion::forString)
+            .ifPresent(builder::setYangVersion);
+
+        root.statements().stream()
+            .filter(stmt -> isStatement(stmt, REVISION))
+            .map(stmt -> Revision.of(safeStringArgument(sourceId, stmt, "revision argument")))
+            .forEach(builder::addRevision);
+
+        root.statements().stream()
+            .filter(stmt -> isStatement(stmt, IMPORT))
+            .map(stmt -> new Import(Unqualified.of(safeStringArgument(sourceId, stmt, "import argument")),
+                extractPrefix(stmt, sourceId), extractRevisionDate(stmt, sourceId)))
+            .forEach(builder::addImport);
+
+        root.statements().stream()
+            .filter(stmt -> isStatement(stmt, INCLUDE))
+            .map(stmt -> new Include(Unqualified.of(safeStringArgument(sourceId, stmt, "include argument")),
+                extractRevisionDate(stmt, sourceId)))
+            .forEach(builder::addInclude);
+    }
+
+    private static @NonNull Unqualified extractPrefix(final IRStatement root, final SourceIdentifier sourceId) {
+        return root.statements().stream()
+            .filter(stmt -> isStatement(stmt, PREFIX))
+            .findFirst()
+            .map(stmt -> Unqualified.of(safeStringArgument(sourceId, stmt, "prefix argument")))
+            .orElseThrow(() -> new IllegalArgumentException("No prefix statement in " + refOf(sourceId, root)));
+    }
+
+    private static @Nullable Revision extractRevisionDate(final IRStatement root, final SourceIdentifier sourceId) {
+        return root.statements().stream()
+            .filter(stmt -> isStatement(stmt, REVISION_DATE))
+            .findFirst()
+            .map(stmt -> Revision.of(safeStringArgument(sourceId, stmt, "revision date argument")))
+            .orElse(null);
+    }
+
+    private static boolean isStatement(final IRStatement stmt, final String name) {
+        return stmt.keyword() instanceof IRKeyword.Unqualified keyword && name.equals(keyword.identifier());
+    }
+
+    private static @NonNull String safeStringArgument(final SourceIdentifier source, final IRStatement stmt,
+            final String desc) {
+        final var ref = refOf(source, stmt);
+        final var arg = stmt.argument();
+        if (arg == null) {
+            throw new IllegalArgumentException("Missing " + desc + " at " + ref);
+        }
+
+        // TODO: we probably need to understand yang version first....
+        return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
+    }
+
+    private static StatementSourceReference refOf(final SourceIdentifier source, final IRStatement stmt) {
+        return ExplicitStatement.atPosition(source.name().getLocalName(), stmt.startLine(), stmt.startColumn() + 1);
+    }
+}
diff --git a/parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfo.java b/parser/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfo.java
deleted file mode 100644 (file)
index c1d35a7..0000000
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. 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.parser.rfc7950.repo;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableSet;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Objects;
-import java.util.Optional;
-import org.eclipse.jdt.annotation.NonNull;
-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.ir.IRKeyword;
-import org.opendaylight.yangtools.yang.ir.IRStatement;
-import org.opendaylight.yangtools.yang.ir.YangIRSchemaSource;
-import org.opendaylight.yangtools.yang.model.api.ModuleImport;
-import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
-import org.opendaylight.yangtools.yang.model.api.meta.StatementSourceReference;
-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.ModuleSourceInfo;
-import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo;
-import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo.Import;
-import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo.Include;
-import org.opendaylight.yangtools.yang.model.spi.source.SubmoduleSourceInfo;
-import org.opendaylight.yangtools.yang.model.spi.source.YangTextSource;
-import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
-import org.opendaylight.yangtools.yang.parser.spi.source.ExplicitStatement;
-
-/**
- * Helper transfer object which holds basic and dependency information for YANG
- * model.
- *
- * <p>
- * There are two concrete implementations of this interface:
- * <ul>
- * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
- * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
- * </ul>
- *
- * @see ModuleDependencyInfo
- * @see SubmoduleDependencyInfo
- */
-public abstract sealed class YangModelDependencyInfo {
-    /**
-     * Dependency information for a YANG module.
-     */
-    public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
-        private ModuleDependencyInfo(final String name, final Revision revision,
-                final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
-            super(name, revision, imports, includes);
-        }
-    }
-
-    /**
-     * Dependency information for a YANG submodule, also provides name for parent module.
-     */
-    public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
-        private final @NonNull Unqualified belongsTo;
-
-        private SubmoduleDependencyInfo(final String name, final Revision revision, final Unqualified belongsTo,
-                final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
-            super(name, revision, imports, includes);
-            this.belongsTo = requireNonNull(belongsTo);
-        }
-
-        /**
-         * Returns name of parent module.
-         *
-         * @return The module this info belongs to
-         */
-        public @NonNull Unqualified getParentModule() {
-            return belongsTo;
-        }
-    }
-
-    private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
-    private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
-    private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
-    private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
-    private static final String NAMESPACE = YangStmtMapping.NAMESPACE.getStatementName().getLocalName();
-    private static final String PREFIX = YangStmtMapping.PREFIX.getStatementName().getLocalName();
-    private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
-    private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
-    private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
-    private static final String YANG_VERSION = YangStmtMapping.YANG_VERSION.getStatementName().getLocalName();
-
-    private final @NonNull String name;
-    private final @Nullable Revision revision;
-    private final @NonNull ImmutableSet<ModuleImport> submoduleIncludes;
-    private final @NonNull ImmutableSet<ModuleImport> moduleImports;
-    private final @NonNull ImmutableSet<ModuleImport> dependencies;
-
-    YangModelDependencyInfo(final String name, final Revision revision, final ImmutableSet<ModuleImport> imports,
-            final ImmutableSet<ModuleImport> includes) {
-        this.name = requireNonNull(name);
-        this.revision = revision;
-        moduleImports = requireNonNull(imports);
-        submoduleIncludes = requireNonNull(includes);
-        dependencies = ImmutableSet.<ModuleImport>builder().addAll(moduleImports).addAll(submoduleIncludes).build();
-    }
-
-    /**
-     * Returns model name.
-     *
-     * @return model name
-     */
-    public final String getName() {
-        return name;
-    }
-
-    /**
-     * Returns formatted revision string.
-     *
-     * @return formatted revision string, or {@code null}
-     */
-    public final @Nullable String getFormattedRevision() {
-        final var local = revision;
-        return local != null ? local.toString() : null;
-    }
-
-    /**
-     * Returns revision.
-     *
-     * @return revision, potentially null
-     */
-    public final Optional<Revision> getRevision() {
-        return Optional.ofNullable(revision);
-    }
-
-    /**
-     * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
-     * and <code>include</code> statements for submodules.
-     *
-     * @return Immutable collection of imports.
-     */
-    public final ImmutableSet<ModuleImport> getDependencies() {
-        return dependencies;
-    }
-
-    @Override
-    public final int hashCode() {
-        return Objects.hash(name, revision);
-    }
-
-    @Override
-    public final boolean equals(final Object obj) {
-        return this == obj || obj instanceof YangModelDependencyInfo other
-            && Objects.equals(name, other.name) && Objects.equals(revision, other.revision);
-    }
-
-    @Override
-    public final String toString() {
-        return MoreObjects.toStringHelper(this).omitNullValues()
-            .add("name", getName())
-            .add("revision", getRevision())
-            .add("dependencies", getDependencies())
-            .toString();
-    }
-
-    /**
-     * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
-     *
-     * @param source Schema source
-     * @return {@link YangModelDependencyInfo}
-     * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
-     */
-    public static @NonNull YangModelDependencyInfo forIR(final YangIRSchemaSource source) {
-        return forIR(source.getRootStatement(), source.sourceId());
-    }
-
-    /**
-     * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
-     *
-     * @param sourceId Source identifier
-     * @param rootStatement root statement
-     * @return {@link YangModelDependencyInfo}
-     * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
-     */
-    static @NonNull YangModelDependencyInfo forIR(final IRStatement rootStatement,
-            final SourceIdentifier sourceId) {
-        final var keyword = rootStatement.keyword();
-        if (!(keyword instanceof IRKeyword.Unqualified)) {
-            throw new IllegalArgumentException("Invalid root statement " + keyword);
-        }
-
-        final String arg = keyword.identifier();
-        if (MODULE.equals(arg)) {
-            return forSourceInfo(moduleForIR(rootStatement, sourceId));
-        }
-        if (SUBMODULE.equals(arg)) {
-            return forSourceInfo(submmoduleForIR(rootStatement, sourceId));
-        }
-        throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
-    }
-
-    public static @NonNull YangModelDependencyInfo forSourceInfo(final SourceInfo info) {
-        if (info instanceof ModuleSourceInfo module) {
-            return forSourceInfo(module);
-        } else if (info instanceof SubmoduleSourceInfo submodule) {
-            return forSourceInfo(submodule);
-        } else {
-            throw new IllegalArgumentException("Unhandled source info " + requireNonNull(info));
-        }
-    }
-
-    public static @NonNull ModuleDependencyInfo forSourceInfo(final @NonNull ModuleSourceInfo info) {
-        return new ModuleDependencyInfo(info.name().getLocalName(), latestRevision(info.revisions()),
-            info.imports().stream().map(ModuleImportImpl::new).collect(ImmutableSet.toImmutableSet()),
-            info.includes().stream().map(ModuleImportImpl::new).collect(ImmutableSet.toImmutableSet()));
-    }
-
-    public static @NonNull SubmoduleDependencyInfo forSourceInfo(final @NonNull SubmoduleSourceInfo info) {
-        return new SubmoduleDependencyInfo(info.name().getLocalName(), latestRevision(info.revisions()),
-            info.belongsTo(), info.imports().stream().map(ModuleImportImpl::new).collect(ImmutableSet.toImmutableSet()),
-            info.includes().stream().map(ModuleImportImpl::new).collect(ImmutableSet.toImmutableSet()));
-    }
-
-    /**
-     * Extracts {@link YangModelDependencyInfo} from a {@link YangTextSource}. This parsing does not validate full YANG
-     * module, only parses header up to the revisions and imports.
-     *
-     * @param yangText {@link YangTextSource}
-     * @return {@link YangModelDependencyInfo}
-     * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
-     * @throws IOException When the resource cannot be read
-     */
-    public static YangModelDependencyInfo forYangText(final YangTextSource yangText)
-            throws IOException, YangSyntaxErrorException {
-        final var source = YangStatementStreamSource.create(yangText);
-        return forIR(source.rootStatement(), source.getIdentifier());
-    }
-
-    private static @NonNull ModuleSourceInfo moduleForIR(final IRStatement root, final SourceIdentifier sourceId) {
-        return new ModuleSourceInfo(Unqualified.of(safeStringArgument(sourceId, root, "module name")),
-            extractYangVersion(root, sourceId), extractNamespace(root, sourceId), extractPrefix(root, sourceId),
-            extractRevisions(root, sourceId), extractImports(root, sourceId), extractIncludes(root, sourceId));
-    }
-
-    private static @NonNull SubmoduleSourceInfo submmoduleForIR(final IRStatement root,
-            final SourceIdentifier sourceId) {
-        return new SubmoduleSourceInfo(Unqualified.of(safeStringArgument(sourceId, root, "submodule name")),
-            extractYangVersion(root, sourceId), extractBelongsTo(root, sourceId),
-            extractRevisions(root, sourceId), extractImports(root, sourceId), extractIncludes(root, sourceId));
-    }
-
-    private static @Nullable Revision latestRevision(final Collection<Revision> revision) {
-        return revision.stream().sorted(Comparator.reverseOrder()).findFirst().orElse(null);
-    }
-
-    private static YangVersion extractYangVersion(final IRStatement root, final SourceIdentifier sourceId) {
-        return root.statements().stream()
-            .filter(stmt -> isBuiltin(stmt, YANG_VERSION))
-            .findFirst()
-            .map(stmt -> safeStringArgument(sourceId, stmt, "yang-version argument"))
-            .map(YangVersion::forString)
-            .orElse(YangVersion.VERSION_1);
-    }
-
-    private static @NonNull XMLNamespace extractNamespace(final IRStatement root, final SourceIdentifier sourceId) {
-        return root.statements().stream()
-            .filter(stmt -> isBuiltin(stmt, NAMESPACE))
-            .findFirst()
-            .map(stmt -> safeStringArgument(sourceId, stmt, "namespace argument"))
-            .map(XMLNamespace::of)
-            .orElseThrow(() -> new IllegalArgumentException("No namespace statement in " + refOf(sourceId, root)));
-    }
-
-    private static @NonNull String extractPrefix(final IRStatement root, final SourceIdentifier sourceId) {
-        return root.statements().stream()
-            .filter(stmt -> isBuiltin(stmt, PREFIX))
-            .findFirst()
-            .map(stmt -> safeStringArgument(sourceId, stmt, "prefix argument"))
-            .orElseThrow(() -> new IllegalArgumentException("No prefix statement in " + refOf(sourceId, root)));
-    }
-
-    private static @NonNull Unqualified extractBelongsTo(final IRStatement root, final SourceIdentifier sourceId) {
-        return root.statements().stream()
-            .filter(stmt -> isBuiltin(stmt, BELONGS_TO))
-            .findFirst()
-            .map(stmt -> Unqualified.of(safeStringArgument(sourceId, stmt, "belongs-to module name")))
-            .orElseThrow(() -> new IllegalArgumentException("No belongs-to statement in " + refOf(sourceId, root)));
-    }
-
-    private static @NonNull ImmutableSet<Revision> extractRevisions(final IRStatement root,
-            final SourceIdentifier sourceId) {
-        return root.statements().stream()
-            .filter(stmt -> isBuiltin(stmt, REVISION))
-            .map(stmt -> Revision.of(safeStringArgument(sourceId, stmt, "revision argument")))
-            .collect(ImmutableSet.toImmutableSet());
-    }
-
-    private static @Nullable Revision extractRevisionDate(final IRStatement root, final SourceIdentifier sourceId) {
-        return root.statements().stream()
-            .filter(stmt -> isBuiltin(stmt, REVISION_DATE))
-            .findFirst()
-            .map(stmt -> Revision.of(safeStringArgument(sourceId, stmt, "revision date argument")))
-            .orElse(null);
-    }
-
-    private static @NonNull ImmutableSet<Import> extractImports(final IRStatement root,
-            final SourceIdentifier sourceId) {
-        return root.statements().stream()
-            .filter(stmt -> isBuiltin(stmt, IMPORT))
-            .map(stmt -> new Import(Unqualified.of(safeStringArgument(sourceId, stmt, "imported module name")),
-                extractPrefix(stmt, sourceId), extractRevisionDate(stmt, sourceId)))
-            .collect(ImmutableSet.toImmutableSet());
-    }
-
-    private static @NonNull ImmutableSet<Include> extractIncludes(final IRStatement root,
-            final SourceIdentifier sourceId) {
-        return root.statements().stream()
-            .filter(stmt -> isBuiltin(stmt, INCLUDE))
-            .map(stmt -> new Include(Unqualified.of(safeStringArgument(sourceId, stmt, "included submodule name")),
-                extractRevisionDate(stmt, sourceId)))
-            .collect(ImmutableSet.toImmutableSet());
-    }
-
-    private static boolean isBuiltin(final IRStatement stmt, final String localName) {
-        return stmt.keyword() instanceof IRKeyword.Unqualified keyword && localName.equals(keyword.identifier());
-    }
-
-    public static String getLatestRevision(final IRStatement module, final SourceIdentifier source) {
-        String latestRevision = null;
-        for (final IRStatement substatement : module.statements()) {
-            if (isBuiltin(substatement, REVISION)) {
-                final String currentRevision = safeStringArgument(source, substatement, "revision date");
-                if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
-                    latestRevision = currentRevision;
-                }
-            }
-        }
-        return latestRevision;
-    }
-
-    static @NonNull String safeStringArgument(final SourceIdentifier source, final IRStatement stmt,
-            final String desc) {
-        final var ref = refOf(source, stmt);
-        final var arg = stmt.argument();
-        if (arg == null) {
-            throw new IllegalArgumentException("Missing " + desc + " at " + ref);
-        }
-
-        // TODO: we probably need to understand yang version first....
-        return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
-    }
-
-    private static StatementSourceReference refOf(final SourceIdentifier source, final IRStatement stmt) {
-        return ExplicitStatement.atPosition(source.name().getLocalName(), stmt.startLine(), stmt.startColumn() + 1);
-    }
-
-    /**
-     * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
-     */
-    // FIXME: this is a rather nasty misuse of APIs :(
-    private static final class ModuleImportImpl implements ModuleImport {
-        private final @NonNull Unqualified moduleName;
-        private final Revision revision;
-
-        ModuleImportImpl(final Import importSpec) {
-            moduleName = importSpec.name();
-            revision = importSpec.revision();
-        }
-
-        ModuleImportImpl(final Include includeSpec) {
-            moduleName = includeSpec.name();
-            revision = includeSpec.revision();
-        }
-
-        @Override
-        public Unqualified getModuleName() {
-            return moduleName;
-        }
-
-        @Override
-        public Optional<Revision> getRevision() {
-            return Optional.ofNullable(revision);
-        }
-
-        @Override
-        public String getPrefix() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public Optional<String> getDescription() {
-            return Optional.empty();
-        }
-
-        @Override
-        public Optional<String> getReference() {
-            return Optional.empty();
-        }
-
-        @Override
-        public ImportEffectiveStatement asEffectiveStatement() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int hashCode() {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + Objects.hashCode(moduleName);
-            result = prime * result + Objects.hashCode(revision);
-            return result;
-        }
-
-        @Override
-        public boolean equals(final Object obj) {
-            return this == obj || obj instanceof ModuleImportImpl other
-                && moduleName.equals(other.moduleName) && Objects.equals(revision, other.revision);
-        }
-
-        @Override
-        public String toString() {
-            return "ModuleImportImpl [name=" + moduleName + ", revision=" + revision + "]";
-        }
-    }
-}
diff --git a/parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangIRSourceInfoExtractorTest.java b/parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangIRSourceInfoExtractorTest.java
new file mode 100644 (file)
index 0000000..f6e3452
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. 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.parser.rfc7950.repo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
+import org.opendaylight.yangtools.yang.common.YangVersion;
+import org.opendaylight.yangtools.yang.model.api.source.SourceDependency.Import;
+import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo;
+import org.opendaylight.yangtools.yang.stmt.StmtTestUtils;
+
+class YangIRSourceInfoExtractorTest {
+    @Test
+    void testModuleWithNoImports() {
+        final var info = forResource("/ietf/ietf-inet-types@2010-09-24.yang");
+        assertEquals(new SourceIdentifier("ietf-inet-types", "2010-09-24"), info.sourceId());
+        assertEquals(YangVersion.VERSION_1, info.yangVersion());
+        assertEquals(Set.of(Revision.of("2010-09-24")), info.revisions());
+        assertEquals(Set.of(), info.imports());
+        assertEquals(Set.of(), info.includes());
+    }
+
+    @Test
+    void testModuleWithImports() {
+        final var info = forResource("/parse-methods/dependencies/m2@2013-09-30.yang");
+        assertEquals(new SourceIdentifier("m2", "2013-09-30"), info.sourceId());
+        assertEquals(YangVersion.VERSION_1, info.yangVersion());
+        assertEquals(Set.of(Revision.of("2013-09-30")), info.revisions());
+        assertEquals(Set.of(
+            new Import(Unqualified.of("m4"), Unqualified.of("m4")),
+            new Import(Unqualified.of("m5"), Unqualified.of("m5"))), info.imports());
+        assertEquals(Set.of(), info.includes());
+    }
+
+    @Test
+    void testModuleWithoutRevision() {
+        final var info = forResource("/no-revision/module-without-revision.yang");
+        assertEquals(new SourceIdentifier("module-without-revision"), info.sourceId());
+        assertEquals(YangVersion.VERSION_1, info.yangVersion());
+        assertEquals(Set.of(), info.revisions());
+        assertEquals(Set.of(
+            new Import(Unqualified.of("ietf-inet-types"), Unqualified.of("inet"), Revision.of("2010-09-24"))),
+            info.imports());
+        assertEquals(Set.of(), info.includes());
+    }
+
+    @Test
+    void testYangtools827() {
+        // Latest revision needs to be picked up irrespective of ordering
+        final var info = forResource("/bugs/YT827/foo.yang");
+        assertEquals(new SourceIdentifier("foo", "2014-12-24"), info.sourceId());
+        assertEquals(YangVersion.VERSION_1, info.yangVersion());
+        assertEquals(Set.of(Revision.of("2010-10-10"), Revision.of("2014-12-24")), info.revisions());
+        assertEquals(Set.of(), info.imports());
+        assertEquals(Set.of(), info.includes());
+    }
+
+    @Test
+    void testMalformedImport() {
+        final var ex = assertIAE("/depinfo-malformed/malformed-import.yang");
+        assertEquals("Missing import argument at malformed-import:4:5", ex.getMessage());
+    }
+
+    @Test
+    void testMalformedImportRev() {
+        final var ex = assertIAE("/depinfo-malformed/malformed-import-rev.yang");
+        assertEquals("Missing revision date argument at malformed-import-rev:4:18", ex.getMessage());
+    }
+
+    @Test
+    void testMalformedModule() {
+        final var ex = assertIAE("/depinfo-malformed/malformed-module.yang");
+        assertEquals("Missing module/submodule argument at malformed-module:1:1", ex.getMessage());
+    }
+
+    @Test
+    void testMalformedRev() {
+        final var ex = assertIAE("/depinfo-malformed/malformed-rev.yang");
+        assertEquals("Missing revision argument at malformed-rev:5:5", ex.getMessage());
+    }
+
+    private static IllegalArgumentException assertIAE(final String resourceName) {
+        return assertThrows(IllegalArgumentException.class, () -> forResource(resourceName));
+    }
+
+    // Utility
+    private static SourceInfo forResource(final String resourceName) {
+        final var source = StmtTestUtils.sourceForResource(resourceName);
+        final var info = YangIRSourceInfoExtractor.forIR(source.rootStatement(), source.getIdentifier());
+        assertNotNull(info);
+        return info;
+    }
+}
diff --git a/parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfoTest.java b/parser/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfoTest.java
deleted file mode 100644 (file)
index 098a926..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. 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.parser.rfc7950.repo;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import org.junit.jupiter.api.Test;
-import org.opendaylight.yangtools.yang.stmt.StmtTestUtils;
-
-class YangModelDependencyInfoTest {
-    // Utility
-    private static YangModelDependencyInfo forResource(final String resourceName) {
-        final var source = StmtTestUtils.sourceForResource(resourceName);
-        return YangModelDependencyInfo.forIR(source.rootStatement(), source.getIdentifier());
-    }
-
-    @Test
-    void testModuleWithNoImports() {
-        final var info = forResource("/ietf/ietf-inet-types@2010-09-24.yang");
-        assertNotNull(info);
-        assertEquals("ietf-inet-types", info.getName());
-        assertEquals("2010-09-24", info.getFormattedRevision());
-        assertNotNull(info.getDependencies());
-
-        assertEquals(info, info);
-    }
-
-    @Test
-    void testModuleWithImports() {
-        final var info = forResource("/parse-methods/dependencies/m2@2013-09-30.yang");
-        assertNotNull(info);
-        assertEquals("m2", info.getName());
-        assertEquals("2013-09-30", info.getFormattedRevision());
-        assertNotNull(info.getDependencies());
-        assertEquals(2, info.getDependencies().size());
-    }
-
-    @Test
-    void testModuleWithoutRevision() {
-        final var info = forResource("/no-revision/module-without-revision.yang");
-        assertNotNull(info);
-        assertEquals("module-without-revision", info.getName());
-        assertNull(info.getFormattedRevision());
-    }
-
-    @Test
-    void testEquals() {
-        final var info1 = forResource("/ietf/ietf-inet-types@2010-09-24.yang");
-        final var info2 = forResource("/no-revision/module-without-revision.yang");
-
-        assertEquals(info1, info1);
-        assertNotEquals(null, info1);
-        assertNotEquals(info1, info2);
-    }
-
-    @Test
-    void testYangtools827() {
-        // Latest revision needs to be picked up irrespective of ordering
-        final var info = forResource("/bugs/YT827/foo.yang");
-        assertEquals("2014-12-24", info.getFormattedRevision());
-    }
-
-    @Test
-    void testHashcode() {
-        final var info = forResource("/no-revision/module-without-revision.yang");
-        assertNotEquals(31, info.hashCode(), "hashcode");
-    }
-
-    @Test
-    void testMalformedImport() {
-        assertThrows(IllegalArgumentException.class, () -> forResource("/depinfo-malformed/malformed-import.yang"));
-    }
-
-    @Test
-    void testMalformedImportRev() {
-        assertThrows(IllegalArgumentException.class, () -> forResource("/depinfo-malformed/malformed-import-rev.yang"));
-    }
-
-    @Test
-    void testMalformedModule() {
-        assertThrows(IllegalArgumentException.class, () -> forResource("/depinfo-malformed/malformed-module.yang"));
-    }
-
-    @Test
-    void testMalformedRev() {
-        assertThrows(IllegalArgumentException.class, () -> forResource("/depinfo-malformed/malformed-rev.yang"));
-    }
-}
index 4314e859e781f1d6627a73cfe6aef6ad7b8d3745..d5f44b535e5d144759c115a6a6817bb5c507c36d 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-model-api</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-model-spi</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
index 9cab0de69d2c4a0636f2151d07a43b5eba1691f7..5a5d176579f06417671e68ee97ac40a398d95f9d 100644 (file)
@@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Multimap;
 import java.util.Collection;
 import org.eclipse.jdt.annotation.NonNull;
-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;
 
 /**
@@ -26,7 +26,7 @@ public class SchemaResolutionException extends SchemaSourceException {
     @java.io.Serial
     private static final long serialVersionUID = 2L;
 
-    private final @NonNull ImmutableMultimap<SourceIdentifier, ModuleImport> unsatisfiedImports;
+    private final @NonNull ImmutableMultimap<SourceIdentifier, SourceDependency> unsatisfiedImports;
     private final @NonNull ImmutableList<SourceIdentifier> resolvedSources;
 
     public SchemaResolutionException(final @NonNull String message, final SourceIdentifier failedSource,
@@ -36,21 +36,20 @@ public class SchemaResolutionException extends SchemaSourceException {
 
     public SchemaResolutionException(final @NonNull String message, final SourceIdentifier failedSource,
             final @NonNull Collection<SourceIdentifier> resolvedSources,
-            final @NonNull Multimap<SourceIdentifier, ModuleImport> unsatisfiedImports) {
+            final @NonNull Multimap<SourceIdentifier, SourceDependency> unsatisfiedImports) {
         this(message, failedSource, null, resolvedSources, unsatisfiedImports);
     }
 
     public SchemaResolutionException(final @NonNull String message, final SourceIdentifier failedSource,
             final Throwable cause, final @NonNull Collection<SourceIdentifier> resolvedSources,
-            final @NonNull Multimap<SourceIdentifier, ModuleImport> unsatisfiedImports) {
+            final @NonNull Multimap<SourceIdentifier, SourceDependency> unsatisfiedImports) {
         super(failedSource, formatMessage(message, failedSource, resolvedSources, unsatisfiedImports), cause);
         this.unsatisfiedImports = ImmutableMultimap.copyOf(unsatisfiedImports);
         this.resolvedSources = ImmutableList.copyOf(resolvedSources);
     }
 
     private static String formatMessage(final String message, final SourceIdentifier failedSource,
-            final Collection<SourceIdentifier> resolvedSources,
-            final Multimap<SourceIdentifier, ModuleImport> unsatisfiedImports) {
+            final Collection<?> resolvedSources, final Multimap<?, ?> unsatisfiedImports) {
         return String.format("%s, failed source: %s, resolved sources: %s, unsatisfied imports: %s", message,
                 failedSource, resolvedSources, unsatisfiedImports);
     }
@@ -60,7 +59,7 @@ public class SchemaResolutionException extends SchemaSourceException {
      *
      * @return Source/reason map.
      */
-    public final @NonNull Multimap<SourceIdentifier, ModuleImport> getUnsatisfiedImports() {
+    public final @NonNull Multimap<SourceIdentifier, SourceDependency> getUnsatisfiedImports() {
         return unsatisfiedImports;
     }