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