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 com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.base.Splitter;
15 import com.google.common.base.Strings;
16 import com.google.common.collect.ImmutableSet;
17 import java.io.IOException;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Objects;
21 import java.util.Optional;
23 import javax.annotation.Nullable;
24 import org.antlr.v4.runtime.ParserRuleContext;
25 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.StatementContext;
26 import org.opendaylight.yangtools.concepts.SemVer;
27 import org.opendaylight.yangtools.openconfig.model.api.OpenConfigStatements;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.common.Revision;
30 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
31 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
32 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
33 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
34 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
35 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
36 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
39 * Helper transfer object which holds basic and dependency information for YANG
43 * There are two concrete implementations of this interface:
45 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
46 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
49 * @see ModuleDependencyInfo
50 * @see SubmoduleDependencyInfo
52 public abstract class YangModelDependencyInfo {
53 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
54 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
55 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
56 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
57 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
58 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
59 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
61 private static final String OPENCONFIG_VERSION = OpenConfigStatements.OPENCONFIG_VERSION.getStatementName()
63 private static final Splitter COLON_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
65 private final String name;
66 private final Revision revision;
67 private final SemVer semVer;
68 private final ImmutableSet<ModuleImport> submoduleIncludes;
69 private final ImmutableSet<ModuleImport> moduleImports;
70 private final ImmutableSet<ModuleImport> dependencies;
72 YangModelDependencyInfo(final String name, final String formattedRevision,
73 final ImmutableSet<ModuleImport> imports,
74 final ImmutableSet<ModuleImport> includes) {
75 this(name, formattedRevision, imports, includes, Optional.empty());
78 YangModelDependencyInfo(final String name, final String formattedRevision,
79 final ImmutableSet<ModuleImport> imports,
80 final ImmutableSet<ModuleImport> includes,
81 final Optional<SemVer> semVer) {
83 this.revision = Revision.ofNullable(formattedRevision).orElse(null);
84 this.moduleImports = imports;
85 this.submoduleIncludes = includes;
86 this.dependencies = ImmutableSet.<ModuleImport>builder()
87 .addAll(moduleImports).addAll(submoduleIncludes).build();
88 this.semVer = semVer.orElse(null);
92 * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
93 * and <code>include</code> statements for submodules.
95 * @return Immutable collection of imports.
97 public ImmutableSet<ModuleImport> getDependencies() {
102 * Returns model name.
106 public String getName() {
111 * Returns formatted revision string.
113 * @return formatted revision string
115 public String getFormattedRevision() {
116 return revision != null ? revision.toString() : null;
122 * @return revision, potentially null
124 public Optional<Revision> getRevision() {
125 return Optional.ofNullable(revision);
129 * Returns semantic version of module.
131 * @return semantic version
133 public Optional<SemVer> getSemanticVersion() {
134 return Optional.ofNullable(semVer);
138 public int hashCode() {
139 final int prime = 31;
141 result = prime * result + Objects.hashCode(name);
142 result = prime * result + Objects.hashCode(revision);
143 result = prime * result + Objects.hashCode(semVer);
148 public boolean equals(final Object obj) {
155 if (!(obj instanceof YangModelDependencyInfo)) {
158 final YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
159 return Objects.equals(name, other.name) && Objects.equals(revision, other.revision)
160 && Objects.equals(semVer, other.semVer);
164 * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of a YANG model.
166 * @param source Source identifier
167 * @param tree Abstract syntax tree
168 * @return {@link YangModelDependencyInfo}
169 * @throws YangSyntaxErrorException If the AST is not a valid YANG module/submodule
171 static YangModelDependencyInfo fromAST(final SourceIdentifier source, final ParserRuleContext tree)
172 throws YangSyntaxErrorException {
174 if (tree instanceof StatementContext) {
175 final StatementContext rootStatement = (StatementContext) tree;
176 return parseAST(rootStatement, source);
179 throw new YangSyntaxErrorException(source, 0, 0, "Unknown YANG text type");
182 private static YangModelDependencyInfo parseAST(final StatementContext rootStatement,
183 final SourceIdentifier source) {
184 final String keyWordText = rootStatement.keyword().getText();
185 if (MODULE.equals(keyWordText)) {
186 return parseModuleContext(rootStatement, source);
188 if (SUBMODULE.equals(keyWordText)) {
189 return parseSubmoduleContext(rootStatement, source);
191 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
195 * Extracts {@link YangModelDependencyInfo} from input stream containing a YANG model. This parsing does not
196 * validate full YANG module, only parses header up to the revisions and imports.
198 * @param refClass Base search class
199 * @param resourceName resource name, relative to refClass
200 * @return {@link YangModelDependencyInfo}
201 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
202 * @throws IOException When the resource cannot be read
203 * @throws IllegalArgumentException
204 * If input stream is not valid YANG stream
207 public static YangModelDependencyInfo forResource(final Class<?> refClass, final String resourceName)
208 throws IOException, YangSyntaxErrorException {
209 final YangStatementStreamSource source = YangStatementStreamSource.create(
210 YangTextSchemaSource.forResource(refClass, resourceName));
211 final ParserRuleContext ast = source.getYangAST();
212 checkArgument(ast instanceof StatementContext);
213 return parseAST((StatementContext) ast, source.getIdentifier());
216 private static YangModelDependencyInfo parseModuleContext(final StatementContext module,
217 final SourceIdentifier source) {
218 final String name = ArgumentContextUtils.stringFromStringContext(module.argument(), getReference(source,
220 final String latestRevision = getLatestRevision(module, source);
221 final Optional<SemVer> semVer = Optional.ofNullable(findSemanticVersion(module, source));
222 final ImmutableSet<ModuleImport> imports = parseImports(module, source);
223 final ImmutableSet<ModuleImport> includes = parseIncludes(module, source);
225 return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
228 private static ImmutableSet<ModuleImport> parseImports(final StatementContext module,
229 final SourceIdentifier source) {
230 final Set<ModuleImport> result = new HashSet<>();
231 for (final StatementContext subStatementContext : module.statement()) {
232 if (IMPORT.equals(subStatementContext.keyword().getText())) {
233 final String revisionDateStr = getRevisionDateString(subStatementContext, source);
234 final String importedModuleName = ArgumentContextUtils.stringFromStringContext(
235 subStatementContext.argument(), getReference(source, subStatementContext));
236 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
237 final SemVer importSemVer = findSemanticVersion(subStatementContext, source);
238 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
241 return ImmutableSet.copyOf(result);
244 private static SemVer findSemanticVersion(final StatementContext statement, final SourceIdentifier source) {
245 String semVerString = null;
246 for (final StatementContext subStatement : statement.statement()) {
247 final String subStatementName = trimPrefix(subStatement.keyword().getText());
248 if (OPENCONFIG_VERSION.equals(subStatementName)) {
249 semVerString = ArgumentContextUtils.stringFromStringContext(subStatement.argument(),
250 getReference(source, subStatement));
255 return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
259 private static String trimPrefix(final String identifier) {
260 final List<String> namesParts = COLON_SPLITTER.splitToList(identifier);
261 if (namesParts.size() == 2) {
262 return namesParts.get(1);
268 private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module,
269 final SourceIdentifier source) {
270 final Set<ModuleImport> result = new HashSet<>();
271 for (final StatementContext subStatementContext : module.statement()) {
272 if (INCLUDE.equals(subStatementContext.keyword().getText())) {
273 final String revisionDateStr = getRevisionDateString(subStatementContext, source);
274 final String IncludeModuleName = ArgumentContextUtils.stringFromStringContext(
275 subStatementContext.argument(), getReference(source, subStatementContext));
276 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
277 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
280 return ImmutableSet.copyOf(result);
283 private static String getRevisionDateString(final StatementContext importStatement, final SourceIdentifier source) {
284 String revisionDateStr = null;
285 for (final StatementContext importSubStatement : importStatement.statement()) {
286 if (REVISION_DATE.equals(importSubStatement.keyword().getText())) {
287 revisionDateStr = ArgumentContextUtils.stringFromStringContext(importSubStatement.argument(),
288 getReference(source, importSubStatement));
291 return revisionDateStr;
294 public static String getLatestRevision(final StatementContext module, final SourceIdentifier source) {
295 String latestRevision = null;
296 for (final StatementContext subStatementContext : module.statement()) {
297 if (REVISION.equals(subStatementContext.keyword().getText())) {
298 final String currentRevision = ArgumentContextUtils.stringFromStringContext(
299 subStatementContext.argument(), getReference(source, subStatementContext));
300 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
301 latestRevision = currentRevision;
305 return latestRevision;
308 private static YangModelDependencyInfo parseSubmoduleContext(final StatementContext submodule,
309 final SourceIdentifier source) {
310 final String name = ArgumentContextUtils.stringFromStringContext(submodule.argument(),
311 getReference(source, submodule));
312 final String belongsTo = parseBelongsTo(submodule, source);
314 final String latestRevision = getLatestRevision(submodule, source);
315 final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
316 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
318 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
321 private static String parseBelongsTo(final StatementContext submodule, final SourceIdentifier source) {
322 for (final StatementContext subStatementContext : submodule.statement()) {
323 if (BELONGS_TO.equals(subStatementContext.keyword().getText())) {
324 return ArgumentContextUtils.stringFromStringContext(subStatementContext.argument(),
325 getReference(source, subStatementContext));
331 private static StatementSourceReference getReference(final SourceIdentifier source,
332 final StatementContext context) {
333 return DeclarationInTextSource.atPosition(source.getName(), context.getStart().getLine(),
334 context.getStart().getCharPositionInLine());
338 * Dependency information for YANG module.
340 public static class ModuleDependencyInfo extends YangModelDependencyInfo {
341 private ModuleDependencyInfo(final String name, final String latestRevision,
342 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
343 super(name, latestRevision, imports, includes);
346 private ModuleDependencyInfo(final String name, final String latestRevision,
347 final ImmutableSet<ModuleImport> imports,
348 final ImmutableSet<ModuleImport> includes,
349 final Optional<SemVer> semVer) {
350 super(name, latestRevision, imports, includes, semVer);
354 public String toString() {
355 return "Module [name=" + getName() + ", revision=" + getRevision()
356 + ", semanticVersion=" + getSemanticVersion().orElse(null)
357 + ", dependencies=" + getDependencies()
363 * Dependency information for submodule, also provides name for parent module.
365 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
366 private final String belongsTo;
368 private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
369 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
370 super(name, latestRevision, imports, includes);
371 this.belongsTo = belongsTo;
375 * Returns name of parent module.
377 public String getParentModule() {
382 public String toString() {
383 return "Submodule [name=" + getName() + ", revision="
384 + getRevision() + ", dependencies=" + getDependencies()
390 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
392 private static final class ModuleImportImpl implements ModuleImport {
394 private final Revision revision;
395 private final SemVer semVer;
396 private final String name;
398 ModuleImportImpl(final String moduleName, final Revision revision) {
399 this(moduleName, revision, null);
402 ModuleImportImpl(final String moduleName, @Nullable final Revision revision, @Nullable final SemVer semVer) {
403 this.name = requireNonNull(moduleName, "Module name must not be null.");
404 this.revision = revision;
405 this.semVer = semVer;
409 public String getModuleName() {
414 public Optional<Revision> getRevision() {
415 return Optional.ofNullable(revision);
419 public Optional<SemVer> getSemanticVersion() {
420 return Optional.ofNullable(semVer);
424 public String getPrefix() {
429 public Optional<String> getDescription() {
430 return Optional.empty();
434 public Optional<String> getReference() {
435 return Optional.empty();
439 public int hashCode() {
440 final int prime = 31;
442 result = prime * result + Objects.hashCode(name);
443 result = prime * result + Objects.hashCode(revision);
444 result = prime * result + Objects.hashCode(semVer);
449 public boolean equals(final Object obj) {
456 if (getClass() != obj.getClass()) {
459 final ModuleImportImpl other = (ModuleImportImpl) obj;
461 if (other.name != null) {
464 } else if (!name.equals(other.name)) {
467 if (revision == null) {
468 if (other.revision != null) {
471 } else if (!revision.equals(other.revision)) {
475 if (!Objects.equals(getSemanticVersion(), other.getSemanticVersion())) {
482 public String toString() {
483 return "ModuleImportImpl [name=" + name + ", revision="
484 + QName.formattedRevision(Optional.ofNullable(revision)) + ", semanticVersion=" + semVer + "]";