Merge "Checkstyle logging rules"
[yangtools.git] / yang / yang-maven-plugin / src / main / java / org / opendaylight / yangtools / 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.yangtools.yang2sources.plugin;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.collect.Maps;
12 import org.apache.maven.model.Resource;
13 import org.apache.maven.plugin.MojoExecutionException;
14 import org.apache.maven.plugin.MojoFailureException;
15 import org.apache.maven.plugin.logging.Log;
16 import org.apache.maven.project.MavenProject;
17 import org.opendaylight.yangtools.yang.model.api.Module;
18 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
19 import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
20 import org.opendaylight.yangtools.yang.parser.util.NamedFileInputStream;
21 import org.opendaylight.yangtools.yang2sources.plugin.ConfigArg.CodeGeneratorArg;
22 import org.opendaylight.yangtools.yang2sources.plugin.Util.ContextHolder;
23 import org.opendaylight.yangtools.yang2sources.plugin.Util.YangsInZipsResult;
24 import org.opendaylight.yangtools.yang2sources.spi.BuildContextAware;
25 import org.opendaylight.yangtools.yang2sources.spi.CodeGenerator;
26 import org.sonatype.plexus.build.incremental.BuildContext;
27 import org.sonatype.plexus.build.incremental.DefaultBuildContext;
28
29 import java.io.Closeable;
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Set;
40
41 import static com.google.common.base.Preconditions.checkNotNull;
42
43 class YangToSourcesProcessor {
44     static final String LOG_PREFIX = "yang-to-sources:";
45     static final String META_INF_YANG_STRING = "META-INF" + File.separator + "yang";
46     static final String META_INF_YANG_STRING_JAR = "META-INF" + "/" + "yang";
47     static final File META_INF_YANG_DIR = new File(META_INF_YANG_STRING);
48
49     private final Log log;
50     private final File yangFilesRootDir;
51     private final File[] excludedFiles;
52     private final List<CodeGeneratorArg> codeGenerators;
53     private final MavenProject project;
54     private final boolean inspectDependencies;
55     private final BuildContext buildContext;
56     private YangProvider yangProvider;
57
58     @VisibleForTesting
59     YangToSourcesProcessor(Log log, File yangFilesRootDir, File[] excludedFiles, List<CodeGeneratorArg> codeGenerators,
60             MavenProject project, boolean inspectDependencies, YangProvider yangProvider) {
61         this(new DefaultBuildContext(), log, yangFilesRootDir, excludedFiles, codeGenerators, project, inspectDependencies, yangProvider);
62     }
63
64     private YangToSourcesProcessor(BuildContext buildContext,  Log log, File yangFilesRootDir, File[] excludedFiles,
65             List<CodeGeneratorArg> codeGenerators, MavenProject project, boolean inspectDependencies, YangProvider yangProvider) {
66         this.buildContext = Util.checkNotNull(buildContext, "buildContext");
67         this.log = Util.checkNotNull(log, "log");
68         this.yangFilesRootDir = Util.checkNotNull(yangFilesRootDir, "yangFilesRootDir");
69         this.excludedFiles = new File[excludedFiles.length];
70         int i = 0;
71         for (File file : excludedFiles) {
72             this.excludedFiles[i++] = new File(file.getPath());
73         }
74         this.codeGenerators = Collections.unmodifiableList(Util.checkNotNull(codeGenerators, "codeGenerators"));
75         this.project = Util.checkNotNull(project, "project");
76         this.inspectDependencies = inspectDependencies;
77         this.yangProvider = yangProvider;
78     }
79
80     YangToSourcesProcessor(BuildContext buildContext, Log log, File yangFilesRootDir, File[] excludedFiles, List<CodeGeneratorArg> codeGenerators,
81             MavenProject project, boolean inspectDependencies) {
82         this(log, yangFilesRootDir, excludedFiles, codeGenerators, project, inspectDependencies, new YangProvider());
83     }
84
85     public void execute() throws MojoExecutionException, MojoFailureException {
86         ContextHolder context = processYang();
87         if (context != null) {
88             generateSources(context);
89             yangProvider.addYangsToMetaInf(log, project, yangFilesRootDir, excludedFiles);
90         }
91     }
92
93     private ContextHolder processYang() throws MojoExecutionException {
94         YangParserImpl parser = new YangParserImpl();
95         List<Closeable> closeables = new ArrayList<>();
96         log.info(Util.message("Inspecting %s", LOG_PREFIX, yangFilesRootDir));
97         try {
98             /*
99              * Collect all files which affect YANG context. This includes all
100              * files in current project and optionally any jars/files in the
101              * dependencies.
102              */
103             final Collection<File> yangFilesInProject = Util.listFiles(yangFilesRootDir, excludedFiles, log);
104             final Collection<File> allFiles = new ArrayList<>(yangFilesInProject);
105             if (inspectDependencies) {
106                 allFiles.addAll(Util.findYangFilesInDependencies(log, project));
107             }
108
109             if (allFiles.isEmpty()) {
110                 log.info(Util.message("No input files found", LOG_PREFIX));
111                 return null;
112             }
113
114             /*
115              * Check if any of the listed files changed. If no changes occurred,
116              * simply return null, which indicates and of execution.
117              */
118             boolean noChange = true;
119             for (final File f : allFiles) {
120                 if (buildContext.hasDelta(f)) {
121                     log.debug(Util.message("buildContext %s indicates %s changed, forcing regeneration", LOG_PREFIX, buildContext, f));
122                     noChange = false;
123                 }
124             }
125
126             if (noChange) {
127                 log.info(Util.message("None of %s input files changed", LOG_PREFIX, allFiles.size()));
128                 return null;
129             }
130
131             final List<InputStream> yangsInProject = new ArrayList<>();
132             for (final File f : yangFilesInProject) {
133                 yangsInProject.add(new NamedFileInputStream(f, META_INF_YANG_STRING + File.separator + f.getName()));
134             }
135
136             List<InputStream> all = new ArrayList<>(yangsInProject);
137             closeables.addAll(yangsInProject);
138             Map<InputStream, Module> allYangModules;
139             Set<Module> projectYangModules;
140             try {
141                 if (inspectDependencies) {
142                     YangsInZipsResult dependentYangResult = Util.findYangFilesInDependenciesAsStream(log, project);
143                     Closeable dependentYangResult1 = dependentYangResult;
144                     closeables.add(dependentYangResult1);
145                     all.addAll(dependentYangResult.getYangStreams());
146                 }
147
148                 allYangModules = parser.parseYangModelsFromStreamsMapped(all);
149
150                 projectYangModules = new HashSet<>();
151                 for (InputStream inProject : yangsInProject) {
152                     Module module = checkNotNull(allYangModules.get(inProject), "Cannot find module by %s", inProject);
153                     projectYangModules.add(module);
154                 }
155
156             } finally {
157                 for (AutoCloseable closeable : closeables) {
158                     closeable.close();
159                 }
160             }
161
162             Set<Module> parsedAllYangModules = new HashSet<>(allYangModules.values());
163             SchemaContext resolveSchemaContext = parser.resolveSchemaContext(parsedAllYangModules);
164             log.info(Util.message("%s files parsed from %s", LOG_PREFIX, Util.YANG_SUFFIX.toUpperCase(), yangsInProject));
165             return new ContextHolder(resolveSchemaContext, projectYangModules);
166
167             // MojoExecutionException is thrown since execution cannot continue
168         } catch (Exception e) {
169             String message = Util.message("Unable to parse %s files from %s", LOG_PREFIX, Util.YANG_SUFFIX,
170                     yangFilesRootDir);
171             log.error(message, e);
172             throw new MojoExecutionException(message, e);
173         }
174     }
175
176     static class YangProvider {
177
178         private static final String YANG_RESOURCE_DIR = "target" + File.separator + "yang";
179
180         void addYangsToMetaInf(Log log, MavenProject project, File yangFilesRootDir, File[] excludedFiles)
181                 throws MojoFailureException {
182             File targetYangDir = new File(project.getBasedir(), YANG_RESOURCE_DIR);
183
184             try {
185                 Collection<File> files = Util.listFiles(yangFilesRootDir, excludedFiles, null);
186                 for (File file : files) {
187                     org.apache.commons.io.FileUtils.copyFile(file, new File(targetYangDir, file.getName()));
188                 }
189             } catch (IOException e) {
190                 String message = "Unable to list yang files into resource folder";
191                 log.warn(message, e);
192                 throw new MojoFailureException(message, e);
193             }
194
195             setResource(targetYangDir, META_INF_YANG_STRING_JAR, project);
196
197             log.debug(Util.message("Yang files from: %s marked as resources: %s", LOG_PREFIX, yangFilesRootDir,
198                     META_INF_YANG_STRING_JAR));
199         }
200
201         private static void setResource(File targetYangDir, String targetPath, MavenProject project) {
202             Resource res = new Resource();
203             res.setDirectory(targetYangDir.getPath());
204             if (targetPath != null) {
205                 res.setTargetPath(targetPath);
206             }
207             project.addResource(res);
208         }
209     }
210
211     /**
212      * Call generate on every generator from plugin configuration
213      */
214     private void generateSources(ContextHolder context) throws MojoFailureException {
215         if (codeGenerators.size() == 0) {
216             log.warn(Util.message("No code generators provided", LOG_PREFIX));
217             return;
218         }
219
220         Map<String, String> thrown = Maps.newHashMap();
221         for (CodeGeneratorArg codeGenerator : codeGenerators) {
222             try {
223                 generateSourcesWithOneGenerator(context, codeGenerator);
224             } catch (Exception e) {
225                 // try other generators, exception will be thrown after
226                 log.error(
227                         Util.message("Unable to generate sources with %s generator", LOG_PREFIX,
228                                 codeGenerator.getCodeGeneratorClass()), e);
229                 thrown.put(codeGenerator.getCodeGeneratorClass(), e.getClass().getCanonicalName());
230             }
231         }
232
233         if (!thrown.isEmpty()) {
234             String message = Util.message(
235                     "One or more code generators failed, including failed list(generatorClass=exception) %s",
236                     LOG_PREFIX, thrown.toString());
237             log.error(message);
238             throw new MojoFailureException(message);
239         }
240     }
241
242     /**
243      * Instantiate generator from class and call required method
244      */
245     private void generateSourcesWithOneGenerator(ContextHolder context, CodeGeneratorArg codeGeneratorCfg)
246             throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
247
248         codeGeneratorCfg.check();
249
250         CodeGenerator g = Util.getInstance(codeGeneratorCfg.getCodeGeneratorClass(), CodeGenerator.class);
251         log.info(Util.message("Code generator instantiated from %s", LOG_PREFIX,
252                 codeGeneratorCfg.getCodeGeneratorClass()));
253
254         File outputDir = codeGeneratorCfg.getOutputBaseDir(project);
255
256         if (outputDir != null) {
257           project.addCompileSourceRoot(outputDir.getAbsolutePath());
258         } else {
259           throw new NullPointerException("outputBaseDir is null. Please provide a valid outputBaseDir value in the pom.xml");
260         }
261
262         log.info(Util.message("Sources will be generated to %s", LOG_PREFIX, outputDir));
263         log.debug(Util.message("Project root dir is %s", LOG_PREFIX, project.getBasedir()));
264         log.debug(Util.message("Additional configuration picked up for : %s: %s", LOG_PREFIX,
265                 codeGeneratorCfg.getCodeGeneratorClass(), codeGeneratorCfg.getAdditionalConfiguration()));
266
267         if (g instanceof BuildContextAware) {
268             ((BuildContextAware)g).setBuildContext(buildContext);
269         }
270         g.setLog(log);
271         g.setMavenProject(project);
272         g.setAdditionalConfig(codeGeneratorCfg.getAdditionalConfiguration());
273         File resourceBaseDir = codeGeneratorCfg.getResourceBaseDir(project);
274
275         YangProvider.setResource(resourceBaseDir, null, project);
276         g.setResourceBaseDir(resourceBaseDir);
277         log.debug(Util.message("Folder: %s marked as resources for generator: %s", LOG_PREFIX, resourceBaseDir,
278                 codeGeneratorCfg.getCodeGeneratorClass()));
279
280         Collection<File> generated = g.generateSources(context.getContext(), outputDir, context.getYangModules());
281
282         log.info(Util.message("Sources generated by %s: %s", LOG_PREFIX, codeGeneratorCfg.getCodeGeneratorClass(),
283                 generated));
284     }
285
286 }