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