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.Beta;
14 import com.google.common.base.Splitter;
15 import com.google.common.base.Strings;
16 import com.google.common.collect.ImmutableSet;
17 import java.io.IOException;
18 import java.util.HashSet;
19 import java.util.Objects;
20 import java.util.Optional;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.yangtools.concepts.SemVer;
25 import org.opendaylight.yangtools.openconfig.model.api.OpenConfigStatements;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.common.Revision;
28 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
29 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
30 import org.opendaylight.yangtools.yang.model.api.stmt.ImportEffectiveStatement;
31 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
32 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
33 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
34 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
35 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword;
36 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Unqualified;
37 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource;
38 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
39 import org.opendaylight.yangtools.yang.parser.spi.source.ExplicitStatement;
40 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
43 * Helper transfer object which holds basic and dependency information for YANG
47 * There are two concrete implementations of this interface:
49 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
50 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
53 * @see ModuleDependencyInfo
54 * @see SubmoduleDependencyInfo
56 public abstract class YangModelDependencyInfo {
57 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
58 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
59 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
60 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
61 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
62 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
63 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
65 private static final String OPENCONFIG_VERSION = OpenConfigStatements.OPENCONFIG_VERSION.getStatementName()
68 private static final Splitter COLON_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
70 private final String name;
71 private final Revision revision;
72 private final SemVer semVer;
73 private final ImmutableSet<ModuleImport> submoduleIncludes;
74 private final ImmutableSet<ModuleImport> moduleImports;
75 private final ImmutableSet<ModuleImport> dependencies;
77 YangModelDependencyInfo(final String name, final String formattedRevision,
78 final ImmutableSet<ModuleImport> imports,
79 final ImmutableSet<ModuleImport> includes) {
80 this(name, formattedRevision, imports, includes, Optional.empty());
83 YangModelDependencyInfo(final String name, final String formattedRevision,
84 final ImmutableSet<ModuleImport> imports,
85 final ImmutableSet<ModuleImport> includes,
86 final Optional<SemVer> semVer) {
88 this.revision = Revision.ofNullable(formattedRevision).orElse(null);
89 this.moduleImports = imports;
90 this.submoduleIncludes = includes;
91 this.dependencies = ImmutableSet.<ModuleImport>builder()
92 .addAll(moduleImports).addAll(submoduleIncludes).build();
93 this.semVer = semVer.orElse(null);
97 * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
98 * and <code>include</code> statements for submodules.
100 * @return Immutable collection of imports.
102 public ImmutableSet<ModuleImport> getDependencies() {
107 * Returns model name.
111 public String getName() {
116 * Returns formatted revision string.
118 * @return formatted revision string
120 public String getFormattedRevision() {
121 return revision != null ? revision.toString() : null;
127 * @return revision, potentially null
129 public Optional<Revision> getRevision() {
130 return Optional.ofNullable(revision);
134 * Returns semantic version of module.
136 * @return semantic version
138 public Optional<SemVer> getSemanticVersion() {
139 return Optional.ofNullable(semVer);
143 public int hashCode() {
144 final int prime = 31;
146 result = prime * result + Objects.hashCode(name);
147 result = prime * result + Objects.hashCode(revision);
148 result = prime * result + Objects.hashCode(semVer);
153 public boolean equals(final Object obj) {
160 if (!(obj instanceof YangModelDependencyInfo)) {
163 final YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
164 return Objects.equals(name, other.name) && Objects.equals(revision, other.revision)
165 && Objects.equals(semVer, other.semVer);
169 * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
171 * @param source Schema source
172 * @return {@link YangModelDependencyInfo}
173 * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
175 public static @NonNull YangModelDependencyInfo forIR(final IRSchemaSource source) {
176 return forIR(source.getRootStatement(), source.getIdentifier());
180 * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
182 * @param source Source identifier
183 * @param rootStatement root statement
184 * @return {@link YangModelDependencyInfo}
185 * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
187 static @NonNull YangModelDependencyInfo forIR(final IRStatement rootStatement,
188 final SourceIdentifier source) {
189 final IRKeyword keyword = rootStatement.keyword();
190 checkArgument(keyword instanceof Unqualified, "Invalid root statement %s", keyword);
192 final String arg = keyword.identifier();
193 if (MODULE.equals(arg)) {
194 return parseModuleContext(rootStatement, source);
196 if (SUBMODULE.equals(arg)) {
197 return parseSubmoduleContext(rootStatement, source);
199 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
203 * Extracts {@link YangModelDependencyInfo} from input stream containing a YANG model. This parsing does not
204 * validate full YANG module, only parses header up to the revisions and imports.
206 * @param refClass Base search class
207 * @param resourceName resource name, relative to refClass
208 * @return {@link YangModelDependencyInfo}
209 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
210 * @throws IOException When the resource cannot be read
211 * @throws IllegalArgumentException
212 * If input stream is not valid YANG stream
213 * @deprecated This method was used by testing framework and was deemed to be potentially useful to the outside
214 * world. With Java Platform Module System, though, the resource loading rules have changed to the point
215 * where we no longer can guarantee it working correctly, as the results depend on the resource path.
216 * Users are advised to use {@link #forYangText(YangTextSchemaSource)}.
218 @Deprecated(forRemoval = true)
219 public static YangModelDependencyInfo forResource(final Class<?> refClass, final String resourceName)
220 throws IOException, YangSyntaxErrorException {
221 return forYangText(YangTextSchemaSource.forResource(refClass, resourceName));
225 * Extracts {@link YangModelDependencyInfo} from a {@link YangTextSchemaSource}. This parsing does not
226 * validate full YANG module, only parses header up to the revisions and imports.
228 * @param yangText {@link YangTextSchemaSource}
229 * @return {@link YangModelDependencyInfo}
230 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
231 * @throws IOException When the resource cannot be read
233 public static YangModelDependencyInfo forYangText(final YangTextSchemaSource yangText)
234 throws IOException, YangSyntaxErrorException {
235 final YangStatementStreamSource source = YangStatementStreamSource.create(yangText);
236 return forIR(source.rootStatement(), source.getIdentifier());
239 private static @NonNull YangModelDependencyInfo parseModuleContext(final IRStatement module,
240 final SourceIdentifier source) {
241 final String name = safeStringArgument(source, module, "module name");
242 final String latestRevision = getLatestRevision(module, source);
243 final Optional<SemVer> semVer = Optional.ofNullable(findSemanticVersion(module, source));
244 final ImmutableSet<ModuleImport> imports = parseImports(module, source);
245 final ImmutableSet<ModuleImport> includes = parseIncludes(module, source);
247 return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
250 private static ImmutableSet<ModuleImport> parseImports(final IRStatement module,
251 final SourceIdentifier source) {
252 final Set<ModuleImport> result = new HashSet<>();
253 for (final IRStatement substatement : module.statements()) {
254 if (isBuiltin(substatement, IMPORT)) {
255 final String importedModuleName = safeStringArgument(source, substatement, "imported module name");
256 final String revisionDateStr = getRevisionDateString(substatement, source);
257 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
258 final SemVer importSemVer = findSemanticVersion(substatement, source);
259 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
262 return ImmutableSet.copyOf(result);
266 public static SemVer findSemanticVersion(final IRStatement statement, final SourceIdentifier source) {
267 String semVerString = null;
268 for (final IRStatement substatement : statement.statements()) {
269 // FIXME: this should also check we are using a prefix
270 if (OPENCONFIG_VERSION.equals(substatement.keyword().identifier())) {
271 semVerString = safeStringArgument(source, substatement, "version string");
276 return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
279 private static boolean isBuiltin(final IRStatement stmt, final String localName) {
280 final IRKeyword keyword = stmt.keyword();
281 return keyword instanceof Unqualified && localName.equals(keyword.identifier());
284 private static ImmutableSet<ModuleImport> parseIncludes(final IRStatement module, final SourceIdentifier source) {
285 final Set<ModuleImport> result = new HashSet<>();
286 for (final IRStatement substatement : module.statements()) {
287 if (isBuiltin(substatement, INCLUDE)) {
288 final String revisionDateStr = getRevisionDateString(substatement, source);
289 final String IncludeModuleName = safeStringArgument(source, substatement, "included submodule name");
290 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
291 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
294 return ImmutableSet.copyOf(result);
297 private static String getRevisionDateString(final IRStatement importStatement, final SourceIdentifier source) {
298 String revisionDateStr = null;
299 for (final IRStatement substatement : importStatement.statements()) {
300 if (isBuiltin(substatement, REVISION_DATE)) {
301 revisionDateStr = safeStringArgument(source, substatement, "imported module revision-date");
304 return revisionDateStr;
307 public static String getLatestRevision(final IRStatement module, final SourceIdentifier source) {
308 String latestRevision = null;
309 for (final IRStatement substatement : module.statements()) {
310 if (isBuiltin(substatement, REVISION)) {
311 final String currentRevision = safeStringArgument(source, substatement, "revision date");
312 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
313 latestRevision = currentRevision;
317 return latestRevision;
320 private static @NonNull YangModelDependencyInfo parseSubmoduleContext(final IRStatement submodule,
321 final SourceIdentifier source) {
322 final String name = safeStringArgument(source, submodule, "submodule name");
323 final String belongsTo = parseBelongsTo(submodule, source);
325 final String latestRevision = getLatestRevision(submodule, source);
326 final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
327 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
329 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
332 private static String parseBelongsTo(final IRStatement submodule, final SourceIdentifier source) {
333 for (final IRStatement substatement : submodule.statements()) {
334 if (isBuiltin(substatement, BELONGS_TO)) {
335 return safeStringArgument(source, substatement, "belongs-to module name");
341 static String safeStringArgument(final SourceIdentifier source, final IRStatement stmt, final String desc) {
342 final StatementSourceReference ref = getReference(source, stmt);
343 final IRArgument arg = stmt.argument();
344 checkArgument(arg != null, "Missing %s at %s", desc, ref);
345 // TODO: we probably need to understand yang version first....
346 return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
349 private static StatementSourceReference getReference(final SourceIdentifier source, final IRStatement stmt) {
350 return ExplicitStatement.atPosition(source.getName(), stmt.startLine(), stmt.startColumn() + 1);
354 * Dependency information for YANG module.
356 public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
357 ModuleDependencyInfo(final String name, final String latestRevision, final ImmutableSet<ModuleImport> imports,
358 final ImmutableSet<ModuleImport> includes, final Optional<SemVer> semVer) {
359 super(name, latestRevision, imports, includes, semVer);
363 public String toString() {
364 return "Module [name=" + getName() + ", revision=" + getRevision()
365 + ", semanticVersion=" + getSemanticVersion().orElse(null)
366 + ", dependencies=" + getDependencies()
372 * Dependency information for submodule, also provides name for parent module.
374 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
375 private final String belongsTo;
377 private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
378 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
379 super(name, latestRevision, imports, includes);
380 this.belongsTo = belongsTo;
384 * Returns name of parent module.
386 public String getParentModule() {
391 public String toString() {
392 return "Submodule [name=" + getName() + ", revision="
393 + getRevision() + ", dependencies=" + getDependencies()
399 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
401 // FIXME: this is a rather nasty misuse of APIs :(
402 private static final class ModuleImportImpl implements ModuleImport {
404 private final Revision revision;
405 private final SemVer semVer;
406 private final String name;
408 ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision) {
409 this(moduleName, revision, null);
412 ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision,
413 final @Nullable SemVer semVer) {
414 this.name = requireNonNull(moduleName, "Module name must not be null.");
415 this.revision = revision;
416 this.semVer = semVer;
420 public String getModuleName() {
425 public Optional<Revision> getRevision() {
426 return Optional.ofNullable(revision);
430 public Optional<SemVer> getSemanticVersion() {
431 return Optional.ofNullable(semVer);
435 public String getPrefix() {
440 public Optional<String> getDescription() {
441 return Optional.empty();
445 public Optional<String> getReference() {
446 return Optional.empty();
450 public ImportEffectiveStatement asEffectiveStatement() {
451 throw new UnsupportedOperationException();
455 public int hashCode() {
456 final int prime = 31;
458 result = prime * result + Objects.hashCode(name);
459 result = prime * result + Objects.hashCode(revision);
460 result = prime * result + Objects.hashCode(semVer);
465 public boolean equals(final Object obj) {
469 if (!(obj instanceof ModuleImportImpl)) {
472 final ModuleImportImpl other = (ModuleImportImpl) obj;
473 return name.equals(other.name) && Objects.equals(revision, other.revision)
474 && Objects.equals(getSemanticVersion(), other.getSemanticVersion());
478 public String toString() {
479 return "ModuleImportImpl [name=" + name + ", revision="
480 + QName.formattedRevision(Optional.ofNullable(revision)) + ", semanticVersion=" + semVer + "]";