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