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