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.Date;
17 import java.util.HashSet;
18 import java.util.Objects;
19 import java.util.Optional;
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.model.api.Module;
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.YangTextSchemaSource;
30 import org.opendaylight.yangtools.yang.parser.rfc6020.repo.YangStatementStreamSource;
31 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
32 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
33 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.SupportedExtensionsMapping;
34 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
37 * Helper transfer object which holds basic and dependency information for YANG
41 * There are two concrete implementations of this interface:
43 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
44 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
47 * @see ModuleDependencyInfo
48 * @see SubmoduleDependencyInfo
50 public abstract class YangModelDependencyInfo {
51 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
52 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
53 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
54 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
55 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
56 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
57 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
59 private static final String OPENCONFIG_VERSION = SupportedExtensionsMapping.OPENCONFIG_VERSION.getStatementName()
62 private final String name;
63 private final String formattedRevision;
64 private final Date revision;
65 private final Optional<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.formattedRevision = formattedRevision;
82 this.revision = formattedRevision == null ? null : QName
83 .parseRevision(formattedRevision);
84 this.moduleImports = imports;
85 this.submoduleIncludes = includes;
86 this.dependencies = ImmutableSet.<ModuleImport>builder()
87 .addAll(moduleImports).addAll(submoduleIncludes).build();
92 * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
93 * and <code>include</code> statements for submodules.
95 * @return Immutable collection of imports.
97 public ImmutableSet<ModuleImport> getDependencies() {
102 * Returns model name.
106 public String getName() {
111 * Returns formatted revision string.
113 * @return formatted revision string
115 public String getFormattedRevision() {
116 return formattedRevision;
129 * Returns semantic version of module.
131 * @return semantic version
133 public Optional<SemVer> getSemanticVersion() {
138 public int hashCode() {
139 final int prime = 31;
141 result = prime * result + Objects.hashCode(formattedRevision);
142 result = prime * result + Objects.hashCode(name);
143 result = prime * result + Objects.hashCode(semVer);
148 public boolean equals(final Object obj) {
155 if (!(obj instanceof YangModelDependencyInfo)) {
158 final YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
159 if (formattedRevision == null) {
160 if (other.formattedRevision != null) {
163 } else if (!formattedRevision.equals(other.formattedRevision)) {
167 if (other.name != null) {
170 } else if (!name.equals(other.name)) {
173 return Objects.equals(semVer, other.semVer);
177 * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of a YANG model.
179 * @param tree Abstract syntax tree
180 * @return {@link YangModelDependencyInfo}
181 * @throws YangSyntaxErrorException If the AST is not a valid YANG module/submodule
183 public static YangModelDependencyInfo fromAST(final String name,
184 final ParserRuleContext tree) throws YangSyntaxErrorException {
186 if (tree instanceof StatementContext) {
187 final StatementContext rootStatement = (StatementContext) tree;
188 return parseAST(rootStatement, name);
191 throw new YangSyntaxErrorException(name, 0, 0, "Unknown YANG text type");
194 private static YangModelDependencyInfo parseAST(final StatementContext rootStatement, final String sourceName) {
195 final String keyWordText = rootStatement.keyword().getText();
196 if (MODULE.equals(keyWordText)) {
197 return parseModuleContext(rootStatement, sourceName);
199 if (SUBMODULE.equals(keyWordText)) {
200 return parseSubmoduleContext(rootStatement, sourceName);
202 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
206 * Extracts {@link YangModelDependencyInfo} from input stream containing a YANG model. This parsing does not
207 * validate full YANG module, only parses header up to the revisions and imports.
209 * @param refClass Base search class
210 * @param resourceName resource name, relative to refClass
211 * @return {@link YangModelDependencyInfo}
212 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
213 * @throws IOException When the resource cannot be read
214 * @throws IllegalArgumentException
215 * If input stream is not valid YANG stream
217 public static YangModelDependencyInfo forResource(final Class<?> refClass, final String resourceName)
218 throws IOException, YangSyntaxErrorException {
219 final YangStatementStreamSource source = YangStatementStreamSource.create(
220 YangTextSchemaSource.forResource(refClass, resourceName));
221 final ParserRuleContext ast = source.getYangAST();
222 checkArgument(ast instanceof StatementContext);
223 return parseAST((StatementContext) ast, source.getIdentifier().toYangFilename());
226 private static YangModelDependencyInfo parseModuleContext(final StatementContext module, final String sourceName) {
227 final String name = Utils.stringFromStringContext(module.argument(), getReference(sourceName, module));
228 final String latestRevision = getLatestRevision(module, sourceName);
229 final Optional<SemVer> semVer = Optional.ofNullable(findSemanticVersion(module, sourceName));
230 final ImmutableSet<ModuleImport> imports = parseImports(module, sourceName);
231 final ImmutableSet<ModuleImport> includes = parseIncludes(module, sourceName);
233 return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
236 private static ImmutableSet<ModuleImport> parseImports(final StatementContext module, final String sourceName) {
237 final Set<ModuleImport> result = new HashSet<>();
238 for (final StatementContext subStatementContext : module.statement()) {
239 if (IMPORT.equals(subStatementContext.keyword().getText())) {
240 final String revisionDateStr = getRevisionDateString(subStatementContext, sourceName);
241 final String importedModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
242 getReference(sourceName, subStatementContext));
243 final Date revisionDate = revisionDateStr == null ? null : QName.parseRevision(revisionDateStr);
244 final Optional<SemVer> importSemVer = Optional.ofNullable(findSemanticVersion(subStatementContext,
246 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
249 return ImmutableSet.copyOf(result);
252 private static SemVer findSemanticVersion(final StatementContext statement, final String sourceName) {
253 String semVerString = null;
254 for (final StatementContext subStatement : statement.statement()) {
255 final String subStatementName = Utils.trimPrefix(subStatement.keyword().getText());
256 if (OPENCONFIG_VERSION.equals(subStatementName)) {
257 semVerString = Utils.stringFromStringContext(subStatement.argument(),
258 getReference(sourceName, subStatement));
263 return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
266 private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module, final String sourceName) {
267 final Set<ModuleImport> result = new HashSet<>();
268 for (final StatementContext subStatementContext : module.statement()) {
269 if (INCLUDE.equals(subStatementContext.keyword().getText())) {
270 final String revisionDateStr = getRevisionDateString(subStatementContext, sourceName);
271 final String IncludeModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
272 getReference(sourceName, subStatementContext));
273 final Date revisionDate = revisionDateStr == null ? null : QName.parseRevision(revisionDateStr);
274 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
277 return ImmutableSet.copyOf(result);
280 private static String getRevisionDateString(final StatementContext importStatement, final String sourceName) {
281 String revisionDateStr = null;
282 for (final StatementContext importSubStatement : importStatement.statement()) {
283 if (REVISION_DATE.equals(importSubStatement.keyword().getText())) {
284 revisionDateStr = Utils.stringFromStringContext(importSubStatement.argument(),
285 getReference(sourceName, importSubStatement));
288 return revisionDateStr;
291 public static String getLatestRevision(final StatementContext module, final String sourceName) {
292 String latestRevision = null;
293 for (final StatementContext subStatementContext : module.statement()) {
294 if (REVISION.equals(subStatementContext.keyword().getText())) {
295 final String currentRevision = Utils.stringFromStringContext(subStatementContext.argument(),
296 getReference(sourceName, subStatementContext));
297 if (latestRevision == null || latestRevision.compareTo(currentRevision) == -1) {
298 latestRevision = currentRevision;
302 return latestRevision;
305 private static YangModelDependencyInfo parseSubmoduleContext(final StatementContext submodule,
306 final String sourceName) {
307 final String name = Utils.stringFromStringContext(submodule.argument(), getReference(sourceName, submodule));
308 final String belongsTo = parseBelongsTo(submodule, sourceName);
310 final String latestRevision = getLatestRevision(submodule, sourceName);
311 final ImmutableSet<ModuleImport> imports = parseImports(submodule, sourceName);
312 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, sourceName);
314 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
317 private static String parseBelongsTo(final StatementContext submodule, final String sourceName) {
318 for (final StatementContext subStatementContext : submodule.statement()) {
319 if (BELONGS_TO.equals(subStatementContext.keyword().getText())) {
320 return Utils.stringFromStringContext(subStatementContext.argument(),
321 getReference(sourceName, subStatementContext));
327 private static StatementSourceReference getReference(final String sourceName,
328 final StatementContext context) {
329 return DeclarationInTextSource.atPosition(sourceName, context.getStart().getLine(),
330 context.getStart().getCharPositionInLine());
334 * Dependency information for YANG module.
336 public static class ModuleDependencyInfo extends YangModelDependencyInfo {
337 private ModuleDependencyInfo(final String name, final String latestRevision,
338 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
339 super(name, latestRevision, imports, includes);
342 private ModuleDependencyInfo(final String name, final String latestRevision,
343 final ImmutableSet<ModuleImport> imports,
344 final ImmutableSet<ModuleImport> includes,
345 final Optional<SemVer> semVer) {
346 super(name, latestRevision, imports, includes, semVer);
350 public String toString() {
351 return "Module [name=" + getName() + ", revision=" + getRevision()
352 + ", semanticVersion=" + getSemanticVersion().orElse(Module.DEFAULT_SEMANTIC_VERSION)
353 + ", dependencies=" + getDependencies()
359 * Dependency information for submodule, also provides name for parent module.
361 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
362 private final String belongsTo;
364 private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
365 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
366 super(name, latestRevision, imports, includes);
367 this.belongsTo = belongsTo;
371 * Returns name of parent module.
373 public String getParentModule() {
378 public String toString() {
379 return "Submodule [name=" + getName() + ", revision="
380 + getRevision() + ", dependencies=" + getDependencies()
386 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
388 private static final class ModuleImportImpl implements ModuleImport {
390 private final Date revision;
391 private final SemVer semVer;
392 private final String name;
394 ModuleImportImpl(final String moduleName, final Date revision) {
395 this(moduleName, revision, Optional.empty());
398 ModuleImportImpl(final String moduleName, final Date revision, final Optional<SemVer> semVer) {
399 this.name = requireNonNull(moduleName, "Module name must not be null.");
400 this.revision = revision;
401 this.semVer = semVer.orElse(Module.DEFAULT_SEMANTIC_VERSION);
405 public String getModuleName() {
410 public Date getRevision() {
411 return this.revision;
415 public SemVer getSemanticVersion() {
420 public String getPrefix() {
425 public int hashCode() {
426 final int prime = 31;
428 result = prime * result + Objects.hashCode(name);
429 result = prime * result + Objects.hashCode(revision);
430 result = prime * result + Objects.hashCode(semVer);
435 public boolean equals(final Object obj) {
442 if (getClass() != obj.getClass()) {
445 final ModuleImportImpl other = (ModuleImportImpl) obj;
447 if (other.name != null) {
450 } else if (!name.equals(other.name)) {
453 if (revision == null) {
454 if (other.revision != null) {
457 } else if (!revision.equals(other.revision)) {
461 if (!Objects.equals(getSemanticVersion(), other.getSemanticVersion())) {
468 public String toString() {
469 return "ModuleImportImpl [name=" + name + ", revision="
470 + QName.formattedRevision(revision) + ", semanticVersion=" + getSemanticVersion() + "]";