Cleanup DocumentedNode
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / impl / util / YangModelDependencyInfo.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.parser.impl.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.Strings;
14 import com.google.common.collect.ImmutableSet;
15 import java.io.IOException;
16 import java.util.HashSet;
17 import java.util.Objects;
18 import java.util.Optional;
19 import java.util.Set;
20 import javax.annotation.Nullable;
21 import org.antlr.v4.runtime.ParserRuleContext;
22 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.StatementContext;
23 import org.opendaylight.yangtools.concepts.SemVer;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.common.Revision;
26 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
27 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
28 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
29 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
30 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
31 import org.opendaylight.yangtools.yang.parser.rfc6020.repo.YangStatementStreamSource;
32 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
33 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
34 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.SupportedExtensionsMapping;
35 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
36
37 /**
38  * Helper transfer object which holds basic and dependency information for YANG
39  * model.
40  *
41  * <p>
42  * There are two concrete implementations of this interface:
43  * <ul>
44  * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
45  * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
46  * </ul>
47  *
48  * @see ModuleDependencyInfo
49  * @see SubmoduleDependencyInfo
50  */
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();
59
60     private static final String OPENCONFIG_VERSION = SupportedExtensionsMapping.OPENCONFIG_VERSION.getStatementName()
61             .getLocalName();
62
63     private final String name;
64     private final Revision revision;
65     private final SemVer semVer;
66     private final ImmutableSet<ModuleImport> submoduleIncludes;
67     private final ImmutableSet<ModuleImport> moduleImports;
68     private final ImmutableSet<ModuleImport> dependencies;
69
70     YangModelDependencyInfo(final String name, final String formattedRevision,
71             final ImmutableSet<ModuleImport> imports,
72             final ImmutableSet<ModuleImport> includes) {
73         this(name, formattedRevision, imports, includes, Optional.empty());
74     }
75
76     YangModelDependencyInfo(final String name, final String formattedRevision,
77             final ImmutableSet<ModuleImport> imports,
78             final ImmutableSet<ModuleImport> includes,
79             final Optional<SemVer> semVer) {
80         this.name = name;
81         this.revision = Revision.ofNullable(formattedRevision).orElse(null);
82         this.moduleImports = imports;
83         this.submoduleIncludes = includes;
84         this.dependencies = ImmutableSet.<ModuleImport>builder()
85                 .addAll(moduleImports).addAll(submoduleIncludes).build();
86         this.semVer = semVer.orElse(null);
87     }
88
89     /**
90      * Returns immutable collection of all module imports. This collection contains both <code>import</code> statements
91      * and <code>include</code> statements for submodules.
92      *
93      * @return Immutable collection of imports.
94      */
95     public ImmutableSet<ModuleImport> getDependencies() {
96         return dependencies;
97     }
98
99     /**
100      * Returns model name.
101      *
102      * @return model name
103      */
104     public String getName() {
105         return name;
106     }
107
108     /**
109      * Returns formatted revision string.
110      *
111      * @return formatted revision string
112      */
113     public String getFormattedRevision() {
114         return revision != null ? revision.toString() : null;
115     }
116
117     /**
118      * Returns revision.
119      *
120      * @return revision, potentially null
121      */
122     public Optional<Revision> getRevision() {
123         return Optional.ofNullable(revision);
124     }
125
126     /**
127      * Returns semantic version of module.
128      *
129      * @return semantic version
130      */
131     public Optional<SemVer> getSemanticVersion() {
132         return Optional.ofNullable(semVer);
133     }
134
135     @Override
136     public int hashCode() {
137         final int prime = 31;
138         int result = 1;
139         result = prime * result + Objects.hashCode(name);
140         result = prime * result + Objects.hashCode(revision);
141         result = prime * result + Objects.hashCode(semVer);
142         return result;
143     }
144
145     @Override
146     public boolean equals(final Object obj) {
147         if (this == obj) {
148             return true;
149         }
150         if (obj == null) {
151             return false;
152         }
153         if (!(obj instanceof YangModelDependencyInfo)) {
154             return false;
155         }
156         final YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
157         return Objects.equals(name, other.name) && Objects.equals(revision, other.revision)
158                 && Objects.equals(semVer, other.semVer);
159     }
160
161     /**
162      * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of a YANG model.
163      *
164      * @param source Source identifier
165      * @param tree Abstract syntax tree
166      * @return {@link YangModelDependencyInfo}
167      * @throws YangSyntaxErrorException If the AST is not a valid YANG module/submodule
168      */
169     public static YangModelDependencyInfo fromAST(final SourceIdentifier source, final ParserRuleContext tree)
170             throws YangSyntaxErrorException {
171
172         if (tree instanceof StatementContext) {
173             final StatementContext rootStatement = (StatementContext) tree;
174             return parseAST(rootStatement, source);
175         }
176
177         throw new YangSyntaxErrorException(source, 0, 0, "Unknown YANG text type");
178     }
179
180     private static YangModelDependencyInfo parseAST(final StatementContext rootStatement,
181             final SourceIdentifier source) {
182         final String keyWordText = rootStatement.keyword().getText();
183         if (MODULE.equals(keyWordText)) {
184             return parseModuleContext(rootStatement, source);
185         }
186         if (SUBMODULE.equals(keyWordText)) {
187             return parseSubmoduleContext(rootStatement, source);
188         }
189         throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
190     }
191
192     /**
193      * Extracts {@link YangModelDependencyInfo} from input stream containing a YANG model. This parsing does not
194      * validate full YANG module, only parses header up to the revisions and imports.
195      *
196      * @param refClass Base search class
197      * @param resourceName resource name, relative to refClass
198      * @return {@link YangModelDependencyInfo}
199      * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
200      * @throws IOException When the resource cannot be read
201      * @throws IllegalArgumentException
202      *             If input stream is not valid YANG stream
203      */
204     public static YangModelDependencyInfo forResource(final Class<?> refClass, final String resourceName)
205             throws IOException, YangSyntaxErrorException {
206         final YangStatementStreamSource source = YangStatementStreamSource.create(
207             YangTextSchemaSource.forResource(refClass, resourceName));
208         final ParserRuleContext ast = source.getYangAST();
209         checkArgument(ast instanceof StatementContext);
210         return parseAST((StatementContext) ast, source.getIdentifier());
211     }
212
213     private static YangModelDependencyInfo parseModuleContext(final StatementContext module,
214             final SourceIdentifier source) {
215         final String name = Utils.stringFromStringContext(module.argument(), getReference(source, module));
216         final String latestRevision = getLatestRevision(module, source);
217         final Optional<SemVer> semVer = Optional.ofNullable(findSemanticVersion(module, source));
218         final ImmutableSet<ModuleImport> imports = parseImports(module, source);
219         final ImmutableSet<ModuleImport> includes = parseIncludes(module, source);
220
221         return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
222     }
223
224     private static ImmutableSet<ModuleImport> parseImports(final StatementContext module,
225             final SourceIdentifier source) {
226         final Set<ModuleImport> result = new HashSet<>();
227         for (final StatementContext subStatementContext : module.statement()) {
228             if (IMPORT.equals(subStatementContext.keyword().getText())) {
229                 final String revisionDateStr = getRevisionDateString(subStatementContext, source);
230                 final String importedModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
231                         getReference(source, subStatementContext));
232                 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
233                 final SemVer importSemVer = findSemanticVersion(subStatementContext, source);
234                 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
235             }
236         }
237         return ImmutableSet.copyOf(result);
238     }
239
240     private static SemVer findSemanticVersion(final StatementContext statement, final SourceIdentifier source) {
241         String semVerString = null;
242         for (final StatementContext subStatement : statement.statement()) {
243             final String subStatementName = Utils.trimPrefix(subStatement.keyword().getText());
244             if (OPENCONFIG_VERSION.equals(subStatementName)) {
245                 semVerString = Utils.stringFromStringContext(subStatement.argument(),
246                         getReference(source, subStatement));
247                 break;
248             }
249         }
250
251         return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
252     }
253
254     private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module,
255             final SourceIdentifier source) {
256         final Set<ModuleImport> result = new HashSet<>();
257         for (final StatementContext subStatementContext : module.statement()) {
258             if (INCLUDE.equals(subStatementContext.keyword().getText())) {
259                 final String revisionDateStr = getRevisionDateString(subStatementContext, source);
260                 final String IncludeModuleName = Utils.stringFromStringContext(subStatementContext.argument(),
261                         getReference(source, subStatementContext));
262                 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
263                 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
264             }
265         }
266         return ImmutableSet.copyOf(result);
267     }
268
269     private static String getRevisionDateString(final StatementContext importStatement, final SourceIdentifier source) {
270         String revisionDateStr = null;
271         for (final StatementContext importSubStatement : importStatement.statement()) {
272             if (REVISION_DATE.equals(importSubStatement.keyword().getText())) {
273                 revisionDateStr = Utils.stringFromStringContext(importSubStatement.argument(),
274                         getReference(source, importSubStatement));
275             }
276         }
277         return revisionDateStr;
278     }
279
280     public static String getLatestRevision(final StatementContext module, final SourceIdentifier source) {
281         String latestRevision = null;
282         for (final StatementContext subStatementContext : module.statement()) {
283             if (REVISION.equals(subStatementContext.keyword().getText())) {
284                 final String currentRevision = Utils.stringFromStringContext(subStatementContext.argument(),
285                         getReference(source, subStatementContext));
286                 if (latestRevision == null || latestRevision.compareTo(currentRevision) == -1) {
287                     latestRevision = currentRevision;
288                 }
289             }
290         }
291         return latestRevision;
292     }
293
294     private static YangModelDependencyInfo parseSubmoduleContext(final StatementContext submodule,
295             final SourceIdentifier source) {
296         final String name = Utils.stringFromStringContext(submodule.argument(), getReference(source, submodule));
297         final String belongsTo = parseBelongsTo(submodule, source);
298
299         final String latestRevision = getLatestRevision(submodule, source);
300         final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
301         final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
302
303         return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
304     }
305
306     private static String parseBelongsTo(final StatementContext submodule, final SourceIdentifier source) {
307         for (final StatementContext subStatementContext : submodule.statement()) {
308             if (BELONGS_TO.equals(subStatementContext.keyword().getText())) {
309                 return Utils.stringFromStringContext(subStatementContext.argument(),
310                     getReference(source, subStatementContext));
311             }
312         }
313         return null;
314     }
315
316     private static StatementSourceReference getReference(final SourceIdentifier source,
317             final StatementContext context) {
318         return DeclarationInTextSource.atPosition(source.getName(), context.getStart().getLine(),
319             context.getStart().getCharPositionInLine());
320     }
321
322     /**
323      * Dependency information for YANG module.
324      */
325     public static class ModuleDependencyInfo extends YangModelDependencyInfo {
326         private ModuleDependencyInfo(final String name, final String latestRevision,
327                 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
328             super(name, latestRevision, imports, includes);
329         }
330
331         private ModuleDependencyInfo(final String name, final String latestRevision,
332                 final ImmutableSet<ModuleImport> imports,
333                 final ImmutableSet<ModuleImport> includes,
334                 final Optional<SemVer> semVer) {
335             super(name, latestRevision, imports, includes, semVer);
336         }
337
338         @Override
339         public String toString() {
340             return "Module [name=" + getName() + ", revision=" + getRevision()
341                 + ", semanticVersion=" + getSemanticVersion().orElse(null)
342                 + ", dependencies=" + getDependencies()
343                 + "]";
344         }
345     }
346
347     /**
348      * Dependency information for submodule, also provides name for parent module.
349      */
350     public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
351         private final String belongsTo;
352
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;
357         }
358
359         /**
360          * Returns name of parent module.
361          */
362         public String getParentModule() {
363             return belongsTo;
364         }
365
366         @Override
367         public String toString() {
368             return "Submodule [name=" + getName() + ", revision="
369                     + getRevision() + ", dependencies=" + getDependencies()
370                     + "]";
371         }
372     }
373
374     /**
375      * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
376      */
377     private static final class ModuleImportImpl implements ModuleImport {
378
379         private final Revision revision;
380         private final SemVer semVer;
381         private final String name;
382
383         ModuleImportImpl(final String moduleName, final Revision revision) {
384             this(moduleName, revision, null);
385         }
386
387         ModuleImportImpl(final String moduleName, @Nullable final Revision revision, @Nullable final SemVer semVer) {
388             this.name = requireNonNull(moduleName, "Module name must not be null.");
389             this.revision = revision;
390             this.semVer = semVer;
391         }
392
393         @Override
394         public String getModuleName() {
395             return name;
396         }
397
398         @Override
399         public Optional<Revision> getRevision() {
400             return Optional.ofNullable(revision);
401         }
402
403         @Override
404         public Optional<SemVer> getSemanticVersion() {
405             return Optional.ofNullable(semVer);
406         }
407
408         @Override
409         public String getPrefix() {
410             return null;
411         }
412
413         @Override
414         public Optional<String> getDescription() {
415             return Optional.empty();
416         }
417
418         @Override
419         public Optional<String> getReference() {
420             return Optional.empty();
421         }
422
423         @Override
424         public int hashCode() {
425             final int prime = 31;
426             int result = 1;
427             result = prime * result + Objects.hashCode(name);
428             result = prime * result + Objects.hashCode(revision);
429             result = prime * result + Objects.hashCode(semVer);
430             return result;
431         }
432
433         @Override
434         public boolean equals(final Object obj) {
435             if (this == obj) {
436                 return true;
437             }
438             if (obj == null) {
439                 return false;
440             }
441             if (getClass() != obj.getClass()) {
442                 return false;
443             }
444             final ModuleImportImpl other = (ModuleImportImpl) obj;
445             if (name == null) {
446                 if (other.name != null) {
447                     return false;
448                 }
449             } else if (!name.equals(other.name)) {
450                 return false;
451             }
452             if (revision == null) {
453                 if (other.revision != null) {
454                     return false;
455                 }
456             } else if (!revision.equals(other.revision)) {
457                 return false;
458             }
459
460             if (!Objects.equals(getSemanticVersion(), other.getSemanticVersion())) {
461                 return false;
462             }
463             return true;
464         }
465
466         @Override
467         public String toString() {
468             return "ModuleImportImpl [name=" + name + ", revision="
469                     + QName.formattedRevision(Optional.ofNullable(revision)) + ", semanticVersion=" + semVer + "]";
470         }
471     }
472 }