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.impl.util;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.Strings;
14 import com.google.common.collect.ImmutableSet;
15 import java.io.IOException;
16 import java.util.HashSet;
17 import java.util.Objects;
18 import java.util.Optional;
20 import javax.annotation.Nullable;
21 import org.antlr.v4.runtime.ParserRuleContext;
22 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.StatementContext;
23 import org.opendaylight.yangtools.concepts.SemVer;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.common.Revision;
26 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
27 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
28 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
29 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
30 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
31 import org.opendaylight.yangtools.yang.parser.rfc6020.repo.YangStatementStreamSource;
32 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
33 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
34 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.SupportedExtensionsMapping;
35 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
38 * Helper transfer object which holds basic and dependency information for YANG
42 * There are two concrete implementations of this interface:
44 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
45 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
48 * @see ModuleDependencyInfo
49 * @see SubmoduleDependencyInfo
51 public abstract class YangModelDependencyInfo {
52 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
53 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
54 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
55 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
56 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
57 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
58 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
60 private static final String OPENCONFIG_VERSION = SupportedExtensionsMapping.OPENCONFIG_VERSION.getStatementName()
63 private final String name;
64 private final Revision revision;
65 private final SemVer semVer;
66 private final ImmutableSet<ModuleImport> submoduleIncludes;
67 private final ImmutableSet<ModuleImport> moduleImports;
68 private final ImmutableSet<ModuleImport> dependencies;
70 YangModelDependencyInfo(final String name, final String formattedRevision,
71 final ImmutableSet<ModuleImport> imports,
72 final ImmutableSet<ModuleImport> includes) {
73 this(name, formattedRevision, imports, includes, Optional.empty());
76 YangModelDependencyInfo(final String name, final String formattedRevision,
77 final ImmutableSet<ModuleImport> imports,
78 final ImmutableSet<ModuleImport> includes,
79 final Optional<SemVer> semVer) {
81 this.revision = Revision.ofNullable(formattedRevision).orElse(null);
82 this.moduleImports = imports;
83 this.submoduleIncludes = includes;
84 this.dependencies = ImmutableSet.<ModuleImport>builder()
85 .addAll(moduleImports).addAll(submoduleIncludes).build();
86 this.semVer = semVer.orElse(null);
90 * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
91 * and <code>include</code> statements for submodules.
93 * @return Immutable collection of imports.
95 public ImmutableSet<ModuleImport> getDependencies() {
100 * Returns model name.
104 public String getName() {
109 * Returns formatted revision string.
111 * @return formatted revision string
113 public String getFormattedRevision() {
114 return revision != null ? revision.toString() : null;
120 * @return revision, potentially null
122 public Optional<Revision> getRevision() {
123 return Optional.ofNullable(revision);
127 * Returns semantic version of module.
129 * @return semantic version
131 public Optional<SemVer> getSemanticVersion() {
132 return Optional.ofNullable(semVer);
136 public int hashCode() {
137 final int prime = 31;
139 result = prime * result + Objects.hashCode(name);
140 result = prime * result + Objects.hashCode(revision);
141 result = prime * result + Objects.hashCode(semVer);
146 public boolean equals(final Object obj) {
153 if (!(obj instanceof YangModelDependencyInfo)) {
156 final YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
157 return Objects.equals(name, other.name) && Objects.equals(revision, other.revision)
158 && Objects.equals(semVer, other.semVer);
162 * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of a YANG model.
164 * @param source Source identifier
165 * @param tree Abstract syntax tree
166 * @return {@link YangModelDependencyInfo}
167 * @throws YangSyntaxErrorException If the AST is not a valid YANG module/submodule
169 public static YangModelDependencyInfo fromAST(final SourceIdentifier source, final ParserRuleContext tree)
170 throws YangSyntaxErrorException {
172 if (tree instanceof StatementContext) {
173 final StatementContext rootStatement = (StatementContext) tree;
174 return parseAST(rootStatement, source);
177 throw new YangSyntaxErrorException(source, 0, 0, "Unknown YANG text type");
180 private static YangModelDependencyInfo parseAST(final StatementContext rootStatement,
181 final SourceIdentifier source) {
182 final String keyWordText = rootStatement.keyword().getText();
183 if (MODULE.equals(keyWordText)) {
184 return parseModuleContext(rootStatement, source);
186 if (SUBMODULE.equals(keyWordText)) {
187 return parseSubmoduleContext(rootStatement, source);
189 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
193 * Extracts {@link YangModelDependencyInfo} from input stream containing a YANG model. This parsing does not
194 * validate full YANG module, only parses header up to the revisions and imports.
196 * @param refClass Base search class
197 * @param resourceName resource name, relative to refClass
198 * @return {@link YangModelDependencyInfo}
199 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
200 * @throws IOException When the resource cannot be read
201 * @throws IllegalArgumentException
202 * If input stream is not valid YANG stream
204 public static YangModelDependencyInfo forResource(final Class<?> refClass, final String resourceName)
205 throws IOException, YangSyntaxErrorException {
206 final YangStatementStreamSource source = YangStatementStreamSource.create(
207 YangTextSchemaSource.forResource(refClass, resourceName));
208 final ParserRuleContext ast = source.getYangAST();
209 checkArgument(ast instanceof StatementContext);
210 return parseAST((StatementContext) ast, source.getIdentifier());
213 private static YangModelDependencyInfo parseModuleContext(final StatementContext module,
214 final SourceIdentifier source) {
215 final String name = Utils.stringFromStringContext(module.argument(), getReference(source, module));
216 final String latestRevision = getLatestRevision(module, source);
217 final Optional<SemVer> semVer = Optional.ofNullable(findSemanticVersion(module, source));
218 final ImmutableSet<ModuleImport> imports = parseImports(module, source);
219 final ImmutableSet<ModuleImport> includes = parseIncludes(module, source);
221 return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
224 private static ImmutableSet<ModuleImport> parseImports(final StatementContext module,
225 final SourceIdentifier source) {
226 final Set<ModuleImport> result = new HashSet<>();
227 for (final StatementContext subStatementContext : module.statement()) {
228 if (IMPORT.equals(subStatementContext.keyword().getText())) {
229 final String revisionDateStr = getRevisionDateString(subStatementContext, source);
230 final String importedModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
231 getReference(source, subStatementContext));
232 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
233 final SemVer importSemVer = findSemanticVersion(subStatementContext, source);
234 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
237 return ImmutableSet.copyOf(result);
240 private static SemVer findSemanticVersion(final StatementContext statement, final SourceIdentifier source) {
241 String semVerString = null;
242 for (final StatementContext subStatement : statement.statement()) {
243 final String subStatementName = Utils.trimPrefix(subStatement.keyword().getText());
244 if (OPENCONFIG_VERSION.equals(subStatementName)) {
245 semVerString = Utils.stringFromStringContext(subStatement.argument(),
246 getReference(source, subStatement));
251 return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
254 private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module,
255 final SourceIdentifier source) {
256 final Set<ModuleImport> result = new HashSet<>();
257 for (final StatementContext subStatementContext : module.statement()) {
258 if (INCLUDE.equals(subStatementContext.keyword().getText())) {
259 final String revisionDateStr = getRevisionDateString(subStatementContext, source);
260 final String IncludeModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
261 getReference(source, subStatementContext));
262 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
263 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
266 return ImmutableSet.copyOf(result);
269 private static String getRevisionDateString(final StatementContext importStatement, final SourceIdentifier source) {
270 String revisionDateStr = null;
271 for (final StatementContext importSubStatement : importStatement.statement()) {
272 if (REVISION_DATE.equals(importSubStatement.keyword().getText())) {
273 revisionDateStr = Utils.stringFromStringContext(importSubStatement.argument(),
274 getReference(source, importSubStatement));
277 return revisionDateStr;
280 public static String getLatestRevision(final StatementContext module, final SourceIdentifier source) {
281 String latestRevision = null;
282 for (final StatementContext subStatementContext : module.statement()) {
283 if (REVISION.equals(subStatementContext.keyword().getText())) {
284 final String currentRevision = Utils.stringFromStringContext(subStatementContext.argument(),
285 getReference(source, subStatementContext));
286 if (latestRevision == null || latestRevision.compareTo(currentRevision) == -1) {
287 latestRevision = currentRevision;
291 return latestRevision;
294 private static YangModelDependencyInfo parseSubmoduleContext(final StatementContext submodule,
295 final SourceIdentifier source) {
296 final String name = Utils.stringFromStringContext(submodule.argument(), getReference(source, submodule));
297 final String belongsTo = parseBelongsTo(submodule, source);
299 final String latestRevision = getLatestRevision(submodule, source);
300 final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
301 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
303 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
306 private static String parseBelongsTo(final StatementContext submodule, final SourceIdentifier source) {
307 for (final StatementContext subStatementContext : submodule.statement()) {
308 if (BELONGS_TO.equals(subStatementContext.keyword().getText())) {
309 return Utils.stringFromStringContext(subStatementContext.argument(),
310 getReference(source, subStatementContext));
316 private static StatementSourceReference getReference(final SourceIdentifier source,
317 final StatementContext context) {
318 return DeclarationInTextSource.atPosition(source.getName(), context.getStart().getLine(),
319 context.getStart().getCharPositionInLine());
323 * Dependency information for YANG module.
325 public static class ModuleDependencyInfo extends YangModelDependencyInfo {
326 private ModuleDependencyInfo(final String name, final String latestRevision,
327 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
328 super(name, latestRevision, imports, includes);
331 private ModuleDependencyInfo(final String name, final String latestRevision,
332 final ImmutableSet<ModuleImport> imports,
333 final ImmutableSet<ModuleImport> includes,
334 final Optional<SemVer> semVer) {
335 super(name, latestRevision, imports, includes, semVer);
339 public String toString() {
340 return "Module [name=" + getName() + ", revision=" + getRevision()
341 + ", semanticVersion=" + getSemanticVersion().orElse(null)
342 + ", dependencies=" + getDependencies()
348 * Dependency information for submodule, also provides name for parent module.
350 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
351 private final String belongsTo;
353 private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
354 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
355 super(name, latestRevision, imports, includes);
356 this.belongsTo = belongsTo;
360 * Returns name of parent module.
362 public String getParentModule() {
367 public String toString() {
368 return "Submodule [name=" + getName() + ", revision="
369 + getRevision() + ", dependencies=" + getDependencies()
375 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
377 private static final class ModuleImportImpl implements ModuleImport {
379 private final Revision revision;
380 private final SemVer semVer;
381 private final String name;
383 ModuleImportImpl(final String moduleName, final Revision revision) {
384 this(moduleName, revision, null);
387 ModuleImportImpl(final String moduleName, @Nullable final Revision revision, @Nullable final SemVer semVer) {
388 this.name = requireNonNull(moduleName, "Module name must not be null.");
389 this.revision = revision;
390 this.semVer = semVer;
394 public String getModuleName() {
399 public Optional<Revision> getRevision() {
400 return Optional.ofNullable(revision);
404 public Optional<SemVer> getSemanticVersion() {
405 return Optional.ofNullable(semVer);
409 public String getPrefix() {
414 public Optional<String> getDescription() {
415 return Optional.empty();
419 public Optional<String> getReference() {
420 return Optional.empty();
424 public int hashCode() {
425 final int prime = 31;
427 result = prime * result + Objects.hashCode(name);
428 result = prime * result + Objects.hashCode(revision);
429 result = prime * result + Objects.hashCode(semVer);
434 public boolean equals(final Object obj) {
441 if (getClass() != obj.getClass()) {
444 final ModuleImportImpl other = (ModuleImportImpl) obj;
446 if (other.name != null) {
449 } else if (!name.equals(other.name)) {
452 if (revision == null) {
453 if (other.revision != null) {
456 } else if (!revision.equals(other.revision)) {
460 if (!Objects.equals(getSemanticVersion(), other.getSemanticVersion())) {
467 public String toString() {
468 return "ModuleImportImpl [name=" + name + ", revision="
469 + QName.formattedRevision(Optional.ofNullable(revision)) + ", semanticVersion=" + semVer + "]";