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.collect.ImmutableSet;
14 import java.io.IOException;
15 import java.util.HashSet;
16 import java.util.Objects;
17 import java.util.Optional;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.yangtools.yang.common.Revision;
22 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
23 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
24 import org.opendaylight.yangtools.yang.model.api.stmt.ImportEffectiveStatement;
25 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
26 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
27 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
28 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
29 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword;
30 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Unqualified;
31 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource;
32 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
33 import org.opendaylight.yangtools.yang.parser.spi.source.ExplicitStatement;
34 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
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 final String name;
60 private final Revision revision;
61 private final ImmutableSet<ModuleImport> submoduleIncludes;
62 private final ImmutableSet<ModuleImport> moduleImports;
63 private final ImmutableSet<ModuleImport> dependencies;
65 YangModelDependencyInfo(final String name, final String formattedRevision, final ImmutableSet<ModuleImport> imports,
66 final ImmutableSet<ModuleImport> includes) {
68 revision = Revision.ofNullable(formattedRevision).orElse(null);
69 moduleImports = imports;
70 submoduleIncludes = includes;
71 dependencies = ImmutableSet.<ModuleImport>builder()
72 .addAll(moduleImports).addAll(submoduleIncludes).build();
76 * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
77 * and <code>include</code> statements for submodules.
79 * @return Immutable collection of imports.
81 public ImmutableSet<ModuleImport> getDependencies() {
90 public String getName() {
95 * Returns formatted revision string.
97 * @return formatted revision string
99 public String getFormattedRevision() {
100 return revision != null ? revision.toString() : null;
106 * @return revision, potentially null
108 public Optional<Revision> getRevision() {
109 return Optional.ofNullable(revision);
113 public int hashCode() {
114 final int prime = 31;
116 result = prime * result + Objects.hashCode(name);
117 result = prime * result + Objects.hashCode(revision);
122 public boolean equals(final Object obj) {
123 return this == obj || obj instanceof YangModelDependencyInfo other
124 && Objects.equals(name, other.name) && Objects.equals(revision, other.revision);
128 * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
130 * @param source Schema source
131 * @return {@link YangModelDependencyInfo}
132 * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
134 public static @NonNull YangModelDependencyInfo forIR(final IRSchemaSource source) {
135 return forIR(source.getRootStatement(), source.getIdentifier());
139 * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
141 * @param source Source identifier
142 * @param rootStatement root statement
143 * @return {@link YangModelDependencyInfo}
144 * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
146 static @NonNull YangModelDependencyInfo forIR(final IRStatement rootStatement,
147 final SourceIdentifier source) {
148 final IRKeyword keyword = rootStatement.keyword();
149 checkArgument(keyword instanceof Unqualified, "Invalid root statement %s", keyword);
151 final String arg = keyword.identifier();
152 if (MODULE.equals(arg)) {
153 return parseModuleContext(rootStatement, source);
155 if (SUBMODULE.equals(arg)) {
156 return parseSubmoduleContext(rootStatement, source);
158 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
162 * Extracts {@link YangModelDependencyInfo} from a {@link YangTextSchemaSource}. This parsing does not
163 * validate full YANG module, only parses header up to the revisions and imports.
165 * @param yangText {@link YangTextSchemaSource}
166 * @return {@link YangModelDependencyInfo}
167 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
168 * @throws IOException When the resource cannot be read
170 public static YangModelDependencyInfo forYangText(final YangTextSchemaSource yangText)
171 throws IOException, YangSyntaxErrorException {
172 final YangStatementStreamSource source = YangStatementStreamSource.create(yangText);
173 return forIR(source.rootStatement(), source.getIdentifier());
176 private static @NonNull YangModelDependencyInfo parseModuleContext(final IRStatement module,
177 final SourceIdentifier source) {
178 final String name = safeStringArgument(source, module, "module name");
179 final String latestRevision = getLatestRevision(module, source);
180 final ImmutableSet<ModuleImport> imports = parseImports(module, source);
181 final ImmutableSet<ModuleImport> includes = parseIncludes(module, source);
183 return new ModuleDependencyInfo(name, latestRevision, imports, includes);
186 private static ImmutableSet<ModuleImport> parseImports(final IRStatement module,
187 final SourceIdentifier source) {
188 final Set<ModuleImport> result = new HashSet<>();
189 for (final IRStatement substatement : module.statements()) {
190 if (isBuiltin(substatement, IMPORT)) {
191 final String importedModuleName = safeStringArgument(source, substatement, "imported module name");
192 final String revisionDateStr = getRevisionDateString(substatement, source);
193 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
194 result.add(new ModuleImportImpl(importedModuleName, revisionDate));
197 return ImmutableSet.copyOf(result);
200 private static boolean isBuiltin(final IRStatement stmt, final String localName) {
201 final IRKeyword keyword = stmt.keyword();
202 return keyword instanceof Unqualified && localName.equals(keyword.identifier());
205 private static ImmutableSet<ModuleImport> parseIncludes(final IRStatement module, final SourceIdentifier source) {
206 final Set<ModuleImport> result = new HashSet<>();
207 for (final IRStatement substatement : module.statements()) {
208 if (isBuiltin(substatement, INCLUDE)) {
209 final String revisionDateStr = getRevisionDateString(substatement, source);
210 final String IncludeModuleName = safeStringArgument(source, substatement, "included submodule name");
211 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
212 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
215 return ImmutableSet.copyOf(result);
218 private static String getRevisionDateString(final IRStatement importStatement, final SourceIdentifier source) {
219 String revisionDateStr = null;
220 for (final IRStatement substatement : importStatement.statements()) {
221 if (isBuiltin(substatement, REVISION_DATE)) {
222 revisionDateStr = safeStringArgument(source, substatement, "imported module revision-date");
225 return revisionDateStr;
228 public static String getLatestRevision(final IRStatement module, final SourceIdentifier source) {
229 String latestRevision = null;
230 for (final IRStatement substatement : module.statements()) {
231 if (isBuiltin(substatement, REVISION)) {
232 final String currentRevision = safeStringArgument(source, substatement, "revision date");
233 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
234 latestRevision = currentRevision;
238 return latestRevision;
241 private static @NonNull YangModelDependencyInfo parseSubmoduleContext(final IRStatement submodule,
242 final SourceIdentifier source) {
243 final String name = safeStringArgument(source, submodule, "submodule name");
244 final String belongsTo = parseBelongsTo(submodule, source);
246 final String latestRevision = getLatestRevision(submodule, source);
247 final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
248 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
250 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
253 private static String parseBelongsTo(final IRStatement submodule, final SourceIdentifier source) {
254 for (final IRStatement substatement : submodule.statements()) {
255 if (isBuiltin(substatement, BELONGS_TO)) {
256 return safeStringArgument(source, substatement, "belongs-to module name");
262 static String safeStringArgument(final SourceIdentifier source, final IRStatement stmt, final String desc) {
263 final StatementSourceReference ref = getReference(source, stmt);
264 final IRArgument arg = stmt.argument();
266 throw new IllegalArgumentException("Missing " + desc + " at " + ref);
269 // TODO: we probably need to understand yang version first....
270 return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
273 private static StatementSourceReference getReference(final SourceIdentifier source, final IRStatement stmt) {
274 return ExplicitStatement.atPosition(source.getName(), stmt.startLine(), stmt.startColumn() + 1);
278 * Dependency information for YANG module.
280 public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
281 ModuleDependencyInfo(final String name, final String latestRevision, final ImmutableSet<ModuleImport> imports,
282 final ImmutableSet<ModuleImport> includes) {
283 super(name, latestRevision, imports, includes);
287 public String toString() {
288 return "Module [name=" + getName() + ", revision=" + getRevision()
289 + ", dependencies=" + getDependencies()
295 * Dependency information for submodule, also provides name for parent module.
297 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
298 private final String belongsTo;
300 private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
301 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
302 super(name, latestRevision, imports, includes);
303 this.belongsTo = belongsTo;
307 * Returns name of parent module.
309 * @return The module this info belongs to
311 public String getParentModule() {
316 public String toString() {
317 return "Submodule [name=" + getName() + ", revision=" + getRevision()
318 + ", dependencies=" + getDependencies()
324 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
326 // FIXME: this is a rather nasty misuse of APIs :(
327 private static final class ModuleImportImpl implements ModuleImport {
328 private final @NonNull String moduleName;
329 private final Revision revision;
331 ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision) {
332 this.moduleName = requireNonNull(moduleName, "Module name must not be null.");
333 this.revision = revision;
337 public String getModuleName() {
342 public Optional<Revision> getRevision() {
343 return Optional.ofNullable(revision);
347 public String getPrefix() {
348 throw new UnsupportedOperationException();
352 public Optional<String> getDescription() {
353 return Optional.empty();
357 public Optional<String> getReference() {
358 return Optional.empty();
362 public ImportEffectiveStatement asEffectiveStatement() {
363 throw new UnsupportedOperationException();
367 public int hashCode() {
368 final int prime = 31;
370 result = prime * result + Objects.hashCode(moduleName);
371 result = prime * result + Objects.hashCode(revision);
376 public boolean equals(final Object obj) {
377 return this == obj || obj instanceof ModuleImportImpl other
378 && moduleName.equals(other.moduleName) && Objects.equals(revision, other.revision);
382 public String toString() {
383 return "ModuleImportImpl [name=" + moduleName + ", revision=" + revision + "]";