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