2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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 static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.Stopwatch;
14 import com.google.common.collect.ListMultimap;
15 import com.google.common.collect.MultimapBuilder;
16 import com.google.common.collect.Table;
17 import com.google.common.collect.Table.Cell;
19 import java.io.IOException;
20 import java.io.OutputStream;
21 import java.nio.file.Files;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.List;
26 import java.util.stream.Collectors;
27 import org.apache.maven.model.Build;
28 import org.apache.maven.model.Resource;
29 import org.apache.maven.project.MavenProject;
30 import org.eclipse.jdt.annotation.NonNull;
31 import org.opendaylight.yangtools.plugin.generator.api.FileGenerator;
32 import org.opendaylight.yangtools.plugin.generator.api.FileGeneratorException;
33 import org.opendaylight.yangtools.plugin.generator.api.GeneratedFile;
34 import org.opendaylight.yangtools.plugin.generator.api.GeneratedFilePath;
35 import org.opendaylight.yangtools.plugin.generator.api.GeneratedFileType;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.sonatype.plexus.build.incremental.BuildContext;
40 final class FileGeneratorTask extends GeneratorTask<FileGeneratorTaskFactory> {
41 private static final Logger LOG = LoggerFactory.getLogger(FileGeneratorTask.class);
43 private final Map<GeneratedFileType, File> persistentDirs = new HashMap<>(4);
44 private final Map<GeneratedFileType, File> transientDirs = new HashMap<>(4);
45 private final MavenProject project;
46 private final File buildDir;
47 private final String suffix;
49 FileGeneratorTask(final @NonNull FileGeneratorTaskFactory factory, final @NonNull ContextHolder context,
50 final MavenProject project) {
51 super(factory, context);
53 final Build build = project.getBuild();
54 final String buildDirectory = build.getDirectory();
55 this.buildDir = new File(buildDirectory);
56 this.suffix = factory.getIdentifier();
57 this.project = project;
61 Collection<File> execute(final FileGeneratorTaskFactory factory, final ContextHolder modelContext,
62 final BuildContext buildContext) throws FileGeneratorException, IOException {
63 // Step one: determine what files are going to be generated
64 final Stopwatch sw = Stopwatch.createStarted();
65 final FileGenerator gen = factory.generator();
66 final Table<GeneratedFileType, GeneratedFilePath, GeneratedFile> generatedFiles = gen.generateFiles(
67 modelContext.getContext(), modelContext.getYangModules(), modelContext);
68 LOG.info("{}: Defined {} files in {}", suffix, generatedFiles.size(), sw);
70 // Step two: create generation tasks for each target file and group them by parent directory
72 final ListMultimap<File, WriteTask> dirs = MultimapBuilder.hashKeys().arrayListValues().build();
73 for (Cell<GeneratedFileType, GeneratedFilePath, GeneratedFile> cell : generatedFiles.cellSet()) {
74 final GeneratedFile file = cell.getValue();
75 final String relativePath = cell.getColumnKey().getPath();
77 switch (file.getLifecycle()) {
79 target = new File(persistentPath(cell.getRowKey()), relativePath);
80 if (target.exists()) {
81 LOG.debug("Skipping existing persistent {}", target);
86 target = new File(transientPath(cell.getRowKey()), relativePath);
89 throw new IllegalStateException("Unsupported file type in " + file);
92 dirs.put(target.getParentFile(), new WriteTask(buildContext, target, cell.getValue()));
94 LOG.info("Sorted {} files into {} directories in {}", dirs.size(), dirs.keySet().size(), sw);
96 // Step three: submit parent directory creation tasks (via parallelStream()) and wait for them to complete
98 dirs.keySet().parallelStream().forEach(path -> {
100 Files.createDirectories(path.toPath());
101 } catch (IOException e) {
102 throw new IllegalStateException("Failed to create " + path, e);
105 LOG.debug("Parent directories created in {}", sw);
107 // Step four: submit all code generation tasks (via parallelStream()) and wait for them to complete
109 final List<File> result = dirs.values().parallelStream()
110 .map(WriteTask::generateFile)
111 .collect(Collectors.toList());
112 LOG.debug("Generated {} files in {}", result.size(), sw);
117 private File persistentPath(final GeneratedFileType fileType) throws FileGeneratorException {
118 final File existing = persistentDirs.get(fileType);
119 if (existing != null) {
122 final File newDir = persistentDirectory(fileType);
123 verify(persistentDirs.put(fileType, newDir) == null);
127 private File transientPath(final GeneratedFileType fileType) throws FileGeneratorException {
128 final File existing = transientDirs.get(fileType);
129 if (existing != null) {
133 final File newDir = transientDirectory(fileType);
134 verify(transientDirs.put(fileType, newDir) == null);
138 private File persistentDirectory(final GeneratedFileType fileType) throws FileGeneratorException {
140 if (GeneratedFileType.SOURCE.equals(fileType)) {
141 ret = new File(project.getBuild().getSourceDirectory());
142 } else if (GeneratedFileType.RESOURCE.equals(fileType)) {
143 ret = new File(new File(project.getBuild().getSourceDirectory()).getParentFile(), "resources");
145 throw new FileGeneratorException("Unknown generated file type " + fileType);
150 private File transientDirectory(final GeneratedFileType fileType) throws FileGeneratorException {
152 if (GeneratedFileType.SOURCE.equals(fileType)) {
153 ret = transientDirectory("generated-sources");
154 project.addCompileSourceRoot(ret.toString());
155 } else if (GeneratedFileType.RESOURCE.equals(fileType)) {
156 ret = transientDirectory("generated-resources");
157 project.addResource(createResouce(ret));
159 throw new FileGeneratorException("Unknown generated file type " + fileType);
164 private File transientDirectory(final String component) {
165 return new File(buildDir, subdirFileName(component));
168 private String subdirFileName(final String component) {
169 return component + File.separatorChar + suffix;
172 private static Resource createResouce(final File directory) {
173 final Resource ret = new Resource();
174 ret.setDirectory(directory.toString());
178 private static final class WriteTask {
179 private final BuildContext buildContext;
180 private final GeneratedFile file;
181 private final File target;
183 WriteTask(final BuildContext buildContext, final File target, final GeneratedFile file) {
184 this.buildContext = requireNonNull(buildContext);
185 this.target = requireNonNull(target);
186 this.file = requireNonNull(file);
189 File generateFile() {
190 try (OutputStream stream = buildContext.newFileOutputStream(target)) {
191 file.writeBody(stream);
192 } catch (IOException e) {
193 throw new IllegalStateException("Failed to generate file " + target, e);