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