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