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