--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
extends DocumentedDeclaredStatement<Unqualified>, NotificationStatementAwareDeclaredStatement<Unqualified>,
DataDefinitionAwareDeclaredStatement.WithReusableDefinitions<Unqualified>
permits ModuleStatement, SubmoduleStatement {
+ @Override
+ Unqualified argument();
+
default Optional<OrganizationStatement> getOrganization() {
return findFirstDeclaredSubstatement(OrganizationStatement.class);
}
+++ /dev/null
-/*
- * 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);
- }
-}
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>
* </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;
+ }
+ }
}
+++ /dev/null
-/*
- * 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);
- }
-}
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;
@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();
*/
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;
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();
}
} 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();
}
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
*/
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);
}
}
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;
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());
}
}
--- /dev/null
+/*
+ * 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);
+ }
+}
+++ /dev/null
-/*
- * 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 + "]";
- }
- }
-}
--- /dev/null
+/*
+ * 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;
+ }
+}
+++ /dev/null
-/*
- * 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"));
- }
-}
<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>
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;
/**
@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,
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);
}
*
* @return Source/reason map.
*/
- public final @NonNull Multimap<SourceIdentifier, ModuleImport> getUnsatisfiedImports() {
+ public final @NonNull Multimap<SourceIdentifier, SourceDependency> getUnsatisfiedImports() {
return unsatisfiedImports;
}