Tolerate null return from execution
[yangtools.git] / plugin / yang-maven-plugin / src / main / java / org / opendaylight / yangtools / yang2sources / plugin / FileGeneratorTask.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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 static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
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;
18 import java.io.File;
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;
25 import java.util.Map;
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;
39
40 final class FileGeneratorTask extends GeneratorTask<FileGeneratorTaskFactory> {
41     private static final Logger LOG = LoggerFactory.getLogger(FileGeneratorTask.class);
42
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;
48
49     FileGeneratorTask(final @NonNull FileGeneratorTaskFactory factory, final @NonNull ContextHolder context,
50             final MavenProject project) {
51         super(factory, context);
52
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;
58     }
59
60     @Override
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);
69
70         // Step two: create generation tasks for each target file and group them by parent directory
71         sw.reset().start();
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();
76             final File target;
77             switch (file.getLifecycle()) {
78                 case PERSISTENT:
79                     target = new File(persistentPath(cell.getRowKey()), relativePath);
80                     if (target.exists()) {
81                         LOG.debug("Skipping existing persistent {}", target);
82                         continue;
83                     }
84                     break;
85                 case TRANSIENT:
86                     target = new File(transientPath(cell.getRowKey()), relativePath);
87                     break;
88                 default:
89                     throw new IllegalStateException("Unsupported file type in " + file);
90             }
91
92             dirs.put(target.getParentFile(), new WriteTask(buildContext, target, cell.getValue()));
93         }
94         LOG.info("Sorted {} files into {} directories in {}", dirs.size(), dirs.keySet().size(), sw);
95
96         // Step three: submit parent directory creation tasks (via parallelStream()) and wait for them to complete
97         sw.reset().start();
98         dirs.keySet().parallelStream().forEach(path -> {
99             try {
100                 Files.createDirectories(path.toPath());
101             } catch (IOException e) {
102                 throw new IllegalStateException("Failed to create " + path, e);
103             }
104         });
105         LOG.debug("Parent directories created in {}", sw);
106
107         // Step four: submit all code generation tasks (via parallelStream()) and wait for them to complete
108         sw.reset().start();
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);
113
114         return result;
115     }
116
117     private File persistentPath(final GeneratedFileType fileType) throws FileGeneratorException {
118         final File existing = persistentDirs.get(fileType);
119         if (existing != null) {
120             return existing;
121         }
122         final File newDir = persistentDirectory(fileType);
123         verify(persistentDirs.put(fileType, newDir) == null);
124         return newDir;
125     }
126
127     private File transientPath(final GeneratedFileType fileType) throws FileGeneratorException {
128         final File existing = transientDirs.get(fileType);
129         if (existing != null) {
130             return existing;
131         }
132
133         final File newDir = transientDirectory(fileType);
134         verify(transientDirs.put(fileType, newDir) == null);
135         return newDir;
136     }
137
138     private File persistentDirectory(final GeneratedFileType fileType) throws FileGeneratorException {
139         final File ret;
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");
144         } else {
145             throw new FileGeneratorException("Unknown generated file type " + fileType);
146         }
147         return ret;
148     }
149
150     private File transientDirectory(final GeneratedFileType fileType) throws FileGeneratorException {
151         final File ret;
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));
158         } else {
159             throw new FileGeneratorException("Unknown generated file type " + fileType);
160         }
161         return ret;
162     }
163
164     private File transientDirectory(final String component) {
165         return new File(buildDir, subdirFileName(component));
166     }
167
168     private String subdirFileName(final String component) {
169         return component + File.separatorChar + suffix;
170     }
171
172     private static Resource createResouce(final File directory) {
173         final Resource ret = new Resource();
174         ret.setDirectory(directory.toString());
175         return ret;
176     }
177
178     private static final class WriteTask {
179         private final BuildContext buildContext;
180         private final GeneratedFile file;
181         private final File target;
182
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);
187         }
188
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);
194             }
195             return target;
196         }
197     }
198 }