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