BUG-869: added proper handling of nullable parameter
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / impl / util / YangModelDependencyInfo.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
3  * This program and the accompanying materials are made available under the
4  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
5  * and is available at http://www.eclipse.org/legal/eplv10.html
6  */
7 package org.opendaylight.yangtools.yang.parser.impl.util;
8
9 import static org.opendaylight.yangtools.yang.parser.impl.ParserListenerUtils.getArgumentString;
10 import static org.opendaylight.yangtools.yang.parser.impl.ParserListenerUtils.getFirstContext;
11
12 import com.google.common.base.Optional;
13 import com.google.common.collect.ImmutableSet;
14 import java.io.InputStream;
15 import java.util.Date;
16 import java.util.List;
17 import org.antlr.v4.runtime.ParserRuleContext;
18 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Belongs_to_stmtContext;
19 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Import_stmtContext;
20 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Include_stmtContext;
21 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Module_stmtContext;
22 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_date_stmtContext;
23 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_stmtContext;
24 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_stmtsContext;
25 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Submodule_stmtContext;
26 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.YangContext;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
29 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
30 import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
31
32 /**
33  * Helper transfer object which holds basic and dependency information for YANG
34  * model.
35  *
36  *
37  *
38  * There are two concrete implementations of this interface:
39  * <ul>
40  * <li>{@link ModuleDependencyInfo} - Dependency information for module</li>
41  * <li>{@link SubmoduleDependencyInfo} - Dependency information for submodule</li>
42  * </ul>
43  *
44  * @see ModuleDependencyInfo
45  * @see SubmoduleDependencyInfo
46  *
47  */
48 public abstract class YangModelDependencyInfo {
49
50     private final String name;
51     private final String formattedRevision;
52     private final Date revision;
53     private final ImmutableSet<ModuleImport> submoduleIncludes;
54     private final ImmutableSet<ModuleImport> moduleImports;
55     private final ImmutableSet<ModuleImport> dependencies;
56
57     YangModelDependencyInfo(final String name, final String formattedRevision,
58             final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
59         this.name = name;
60         this.formattedRevision = formattedRevision;
61         this.revision = formattedRevision == null ? null : QName.parseRevision(formattedRevision);
62         this.moduleImports = imports;
63         this.submoduleIncludes = includes;
64         this.dependencies = ImmutableSet.<ModuleImport> builder() //
65                 .addAll(moduleImports) //
66                 .addAll(submoduleIncludes) //
67                 .build();
68     }
69
70     /**
71      * Returns immutable collection of all module imports.
72      *
73      * This collection contains both <code>import</code> statements
74      * and <code>include</code> statements for submodules.
75      *
76      * @return Immutable collection of imports.
77      */
78     public ImmutableSet<ModuleImport> getDependencies() {
79         return dependencies;
80     }
81
82     /**
83      * Returns model name
84      *
85      * @return model name
86      */
87     public String getName() {
88         return name;
89     }
90
91     /**
92      * Returns formatted revision string
93      *
94      * @return formatted revision string
95      */
96     public String getFormattedRevision() {
97         return formattedRevision;
98     }
99
100     /**
101      * Returns revision
102      *
103      * @return revision
104      */
105     Date getRevision() {
106         return revision;
107     }
108
109     @Override
110     public int hashCode() {
111         final int prime = 31;
112         int result = 1;
113         result = prime * result + ((formattedRevision == null) ? 0 : formattedRevision.hashCode());
114         result = prime * result + ((name == null) ? 0 : name.hashCode());
115         return result;
116     }
117
118     @Override
119     public boolean equals(final Object obj) {
120         if (this == obj) {
121             return true;
122         }
123         if (obj == null) {
124             return false;
125         }
126         if (!(obj instanceof YangModelDependencyInfo)) {
127             return false;
128         }
129         YangModelDependencyInfo other = (YangModelDependencyInfo) obj;
130         if (formattedRevision == null) {
131             if (other.formattedRevision != null) {
132                 return false;
133             }
134         } else if (!formattedRevision.equals(other.formattedRevision)) {
135             return false;
136         }
137         if (name == null) {
138             if (other.name != null) {
139                 return false;
140             }
141         } else if (!name.equals(other.name)) {
142             return false;
143         }
144         return true;
145     }
146
147     /**
148      * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree
149      * of a YANG model.
150      *
151      * @param tree Abstract syntax tree
152      * @return {@link YangModelDependencyInfo}
153      * @throws YangSyntaxErrorException
154      *             If the AST is not a valid YANG module/submodule
155      */
156     public static YangModelDependencyInfo fromAST(final String name, final ParserRuleContext tree) throws YangSyntaxErrorException {
157         final Optional<Module_stmtContext> moduleCtx = getFirstContext(tree, Module_stmtContext.class);
158         if (moduleCtx.isPresent()) {
159             return parseModuleContext(moduleCtx.get());
160         }
161
162         final Optional<Submodule_stmtContext> submoduleCtx = getFirstContext(tree, Submodule_stmtContext.class);
163         if (submoduleCtx.isPresent()) {
164             return parseSubmoduleContext(submoduleCtx.get());
165         }
166
167         throw new YangSyntaxErrorException(name, 0, 0, "Unknown YANG text type");
168     }
169
170     /**
171      * Extracts {@link YangModelDependencyInfo} from input stream
172      * containing YANG model.
173      *
174      * This parsing does not validate full YANG module, only
175      * parses header up to the revisions and imports.
176      *
177      * @param yangStream
178      *            Opened Input stream containing text source of YANG model
179      * @return {@link YangModelDependencyInfo}
180      * @throws IllegalArgumentException
181      *             If input stream is not valid YANG stream
182      */
183     public static YangModelDependencyInfo fromInputStream(final InputStream yangStream) {
184         YangContext yangContext = YangParserImpl.parseStreamWithoutErrorListeners(yangStream);
185
186         Optional<Module_stmtContext> moduleCtx = getFirstContext(yangContext, Module_stmtContext.class);
187         if (moduleCtx.isPresent()) {
188             return parseModuleContext(moduleCtx.get());
189         }
190         Optional<Submodule_stmtContext> submoduleCtx = getFirstContext(yangContext, Submodule_stmtContext.class);
191         if (submoduleCtx.isPresent()) {
192             return parseSubmoduleContext(submoduleCtx.get());
193         }
194         throw new IllegalArgumentException("Supplied stream is not valid yang file.");
195     }
196
197     private static YangModelDependencyInfo parseModuleContext(final Module_stmtContext module) {
198         String name = getArgumentString(module);
199         // String prefix =
200         // getArgumentString(module.module_header_stmts().prefix_stmt(0));
201         String namespace = getArgumentString(module.module_header_stmts().namespace_stmt(0));
202         String latestRevision = getLatestRevision(module.revision_stmts());
203         ImmutableSet<ModuleImport> imports = parseImports(module.linkage_stmts().import_stmt());
204         ImmutableSet<ModuleImport> includes = parseIncludes(module.linkage_stmts().include_stmt());
205
206         return new ModuleDependencyInfo(name, latestRevision, namespace, imports, includes);
207     }
208
209     private static ImmutableSet<ModuleImport> parseImports(final List<Import_stmtContext> importStatements) {
210         ImmutableSet.Builder<ModuleImport> builder = ImmutableSet.builder();
211         for (Import_stmtContext importStmt : importStatements) {
212             String moduleName = getArgumentString(importStmt);
213             Date revision = getRevision(importStmt.revision_date_stmt());
214             builder.add(new ModuleImportImpl(moduleName, revision));
215         }
216         return builder.build();
217     }
218
219     public static String getLatestRevision(final Revision_stmtsContext revision_stmts) {
220         List<Revision_stmtContext> revisions = revision_stmts.getRuleContexts(Revision_stmtContext.class);
221         String latestRevision = null;
222         for (Revision_stmtContext revisionStmt : revisions) {
223             String currentRevision = getArgumentString(revisionStmt);
224             if (latestRevision == null || latestRevision.compareTo(currentRevision) == -1) {
225                 latestRevision = currentRevision;
226             }
227         }
228         return latestRevision;
229     }
230
231     private static YangModelDependencyInfo parseSubmoduleContext(final Submodule_stmtContext submodule) {
232         String name = getArgumentString(submodule);
233         Belongs_to_stmtContext belongsToStmt = submodule.submodule_header_stmts().belongs_to_stmt(0);
234         String belongsTo = getArgumentString(belongsToStmt);
235
236         String latestRevision = getLatestRevision(submodule.revision_stmts());
237         ImmutableSet<ModuleImport> imports = parseImports(submodule.linkage_stmts().import_stmt());
238         ImmutableSet<ModuleImport> includes = parseIncludes(submodule.linkage_stmts().include_stmt());
239
240         return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
241     }
242
243     private static ImmutableSet<ModuleImport> parseIncludes(final List<Include_stmtContext> importStatements) {
244         ImmutableSet.Builder<ModuleImport> builder = ImmutableSet.builder();
245         for (Include_stmtContext importStmt : importStatements) {
246             String moduleName = getArgumentString(importStmt);
247             Date revision = getRevision(importStmt.revision_date_stmt());
248             builder.add(new ModuleImportImpl(moduleName, revision));
249         }
250         return builder.build();
251     }
252
253     private static Date getRevision(final Revision_date_stmtContext revision_date_stmt) {
254         if (revision_date_stmt == null) {
255             return null;
256         }
257         String formatedDate = getArgumentString(revision_date_stmt);
258         return QName.parseRevision(formatedDate);
259     }
260
261     /**
262      *
263      * Dependency information for YANG module.
264      *
265      */
266     public static final class ModuleDependencyInfo extends YangModelDependencyInfo {
267
268         private ModuleDependencyInfo(final String name, final String latestRevision, final String namespace,
269                 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
270             super(name, latestRevision, imports, includes);
271         }
272
273         @Override
274         public String toString() {
275             return "Module [name=" + getName() + ", revision=" + getRevision() + ", dependencies=" + getDependencies()
276                     + "]";
277         }
278     }
279
280     /**
281      *
282      * Dependency information for submodule, also provides name
283      * for parent module.
284      *
285      */
286     public static final class SubmoduleDependencyInfo extends YangModelDependencyInfo {
287
288         private final String belongsTo;
289
290         /**
291          * Returns name of parent module.
292          *
293          */
294         public String getParentModule() {
295             return belongsTo;
296         }
297
298         private SubmoduleDependencyInfo(final String name, final String latestRevision, final String belongsTo,
299                 final ImmutableSet<ModuleImport> imports, final ImmutableSet<ModuleImport> includes) {
300             super(name, latestRevision, imports, includes);
301             this.belongsTo = belongsTo;
302         }
303
304         @Override
305         public String toString() {
306             return "Submodule [name=" + getName() + ", revision=" + getRevision() + ", dependencies="
307                     + getDependencies() + "]";
308         }
309     }
310
311     /**
312      * Utility implementation of {@link ModuleImport} to be used by
313      * {@link YangModelDependencyInfo}.
314      *
315      */
316     private static final class ModuleImportImpl implements ModuleImport {
317
318         private final Date revision;
319         private final String name;
320
321         public ModuleImportImpl(final String moduleName, final Date revision) {
322             this.name = moduleName;
323             this.revision = revision;
324         }
325
326         @Override
327         public String getModuleName() {
328             return this.name;
329         }
330
331         @Override
332         public Date getRevision() {
333             return this.revision;
334         }
335
336         @Override
337         public String getPrefix() {
338             return null;
339         }
340
341         @Override
342         public int hashCode() {
343             final int prime = 31;
344             int result = 1;
345             result = prime * result + ((name == null) ? 0 : name.hashCode());
346             result = prime * result + ((revision == null) ? 0 : revision.hashCode());
347             return result;
348         }
349
350         @Override
351         public boolean equals(final Object obj) {
352             if (this == obj) {
353                 return true;
354             }
355             if (obj == null) {
356                 return false;
357             }
358             if (getClass() != obj.getClass()) {
359                 return false;
360             }
361             ModuleImportImpl other = (ModuleImportImpl) obj;
362             if (name == null) {
363                 if (other.name != null) {
364                     return false;
365                 }
366             } else if (!name.equals(other.name)) {
367                 return false;
368             }
369             if (revision == null) {
370                 if (other.revision != null) {
371                     return false;
372                 }
373             } else if (!revision.equals(other.revision)) {
374                 return false;
375             }
376             return true;
377         }
378
379         @Override
380         public String toString() {
381             return "ModuleImportImpl [name=" + name + ", revision=" + QName.formattedRevision(revision) + "]";
382         }
383     }
384 }