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 com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Strings;
13 import com.google.common.collect.ImmutableSet;
14 import java.io.InputStream;
15 import java.util.Date;
16 import java.util.HashSet;
17 import java.util.Objects;
19 import org.antlr.v4.runtime.ParserRuleContext;
20 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.StatementContext;
21 import org.opendaylight.yangtools.concepts.SemVer;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.model.api.Module;
24 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
25 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
26 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
27 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
28 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
29 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.SupportedExtensionsMapping;
30 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
31 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
32 import org.opendaylight.yangtools.yang.parser.util.NamedInputStream;
35 * Helper transfer object which holds basic and dependency information for YANG
39 * There are two concrete implementations of this interface:
41 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
42 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
45 * @see ModuleDependencyInfo
46 * @see SubmoduleDependencyInfo
48 public abstract class YangModelDependencyInfo {
49 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
50 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
51 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
52 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
53 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
54 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
55 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
57 private static final String OPENCONFIG_VERSION = SupportedExtensionsMapping.OPENCONFIG_VERSION.getStatementName()
60 private final String name;
61 private final String formattedRevision;
62 private final Date revision;
63 private final Optional<SemVer> semVer;
64 private final ImmutableSet<ModuleImport> submoduleIncludes;
65 private final ImmutableSet<ModuleImport> moduleImports;
66 private final ImmutableSet<ModuleImport> dependencies;
68 YangModelDependencyInfo(final String name, final String formattedRevision,
69 final ImmutableSet<ModuleImport> imports,
70 final ImmutableSet<ModuleImport> includes) {
71 this(name, formattedRevision, imports, includes, Optional.absent());
74 YangModelDependencyInfo(final String name, final String formattedRevision,
75 final ImmutableSet<ModuleImport> imports,
76 final ImmutableSet<ModuleImport> includes,
77 final Optional<SemVer> semVer) {
79 this.formattedRevision = formattedRevision;
80 this.revision = formattedRevision == null ? null : QName
81 .parseRevision(formattedRevision);
82 this.moduleImports = imports;
83 this.submoduleIncludes = includes;
84 this.dependencies = ImmutableSet.<ModuleImport>builder()
85 .addAll(moduleImports).addAll(submoduleIncludes).build();
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 formattedRevision;
127 * Returns semantic version of module.
129 * @return semantic version
131 public Optional<SemVer> getSemanticVersion() {
136 public int hashCode() {
137 final int prime = 31;
139 result = prime * result + Objects.hashCode(formattedRevision);
140 result = prime * result + Objects.hashCode(name);
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 if (formattedRevision == null) {
158 if (other.formattedRevision != null) {
161 } else if (!formattedRevision.equals(other.formattedRevision)) {
165 if (other.name != null) {
168 } else if (!name.equals(other.name)) {
171 return Objects.equals(semVer, other.semVer);
175 * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of a YANG model.
177 * @param tree Abstract syntax tree
178 * @return {@link YangModelDependencyInfo}
179 * @throws YangSyntaxErrorException If the AST is not a valid YANG module/submodule
181 public static YangModelDependencyInfo fromAST(final String name,
182 final ParserRuleContext tree) throws YangSyntaxErrorException {
184 if (tree instanceof StatementContext) {
185 final StatementContext rootStatement = (StatementContext) tree;
186 return parseAST(rootStatement, name);
189 throw new YangSyntaxErrorException(name, 0, 0, "Unknown YANG text type");
192 private static YangModelDependencyInfo parseAST(final StatementContext rootStatement, final String sourceName) {
193 final String keyWordText = rootStatement.keyword().getText();
194 if (MODULE.equals(keyWordText)) {
195 return parseModuleContext(rootStatement, sourceName);
197 if (SUBMODULE.equals(keyWordText)) {
198 return parseSubmoduleContext(rootStatement, sourceName);
200 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
204 * Extracts {@link YangModelDependencyInfo} from input stream containing a YANG model. This parsing does not
205 * validate full YANG module, only parses header up to the revisions and imports.
207 * @param yangStream Opened Input stream containing text source of YANG model
208 * @return {@link YangModelDependencyInfo}
209 * @throws IllegalArgumentException If input stream is not valid YANG stream
211 public static YangModelDependencyInfo fromInputStream(final InputStream yangStream) {
212 final StatementContext yangAST = new YangStatementSourceImpl(yangStream).getYangAST();
213 return parseAST(yangAST, yangStream instanceof NamedInputStream ? yangStream.toString() : null);
216 private static YangModelDependencyInfo parseModuleContext(final StatementContext module, final String sourceName) {
217 final String name = Utils.stringFromStringContext(module.argument(), getReference(sourceName, module));
218 final String latestRevision = getLatestRevision(module, sourceName);
219 final Optional<SemVer> semVer = Optional.fromNullable(findSemanticVersion(module, sourceName));
220 final ImmutableSet<ModuleImport> imports = parseImports(module, sourceName);
221 final ImmutableSet<ModuleImport> includes = parseIncludes(module, sourceName);
223 return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
226 private static ImmutableSet<ModuleImport> parseImports(final StatementContext module, final String sourceName) {
227 final Set<ModuleImport> result = new HashSet<>();
228 for (final StatementContext subStatementContext : module.statement()) {
229 if (IMPORT.equals(subStatementContext.keyword().getText())) {
230 final String revisionDateStr = getRevisionDateString(subStatementContext, sourceName);
231 final String importedModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
232 getReference(sourceName, subStatementContext));
233 final Date revisionDate = revisionDateStr == null ? null : QName.parseRevision(revisionDateStr);
234 final Optional<SemVer> importSemVer = Optional.fromNullable(findSemanticVersion(subStatementContext,
236 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
239 return ImmutableSet.copyOf(result);
242 private static SemVer findSemanticVersion(final StatementContext statement, final String sourceName) {
243 String semVerString = null;
244 for (final StatementContext subStatement : statement.statement()) {
245 final String subStatementName = Utils.trimPrefix(subStatement.keyword().getText());
246 if (OPENCONFIG_VERSION.equals(subStatementName)) {
247 semVerString = Utils.stringFromStringContext(subStatement.argument(),
248 getReference(sourceName, subStatement));
253 return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
256 private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module, final String sourceName) {
257 final Set<ModuleImport> result = new HashSet<>();
258 for (final StatementContext subStatementContext : module.statement()) {
259 if (INCLUDE.equals(subStatementContext.keyword().getText())) {
260 final String revisionDateStr = getRevisionDateString(subStatementContext, sourceName);
261 final String IncludeModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
262 getReference(sourceName, subStatementContext));
263 final Date revisionDate = revisionDateStr == null ? null : QName.parseRevision(revisionDateStr);
264 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
267 return ImmutableSet.copyOf(result);
270 private static String getRevisionDateString(final StatementContext importStatement, final String sourceName) {
271 String revisionDateStr = null;
272 for (final StatementContext importSubStatement : importStatement.statement()) {
273 if (REVISION_DATE.equals(importSubStatement.keyword().getText())) {
274 revisionDateStr = Utils.stringFromStringContext(importSubStatement.argument(),
275 getReference(sourceName, importSubStatement));
278 return revisionDateStr;
281 public static String getLatestRevision(final StatementContext module, final String sourceName) {
282 String latestRevision = null;
283 for (final StatementContext subStatementContext : module.statement()) {
284 if (REVISION.equals(subStatementContext.keyword().getText())) {
285 final String currentRevision = Utils.stringFromStringContext(subStatementContext.argument(),
286 getReference(sourceName, subStatementContext));
287 if (latestRevision == null || latestRevision.compareTo(currentRevision) == -1) {
288 latestRevision = currentRevision;
292 return latestRevision;
295 private static YangModelDependencyInfo parseSubmoduleContext(final StatementContext submodule,
296 final String sourceName) {
297 final String name = Utils.stringFromStringContext(submodule.argument(), getReference(sourceName, submodule));
298 final String belongsTo = parseBelongsTo(submodule, sourceName);
300 final String latestRevision = getLatestRevision(submodule, sourceName);
301 final ImmutableSet<ModuleImport> imports = parseImports(submodule, sourceName);
302 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, sourceName);
304 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
307 private static String parseBelongsTo(final StatementContext submodule, final String sourceName) {
308 for (final StatementContext subStatementContext : submodule.statement()) {
309 if (BELONGS_TO.equals(subStatementContext.keyword().getText())) {
310 return Utils.stringFromStringContext(subStatementContext.argument(),
311 getReference(sourceName, subStatementContext));
317 private static StatementSourceReference getReference(final String sourceName,
318 final StatementContext context) {
319 return DeclarationInTextSource.atPosition(sourceName, context.getStart().getLine(),
320 context.getStart().getCharPositionInLine());
324 * Dependency information for YANG module.
326 public static class ModuleDependencyInfo extends YangModelDependencyInfo {
327 private ModuleDependencyInfo(final String name, final String latestRevision,
328 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
329 super(name, latestRevision, imports, includes);
332 private ModuleDependencyInfo(final String name, final String latestRevision,
333 final ImmutableSet<ModuleImport> imports,
334 final ImmutableSet<ModuleImport> includes,
335 final Optional<SemVer> semVer) {
336 super(name, latestRevision, imports, includes, semVer);
340 public String toString() {
341 return "Module [name=" + getName() + ", revision=" + getRevision() + ", semanticVersion="
342 + getSemanticVersion().or(Module.DEFAULT_SEMANTIC_VERSION) + ", 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 Date revision;
380 private final SemVer semVer;
381 private final String name;
383 ModuleImportImpl(final String moduleName, final Date revision) {
384 this(moduleName, revision, Optional.absent());
387 ModuleImportImpl(final String moduleName, final Date revision, final Optional<SemVer> semVer) {
388 this.name = Preconditions.checkNotNull(moduleName, "Module name must not be null.");
389 this.revision = revision;
390 this.semVer = semVer.or(Module.DEFAULT_SEMANTIC_VERSION);
394 public String getModuleName() {
399 public Date getRevision() {
400 return this.revision;
404 public SemVer getSemanticVersion() {
409 public String getPrefix() {
414 public int hashCode() {
415 final int prime = 31;
417 result = prime * result + Objects.hashCode(name);
418 result = prime * result + Objects.hashCode(revision);
419 result = prime * result + Objects.hashCode(semVer);
424 public boolean equals(final Object obj) {
431 if (getClass() != obj.getClass()) {
434 final ModuleImportImpl other = (ModuleImportImpl) obj;
436 if (other.name != null) {
439 } else if (!name.equals(other.name)) {
442 if (revision == null) {
443 if (other.revision != null) {
446 } else if (!revision.equals(other.revision)) {
450 if (!Objects.equals(getSemanticVersion(), other.getSemanticVersion())) {
457 public String toString() {
458 return "ModuleImportImpl [name=" + name + ", revision="
459 + QName.formattedRevision(revision) + ", semanticVersion=" + getSemanticVersion() + "]";