2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.parser.rfc7950.repo;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.base.MoreObjects;
13 import com.google.common.collect.ImmutableSet;
14 import java.io.IOException;
15 import java.util.Collection;
16 import java.util.Comparator;
17 import java.util.Objects;
18 import java.util.Optional;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.yangtools.yang.common.Revision;
22 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
23 import org.opendaylight.yangtools.yang.common.XMLNamespace;
24 import org.opendaylight.yangtools.yang.common.YangVersion;
25 import org.opendaylight.yangtools.yang.ir.IRKeyword;
26 import org.opendaylight.yangtools.yang.ir.IRStatement;
27 import org.opendaylight.yangtools.yang.ir.YangIRSchemaSource;
28 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
29 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
30 import org.opendaylight.yangtools.yang.model.api.meta.StatementSourceReference;
31 import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
32 import org.opendaylight.yangtools.yang.model.api.stmt.ImportEffectiveStatement;
33 import org.opendaylight.yangtools.yang.model.spi.source.ModuleSourceInfo;
34 import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo;
35 import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo.Import;
36 import org.opendaylight.yangtools.yang.model.spi.source.SourceInfo.Include;
37 import org.opendaylight.yangtools.yang.model.spi.source.SubmoduleSourceInfo;
38 import org.opendaylight.yangtools.yang.model.spi.source.YangTextSource;
39 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
40 import org.opendaylight.yangtools.yang.parser.spi.source.ExplicitStatement;
43 * Helper transfer object which holds basic and dependency information for YANG
47 * There are two concrete implementations of this interface:
49 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
50 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
53 * @see ModuleDependencyInfo
54 * @see SubmoduleDependencyInfo
56 public abstract sealed class YangModelDependencyInfo {
58 * Dependency information for a YANG module.
60 public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
61 private ModuleDependencyInfo(final String name, final Revision revision,
62 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
63 super(name, revision, imports, includes);
68 * Dependency information for a YANG submodule, also provides name for parent module.
70 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
71 private final @NonNull Unqualified belongsTo;
73 private SubmoduleDependencyInfo(final String name, final Revision revision, final Unqualified belongsTo,
74 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
75 super(name, revision, imports, includes);
76 this.belongsTo = requireNonNull(belongsTo);
80 * Returns name of parent module.
82 * @return The module this info belongs to
84 public @NonNull Unqualified getParentModule() {
89 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
90 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
91 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
92 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
93 private static final String NAMESPACE = YangStmtMapping.NAMESPACE.getStatementName().getLocalName();
94 private static final String PREFIX = YangStmtMapping.PREFIX.getStatementName().getLocalName();
95 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
96 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
97 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
98 private static final String YANG_VERSION = YangStmtMapping.YANG_VERSION.getStatementName().getLocalName();
100 private final @NonNull String name;
101 private final @Nullable Revision revision;
102 private final @NonNull ImmutableSet<ModuleImport> submoduleIncludes;
103 private final @NonNull ImmutableSet<ModuleImport> moduleImports;
104 private final @NonNull ImmutableSet<ModuleImport> dependencies;
106 YangModelDependencyInfo(final String name, final Revision revision, final ImmutableSet<ModuleImport> imports,
107 final ImmutableSet<ModuleImport> includes) {
108 this.name = requireNonNull(name);
109 this.revision = revision;
110 moduleImports = requireNonNull(imports);
111 submoduleIncludes = requireNonNull(includes);
112 dependencies = ImmutableSet.<ModuleImport>builder().addAll(moduleImports).addAll(submoduleIncludes).build();
116 * Returns model name.
120 public final String getName() {
125 * Returns formatted revision string.
127 * @return formatted revision string, or {@code null}
129 public final @Nullable String getFormattedRevision() {
130 final var local = revision;
131 return local != null ? local.toString() : null;
137 * @return revision, potentially null
139 public final Optional<Revision> getRevision() {
140 return Optional.ofNullable(revision);
144 * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
145 * and <code>include</code> statements for submodules.
147 * @return Immutable collection of imports.
149 public final ImmutableSet<ModuleImport> getDependencies() {
154 public final int hashCode() {
155 return Objects.hash(name, revision);
159 public final boolean equals(final Object obj) {
160 return this == obj || obj instanceof YangModelDependencyInfo other
161 && Objects.equals(name, other.name) && Objects.equals(revision, other.revision);
165 public final String toString() {
166 return MoreObjects.toStringHelper(this).omitNullValues()
167 .add("name", getName())
168 .add("revision", getRevision())
169 .add("dependencies", getDependencies())
174 * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
176 * @param source Schema source
177 * @return {@link YangModelDependencyInfo}
178 * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
180 public static @NonNull YangModelDependencyInfo forIR(final YangIRSchemaSource source) {
181 return forIR(source.getRootStatement(), source.sourceId());
185 * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
187 * @param sourceId Source identifier
188 * @param rootStatement root statement
189 * @return {@link YangModelDependencyInfo}
190 * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
192 static @NonNull YangModelDependencyInfo forIR(final IRStatement rootStatement,
193 final SourceIdentifier sourceId) {
194 final var keyword = rootStatement.keyword();
195 if (!(keyword instanceof IRKeyword.Unqualified)) {
196 throw new IllegalArgumentException("Invalid root statement " + keyword);
199 final String arg = keyword.identifier();
200 if (MODULE.equals(arg)) {
201 return forSourceInfo(moduleForIR(rootStatement, sourceId));
203 if (SUBMODULE.equals(arg)) {
204 return forSourceInfo(submmoduleForIR(rootStatement, sourceId));
206 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
209 public static @NonNull YangModelDependencyInfo forSourceInfo(final SourceInfo info) {
210 if (info instanceof ModuleSourceInfo module) {
211 return forSourceInfo(module);
212 } else if (info instanceof SubmoduleSourceInfo submodule) {
213 return forSourceInfo(submodule);
215 throw new IllegalArgumentException("Unhandled source info " + requireNonNull(info));
219 public static @NonNull ModuleDependencyInfo forSourceInfo(final @NonNull ModuleSourceInfo info) {
220 return new ModuleDependencyInfo(info.name().getLocalName(), latestRevision(info.revisions()),
221 info.imports().stream().map(ModuleImportImpl::new).collect(ImmutableSet.toImmutableSet()),
222 info.includes().stream().map(ModuleImportImpl::new).collect(ImmutableSet.toImmutableSet()));
225 public static @NonNull SubmoduleDependencyInfo forSourceInfo(final @NonNull SubmoduleSourceInfo info) {
226 return new SubmoduleDependencyInfo(info.name().getLocalName(), latestRevision(info.revisions()),
227 info.belongsTo(), info.imports().stream().map(ModuleImportImpl::new).collect(ImmutableSet.toImmutableSet()),
228 info.includes().stream().map(ModuleImportImpl::new).collect(ImmutableSet.toImmutableSet()));
232 * Extracts {@link YangModelDependencyInfo} from a {@link YangTextSource}. This parsing does not validate full YANG
233 * module, only parses header up to the revisions and imports.
235 * @param yangText {@link YangTextSource}
236 * @return {@link YangModelDependencyInfo}
237 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
238 * @throws IOException When the resource cannot be read
240 public static YangModelDependencyInfo forYangText(final YangTextSource yangText)
241 throws IOException, YangSyntaxErrorException {
242 final var source = YangStatementStreamSource.create(yangText);
243 return forIR(source.rootStatement(), source.getIdentifier());
246 private static @NonNull ModuleSourceInfo moduleForIR(final IRStatement root, final SourceIdentifier sourceId) {
247 return new ModuleSourceInfo(Unqualified.of(safeStringArgument(sourceId, root, "module name")),
248 extractYangVersion(root, sourceId), extractNamespace(root, sourceId), extractPrefix(root, sourceId),
249 extractRevisions(root, sourceId), extractImports(root, sourceId), extractIncludes(root, sourceId));
252 private static @NonNull SubmoduleSourceInfo submmoduleForIR(final IRStatement root,
253 final SourceIdentifier sourceId) {
254 return new SubmoduleSourceInfo(Unqualified.of(safeStringArgument(sourceId, root, "submodule name")),
255 extractYangVersion(root, sourceId), extractBelongsTo(root, sourceId),
256 extractRevisions(root, sourceId), extractImports(root, sourceId), extractIncludes(root, sourceId));
259 private static @Nullable Revision latestRevision(final Collection<Revision> revision) {
260 return revision.stream().sorted(Comparator.reverseOrder()).findFirst().orElse(null);
263 private static YangVersion extractYangVersion(final IRStatement root, final SourceIdentifier sourceId) {
264 return root.statements().stream()
265 .filter(stmt -> isBuiltin(stmt, YANG_VERSION))
267 .map(stmt -> safeStringArgument(sourceId, stmt, "yang-version argument"))
268 .map(YangVersion::forString)
269 .orElse(YangVersion.VERSION_1);
272 private static @NonNull XMLNamespace extractNamespace(final IRStatement root, final SourceIdentifier sourceId) {
273 return root.statements().stream()
274 .filter(stmt -> isBuiltin(stmt, NAMESPACE))
276 .map(stmt -> safeStringArgument(sourceId, stmt, "namespace argument"))
277 .map(XMLNamespace::of)
278 .orElseThrow(() -> new IllegalArgumentException("No namespace statement in " + refOf(sourceId, root)));
281 private static @NonNull String extractPrefix(final IRStatement root, final SourceIdentifier sourceId) {
282 return root.statements().stream()
283 .filter(stmt -> isBuiltin(stmt, PREFIX))
285 .map(stmt -> safeStringArgument(sourceId, stmt, "prefix argument"))
286 .orElseThrow(() -> new IllegalArgumentException("No prefix statement in " + refOf(sourceId, root)));
289 private static @NonNull Unqualified extractBelongsTo(final IRStatement root, final SourceIdentifier sourceId) {
290 return root.statements().stream()
291 .filter(stmt -> isBuiltin(stmt, BELONGS_TO))
293 .map(stmt -> Unqualified.of(safeStringArgument(sourceId, stmt, "belongs-to module name")))
294 .orElseThrow(() -> new IllegalArgumentException("No belongs-to statement in " + refOf(sourceId, root)));
297 private static @NonNull ImmutableSet<Revision> extractRevisions(final IRStatement root,
298 final SourceIdentifier sourceId) {
299 return root.statements().stream()
300 .filter(stmt -> isBuiltin(stmt, REVISION))
301 .map(stmt -> Revision.of(safeStringArgument(sourceId, stmt, "revision argument")))
302 .collect(ImmutableSet.toImmutableSet());
305 private static @Nullable Revision extractRevisionDate(final IRStatement root, final SourceIdentifier sourceId) {
306 return root.statements().stream()
307 .filter(stmt -> isBuiltin(stmt, REVISION_DATE))
309 .map(stmt -> Revision.of(safeStringArgument(sourceId, stmt, "revision date argument")))
313 private static @NonNull ImmutableSet<Import> extractImports(final IRStatement root,
314 final SourceIdentifier sourceId) {
315 return root.statements().stream()
316 .filter(stmt -> isBuiltin(stmt, IMPORT))
317 .map(stmt -> new Import(Unqualified.of(safeStringArgument(sourceId, stmt, "imported module name")),
318 extractPrefix(stmt, sourceId), extractRevisionDate(stmt, sourceId)))
319 .collect(ImmutableSet.toImmutableSet());
322 private static @NonNull ImmutableSet<Include> extractIncludes(final IRStatement root,
323 final SourceIdentifier sourceId) {
324 return root.statements().stream()
325 .filter(stmt -> isBuiltin(stmt, INCLUDE))
326 .map(stmt -> new Include(Unqualified.of(safeStringArgument(sourceId, stmt, "included submodule name")),
327 extractRevisionDate(stmt, sourceId)))
328 .collect(ImmutableSet.toImmutableSet());
331 private static boolean isBuiltin(final IRStatement stmt, final String localName) {
332 return stmt.keyword() instanceof IRKeyword.Unqualified keyword && localName.equals(keyword.identifier());
335 public static String getLatestRevision(final IRStatement module, final SourceIdentifier source) {
336 String latestRevision = null;
337 for (final IRStatement substatement : module.statements()) {
338 if (isBuiltin(substatement, REVISION)) {
339 final String currentRevision = safeStringArgument(source, substatement, "revision date");
340 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
341 latestRevision = currentRevision;
345 return latestRevision;
348 static @NonNull String safeStringArgument(final SourceIdentifier source, final IRStatement stmt,
350 final var ref = refOf(source, stmt);
351 final var arg = stmt.argument();
353 throw new IllegalArgumentException("Missing " + desc + " at " + ref);
356 // TODO: we probably need to understand yang version first....
357 return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
360 private static StatementSourceReference refOf(final SourceIdentifier source, final IRStatement stmt) {
361 return ExplicitStatement.atPosition(source.name().getLocalName(), stmt.startLine(), stmt.startColumn() + 1);
365 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
367 // FIXME: this is a rather nasty misuse of APIs :(
368 private static final class ModuleImportImpl implements ModuleImport {
369 private final @NonNull Unqualified moduleName;
370 private final Revision revision;
372 ModuleImportImpl(final Import importSpec) {
373 moduleName = importSpec.name();
374 revision = importSpec.revision();
377 ModuleImportImpl(final Include includeSpec) {
378 moduleName = includeSpec.name();
379 revision = includeSpec.revision();
383 public Unqualified getModuleName() {
388 public Optional<Revision> getRevision() {
389 return Optional.ofNullable(revision);
393 public String getPrefix() {
394 throw new UnsupportedOperationException();
398 public Optional<String> getDescription() {
399 return Optional.empty();
403 public Optional<String> getReference() {
404 return Optional.empty();
408 public ImportEffectiveStatement asEffectiveStatement() {
409 throw new UnsupportedOperationException();
413 public int hashCode() {
414 final int prime = 31;
416 result = prime * result + Objects.hashCode(moduleName);
417 result = prime * result + Objects.hashCode(revision);
422 public boolean equals(final Object obj) {
423 return this == obj || obj instanceof ModuleImportImpl other
424 && moduleName.equals(other.moduleName) && Objects.equals(revision, other.revision);
428 public String toString() {
429 return "ModuleImportImpl [name=" + moduleName + ", revision=" + revision + "]";