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