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