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.rfc7950.repo;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.ImmutableSet;
16 import java.io.IOException;
17 import java.util.HashSet;
18 import java.util.Objects;
19 import java.util.Optional;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.opendaylight.yangtools.concepts.SemVer;
24 import org.opendaylight.yangtools.openconfig.model.api.OpenConfigStatements;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.common.Revision;
27 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
28 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
29 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
30 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
31 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
32 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
33 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword;
34 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Unqualified;
35 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
36 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
37 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
40 * Helper transfer object which holds basic and dependency information for YANG
44 * There are two concrete implementations of this interface:
46 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
47 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
50 * @see ModuleDependencyInfo
51 * @see SubmoduleDependencyInfo
53 public abstract class YangModelDependencyInfo {
54 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
55 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
56 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
57 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
58 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
59 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
60 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
62 private static final String OPENCONFIG_VERSION = OpenConfigStatements.OPENCONFIG_VERSION.getStatementName()
65 private final String name;
66 private final Revision revision;
67 private final SemVer semVer;
68 private final ImmutableSet<ModuleImport> submoduleIncludes;
69 private final ImmutableSet<ModuleImport> moduleImports;
70 private final ImmutableSet<ModuleImport> dependencies;
72 YangModelDependencyInfo(final String name, final String formattedRevision,
73 final ImmutableSet<ModuleImport> imports,
74 final ImmutableSet<ModuleImport> includes) {
75 this(name, formattedRevision, imports, includes, Optional.empty());
78 YangModelDependencyInfo(final String name, final String formattedRevision,
79 final ImmutableSet<ModuleImport> imports,
80 final ImmutableSet<ModuleImport> includes,
81 final Optional<SemVer> semVer) {
83 this.revision = Revision.ofNullable(formattedRevision).orElse(null);
84 this.moduleImports = imports;
85 this.submoduleIncludes = includes;
86 this.dependencies = ImmutableSet.<ModuleImport>builder()
87 .addAll(moduleImports).addAll(submoduleIncludes).build();
88 this.semVer = semVer.orElse(null);
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 revision != null ? revision.toString() : null;
122 * @return revision, potentially null
124 public Optional<Revision> getRevision() {
125 return Optional.ofNullable(revision);
129 * Returns semantic version of module.
131 * @return semantic version
133 public Optional<SemVer> getSemanticVersion() {
134 return Optional.ofNullable(semVer);
138 public int hashCode() {
139 final int prime = 31;
141 result = prime * result + Objects.hashCode(name);
142 result = prime * result + Objects.hashCode(revision);
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 return Objects.equals(name, other.name) && Objects.equals(revision, other.revision)
160 && Objects.equals(semVer, other.semVer);
164 * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of a YANG model.
166 * @param source Source identifier
167 * @param rootStatement root statement
168 * @return {@link YangModelDependencyInfo}
169 * @throws IllegalArgumentException If the AST is not a valid YANG module/submodule
171 static @NonNull YangModelDependencyInfo parseAST(final IRStatement rootStatement,
172 final SourceIdentifier source) {
173 final IRKeyword keyword = rootStatement.keyword();
174 checkArgument(keyword instanceof Unqualified, "Invalid root statement %s", keyword);
176 final String arg = keyword.identifier();
177 if (MODULE.equals(arg)) {
178 return parseModuleContext(rootStatement, source);
180 if (SUBMODULE.equals(arg)) {
181 return parseSubmoduleContext(rootStatement, source);
183 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
187 * Extracts {@link YangModelDependencyInfo} from input stream containing a YANG model. This parsing does not
188 * validate full YANG module, only parses header up to the revisions and imports.
190 * @param refClass Base search class
191 * @param resourceName resource name, relative to refClass
192 * @return {@link YangModelDependencyInfo}
193 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
194 * @throws IOException When the resource cannot be read
195 * @throws IllegalArgumentException
196 * If input stream is not valid YANG stream
199 public static YangModelDependencyInfo forResource(final Class<?> refClass, final String resourceName)
200 throws IOException, YangSyntaxErrorException {
201 final YangStatementStreamSource source = YangStatementStreamSource.create(
202 YangTextSchemaSource.forResource(refClass, resourceName));
203 return parseAST(source.rootStatement(), source.getIdentifier());
206 private static @NonNull YangModelDependencyInfo parseModuleContext(final IRStatement module,
207 final SourceIdentifier source) {
208 final String name = safeStringArgument(source, module, "module name");
209 final String latestRevision = getLatestRevision(module, source);
210 final Optional<SemVer> semVer = Optional.ofNullable(findSemanticVersion(module, source));
211 final ImmutableSet<ModuleImport> imports = parseImports(module, source);
212 final ImmutableSet<ModuleImport> includes = parseIncludes(module, source);
214 return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
217 private static ImmutableSet<ModuleImport> parseImports(final IRStatement module,
218 final SourceIdentifier source) {
219 final Set<ModuleImport> result = new HashSet<>();
220 for (final IRStatement substatement : module.statements()) {
221 if (isBuiltin(substatement, IMPORT)) {
222 final String importedModuleName = safeStringArgument(source, substatement, "imported module name");
223 final String revisionDateStr = getRevisionDateString(substatement, source);
224 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
225 final SemVer importSemVer = findSemanticVersion(substatement, source);
226 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
229 return ImmutableSet.copyOf(result);
232 private static SemVer findSemanticVersion(final IRStatement statement, final SourceIdentifier source) {
233 String semVerString = null;
234 for (final IRStatement substatement : statement.statements()) {
235 // FIXME: this should also check we are using a prefix
236 if (OPENCONFIG_VERSION.equals(substatement.keyword().identifier())) {
237 semVerString = safeStringArgument(source, substatement, "version string");
242 return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
245 private static boolean isBuiltin(final IRStatement stmt, final String localName) {
246 final IRKeyword keyword = stmt.keyword();
247 return keyword instanceof Unqualified && localName.equals(keyword.identifier());
250 private static ImmutableSet<ModuleImport> parseIncludes(final IRStatement module, final SourceIdentifier source) {
251 final Set<ModuleImport> result = new HashSet<>();
252 for (final IRStatement substatement : module.statements()) {
253 if (isBuiltin(substatement, INCLUDE)) {
254 final String revisionDateStr = getRevisionDateString(substatement, source);
255 final String IncludeModuleName = safeStringArgument(source, substatement, "included submodule name");
256 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
257 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
260 return ImmutableSet.copyOf(result);
263 private static String getRevisionDateString(final IRStatement importStatement, final SourceIdentifier source) {
264 String revisionDateStr = null;
265 for (final IRStatement substatement : importStatement.statements()) {
266 if (isBuiltin(substatement, REVISION_DATE)) {
267 revisionDateStr = safeStringArgument(source, substatement, "imported module revision-date");
270 return revisionDateStr;
273 public static String getLatestRevision(final IRStatement module, final SourceIdentifier source) {
274 String latestRevision = null;
275 for (final IRStatement substatement : module.statements()) {
276 if (isBuiltin(substatement, REVISION)) {
277 final String currentRevision = safeStringArgument(source, substatement, "revision date");
278 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
279 latestRevision = currentRevision;
283 return latestRevision;
286 private static @NonNull YangModelDependencyInfo parseSubmoduleContext(final IRStatement submodule,
287 final SourceIdentifier source) {
288 final String name = safeStringArgument(source, submodule, "submodule name");
289 final String belongsTo = parseBelongsTo(submodule, source);
291 final String latestRevision = getLatestRevision(submodule, source);
292 final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
293 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
295 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
298 private static String parseBelongsTo(final IRStatement submodule, final SourceIdentifier source) {
299 for (final IRStatement substatement : submodule.statements()) {
300 if (isBuiltin(substatement, BELONGS_TO)) {
301 return safeStringArgument(source, substatement, "belongs-to module name");
307 private static String safeStringArgument(final SourceIdentifier source, final IRStatement stmt, final String desc) {
308 final StatementSourceReference ref = getReference(source, stmt);
309 final IRArgument arg = stmt.argument();
310 checkArgument(arg != null, "Missing %s at %s", desc, ref);
311 // TODO: we probably need to understand yang version first....
312 return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
315 private static StatementSourceReference getReference(final SourceIdentifier source, final IRStatement stmt) {
316 return DeclarationInTextSource.atPosition(source.getName(), stmt.startLine(), stmt.startColumn());
320 * Dependency information for YANG module.
322 public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
323 ModuleDependencyInfo(final String name, final String latestRevision, final ImmutableSet<ModuleImport> imports,
324 final ImmutableSet<ModuleImport> includes, final Optional<SemVer> semVer) {
325 super(name, latestRevision, imports, includes, semVer);
329 public String toString() {
330 return "Module [name=" + getName() + ", revision=" + getRevision()
331 + ", semanticVersion=" + getSemanticVersion().orElse(null)
332 + ", dependencies=" + getDependencies()
338 * Dependency information for submodule, also provides name for parent module.
340 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
341 private final String belongsTo;
343 private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
344 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
345 super(name, latestRevision, imports, includes);
346 this.belongsTo = belongsTo;
350 * Returns name of parent module.
352 public String getParentModule() {
357 public String toString() {
358 return "Submodule [name=" + getName() + ", revision="
359 + getRevision() + ", dependencies=" + getDependencies()
365 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
367 private static final class ModuleImportImpl implements ModuleImport {
369 private final Revision revision;
370 private final SemVer semVer;
371 private final String name;
373 ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision) {
374 this(moduleName, revision, null);
377 ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision,
378 final @Nullable SemVer semVer) {
379 this.name = requireNonNull(moduleName, "Module name must not be null.");
380 this.revision = revision;
381 this.semVer = semVer;
385 public String getModuleName() {
390 public Optional<Revision> getRevision() {
391 return Optional.ofNullable(revision);
395 public Optional<SemVer> getSemanticVersion() {
396 return Optional.ofNullable(semVer);
400 public String getPrefix() {
405 public Optional<String> getDescription() {
406 return Optional.empty();
410 public Optional<String> getReference() {
411 return Optional.empty();
415 public int hashCode() {
416 final int prime = 31;
418 result = prime * result + Objects.hashCode(name);
419 result = prime * result + Objects.hashCode(revision);
420 result = prime * result + Objects.hashCode(semVer);
425 public boolean equals(final Object obj) {
429 if (!(obj instanceof ModuleImportImpl)) {
432 final ModuleImportImpl other = (ModuleImportImpl) obj;
433 return name.equals(other.name) && Objects.equals(revision, other.revision)
434 && Objects.equals(getSemanticVersion(), other.getSemanticVersion());
438 public String toString() {
439 return "ModuleImportImpl [name=" + name + ", revision="
440 + QName.formattedRevision(Optional.ofNullable(revision)) + ", semanticVersion=" + semVer + "]";