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 org.antlr.v4.runtime.ParserRuleContext;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.ArgumentContext;
27 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.StatementContext;
28 import org.opendaylight.yangtools.concepts.SemVer;
29 import org.opendaylight.yangtools.openconfig.model.api.OpenConfigStatements;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.common.Revision;
32 import org.opendaylight.yangtools.yang.common.YangVersion;
33 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
34 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
35 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
36 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
37 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
38 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
39 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
42 * Helper transfer object which holds basic and dependency information for YANG
46 * There are two concrete implementations of this interface:
48 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
49 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
52 * @see ModuleDependencyInfo
53 * @see SubmoduleDependencyInfo
55 public abstract class YangModelDependencyInfo {
56 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
57 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
58 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
59 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
60 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
61 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
62 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
64 private static final String OPENCONFIG_VERSION = OpenConfigStatements.OPENCONFIG_VERSION.getStatementName()
66 private static final Splitter COLON_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
68 private final String name;
69 private final Revision revision;
70 private final SemVer semVer;
71 private final ImmutableSet<ModuleImport> submoduleIncludes;
72 private final ImmutableSet<ModuleImport> moduleImports;
73 private final ImmutableSet<ModuleImport> dependencies;
75 YangModelDependencyInfo(final String name, final String formattedRevision,
76 final ImmutableSet<ModuleImport> imports,
77 final ImmutableSet<ModuleImport> includes) {
78 this(name, formattedRevision, imports, includes, Optional.empty());
81 YangModelDependencyInfo(final String name, final String formattedRevision,
82 final ImmutableSet<ModuleImport> imports,
83 final ImmutableSet<ModuleImport> includes,
84 final Optional<SemVer> semVer) {
86 this.revision = Revision.ofNullable(formattedRevision).orElse(null);
87 this.moduleImports = imports;
88 this.submoduleIncludes = includes;
89 this.dependencies = ImmutableSet.<ModuleImport>builder()
90 .addAll(moduleImports).addAll(submoduleIncludes).build();
91 this.semVer = semVer.orElse(null);
95 * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
96 * and <code>include</code> statements for submodules.
98 * @return Immutable collection of imports.
100 public ImmutableSet<ModuleImport> getDependencies() {
105 * Returns model name.
109 public String getName() {
114 * Returns formatted revision string.
116 * @return formatted revision string
118 public String getFormattedRevision() {
119 return revision != null ? revision.toString() : null;
125 * @return revision, potentially null
127 public Optional<Revision> getRevision() {
128 return Optional.ofNullable(revision);
132 * Returns semantic version of module.
134 * @return semantic version
136 public Optional<SemVer> getSemanticVersion() {
137 return Optional.ofNullable(semVer);
141 public int hashCode() {
142 final int prime = 31;
144 result = prime * result + Objects.hashCode(name);
145 result = prime * result + Objects.hashCode(revision);
146 result = prime * result + Objects.hashCode(semVer);
151 public boolean equals(final Object obj) {
158 if (!(obj instanceof YangModelDependencyInfo)) {
161 final YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
162 return Objects.equals(name, other.name) && Objects.equals(revision, other.revision)
163 && Objects.equals(semVer, other.semVer);
167 * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of a YANG model.
169 * @param source Source identifier
170 * @param tree Abstract syntax tree
171 * @return {@link YangModelDependencyInfo}
172 * @throws YangSyntaxErrorException If the AST is not a valid YANG module/submodule
174 static @NonNull YangModelDependencyInfo fromAST(final SourceIdentifier source, final ParserRuleContext tree)
175 throws YangSyntaxErrorException {
177 if (tree instanceof StatementContext) {
178 final StatementContext rootStatement = (StatementContext) tree;
179 return parseAST(rootStatement, source);
182 throw new YangSyntaxErrorException(source, 0, 0, "Unknown YANG text type");
185 private static @NonNull YangModelDependencyInfo parseAST(final StatementContext rootStatement,
186 final SourceIdentifier source) {
187 final String keyWordText = rootStatement.keyword().getText();
188 if (MODULE.equals(keyWordText)) {
189 return parseModuleContext(rootStatement, source);
191 if (SUBMODULE.equals(keyWordText)) {
192 return parseSubmoduleContext(rootStatement, source);
194 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
198 * Extracts {@link YangModelDependencyInfo} from input stream containing a YANG model. This parsing does not
199 * validate full YANG module, only parses header up to the revisions and imports.
201 * @param refClass Base search class
202 * @param resourceName resource name, relative to refClass
203 * @return {@link YangModelDependencyInfo}
204 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
205 * @throws IOException When the resource cannot be read
206 * @throws IllegalArgumentException
207 * If input stream is not valid YANG stream
210 public static YangModelDependencyInfo forResource(final Class<?> refClass, final String resourceName)
211 throws IOException, YangSyntaxErrorException {
212 final YangStatementStreamSource source = YangStatementStreamSource.create(
213 YangTextSchemaSource.forResource(refClass, resourceName));
214 final ParserRuleContext ast = source.getYangAST();
215 checkArgument(ast instanceof StatementContext);
216 return parseAST((StatementContext) ast, source.getIdentifier());
219 private static @NonNull YangModelDependencyInfo parseModuleContext(final StatementContext module,
220 final SourceIdentifier source) {
221 final String name = safeStringArgument(source, module, "module name");
222 final String latestRevision = getLatestRevision(module, source);
223 final Optional<SemVer> semVer = Optional.ofNullable(findSemanticVersion(module, source));
224 final ImmutableSet<ModuleImport> imports = parseImports(module, source);
225 final ImmutableSet<ModuleImport> includes = parseIncludes(module, source);
227 return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
230 private static ImmutableSet<ModuleImport> parseImports(final StatementContext module,
231 final SourceIdentifier source) {
232 final Set<ModuleImport> result = new HashSet<>();
233 for (final StatementContext subStatementContext : module.statement()) {
234 if (IMPORT.equals(subStatementContext.keyword().getText())) {
235 final String importedModuleName = safeStringArgument(source, subStatementContext,
236 "imported module name");
237 final String revisionDateStr = getRevisionDateString(subStatementContext, source);
238 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
239 final SemVer importSemVer = findSemanticVersion(subStatementContext, source);
240 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
243 return ImmutableSet.copyOf(result);
246 private static SemVer findSemanticVersion(final StatementContext statement, final SourceIdentifier source) {
247 String semVerString = null;
248 for (final StatementContext subStatement : statement.statement()) {
249 final String subStatementName = trimPrefix(subStatement.keyword().getText());
250 if (OPENCONFIG_VERSION.equals(subStatementName)) {
251 semVerString = safeStringArgument(source, subStatement, "version string");
256 return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
260 private static String trimPrefix(final String identifier) {
261 final List<String> namesParts = COLON_SPLITTER.splitToList(identifier);
262 if (namesParts.size() == 2) {
263 return namesParts.get(1);
269 private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module,
270 final SourceIdentifier source) {
271 final Set<ModuleImport> result = new HashSet<>();
272 for (final StatementContext subStatementContext : module.statement()) {
273 if (INCLUDE.equals(subStatementContext.keyword().getText())) {
274 final String revisionDateStr = getRevisionDateString(subStatementContext, source);
275 final String IncludeModuleName = safeStringArgument(source, subStatementContext,
276 "included submodule name");
277 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
278 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
281 return ImmutableSet.copyOf(result);
284 private static String getRevisionDateString(final StatementContext importStatement, final SourceIdentifier source) {
285 String revisionDateStr = null;
286 for (final StatementContext importSubStatement : importStatement.statement()) {
287 if (REVISION_DATE.equals(importSubStatement.keyword().getText())) {
288 revisionDateStr = safeStringArgument(source, importSubStatement, "imported module revision-date");
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 = safeStringArgument(source, subStatementContext, "revision date");
299 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
300 latestRevision = currentRevision;
304 return latestRevision;
307 private static @NonNull YangModelDependencyInfo parseSubmoduleContext(final StatementContext submodule,
308 final SourceIdentifier source) {
309 final String name = safeStringArgument(source, submodule, "submodule name");
310 final String belongsTo = parseBelongsTo(submodule, source);
312 final String latestRevision = getLatestRevision(submodule, source);
313 final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
314 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
316 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
319 private static String parseBelongsTo(final StatementContext submodule, final SourceIdentifier source) {
320 for (final StatementContext subStatementContext : submodule.statement()) {
321 if (BELONGS_TO.equals(subStatementContext.keyword().getText())) {
322 return safeStringArgument(source, subStatementContext, "belongs-to module name");
328 private static String safeStringArgument(final SourceIdentifier source, final StatementContext stmt,
330 final StatementSourceReference ref = getReference(source, stmt);
331 final ArgumentContext arg = stmt.argument();
332 checkArgument(arg != null, "Missing %s at %s", desc, ref);
333 return ArgumentContextUtils.stringFromStringContext(arg, YangVersion.VERSION_1, ref);
336 private static StatementSourceReference getReference(final SourceIdentifier source,
337 final StatementContext context) {
338 return DeclarationInTextSource.atPosition(source.getName(), context.getStart().getLine(),
339 context.getStart().getCharPositionInLine());
343 * Dependency information for YANG module.
345 public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
346 ModuleDependencyInfo(final String name, final String latestRevision, final ImmutableSet<ModuleImport> imports,
347 final ImmutableSet<ModuleImport> includes, final Optional<SemVer> semVer) {
348 super(name, latestRevision, imports, includes, semVer);
352 public String toString() {
353 return "Module [name=" + getName() + ", revision=" + getRevision()
354 + ", semanticVersion=" + getSemanticVersion().orElse(null)
355 + ", dependencies=" + getDependencies()
361 * Dependency information for submodule, also provides name for parent module.
363 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
364 private final String belongsTo;
366 private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
367 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
368 super(name, latestRevision, imports, includes);
369 this.belongsTo = belongsTo;
373 * Returns name of parent module.
375 public String getParentModule() {
380 public String toString() {
381 return "Submodule [name=" + getName() + ", revision="
382 + getRevision() + ", dependencies=" + getDependencies()
388 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
390 private static final class ModuleImportImpl implements ModuleImport {
392 private final Revision revision;
393 private final SemVer semVer;
394 private final String name;
396 ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision) {
397 this(moduleName, revision, null);
400 ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision,
401 final @Nullable SemVer semVer) {
402 this.name = requireNonNull(moduleName, "Module name must not be null.");
403 this.revision = revision;
404 this.semVer = semVer;
408 public String getModuleName() {
413 public Optional<Revision> getRevision() {
414 return Optional.ofNullable(revision);
418 public Optional<SemVer> getSemanticVersion() {
419 return Optional.ofNullable(semVer);
423 public String getPrefix() {
428 public Optional<String> getDescription() {
429 return Optional.empty();
433 public Optional<String> getReference() {
434 return Optional.empty();
438 public int hashCode() {
439 final int prime = 31;
441 result = prime * result + Objects.hashCode(name);
442 result = prime * result + Objects.hashCode(revision);
443 result = prime * result + Objects.hashCode(semVer);
448 public boolean equals(final Object obj) {
452 if (!(obj instanceof ModuleImportImpl)) {
455 final ModuleImportImpl other = (ModuleImportImpl) obj;
456 return name.equals(other.name) && Objects.equals(revision, other.revision)
457 && Objects.equals(getSemanticVersion(), other.getSemanticVersion());
461 public String toString() {
462 return "ModuleImportImpl [name=" + name + ", revision="
463 + QName.formattedRevision(Optional.ofNullable(revision)) + ", semanticVersion=" + semVer + "]";