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