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.common.UnresolvedQName;
23 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
24 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
25 import org.opendaylight.yangtools.yang.model.api.stmt.ImportEffectiveStatement;
26 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
27 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
28 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
29 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
30 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword;
31 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Unqualified;
32 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource;
33 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
34 import org.opendaylight.yangtools.yang.parser.spi.source.ExplicitStatement;
35 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
38 * Helper transfer object which holds basic and dependency information for YANG
42 * There are two concrete implementations of this interface:
44 * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
45 * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
48 * @see ModuleDependencyInfo
49 * @see SubmoduleDependencyInfo
51 public abstract class YangModelDependencyInfo {
52 private static final String BELONGS_TO = YangStmtMapping.BELONGS_TO.getStatementName().getLocalName();
53 private static final String IMPORT = YangStmtMapping.IMPORT.getStatementName().getLocalName();
54 private static final String INCLUDE = YangStmtMapping.INCLUDE.getStatementName().getLocalName();
55 private static final String MODULE = YangStmtMapping.MODULE.getStatementName().getLocalName();
56 private static final String REVISION = YangStmtMapping.REVISION.getStatementName().getLocalName();
57 private static final String REVISION_DATE = YangStmtMapping.REVISION_DATE.getStatementName().getLocalName();
58 private static final String SUBMODULE = YangStmtMapping.SUBMODULE.getStatementName().getLocalName();
60 private final String name;
61 private final Revision revision;
62 private final ImmutableSet<ModuleImport> submoduleIncludes;
63 private final ImmutableSet<ModuleImport> moduleImports;
64 private final ImmutableSet<ModuleImport> dependencies;
66 YangModelDependencyInfo(final String name, final String formattedRevision, final ImmutableSet<ModuleImport> imports,
67 final ImmutableSet<ModuleImport> includes) {
69 revision = Revision.ofNullable(formattedRevision).orElse(null);
70 moduleImports = imports;
71 submoduleIncludes = includes;
72 dependencies = ImmutableSet.<ModuleImport>builder()
73 .addAll(moduleImports).addAll(submoduleIncludes).build();
77 * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
78 * and <code>include</code> statements for submodules.
80 * @return Immutable collection of imports.
82 public ImmutableSet<ModuleImport> getDependencies() {
91 public String getName() {
96 * Returns formatted revision string.
98 * @return formatted revision string
100 public String getFormattedRevision() {
101 return revision != null ? revision.toString() : null;
107 * @return revision, potentially null
109 public Optional<Revision> getRevision() {
110 return Optional.ofNullable(revision);
114 public int hashCode() {
115 final int prime = 31;
117 result = prime * result + Objects.hashCode(name);
118 result = prime * result + Objects.hashCode(revision);
123 public boolean equals(final Object obj) {
124 return this == obj || obj instanceof YangModelDependencyInfo other
125 && Objects.equals(name, other.name) && Objects.equals(revision, other.revision);
129 * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
131 * @param source Schema source
132 * @return {@link YangModelDependencyInfo}
133 * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
135 public static @NonNull YangModelDependencyInfo forIR(final IRSchemaSource source) {
136 return forIR(source.getRootStatement(), source.getIdentifier());
140 * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
142 * @param source Source identifier
143 * @param rootStatement root statement
144 * @return {@link YangModelDependencyInfo}
145 * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
147 static @NonNull YangModelDependencyInfo forIR(final IRStatement rootStatement,
148 final SourceIdentifier source) {
149 final IRKeyword keyword = rootStatement.keyword();
150 checkArgument(keyword instanceof Unqualified, "Invalid root statement %s", keyword);
152 final String arg = keyword.identifier();
153 if (MODULE.equals(arg)) {
154 return parseModuleContext(rootStatement, source);
156 if (SUBMODULE.equals(arg)) {
157 return parseSubmoduleContext(rootStatement, source);
159 throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
163 * Extracts {@link YangModelDependencyInfo} from a {@link YangTextSchemaSource}. This parsing does not
164 * validate full YANG module, only parses header up to the revisions and imports.
166 * @param yangText {@link YangTextSchemaSource}
167 * @return {@link YangModelDependencyInfo}
168 * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
169 * @throws IOException When the resource cannot be read
171 public static YangModelDependencyInfo forYangText(final YangTextSchemaSource yangText)
172 throws IOException, YangSyntaxErrorException {
173 final YangStatementStreamSource source = YangStatementStreamSource.create(yangText);
174 return forIR(source.rootStatement(), source.getIdentifier());
177 private static @NonNull YangModelDependencyInfo parseModuleContext(final IRStatement module,
178 final SourceIdentifier source) {
179 final String name = safeStringArgument(source, module, "module name");
180 final String latestRevision = getLatestRevision(module, source);
181 final ImmutableSet<ModuleImport> imports = parseImports(module, source);
182 final ImmutableSet<ModuleImport> includes = parseIncludes(module, source);
184 return new ModuleDependencyInfo(name, latestRevision, imports, includes);
187 private static ImmutableSet<ModuleImport> parseImports(final IRStatement module,
188 final SourceIdentifier source) {
189 final Set<ModuleImport> result = new HashSet<>();
190 for (final IRStatement substatement : module.statements()) {
191 if (isBuiltin(substatement, IMPORT)) {
192 final String importedModuleName = safeStringArgument(source, substatement, "imported module name");
193 final String revisionDateStr = getRevisionDateString(substatement, source);
194 result.add(new ModuleImportImpl(UnresolvedQName.Unqualified.of(importedModuleName),
195 revisionDateStr != null ? Revision.of(revisionDateStr) : null));
198 return ImmutableSet.copyOf(result);
201 private static boolean isBuiltin(final IRStatement stmt, final String localName) {
202 final IRKeyword keyword = stmt.keyword();
203 return keyword instanceof Unqualified && localName.equals(keyword.identifier());
206 private static ImmutableSet<ModuleImport> parseIncludes(final IRStatement module, final SourceIdentifier source) {
207 final Set<ModuleImport> result = new HashSet<>();
208 for (final IRStatement substatement : module.statements()) {
209 if (isBuiltin(substatement, INCLUDE)) {
210 final String revisionDateStr = getRevisionDateString(substatement, source);
211 final String includeModuleName = safeStringArgument(source, substatement, "included submodule name");
212 result.add(new ModuleImportImpl(UnresolvedQName.Unqualified.of(includeModuleName),
213 revisionDateStr == null ? null : Revision.of(revisionDateStr)));
216 return ImmutableSet.copyOf(result);
219 private static String getRevisionDateString(final IRStatement importStatement, final SourceIdentifier source) {
220 String revisionDateStr = null;
221 for (final IRStatement substatement : importStatement.statements()) {
222 if (isBuiltin(substatement, REVISION_DATE)) {
223 revisionDateStr = safeStringArgument(source, substatement, "imported module revision-date");
226 return revisionDateStr;
229 public static String getLatestRevision(final IRStatement module, final SourceIdentifier source) {
230 String latestRevision = null;
231 for (final IRStatement substatement : module.statements()) {
232 if (isBuiltin(substatement, REVISION)) {
233 final String currentRevision = safeStringArgument(source, substatement, "revision date");
234 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
235 latestRevision = currentRevision;
239 return latestRevision;
242 private static @NonNull YangModelDependencyInfo parseSubmoduleContext(final IRStatement submodule,
243 final SourceIdentifier source) {
244 final String name = safeStringArgument(source, submodule, "submodule name");
245 final UnresolvedQName.Unqualified belongsTo = UnresolvedQName.Unqualified.of(parseBelongsTo(submodule, source));
247 final String latestRevision = getLatestRevision(submodule, source);
248 final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
249 final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
251 return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
254 private static String parseBelongsTo(final IRStatement submodule, final SourceIdentifier source) {
255 for (final IRStatement substatement : submodule.statements()) {
256 if (isBuiltin(substatement, BELONGS_TO)) {
257 return safeStringArgument(source, substatement, "belongs-to module name");
263 static String safeStringArgument(final SourceIdentifier source, final IRStatement stmt, final String desc) {
264 final StatementSourceReference ref = getReference(source, stmt);
265 final IRArgument arg = stmt.argument();
267 throw new IllegalArgumentException("Missing " + desc + " at " + ref);
270 // TODO: we probably need to understand yang version first....
271 return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
274 private static StatementSourceReference getReference(final SourceIdentifier source, final IRStatement stmt) {
275 return ExplicitStatement.atPosition(source.name().getLocalName(), stmt.startLine(), stmt.startColumn() + 1);
279 * Dependency information for YANG module.
281 public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
282 ModuleDependencyInfo(final String name, final String latestRevision, final ImmutableSet<ModuleImport> imports,
283 final ImmutableSet<ModuleImport> includes) {
284 super(name, latestRevision, imports, includes);
288 public String toString() {
289 return "Module [name=" + getName() + ", revision=" + getRevision()
290 + ", dependencies=" + getDependencies()
296 * Dependency information for submodule, also provides name for parent module.
298 public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
299 private final UnresolvedQName.Unqualified belongsTo;
301 private SubmoduleDependencyInfo(final String name, final String latestRevision,
302 final UnresolvedQName.Unqualified belongsTo, final ImmutableSet<ModuleImport> imports,
303 final ImmutableSet<ModuleImport> includes) {
304 super(name, latestRevision, imports, includes);
305 this.belongsTo = belongsTo;
309 * Returns name of parent module.
311 * @return The module this info belongs to
313 public UnresolvedQName.Unqualified getParentModule() {
318 public String toString() {
319 return "Submodule [name=" + getName() + ", revision=" + getRevision()
320 + ", dependencies=" + getDependencies()
326 * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
328 // FIXME: this is a rather nasty misuse of APIs :(
329 private static final class ModuleImportImpl implements ModuleImport {
330 private final UnresolvedQName.@NonNull Unqualified moduleName;
331 private final Revision revision;
333 ModuleImportImpl(final UnresolvedQName.@NonNull Unqualified moduleName, final @Nullable Revision revision) {
334 this.moduleName = requireNonNull(moduleName, "Module name must not be null.");
335 this.revision = revision;
339 public UnresolvedQName.Unqualified getModuleName() {
344 public Optional<Revision> getRevision() {
345 return Optional.ofNullable(revision);
349 public String getPrefix() {
350 throw new UnsupportedOperationException();
354 public Optional<String> getDescription() {
355 return Optional.empty();
359 public Optional<String> getReference() {
360 return Optional.empty();
364 public ImportEffectiveStatement asEffectiveStatement() {
365 throw new UnsupportedOperationException();
369 public int hashCode() {
370 final int prime = 31;
372 result = prime * result + Objects.hashCode(moduleName);
373 result = prime * result + Objects.hashCode(revision);
378 public boolean equals(final Object obj) {
379 return this == obj || obj instanceof ModuleImportImpl other
380 && moduleName.equals(other.moduleName) && Objects.equals(revision, other.revision);
384 public String toString() {
385 return "ModuleImportImpl [name=" + moduleName + ", revision=" + revision + "]";