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