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 org.opendaylight.yangtools.yang.parser.impl.ParserListenerUtils.getArgumentString;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.ImmutableSet;
16 import java.io.InputStream;
17 import java.util.Date;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Objects;
22 import org.antlr.v4.runtime.ParserRuleContext;
23 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Belongs_to_stmtContext;
24 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Import_stmtContext;
25 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Include_stmtContext;
26 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Module_stmtContext;
27 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_date_stmtContext;
28 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_stmtContext;
29 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_stmtsContext;
30 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Submodule_stmtContext;
31 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
32 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.StatementContext;
33 import org.opendaylight.yangtools.concepts.SemVer;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.model.api.Module;
36 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
37 import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
38 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
39 import org.opendaylight.yangtools.yang.parser.impl.ParserListenerUtils;
40 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.SupportedExtensionsMapping;
41 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
42 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
45 * Helper transfer object which holds basic and dependency information for YANG
50 * There are two concrete implementations of this interface:
52 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
53 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
56 * @see ModuleDependencyInfo
57 * @see SubmoduleDependencyInfo
61 public abstract class YangModelDependencyInfo {
63 private final String name;
64 private final String formattedRevision;
65 private final Date revision;
66 private final Optional<SemVer> semVer;
67 private final ImmutableSet<ModuleImport> submoduleIncludes;
68 private final ImmutableSet<ModuleImport> moduleImports;
69 private final ImmutableSet<ModuleImport> dependencies;
71 YangModelDependencyInfo(final String name, final String formattedRevision,
72 final ImmutableSet<ModuleImport> imports,
73 final ImmutableSet<ModuleImport> includes) {
74 this(name, formattedRevision, imports, includes, Optional.absent());
77 YangModelDependencyInfo(final String name, final String formattedRevision,
78 final ImmutableSet<ModuleImport> imports,
79 final ImmutableSet<ModuleImport> includes,
80 final Optional<SemVer> semVer) {
82 this.formattedRevision = formattedRevision;
83 this.revision = formattedRevision == null ? null : QName
84 .parseRevision(formattedRevision);
85 this.moduleImports = imports;
86 this.submoduleIncludes = includes;
87 this.dependencies = ImmutableSet.<ModuleImport> builder()
88 .addAll(moduleImports).addAll(submoduleIncludes).build();
93 * Returns immutable collection of all module imports.
95 * This collection contains both <code>import</code> statements and
96 * <code>include</code> statements for submodules.
98 * @return Immutable collection of imports.
100 public ImmutableSet<ModuleImport> getDependencies() {
109 public String getName() {
114 * Returns formatted revision string
116 * @return formatted revision string
118 public String getFormattedRevision() {
119 return formattedRevision;
132 * Returns semantic version of module
134 * @return semantic version
136 public Optional<SemVer> getSemanticVersion() {
141 public int hashCode() {
142 final int prime = 31;
144 result = prime * result + Objects.hashCode(formattedRevision);
145 result = prime * result + Objects.hashCode(name);
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 if (formattedRevision == null) {
163 if (other.formattedRevision != null) {
166 } else if (!formattedRevision.equals(other.formattedRevision)) {
170 if (other.name != null) {
173 } else if (!name.equals(other.name)) {
176 if(!Objects.equals(semVer, other.semVer)) {
184 * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of
188 * Abstract syntax tree
189 * @return {@link YangModelDependencyInfo}
190 * @throws YangSyntaxErrorException
191 * If the AST is not a valid YANG module/submodule
193 public static YangModelDependencyInfo fromAST(final String name,
194 final ParserRuleContext tree) throws YangSyntaxErrorException {
196 if (tree instanceof YangStatementParser.StatementContext) {
197 final YangStatementParser.StatementContext rootStatement = (YangStatementParser.StatementContext) tree;
198 return parseAST(rootStatement);
201 final Optional<Module_stmtContext> moduleCtx = ParserListenerUtils
202 .getFirstContext(tree, Module_stmtContext.class);
203 if (moduleCtx.isPresent()) {
204 return parseModuleContext(moduleCtx.get());
207 final Optional<Submodule_stmtContext> submoduleCtx = ParserListenerUtils
208 .getFirstContext(tree, Submodule_stmtContext.class);
209 if (submoduleCtx.isPresent()) {
210 return parseSubmoduleContext(submoduleCtx.get());
213 throw new YangSyntaxErrorException(name, 0, 0, "Unknown YANG text type");
216 private static YangModelDependencyInfo parseAST(
217 final YangStatementParser.StatementContext rootStatement) {
221 .equals(Rfc6020Mapping.MODULE.getStatementName().getLocalName())) {
222 return parseModuleContext(rootStatement);
223 } else if (rootStatement
226 .equals(Rfc6020Mapping.SUBMODULE.getStatementName()
228 return parseSubmoduleContext(rootStatement);
231 throw new IllegalArgumentException(
232 "Root of parsed AST must be either module or submodule");
236 * Extracts {@link YangModelDependencyInfo} from input stream containing
239 * This parsing does not validate full YANG module, only parses header up to
240 * the revisions and imports.
243 * Opened Input stream containing text source of YANG model
244 * @return {@link YangModelDependencyInfo}
245 * @throws IllegalArgumentException
246 * If input stream is not valid YANG stream
248 public static YangModelDependencyInfo fromInputStream(
249 final InputStream yangStream) {
250 final StatementContext yangAST = new YangStatementSourceImpl(yangStream)
252 return parseAST(yangAST);
255 private static YangModelDependencyInfo parseModuleContext(
256 final Module_stmtContext module) {
257 final String name = getArgumentString(module);
258 final String latestRevision = getLatestRevision(module.revision_stmts());
259 final ImmutableSet<ModuleImport> imports = parseImports(module
260 .linkage_stmts().import_stmt());
261 final ImmutableSet<ModuleImport> includes = parseIncludes(module
262 .linkage_stmts().include_stmt());
264 return new ModuleDependencyInfo(name, latestRevision, imports, includes);
267 private static YangModelDependencyInfo parseModuleContext(
268 final YangStatementParser.StatementContext module) {
269 final String name = Utils.stringFromStringContext(module.argument());
270 final String latestRevision = getLatestRevision(module);
271 final Optional<SemVer> semVer = Optional.fromNullable(getSemanticVersion(module));
272 final ImmutableSet<ModuleImport> imports = parseImports(module);
273 final ImmutableSet<ModuleImport> includes = parseIncludes(module);
275 return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
278 private static ImmutableSet<ModuleImport> parseImports(
279 final YangStatementParser.StatementContext module) {
280 final Set<ModuleImport> result = new HashSet<>();
281 final List<StatementContext> subStatements = module.statement();
282 for (final StatementContext subStatementContext : subStatements) {
283 if (subStatementContext
286 .equals(Rfc6020Mapping.IMPORT.getStatementName()
288 final String revisionDateStr = getRevisionDateString(subStatementContext);
289 final String importedModuleName = Utils
290 .stringFromStringContext(subStatementContext.argument());
291 final Date revisionDate = (revisionDateStr == null) ? null : QName
292 .parseRevision(revisionDateStr);
293 final Optional<SemVer> importSemVer = Optional.fromNullable(getSemanticVersion(subStatementContext));
294 result.add(new ModuleImportImpl(importedModuleName,
295 revisionDate, importSemVer));
298 return ImmutableSet.copyOf(result);
301 private static SemVer getSemanticVersion(final StatementContext statement) {
302 final List<StatementContext> subStatements = statement.statement();
303 String semVerString = null;
304 final String semVerStmtName = SupportedExtensionsMapping.SEMANTIC_VERSION.getStatementName().getLocalName();
305 for (final StatementContext subStatement : subStatements) {
306 final String subStatementName = Utils.trimPrefix(subStatement.keyword().getText());
307 if (semVerStmtName.equals(subStatementName)) {
308 semVerString = Utils.stringFromStringContext(subStatement.argument());
313 if (Strings.isNullOrEmpty(semVerString)) {
317 return SemVer.valueOf(semVerString);
320 private static ImmutableSet<ModuleImport> parseIncludes(
321 final YangStatementParser.StatementContext module) {
322 final Set<ModuleImport> result = new HashSet<>();
323 final List<StatementContext> subStatements = module.statement();
324 for (final StatementContext subStatementContext : subStatements) {
325 if (subStatementContext
328 .equals(Rfc6020Mapping.INCLUDE.getStatementName()
330 final String revisionDateStr = getRevisionDateString(subStatementContext);
331 final String IncludeModuleName = Utils
332 .stringFromStringContext(subStatementContext.argument());
333 final Date revisionDate = (revisionDateStr == null) ? null : QName
334 .parseRevision(revisionDateStr);
335 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
338 return ImmutableSet.copyOf(result);
341 private static String getRevisionDateString(final StatementContext importStatement) {
342 final List<StatementContext> importSubStatements = importStatement
344 String revisionDateStr = null;
345 for (final StatementContext importSubStatement : importSubStatements) {
346 if (importSubStatement
349 .equals(Rfc6020Mapping.REVISION_DATE.getStatementName()
351 revisionDateStr = Utils
352 .stringFromStringContext(importSubStatement.argument());
355 return revisionDateStr;
358 private static ImmutableSet<ModuleImport> parseImports(
359 final List<Import_stmtContext> importStatements) {
360 final ImmutableSet.Builder<ModuleImport> builder = ImmutableSet.builder();
361 for (final Import_stmtContext importStmt : importStatements) {
362 final String moduleName = getArgumentString(importStmt);
363 final Date revision = getRevision(importStmt.revision_date_stmt());
364 builder.add(new ModuleImportImpl(moduleName, revision));
366 return builder.build();
369 public static String getLatestRevision(
370 final YangStatementParser.StatementContext module) {
371 final List<StatementContext> subStatements = module.statement();
372 String latestRevision = null;
373 for (final StatementContext subStatementContext : subStatements) {
374 if (subStatementContext
377 .equals(Rfc6020Mapping.REVISION.getStatementName()
379 final String currentRevision = Utils
380 .stringFromStringContext(subStatementContext.argument());
381 if (latestRevision == null
382 || latestRevision.compareTo(currentRevision) == -1) {
383 latestRevision = currentRevision;
387 return latestRevision;
390 public static String getLatestRevision(
391 final Revision_stmtsContext revisionStmts) {
392 final List<Revision_stmtContext> revisions = revisionStmts
393 .getRuleContexts(Revision_stmtContext.class);
394 String latestRevision = null;
395 for (final Revision_stmtContext revisionStmt : revisions) {
396 final String currentRevision = getArgumentString(revisionStmt);
397 if (latestRevision == null
398 || latestRevision.compareTo(currentRevision) == -1) {
399 latestRevision = currentRevision;
402 return latestRevision;
405 private static YangModelDependencyInfo parseSubmoduleContext(
406 final YangStatementParser.StatementContext submodule) {
407 final String name = Utils.stringFromStringContext(submodule.argument());
408 final String belongsTo = parseBelongsTo(submodule);
410 final String latestRevision = getLatestRevision(submodule);
411 final ImmutableSet<ModuleImport> imports = parseImports(submodule);
412 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule);
414 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo,
418 private static String parseBelongsTo(final StatementContext submodule) {
419 final List<StatementContext> subStatements = submodule.statement();
420 for (final StatementContext subStatementContext : subStatements) {
421 if (subStatementContext
424 .equals(Rfc6020Mapping.BELONGS_TO.getStatementName()
426 return Utils.stringFromStringContext(subStatementContext
433 private static YangModelDependencyInfo parseSubmoduleContext(
434 final Submodule_stmtContext submodule) {
435 final String name = getArgumentString(submodule);
436 final Belongs_to_stmtContext belongsToStmt = submodule
437 .submodule_header_stmts().belongs_to_stmt(0);
438 final String belongsTo = getArgumentString(belongsToStmt);
440 final String latestRevision = getLatestRevision(submodule.revision_stmts());
441 final ImmutableSet<ModuleImport> imports = parseImports(submodule
442 .linkage_stmts().import_stmt());
443 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule
444 .linkage_stmts().include_stmt());
446 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo,
450 private static ImmutableSet<ModuleImport> parseIncludes(
451 final List<Include_stmtContext> importStatements) {
452 final ImmutableSet.Builder<ModuleImport> builder = ImmutableSet.builder();
453 for (final Include_stmtContext importStmt : importStatements) {
454 final String moduleName = getArgumentString(importStmt);
455 final Date revision = getRevision(importStmt.revision_date_stmt());
456 builder.add(new ModuleImportImpl(moduleName, revision));
458 return builder.build();
461 private static Date getRevision(
462 final Revision_date_stmtContext revisionDateStmt) {
463 if (revisionDateStmt == null) {
466 final String formatedDate = getArgumentString(revisionDateStmt);
467 return QName.parseRevision(formatedDate);
472 * Dependency information for YANG module.
475 public static class ModuleDependencyInfo extends
476 YangModelDependencyInfo {
478 private ModuleDependencyInfo(final String name,
479 final String latestRevision,
480 final ImmutableSet<ModuleImport> imports,
481 final ImmutableSet<ModuleImport> includes) {
482 super(name, latestRevision, imports, includes);
485 private ModuleDependencyInfo(final String name,
486 final String latestRevision,
487 final ImmutableSet<ModuleImport> imports,
488 final ImmutableSet<ModuleImport> includes,
489 final Optional<SemVer> semVer) {
490 super(name, latestRevision, imports, includes, semVer);
494 public String toString() {
495 return "Module [name=" + getName() + ", revision=" + getRevision() + ", semanticVersion="
496 + getSemanticVersion().or(Module.DEFAULT_SEMANTIC_VERSION) + ", dependencies=" + getDependencies()
503 * Dependency information for submodule, also provides name for parent
507 public static final class SubmoduleDependencyInfo extends
508 YangModelDependencyInfo {
510 private final String belongsTo;
512 private SubmoduleDependencyInfo(final String name,
513 final String latestRevision, final String belongsTo,
514 final ImmutableSet<ModuleImport> imports,
515 final ImmutableSet<ModuleImport> includes) {
516 super(name, latestRevision, imports, includes);
517 this.belongsTo = belongsTo;
521 * Returns name of parent module.
524 public String getParentModule() {
529 public String toString() {
530 return "Submodule [name=" + getName() + ", revision="
531 + getRevision() + ", dependencies=" + getDependencies()
537 * Utility implementation of {@link ModuleImport} to be used by
538 * {@link YangModelDependencyInfo}.
541 private static final class ModuleImportImpl implements ModuleImport {
543 private final Date revision;
544 private final SemVer semVer;
545 private final String name;
547 public ModuleImportImpl(final String moduleName, final Date revision) {
548 this(moduleName, revision, Optional.absent());
551 public ModuleImportImpl(final String moduleName, final Date revision, final Optional<SemVer> semVer) {
552 this.name = Preconditions.checkNotNull(moduleName, "Module name must not be null.");
553 this.revision = revision;
554 this.semVer = semVer.or(Module.DEFAULT_SEMANTIC_VERSION);
558 public String getModuleName() {
563 public Date getRevision() {
564 return this.revision;
568 public SemVer getSemanticVersion() {
573 public String getPrefix() {
578 public int hashCode() {
579 final int prime = 31;
581 result = prime * result + Objects.hashCode(name);
582 result = prime * result + Objects.hashCode(revision);
583 result = prime * result + Objects.hashCode(semVer);
588 public boolean equals(final Object obj) {
595 if (getClass() != obj.getClass()) {
598 final ModuleImportImpl other = (ModuleImportImpl) obj;
600 if (other.name != null) {
603 } else if (!name.equals(other.name)) {
606 if (revision == null) {
607 if (other.revision != null) {
610 } else if (!revision.equals(other.revision)) {
614 if (!Objects.equals(getSemanticVersion(), other.getSemanticVersion())) {
621 public String toString() {
622 return "ModuleImportImpl [name=" + name + ", revision="
623 + QName.formattedRevision(revision) + ", semanticVersion=" + getSemanticVersion() + "]";