/* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.yang.parser.impl.util; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import java.io.InputStream; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.antlr.v4.runtime.ParserRuleContext; import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.StatementContext; import org.opendaylight.yangtools.concepts.SemVer; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.ModuleImport; import org.opendaylight.yangtools.yang.model.api.YangStmtMapping; import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource; import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.SupportedExtensionsMapping; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl; import org.opendaylight.yangtools.yang.parser.util.NamedInputStream; /** * Helper transfer object which holds basic and dependency information for YANG * model. * * * * There are two concrete implementations of this interface: * <ul> * <li>{@link ModuleDependencyInfo} - Dependency information for module</li> * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li> * </ul> * * @see ModuleDependencyInfo * @see SubmoduleDependencyInfo * */ public abstract class YangModelDependencyInfo { private final String name; private final String formattedRevision; private final Date revision; private final Optional<SemVer> semVer; private final ImmutableSet<ModuleImport> submoduleIncludes; private final ImmutableSet<ModuleImport> moduleImports; private final ImmutableSet<ModuleImport> dependencies; YangModelDependencyInfo(final String name, final String formattedRevision, final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) { this(name, formattedRevision, imports, includes, Optional.absent()); } YangModelDependencyInfo(final String name, final String formattedRevision, final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes, final Optional<SemVer> semVer) { this.name = name; this.formattedRevision = formattedRevision; this.revision = formattedRevision == null ? null : QName .parseRevision(formattedRevision); this.moduleImports = imports; this.submoduleIncludes = includes; this.dependencies = ImmutableSet.<ModuleImport> builder() .addAll(moduleImports).addAll(submoduleIncludes).build(); this.semVer = semVer; } /** * Returns immutable collection of all module imports. * * This collection contains both <code>import</code> statements and * <code>include</code> statements for submodules. * * @return Immutable collection of imports. */ public ImmutableSet<ModuleImport> getDependencies() { return dependencies; } /** * Returns model name * * @return model name */ public String getName() { return name; } /** * Returns formatted revision string * * @return formatted revision string */ public String getFormattedRevision() { return formattedRevision; } /** * Returns revision * * @return revision */ Date getRevision() { return revision; } /** * Returns semantic version of module * * @return semantic version */ public Optional<SemVer> getSemanticVersion() { return semVer; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Objects.hashCode(formattedRevision); result = prime * result + Objects.hashCode(name); result = prime * result + Objects.hashCode(semVer); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof YangModelDependencyInfo)) { return false; } final YangModelDependencyInfo other = (YangModelDependencyInfo) obj; if (formattedRevision == null) { if (other.formattedRevision != null) { return false; } } else if (!formattedRevision.equals(other.formattedRevision)) { return false; } if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } if(!Objects.equals(semVer, other.semVer)) { return false; } return true; } /** * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of * a YANG model. * * @param tree * Abstract syntax tree * @return {@link YangModelDependencyInfo} * @throws YangSyntaxErrorException * If the AST is not a valid YANG module/submodule */ public static YangModelDependencyInfo fromAST(final String name, final ParserRuleContext tree) throws YangSyntaxErrorException { if (tree instanceof StatementContext) { final StatementContext rootStatement = (StatementContext) tree; return parseAST(rootStatement, name); } throw new YangSyntaxErrorException(name, 0, 0, "Unknown YANG text type"); } private static YangModelDependencyInfo parseAST(final StatementContext rootStatement, final String sourceName) { final String keyWordText = rootStatement.keyword().getText(); if (YangStmtMapping.MODULE.getStatementName().getLocalName().equals(keyWordText)) { return parseModuleContext(rootStatement, sourceName); } if (YangStmtMapping.SUBMODULE.getStatementName().getLocalName().equals(keyWordText)) { return parseSubmoduleContext(rootStatement, sourceName); } throw new IllegalArgumentException("Root of parsed AST must be either module or submodule"); } /** * Extracts {@link YangModelDependencyInfo} from input stream containing * YANG model. * * This parsing does not validate full YANG module, only parses header up to * the revisions and imports. * * @param yangStream * Opened Input stream containing text source of YANG model * @return {@link YangModelDependencyInfo} * @throws IllegalArgumentException * If input stream is not valid YANG stream */ public static YangModelDependencyInfo fromInputStream(final InputStream yangStream) { final StatementContext yangAST = new YangStatementSourceImpl(yangStream).getYangAST(); return parseAST(yangAST, yangStream instanceof NamedInputStream ? yangStream.toString() : null); } private static YangModelDependencyInfo parseModuleContext(final StatementContext module, final String sourceName) { final String name = Utils.stringFromStringContext(module.argument(), getReference(sourceName, module)); final String latestRevision = getLatestRevision(module, sourceName); final Optional<SemVer> semVer = Optional.fromNullable(getSemanticVersion(module, sourceName)); final ImmutableSet<ModuleImport> imports = parseImports(module, sourceName); final ImmutableSet<ModuleImport> includes = parseIncludes(module, sourceName); return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer); } private static ImmutableSet<ModuleImport> parseImports(final StatementContext module, final String sourceName) { final Set<ModuleImport> result = new HashSet<>(); final List<StatementContext> subStatements = module.statement(); for (final StatementContext subStatementContext : subStatements) { if (subStatementContext .keyword() .getText() .equals(YangStmtMapping.IMPORT.getStatementName() .getLocalName())) { final String revisionDateStr = getRevisionDateString(subStatementContext, sourceName); final String importedModuleName = Utils.stringFromStringContext(subStatementContext.argument(), getReference(sourceName, subStatementContext)); final Date revisionDate = (revisionDateStr == null) ? null : QName .parseRevision(revisionDateStr); final Optional<SemVer> importSemVer = Optional.fromNullable(getSemanticVersion(subStatementContext, sourceName)); result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer)); } } return ImmutableSet.copyOf(result); } private static SemVer getSemanticVersion(final StatementContext statement, final String sourceName) { final List<StatementContext> subStatements = statement.statement(); String semVerString = null; final String semVerStmtName = SupportedExtensionsMapping.SEMANTIC_VERSION.getStatementName().getLocalName(); for (final StatementContext subStatement : subStatements) { final String subStatementName = Utils.trimPrefix(subStatement.keyword().getText()); if (semVerStmtName.equals(subStatementName)) { semVerString = Utils.stringFromStringContext(subStatement.argument(), getReference(sourceName, subStatement)); break; } } if (Strings.isNullOrEmpty(semVerString)) { return null; } return SemVer.valueOf(semVerString); } private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module, final String sourceName) { final Set<ModuleImport> result = new HashSet<>(); final List<StatementContext> subStatements = module.statement(); for (final StatementContext subStatementContext : subStatements) { if (subStatementContext .keyword() .getText() .equals(YangStmtMapping.INCLUDE.getStatementName() .getLocalName())) { final String revisionDateStr = getRevisionDateString(subStatementContext, sourceName); final String IncludeModuleName = Utils.stringFromStringContext(subStatementContext.argument(), getReference(sourceName, subStatementContext)); final Date revisionDate = (revisionDateStr == null) ? null : QName .parseRevision(revisionDateStr); result.add(new ModuleImportImpl(IncludeModuleName, revisionDate)); } } return ImmutableSet.copyOf(result); } private static String getRevisionDateString(final StatementContext importStatement, final String sourceName) { final List<StatementContext> importSubStatements = importStatement.statement(); String revisionDateStr = null; for (final StatementContext importSubStatement : importSubStatements) { if (importSubStatement .keyword() .getText() .equals(YangStmtMapping.REVISION_DATE.getStatementName() .getLocalName())) { revisionDateStr = Utils.stringFromStringContext(importSubStatement.argument(), getReference(sourceName, importSubStatement)); } } return revisionDateStr; } public static String getLatestRevision(final StatementContext module, final String sourceName) { final List<StatementContext> subStatements = module.statement(); String latestRevision = null; for (final StatementContext subStatementContext : subStatements) { if (subStatementContext .keyword() .getText() .equals(YangStmtMapping.REVISION.getStatementName() .getLocalName())) { final String currentRevision = Utils.stringFromStringContext(subStatementContext.argument(), getReference(sourceName, subStatementContext)); if (latestRevision == null || latestRevision.compareTo(currentRevision) == -1) { latestRevision = currentRevision; } } } return latestRevision; } private static YangModelDependencyInfo parseSubmoduleContext(final StatementContext submodule, final String sourceName) { final String name = Utils.stringFromStringContext(submodule.argument(), getReference(sourceName, submodule)); final String belongsTo = parseBelongsTo(submodule, sourceName); final String latestRevision = getLatestRevision(submodule, sourceName); final ImmutableSet<ModuleImport> imports = parseImports(submodule, sourceName); final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, sourceName); return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes); } private static String parseBelongsTo(final StatementContext submodule, final String sourceName) { final List<StatementContext> subStatements = submodule.statement(); for (final StatementContext subStatementContext : subStatements) { if (subStatementContext .keyword() .getText() .equals(YangStmtMapping.BELONGS_TO.getStatementName() .getLocalName())) { return Utils.stringFromStringContext(subStatementContext .argument(), getReference(sourceName, subStatementContext)); } } return null; } private static StatementSourceReference getReference(final String sourceName, final StatementContext context) { return DeclarationInTextSource.atPosition(sourceName, context.getStart().getLine(), context.getStart() .getCharPositionInLine()); } /** * * Dependency information for YANG module. * */ public static class ModuleDependencyInfo extends YangModelDependencyInfo { private ModuleDependencyInfo(final String name, final String latestRevision, final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) { super(name, latestRevision, imports, includes); } private ModuleDependencyInfo(final String name, final String latestRevision, final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes, final Optional<SemVer> semVer) { super(name, latestRevision, imports, includes, semVer); } @Override public String toString() { return "Module [name=" + getName() + ", revision=" + getRevision() + ", semanticVersion=" + getSemanticVersion().or(Module.DEFAULT_SEMANTIC_VERSION) + ", dependencies=" + getDependencies() + "]"; } } /** * * Dependency information for submodule, also provides name for parent * module. * */ public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo { private final String belongsTo; private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo, final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) { super(name, latestRevision, imports, includes); this.belongsTo = belongsTo; } /** * Returns name of parent module. * */ public String getParentModule() { return belongsTo; } @Override public String toString() { return "Submodule [name=" + getName() + ", revision=" + getRevision() + ", dependencies=" + getDependencies() + "]"; } } /** * Utility implementation of {@link ModuleImport} to be used by * {@link YangModelDependencyInfo}. * */ private static final class ModuleImportImpl implements ModuleImport { private final Date revision; private final SemVer semVer; private final String name; public ModuleImportImpl(final String moduleName, final Date revision) { this(moduleName, revision, Optional.absent()); } public ModuleImportImpl(final String moduleName, final Date revision, final Optional<SemVer> semVer) { this.name = Preconditions.checkNotNull(moduleName, "Module name must not be null."); this.revision = revision; this.semVer = semVer.or(Module.DEFAULT_SEMANTIC_VERSION); } @Override public String getModuleName() { return this.name; } @Override public Date getRevision() { return this.revision; } @Override public SemVer getSemanticVersion() { return this.semVer; } @Override public String getPrefix() { return null; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Objects.hashCode(name); result = prime * result + Objects.hashCode(revision); result = prime * result + Objects.hashCode(semVer); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final ModuleImportImpl other = (ModuleImportImpl) obj; if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } if (revision == null) { if (other.revision != null) { return false; } } else if (!revision.equals(other.revision)) { return false; } if (!Objects.equals(getSemanticVersion(), other.getSemanticVersion())) { return false; } return true; } @Override public String toString() { return "ModuleImportImpl [name=" + name + ", revision=" + QName.formattedRevision(revision) + ", semanticVersion=" + getSemanticVersion() + "]"; } } }