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