BUG-4688: Make SourceIdentifier use Revision
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / impl / util / YangModelDependencyInfo.java
index 6530c92510c8d6aabd43eb5640a4ae146b5c71ca..09bf8a2588a2a4b2bd09737b0ca4ea57766beaa9 100644 (file)
  *
  * 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/eplv10.html
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
 package org.opendaylight.yangtools.yang.parser.impl.util;
 
-import static org.opendaylight.yangtools.yang.parser.util.ParserListenerUtils.getArgumentString;
-import static org.opendaylight.yangtools.yang.parser.util.ParserListenerUtils.getFirstContext;
-
-import java.io.InputStream;
-import java.util.Date;
-import java.util.List;
-
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Belongs_to_stmtContext;
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Import_stmtContext;
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Include_stmtContext;
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Module_stmtContext;
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_date_stmtContext;
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_stmtContext;
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_stmtsContext;
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Submodule_stmtContext;
-import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.YangContext;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.ModuleImport;
-import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
 
-import com.google.common.base.Optional;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
-
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Nullable;
+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.common.Revision;
+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.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.parser.rfc6020.repo.YangStatementStreamSource;
+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;
+
+/**
+ * Helper transfer object which holds basic and dependency information for YANG
+ * model.
+ *
+ * <p>
+ * 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 static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
+    private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
+    private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
+    private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
+    private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
+    private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
+    private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
+
+    private static final String OPENCONFIG_VERSION = SupportedExtensionsMapping.OPENCONFIG_VERSION.getStatementName()
+            .getLocalName();
 
     private final String name;
-    private final String formattedRevision;
-    private final Date revision;
+    private final Revision revision;
+    private final SemVer semVer;
     private final ImmutableSet<ModuleImport> submoduleIncludes;
     private final ImmutableSet<ModuleImport> moduleImports;
     private final ImmutableSet<ModuleImport> dependencies;
 
-    public YangModelDependencyInfo(final String name, final String formattedRevision, final ImmutableSet<ModuleImport> imports,
+    YangModelDependencyInfo(final String name, final String formattedRevision,
+            final ImmutableSet<ModuleImport> imports,
             final ImmutableSet<ModuleImport> includes) {
+        this(name, formattedRevision, imports, includes, Optional.empty());
+    }
+
+    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 = QName.parseRevision(formattedRevision);
+        this.revision = formattedRevision == null ? null : Revision.valueOf(formattedRevision);
         this.moduleImports = imports;
         this.submoduleIncludes = includes;
-        this.dependencies = ImmutableSet.<ModuleImport> builder() //
-                .addAll(moduleImports) //
-                .addAll(submoduleIncludes) //
-                .build();
+        this.dependencies = ImmutableSet.<ModuleImport>builder()
+                .addAll(moduleImports).addAll(submoduleIncludes).build();
+        this.semVer = semVer.orElse(null);
     }
 
+    /**
+     * 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;
+        return revision != null ? revision.toString() : null;
+    }
+
+    /**
+     * Returns revision.
+     *
+     * @return revision, potentially null
+     */
+    public Optional<Revision> getRevision() {
+        return Optional.ofNullable(revision);
     }
 
-    public Date getRevision() {
-        return revision;
+    /**
+     * Returns semantic version of module.
+     *
+     * @return semantic version
+     */
+    public Optional<SemVer> getSemanticVersion() {
+        return Optional.ofNullable(semVer);
     }
 
     @Override
     public int hashCode() {
         final int prime = 31;
         int result = 1;
-        result = prime * result + ((formattedRevision == null) ? 0 : formattedRevision.hashCode());
-        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + Objects.hashCode(name);
+        result = prime * result + Objects.hashCode(revision);
+        result = prime * result + Objects.hashCode(semVer);
         return result;
     }
 
@@ -88,155 +152,251 @@ public abstract class YangModelDependencyInfo {
         if (!(obj instanceof YangModelDependencyInfo)) {
             return false;
         }
-        YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
-        if (formattedRevision == null) {
-            if (other.formattedRevision != null) {
-                return false;
-            }
-        } else if (!formattedRevision.equals(other.formattedRevision)) {
-            return false;
+        final YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
+        return Objects.equals(name, other.name) && Objects.equals(revision, other.revision)
+                && Objects.equals(semVer, other.semVer);
+    }
+
+    /**
+     * 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);
         }
-        if (name == null) {
-            if (other.name != null) {
-                return false;
-            }
-        } else if (!name.equals(other.name)) {
-            return false;
+
+        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 (MODULE.equals(keyWordText)) {
+            return parseModuleContext(rootStatement, sourceName);
         }
-        return true;
+        if (SUBMODULE.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 a YANG model. This parsing does not
+     * validate full YANG module, only parses header up to the revisions and imports.
+     *
+     * @param refClass Base search class
+     * @param resourceName resource name, relative to refClass
+     * @return {@link YangModelDependencyInfo}
+     * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
+     * @throws IOException When the resource cannot be read
+     * @throws IllegalArgumentException
+     *             If input stream is not valid YANG stream
+     */
+    public static YangModelDependencyInfo forResource(final Class<?> refClass, final String resourceName)
+            throws IOException, YangSyntaxErrorException {
+        final YangStatementStreamSource source = YangStatementStreamSource.create(
+            YangTextSchemaSource.forResource(refClass, resourceName));
+        final ParserRuleContext ast = source.getYangAST();
+        checkArgument(ast instanceof StatementContext);
+        return parseAST((StatementContext) ast, source.getIdentifier().toYangFilename());
     }
 
-    public static YangModelDependencyInfo fromInputStream(final InputStream yangStream) {
-        YangContext yangContext = YangParserImpl.parseStreamWithoutErrorListeners(yangStream);
+    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.ofNullable(findSemanticVersion(module, sourceName));
+        final ImmutableSet<ModuleImport> imports = parseImports(module, sourceName);
+        final ImmutableSet<ModuleImport> includes = parseIncludes(module, sourceName);
 
-        Optional<Module_stmtContext> moduleCtx = getFirstContext(yangContext, Module_stmtContext.class);
-        if (moduleCtx.isPresent()) {
-            return fromModuleContext(moduleCtx.get());
-        }
-        Optional<Submodule_stmtContext> submoduleCtx = getFirstContext(yangContext, Submodule_stmtContext.class);
-        if (submoduleCtx.isPresent()) {
-            return fromSubmoduleContext(submoduleCtx.get());
+        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<>();
+        for (final StatementContext subStatementContext : module.statement()) {
+            if (IMPORT.equals(subStatementContext.keyword().getText())) {
+                final String revisionDateStr = getRevisionDateString(subStatementContext, sourceName);
+                final String importedModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
+                        getReference(sourceName, subStatementContext));
+                final Revision revisionDate = revisionDateStr == null ? null : Revision.valueOf(revisionDateStr);
+                final SemVer importSemVer = findSemanticVersion(subStatementContext, sourceName);
+                result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
+            }
         }
-        throw new IllegalArgumentException("Supplied stream is not valid yang file.");
+        return ImmutableSet.copyOf(result);
     }
 
-    private static YangModelDependencyInfo fromModuleContext(final Module_stmtContext module) {
-        String name = getArgumentString(module);
-        // String prefix =
-        // getArgumentString(module.module_header_stmts().prefix_stmt(0));
-        String namespace = getArgumentString(module.module_header_stmts().namespace_stmt(0));
-        String latestRevision = getLatestRevision(module.revision_stmts());
-        ImmutableSet<ModuleImport> imports = getImports(module.linkage_stmts().import_stmt());
-        ImmutableSet<ModuleImport> includes = getIncludes(module.linkage_stmts().include_stmt());
+    private static SemVer findSemanticVersion(final StatementContext statement, final String sourceName) {
+        String semVerString = null;
+        for (final StatementContext subStatement : statement.statement()) {
+            final String subStatementName = Utils.trimPrefix(subStatement.keyword().getText());
+            if (OPENCONFIG_VERSION.equals(subStatementName)) {
+                semVerString = Utils.stringFromStringContext(subStatement.argument(),
+                        getReference(sourceName, subStatement));
+                break;
+            }
+        }
+
+        return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
+    }
 
-        return new ModuleDependencyInfo(name, latestRevision, namespace, imports, includes);
+    private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module, final String sourceName) {
+        final Set<ModuleImport> result = new HashSet<>();
+        for (final StatementContext subStatementContext : module.statement()) {
+            if (INCLUDE.equals(subStatementContext.keyword().getText())) {
+                final String revisionDateStr = getRevisionDateString(subStatementContext, sourceName);
+                final String IncludeModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
+                        getReference(sourceName, subStatementContext));
+                final Revision revisionDate = revisionDateStr == null ? null : Revision.valueOf(revisionDateStr);
+                result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
+            }
+        }
+        return ImmutableSet.copyOf(result);
     }
 
-    private static ImmutableSet<ModuleImport> getImports(final List<Import_stmtContext> importStatements) {
-        ImmutableSet.Builder<ModuleImport> builder = ImmutableSet.builder();
-        for (Import_stmtContext importStmt : importStatements) {
-            String moduleName = getArgumentString(importStmt);
-            Date revision = getRevision(importStmt.revision_date_stmt());
-            builder.add(new ModuleImportImpl(moduleName, revision));
+    private static String getRevisionDateString(final StatementContext importStatement, final String sourceName) {
+        String revisionDateStr = null;
+        for (final StatementContext importSubStatement : importStatement.statement()) {
+            if (REVISION_DATE.equals(importSubStatement.keyword().getText())) {
+                revisionDateStr = Utils.stringFromStringContext(importSubStatement.argument(),
+                        getReference(sourceName, importSubStatement));
+            }
         }
-        return builder.build();
+        return revisionDateStr;
     }
 
-    private static String getLatestRevision(final Revision_stmtsContext revision_stmts) {
-        List<Revision_stmtContext> revisions = revision_stmts.getRuleContexts(Revision_stmtContext.class);
+    public static String getLatestRevision(final StatementContext module, final String sourceName) {
         String latestRevision = null;
-        for (Revision_stmtContext revisionStmt : revisions) {
-            String currentRevision = getArgumentString(revisionStmt);
-            if (latestRevision == null || latestRevision.compareTo(currentRevision) == -1) {
-                latestRevision = currentRevision;
+        for (final StatementContext subStatementContext : module.statement()) {
+            if (REVISION.equals(subStatementContext.keyword().getText())) {
+                final String currentRevision = Utils.stringFromStringContext(subStatementContext.argument(),
+                        getReference(sourceName, subStatementContext));
+                if (latestRevision == null || latestRevision.compareTo(currentRevision) == -1) {
+                    latestRevision = currentRevision;
+                }
             }
         }
         return latestRevision;
     }
 
-    private static YangModelDependencyInfo fromSubmoduleContext(final Submodule_stmtContext submodule) {
-        String name = getArgumentString(submodule);
-        Belongs_to_stmtContext belongsToStmt = submodule.submodule_header_stmts().belongs_to_stmt(0);
-        String belongsTo = getArgumentString(belongsToStmt);
+    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);
 
-        String latestRevision = getLatestRevision(submodule.revision_stmts());
-        ImmutableSet<ModuleImport> imports = getImports(submodule.linkage_stmts().import_stmt());
-        ImmutableSet<ModuleImport> includes = getIncludes(submodule.linkage_stmts().include_stmt());
+        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 ImmutableSet<ModuleImport> getIncludes(final List<Include_stmtContext> importStatements) {
-        ImmutableSet.Builder<ModuleImport> builder = ImmutableSet.builder();
-        for (Include_stmtContext importStmt : importStatements) {
-            String moduleName = getArgumentString(importStmt);
-            Date revision = getRevision(importStmt.revision_date_stmt());
-            builder.add(new ModuleImportImpl(moduleName, revision));
+    private static String parseBelongsTo(final StatementContext submodule, final String sourceName) {
+        for (final StatementContext subStatementContext : submodule.statement()) {
+            if (BELONGS_TO.equals(subStatementContext.keyword().getText())) {
+                return Utils.stringFromStringContext(subStatementContext.argument(),
+                    getReference(sourceName, subStatementContext));
+            }
         }
-        return builder.build();
+        return null;
     }
 
-    private static Date getRevision(final Revision_date_stmtContext revision_date_stmt) {
-        if (revision_date_stmt == null) {
-            return null;
-        }
-        String formatedDate = getArgumentString(revision_date_stmt);
-        return QName.parseRevision(formatedDate);
+    private static StatementSourceReference getReference(final String sourceName,
+            final StatementContext context) {
+        return DeclarationInTextSource.atPosition(sourceName, context.getStart().getLine(),
+            context.getStart().getCharPositionInLine());
     }
 
-    public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
-
-        private ModuleDependencyInfo(final String name, final String latestRevision, final String namespace,
+    /**
+     * 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()
-                    + ", dependencies=" + getDependencies() + "]";
+            + ", semanticVersion=" + getSemanticVersion().orElse(null)
+            + ", dependencies=" + getDependencies()
+            + "]";
         }
     }
 
+    /**
+     * Dependency information for submodule, also provides name for parent module.
+     */
     public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
-
         private final String belongsTo;
 
-        public String getParentModule() {
-            return 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() + "]";
+            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 Revision revision;
+        private final SemVer semVer;
         private final String name;
 
-        public ModuleImportImpl(final String moduleName, final Date revision) {
-            this.name = moduleName;
+        ModuleImportImpl(final String moduleName, final Revision revision) {
+            this(moduleName, revision, null);
+        }
+
+        ModuleImportImpl(final String moduleName, @Nullable final Revision revision, @Nullable final SemVer semVer) {
+            this.name = requireNonNull(moduleName, "Module name must not be null.");
             this.revision = revision;
+            this.semVer = semVer;
         }
 
         @Override
         public String getModuleName() {
-            return this.name;
+            return name;
+        }
+
+        @Override
+        public Optional<Revision> getRevision() {
+            return Optional.ofNullable(revision);
         }
 
         @Override
-        public Date getRevision() {
-            return this.revision;
+        public Optional<SemVer> getSemanticVersion() {
+            return Optional.ofNullable(semVer);
         }
 
         @Override
@@ -248,8 +408,9 @@ public abstract class YangModelDependencyInfo {
         public int hashCode() {
             final int prime = 31;
             int result = 1;
-            result = prime * result + ((name == null) ? 0 : name.hashCode());
-            result = prime * result + ((revision == null) ? 0 : revision.hashCode());
+            result = prime * result + Objects.hashCode(name);
+            result = prime * result + Objects.hashCode(revision);
+            result = prime * result + Objects.hashCode(semVer);
             return result;
         }
 
@@ -264,7 +425,7 @@ public abstract class YangModelDependencyInfo {
             if (getClass() != obj.getClass()) {
                 return false;
             }
-            ModuleImportImpl other = (ModuleImportImpl) obj;
+            final ModuleImportImpl other = (ModuleImportImpl) obj;
             if (name == null) {
                 if (other.name != null) {
                     return false;
@@ -279,12 +440,17 @@ public abstract class YangModelDependencyInfo {
             } 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) + "]";
+            return "ModuleImportImpl [name=" + name + ", revision="
+                    + QName.formattedRevision(Optional.ofNullable(revision)) + ", semanticVersion=" + semVer + "]";
         }
     }
 }