043ca62d4f80918af4d94877b5b1d29af7eae338
[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.annotations.Beta;
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.api.stmt.ImportEffectiveStatement;
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.api.YangSyntaxErrorException;
33 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
34 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword;
35 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Unqualified;
36 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRSchemaSource;
37 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
38 import org.opendaylight.yangtools.yang.parser.spi.source.ExplicitStatement;
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
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         revision = Revision.ofNullable(formattedRevision).orElse(null);
86         moduleImports = imports;
87         submoduleIncludes = includes;
88         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      * @deprecated Semantic versioning is deprecated
135      */
136     @Deprecated(since = "8.0.4", forRemoval = true)
137     public Optional<SemVer> getSemanticVersion() {
138         return Optional.ofNullable(semVer);
139     }
140
141     @Override
142     public int hashCode() {
143         final int prime = 31;
144         int result = 1;
145         result = prime * result + Objects.hashCode(name);
146         result = prime * result + Objects.hashCode(revision);
147         result = prime * result + Objects.hashCode(semVer);
148         return result;
149     }
150
151     @Override
152     public boolean equals(final Object obj) {
153         if (this == obj) {
154             return true;
155         }
156         if (obj == null) {
157             return false;
158         }
159         if (!(obj instanceof YangModelDependencyInfo)) {
160             return false;
161         }
162         final YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
163         return Objects.equals(name, other.name) && Objects.equals(revision, other.revision)
164                 && Objects.equals(semVer, other.semVer);
165     }
166
167     /**
168      * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
169      *
170      * @param source Schema source
171      * @return {@link YangModelDependencyInfo}
172      * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
173      */
174     public static @NonNull YangModelDependencyInfo forIR(final IRSchemaSource source) {
175         return forIR(source.getRootStatement(), source.getIdentifier());
176     }
177
178     /**
179      * Extracts {@link YangModelDependencyInfo} from an intermediate representation root statement of a YANG model.
180      *
181      * @param source Source identifier
182      * @param rootStatement root statement
183      * @return {@link YangModelDependencyInfo}
184      * @throws IllegalArgumentException If the root statement is not a valid YANG module/submodule
185      */
186     static @NonNull YangModelDependencyInfo forIR(final IRStatement rootStatement,
187             final SourceIdentifier source) {
188         final IRKeyword keyword = rootStatement.keyword();
189         checkArgument(keyword instanceof Unqualified, "Invalid root statement %s", keyword);
190
191         final String arg = keyword.identifier();
192         if (MODULE.equals(arg)) {
193             return parseModuleContext(rootStatement, source);
194         }
195         if (SUBMODULE.equals(arg)) {
196             return parseSubmoduleContext(rootStatement, source);
197         }
198         throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
199     }
200
201     /**
202      * Extracts {@link YangModelDependencyInfo} from a {@link YangTextSchemaSource}. This parsing does not
203      * validate full YANG module, only parses header up to the revisions and imports.
204      *
205      * @param yangText {@link YangTextSchemaSource}
206      * @return {@link YangModelDependencyInfo}
207      * @throws YangSyntaxErrorException If the resource does not pass syntactic analysis
208      * @throws IOException When the resource cannot be read
209      */
210     public static YangModelDependencyInfo forYangText(final YangTextSchemaSource yangText)
211             throws IOException, YangSyntaxErrorException {
212         final YangStatementStreamSource source = YangStatementStreamSource.create(yangText);
213         return forIR(source.rootStatement(), source.getIdentifier());
214     }
215
216     private static @NonNull YangModelDependencyInfo parseModuleContext(final IRStatement 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 IRStatement module,
228             final SourceIdentifier source) {
229         final Set<ModuleImport> result = new HashSet<>();
230         for (final IRStatement substatement : module.statements()) {
231             if (isBuiltin(substatement, IMPORT)) {
232                 final String importedModuleName = safeStringArgument(source, substatement, "imported module name");
233                 final String revisionDateStr = getRevisionDateString(substatement, source);
234                 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
235                 final SemVer importSemVer = findSemanticVersion(substatement, source);
236                 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
237             }
238         }
239         return ImmutableSet.copyOf(result);
240     }
241
242     @Beta
243     public static SemVer findSemanticVersion(final IRStatement statement, final SourceIdentifier source) {
244         String semVerString = null;
245         for (final IRStatement substatement : statement.statements()) {
246             // FIXME: this should also check we are using a prefix
247             if (OPENCONFIG_VERSION.equals(substatement.keyword().identifier())) {
248                 semVerString = safeStringArgument(source,  substatement, "version string");
249                 break;
250             }
251         }
252
253         return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
254     }
255
256     private static boolean isBuiltin(final IRStatement stmt, final String localName) {
257         final IRKeyword keyword = stmt.keyword();
258         return keyword instanceof Unqualified && localName.equals(keyword.identifier());
259     }
260
261     private static ImmutableSet<ModuleImport> parseIncludes(final IRStatement module, final SourceIdentifier source) {
262         final Set<ModuleImport> result = new HashSet<>();
263         for (final IRStatement substatement : module.statements()) {
264             if (isBuiltin(substatement, INCLUDE)) {
265                 final String revisionDateStr = getRevisionDateString(substatement, source);
266                 final String IncludeModuleName = safeStringArgument(source, substatement, "included submodule name");
267                 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
268                 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
269             }
270         }
271         return ImmutableSet.copyOf(result);
272     }
273
274     private static String getRevisionDateString(final IRStatement importStatement, final SourceIdentifier source) {
275         String revisionDateStr = null;
276         for (final IRStatement substatement : importStatement.statements()) {
277             if (isBuiltin(substatement, REVISION_DATE)) {
278                 revisionDateStr = safeStringArgument(source, substatement, "imported module revision-date");
279             }
280         }
281         return revisionDateStr;
282     }
283
284     public static String getLatestRevision(final IRStatement module, final SourceIdentifier source) {
285         String latestRevision = null;
286         for (final IRStatement substatement : module.statements()) {
287             if (isBuiltin(substatement, REVISION)) {
288                 final String currentRevision = safeStringArgument(source, substatement, "revision date");
289                 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
290                     latestRevision = currentRevision;
291                 }
292             }
293         }
294         return latestRevision;
295     }
296
297     private static @NonNull YangModelDependencyInfo parseSubmoduleContext(final IRStatement submodule,
298             final SourceIdentifier source) {
299         final String name = safeStringArgument(source, submodule, "submodule name");
300         final String belongsTo = parseBelongsTo(submodule, source);
301
302         final String latestRevision = getLatestRevision(submodule, source);
303         final ImmutableSet<ModuleImport> imports = parseImports(submodule, source);
304         final ImmutableSet<ModuleImport> includes = parseIncludes(submodule, source);
305
306         return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
307     }
308
309     private static String parseBelongsTo(final IRStatement submodule, final SourceIdentifier source) {
310         for (final IRStatement substatement : submodule.statements()) {
311             if (isBuiltin(substatement, BELONGS_TO)) {
312                 return safeStringArgument(source, substatement, "belongs-to module name");
313             }
314         }
315         return null;
316     }
317
318     static String safeStringArgument(final SourceIdentifier source, final IRStatement stmt, final String desc) {
319         final StatementSourceReference ref = getReference(source, stmt);
320         final IRArgument arg = stmt.argument();
321         if (arg == null) {
322             throw new IllegalArgumentException("Missing " + desc + " at " + ref);
323         }
324
325         // TODO: we probably need to understand yang version first....
326         return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
327     }
328
329     private static StatementSourceReference getReference(final SourceIdentifier source, final IRStatement stmt) {
330         return ExplicitStatement.atPosition(source.getName(), stmt.startLine(), stmt.startColumn() + 1);
331     }
332
333     /**
334      * Dependency information for YANG module.
335      */
336     public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
337         ModuleDependencyInfo(final String name, final String latestRevision, final ImmutableSet<ModuleImport> imports,
338                 final ImmutableSet<ModuleImport> includes, final Optional<SemVer> semVer) {
339             super(name, latestRevision, imports, includes, semVer);
340         }
341
342         @Override
343         public String toString() {
344             return "Module [name=" + getName() + ", revision=" + getRevision()
345                 + ", semanticVersion=" + getSemanticVersion().orElse(null)
346                 + ", dependencies=" + getDependencies()
347                 + "]";
348         }
349     }
350
351     /**
352      * Dependency information for submodule, also provides name for parent module.
353      */
354     public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
355         private final String belongsTo;
356
357         private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
358                 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
359             super(name, latestRevision, imports, includes);
360             this.belongsTo = belongsTo;
361         }
362
363         /**
364          * Returns name of parent module.
365          *
366          * @return The module this info belongs to
367          */
368         public String getParentModule() {
369             return belongsTo;
370         }
371
372         @Override
373         public String toString() {
374             return "Submodule [name=" + getName() + ", revision="
375                     + getRevision() + ", dependencies=" + getDependencies()
376                     + "]";
377         }
378     }
379
380     /**
381      * Utility implementation of {@link ModuleImport} to be used by {@link YangModelDependencyInfo}.
382      */
383     // FIXME: this is a rather nasty misuse of APIs :(
384     private static final class ModuleImportImpl implements ModuleImport {
385
386         private final Revision revision;
387         private final SemVer semVer;
388         private final String name;
389
390         ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision) {
391             this(moduleName, revision, null);
392         }
393
394         ModuleImportImpl(final @NonNull String moduleName, final @Nullable Revision revision,
395                 final @Nullable SemVer semVer) {
396             name = requireNonNull(moduleName, "Module name must not be null.");
397             this.revision = revision;
398             this.semVer = semVer;
399         }
400
401         @Override
402         public String getModuleName() {
403             return name;
404         }
405
406         @Override
407         public Optional<Revision> getRevision() {
408             return Optional.ofNullable(revision);
409         }
410
411         @Override
412         @Deprecated(forRemoval = true)
413         public Optional<SemVer> getSemanticVersion() {
414             return Optional.ofNullable(semVer);
415         }
416
417         @Override
418         public String getPrefix() {
419             throw new UnsupportedOperationException();
420         }
421
422         @Override
423         public Optional<String> getDescription() {
424             return Optional.empty();
425         }
426
427         @Override
428         public Optional<String> getReference() {
429             return Optional.empty();
430         }
431
432         @Override
433         public ImportEffectiveStatement asEffectiveStatement() {
434             throw new UnsupportedOperationException();
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 }