2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang2sources.plugin;
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;
29 import java.io.Closeable;
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;
41 import static com.google.common.base.Preconditions.checkNotNull;
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);
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;
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);
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];
71 for (File file : excludedFiles) {
72 this.excludedFiles[i++] = new File(file.getPath());
74 this.codeGenerators = Collections.unmodifiableList(Util.checkNotNull(codeGenerators, "codeGenerators"));
75 this.project = Util.checkNotNull(project, "project");
76 this.inspectDependencies = inspectDependencies;
77 this.yangProvider = yangProvider;
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());
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);
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));
99 * Collect all files which affect YANG context. This includes all
100 * files in current project and optionally any jars/files in the
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));
109 if (allFiles.isEmpty()) {
110 log.info(Util.message("No input files found", LOG_PREFIX));
115 * Check if any of the listed files changed. If no changes occurred,
116 * simply return null, which indicates and of execution.
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));
127 log.info(Util.message("None of %s input files changed", LOG_PREFIX, allFiles.size()));
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()));
136 List<InputStream> all = new ArrayList<>(yangsInProject);
137 closeables.addAll(yangsInProject);
138 Map<InputStream, Module> allYangModules;
139 Set<Module> projectYangModules;
141 if (inspectDependencies) {
142 YangsInZipsResult dependentYangResult = Util.findYangFilesInDependenciesAsStream(log, project);
143 Closeable dependentYangResult1 = dependentYangResult;
144 closeables.add(dependentYangResult1);
145 all.addAll(dependentYangResult.getYangStreams());
148 allYangModules = parser.parseYangModelsFromStreamsMapped(all);
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);
157 for (AutoCloseable closeable : closeables) {
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);
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,
171 log.error(message, e);
172 throw new MojoExecutionException(message, e);
176 static class YangProvider {
178 private static final String YANG_RESOURCE_DIR = "target" + File.separator + "yang";
180 void addYangsToMetaInf(Log log, MavenProject project, File yangFilesRootDir, File[] excludedFiles)
181 throws MojoFailureException {
182 File targetYangDir = new File(project.getBasedir(), YANG_RESOURCE_DIR);
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()));
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);
195 setResource(targetYangDir, META_INF_YANG_STRING_JAR, project);
197 log.debug(Util.message("Yang files from: %s marked as resources: %s", LOG_PREFIX, yangFilesRootDir,
198 META_INF_YANG_STRING_JAR));
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);
207 project.addResource(res);
212 * Call generate on every generator from plugin configuration
214 private void generateSources(ContextHolder context) throws MojoFailureException {
215 if (codeGenerators.size() == 0) {
216 log.warn(Util.message("No code generators provided", LOG_PREFIX));
220 Map<String, String> thrown = Maps.newHashMap();
221 for (CodeGeneratorArg codeGenerator : codeGenerators) {
223 generateSourcesWithOneGenerator(context, codeGenerator);
224 } catch (Exception e) {
225 // try other generators, exception will be thrown after
227 Util.message("Unable to generate sources with %s generator", LOG_PREFIX,
228 codeGenerator.getCodeGeneratorClass()), e);
229 thrown.put(codeGenerator.getCodeGeneratorClass(), e.getClass().getCanonicalName());
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());
238 throw new MojoFailureException(message);
243 * Instantiate generator from class and call required method
245 private void generateSourcesWithOneGenerator(ContextHolder context, CodeGeneratorArg codeGeneratorCfg)
246 throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
248 codeGeneratorCfg.check();
250 CodeGenerator g = Util.getInstance(codeGeneratorCfg.getCodeGeneratorClass(), CodeGenerator.class);
251 log.info(Util.message("Code generator instantiated from %s", LOG_PREFIX,
252 codeGeneratorCfg.getCodeGeneratorClass()));
254 File outputDir = codeGeneratorCfg.getOutputBaseDir(project);
256 if (outputDir != null) {
257 project.addCompileSourceRoot(outputDir.getAbsolutePath());
259 throw new NullPointerException("outputBaseDir is null. Please provide a valid outputBaseDir value in the pom.xml");
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()));
267 if (g instanceof BuildContextAware) {
268 ((BuildContextAware)g).setBuildContext(buildContext);
271 g.setMavenProject(project);
272 g.setAdditionalConfig(codeGeneratorCfg.getAdditionalConfiguration());
273 File resourceBaseDir = codeGeneratorCfg.getResourceBaseDir(project);
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()));
280 Collection<File> generated = g.generateSources(context.getContext(), outputDir, context.getYangModules());
282 log.info(Util.message("Sources generated by %s: %s", LOG_PREFIX, codeGeneratorCfg.getCodeGeneratorClass(),