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