Yang-maven-plugin refactored + fixed bugs.
[controller.git] / opendaylight / sal / yang-prototype / code-generator / maven-yang-plugin / src / main / java / org / opendaylight / controller / yang2sources / plugin / YangToSourcesProcessor.java
1 /*
2  * Copyright (c) 2013 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.controller.yang2sources.plugin;
9
10 import java.io.Closeable;
11 import java.io.File;
12 import java.io.FilenameFilter;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Enumeration;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.zip.ZipEntry;
24 import java.util.zip.ZipFile;
25
26 import org.apache.maven.model.Resource;
27 import org.apache.maven.plugin.MojoExecutionException;
28 import org.apache.maven.plugin.MojoFailureException;
29 import org.apache.maven.plugin.logging.Log;
30 import org.apache.maven.project.MavenProject;
31 import org.codehaus.plexus.util.FileUtils;
32 import org.opendaylight.controller.yang.model.api.Module;
33 import org.opendaylight.controller.yang.model.api.SchemaContext;
34 import org.opendaylight.controller.yang.parser.impl.YangParserImpl;
35 import org.opendaylight.controller.yang2sources.plugin.ConfigArg.CodeGeneratorArg;
36 import org.opendaylight.controller.yang2sources.plugin.Util.NamedFileInputStream;
37 import org.opendaylight.controller.yang2sources.spi.CodeGenerator;
38
39 import com.google.common.collect.Maps;
40
41 class YangToSourcesProcessor {
42     private static final String LOG_PREFIX = "yang-to-sources:";
43     private static final String META_INF_YANG_STRING = "META-INF"
44             + File.separator + "yang";
45     private static final File META_INF_YANG_DIR = new File(META_INF_YANG_STRING);
46
47     private final Log log;
48     private final File yangFilesRootDir;
49     private final List<CodeGeneratorArg> codeGenerators;
50     private final MavenProject project;
51     private final boolean inspectDependencies;
52
53     YangToSourcesProcessor(Log log, File yangFilesRootDir,
54             List<CodeGeneratorArg> codeGenerators, MavenProject project,
55             boolean inspectDependencies) {
56         this.log = checkNotNull(log, "log");
57         this.yangFilesRootDir = checkNotNull(yangFilesRootDir,
58                 "yangFilesRootDir");
59         this.codeGenerators = Collections.unmodifiableList(checkNotNull(
60                 codeGenerators, "codeGenerators"));
61         this.project = checkNotNull(project, "project");
62         this.inspectDependencies = inspectDependencies;
63     }
64
65     private static <T> T checkNotNull(T obj, String paramName) {
66         if (obj == null)
67             throw new NullPointerException("Parameter '" + paramName
68                     + "' is null");
69         return obj;
70     }
71
72     public void execute() throws MojoExecutionException, MojoFailureException {
73         ContextHolder context = processYang();
74         generateSources(context);
75         addYangsToMETA_INF();
76     }
77
78     private ContextHolder processYang() throws MojoExecutionException {
79         YangParserImpl parser = new YangParserImpl();
80         List<Closeable> closeables = new ArrayList<>();
81         log.info(Util.message("Inspecting %s", LOG_PREFIX, yangFilesRootDir));
82         try {
83             List<InputStream> yangsInProject = Util
84                     .listFilesAsStream(yangFilesRootDir);
85             List<InputStream> all = new ArrayList<>(yangsInProject);
86             closeables.addAll(yangsInProject);
87             Map<InputStream, Module> allYangModules;
88             Set<Module> projectYangModules;
89             try {
90                 if (inspectDependencies) {
91                     YangsInZipsResult dependentYangResult = findYangFilesInDependenciesAsStream();
92                     Closeable dependentYangResult1 = dependentYangResult;
93                     closeables.add(dependentYangResult1);
94                     all.addAll(dependentYangResult.yangStreams);
95                 }
96
97                 allYangModules = parser.parseYangModelsFromStreamsMapped(all);
98
99                 projectYangModules = new HashSet<>();
100                 for (InputStream inProject : yangsInProject) {
101                     projectYangModules.add(allYangModules.get(inProject));
102                 }
103
104             } finally {
105                 for (AutoCloseable closeable : closeables) {
106                     closeable.close();
107                 }
108             }
109
110             Set<Module> parsedAllYangModules = new HashSet<>(
111                     allYangModules.values());
112             SchemaContext resolveSchemaContext = parser
113                     .resolveSchemaContext(parsedAllYangModules);
114             log.info(Util.message("%s files parsed from %s", LOG_PREFIX,
115                     Util.YANG_SUFFIX.toUpperCase(), yangsInProject));
116             return new ContextHolder(resolveSchemaContext, projectYangModules);
117
118             // MojoExecutionException is thrown since execution cannot continue
119         } catch (Exception e) {
120             String message = Util.message("Unable to parse %s files from %s",
121                     LOG_PREFIX, Util.YANG_SUFFIX, yangFilesRootDir);
122             log.error(message, e);
123             throw new MojoExecutionException(message, e);
124         }
125     }
126
127     private void addYangsToMETA_INF() throws MojoFailureException {
128         Resource res = new Resource();
129
130         File targetYangDir = new File(project.getBasedir(), "target"
131                 + File.separator + "yang");
132         res.setDirectory(targetYangDir.getPath());
133
134         res.setTargetPath(META_INF_YANG_DIR.getPath());
135         try {
136             FileUtils.copyDirectory(yangFilesRootDir, targetYangDir);
137         } catch (IOException e) {
138             throw new MojoFailureException(e.getMessage(), e);
139         }
140         project.addResource(res);
141     }
142
143     /**
144      * Call generate on every generator from plugin configuration
145      */
146     private void generateSources(ContextHolder context)
147             throws MojoFailureException {
148         if (codeGenerators.size() == 0) {
149             log.warn(Util.message("No code generators provided", LOG_PREFIX));
150             return;
151         }
152
153         Map<String, String> thrown = Maps.newHashMap();
154         for (CodeGeneratorArg codeGenerator : codeGenerators) {
155             try {
156                 generateSourcesWithOneGenerator(context, codeGenerator);
157             } catch (Exception e) {
158                 // try other generators, exception will be thrown after
159                 log.error(Util.message(
160                         "Unable to generate sources with %s generator",
161                         LOG_PREFIX, codeGenerator.getCodeGeneratorClass()), e);
162                 thrown.put(codeGenerator.getCodeGeneratorClass(), e.getClass()
163                         .getCanonicalName());
164             }
165         }
166
167         if (!thrown.isEmpty()) {
168             String message = Util
169                     .message(
170                             "One or more code generators failed, including failed list(generatorClass=exception) %s",
171                             LOG_PREFIX, thrown.toString());
172             log.error(message);
173             throw new MojoFailureException(message);
174         }
175     }
176
177     /**
178      * Instantiate generator from class and call required method
179      */
180     private void generateSourcesWithOneGenerator(ContextHolder context,
181             CodeGeneratorArg codeGeneratorCfg) throws ClassNotFoundException,
182             InstantiationException, IllegalAccessException, IOException {
183
184         codeGeneratorCfg.check();
185
186         CodeGenerator g = Util.getInstance(
187                 codeGeneratorCfg.getCodeGeneratorClass(), CodeGenerator.class);
188         log.info(Util.message("Code generator instantiated from %s",
189                 LOG_PREFIX, codeGeneratorCfg.getCodeGeneratorClass()));
190
191         File outputDir = codeGeneratorCfg.getOutputBaseDir(project);
192
193         log.info(Util.message("Sources will be generated to %s", LOG_PREFIX,
194                 outputDir));
195         log.info(Util.message("Project root dir is %s", LOG_PREFIX,
196                 project.getBasedir()));
197         log.info(Util.message(
198                 "Additional configuration picked up for : %s: %s", LOG_PREFIX,
199                 codeGeneratorCfg.getCodeGeneratorClass(),
200                 codeGeneratorCfg.getAdditionalConfiguration()));
201         project.addCompileSourceRoot(outputDir.getAbsolutePath());
202         g.setLog(log);
203         g.setAdditionalConfig(codeGeneratorCfg.getAdditionalConfiguration());
204         Collection<File> generated = g.generateSources(context.getContext(),
205                 outputDir, context.getYangModules(), project.getBasedir());
206         log.info(Util.message("Sources generated by %s: %s", LOG_PREFIX,
207                 codeGeneratorCfg.getCodeGeneratorClass(), generated));
208     }
209
210     private class YangsInZipsResult implements Closeable {
211         private final List<InputStream> yangStreams;
212         private final List<Closeable> zipInputStreams;
213
214         private YangsInZipsResult(List<InputStream> yangStreams,
215                 List<Closeable> zipInputStreams) {
216             this.yangStreams = yangStreams;
217             this.zipInputStreams = zipInputStreams;
218         }
219
220         @Override
221         public void close() throws IOException {
222             for (InputStream is : yangStreams) {
223                 is.close();
224             }
225             for (Closeable is : zipInputStreams) {
226                 is.close();
227             }
228         }
229     }
230
231     private YangsInZipsResult findYangFilesInDependenciesAsStream()
232             throws MojoFailureException {
233         List<InputStream> yangsFromDependencies = new ArrayList<>();
234         List<Closeable> zips = new ArrayList<>();
235         try {
236             List<File> filesOnCp = Util.getClassPath(project);
237             log.info(Util.message(
238                     "Searching for yang files in following dependencies: %s",
239                     LOG_PREFIX, filesOnCp));
240
241             for (File file : filesOnCp) {
242                 List<String> foundFilesForReporting = new ArrayList<>();
243                 // is it jar file or directory?
244                 if (file.isDirectory()) {
245                     File yangDir = new File(file, META_INF_YANG_STRING);
246                     if (yangDir.exists() && yangDir.isDirectory()) {
247                         File[] yangFiles = yangDir
248                                 .listFiles(new FilenameFilter() {
249                                     @Override
250                                     public boolean accept(File dir, String name) {
251                                         return name.endsWith(".yang")
252                                                 && new File(dir, name).isFile();
253                                     }
254                                 });
255                         for (File yangFile : yangFiles) {
256                             yangsFromDependencies.add(new NamedFileInputStream(
257                                     yangFile));
258                         }
259                     }
260
261                 } else {
262                     ZipFile zip = new ZipFile(file);
263                     zips.add(zip);
264
265                     Enumeration<? extends ZipEntry> entries = zip.entries();
266                     while (entries.hasMoreElements()) {
267                         ZipEntry entry = entries.nextElement();
268                         String entryName = entry.getName();
269
270                         if (entryName.startsWith(META_INF_YANG_STRING)) {
271                             if (entry.isDirectory() == false
272                                     && entryName.endsWith(".yang")) {
273                                 foundFilesForReporting.add(entryName);
274                                 // This will be closed after all strams are
275                                 // parsed.
276                                 InputStream entryStream = zip
277                                         .getInputStream(entry);
278                                 yangsFromDependencies.add(entryStream);
279                             }
280                         }
281                     }
282                 }
283                 if (foundFilesForReporting.size() > 0) {
284                     log.info(Util.message("Found %d yang files in %s: %s",
285                             LOG_PREFIX, foundFilesForReporting.size(), file,
286                             foundFilesForReporting));
287                 }
288
289             }
290         } catch (Exception e) {
291             throw new MojoFailureException(e.getMessage(), e);
292         }
293         return new YangsInZipsResult(yangsFromDependencies, zips);
294     }
295
296     private class ContextHolder {
297         private final SchemaContext context;
298         private final Set<Module> yangModules;
299
300         private ContextHolder(SchemaContext context, Set<Module> yangModules) {
301             this.context = context;
302             this.yangModules = yangModules;
303         }
304
305         public SchemaContext getContext() {
306             return context;
307         }
308
309         public Set<Module> getYangModules() {
310             return yangModules;
311         }
312     }
313 }