import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.maven.project.MavenProject;
-import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.concepts.Identifiable;
import org.opendaylight.yangtools.plugin.generator.api.FileGenerator;
import org.opendaylight.yangtools.plugin.generator.api.FileGeneratorException;
import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.sonatype.plexus.build.incremental.BuildContext;
-@NonNullByDefault
final class GeneratorTask implements Identifiable<String> {
private static final Logger LOG = LoggerFactory.getLogger(GeneratorTask.class);
return arg.getIdentifier();
}
- YangParserConfiguration parserConfig() {
+ @NonNull YangParserConfiguration parserConfig() {
return parserConfig;
}
- FileGeneratorArg arg() {
+ @NonNull FileGeneratorArg arg() {
return arg;
}
- String generatorName() {
+ @NonNull String generatorName() {
return gen.getClass().getName();
}
* model held in specified {@link ContextHolder}.
*
* @param project current Maven Project
+ * @param buildContext Incremental BuildContext
* @param context model generation context
* @return {@link FileState} for every generated file
* @throws FileGeneratorException if the underlying generator fails
* @throws IOException when a generated file cannot be written
*/
- List<FileState> execute(final MavenProject project, final ContextHolder context)
- throws FileGeneratorException, IOException {
+ @NonNull List<FileState> execute(final MavenProject project, final BuildContext buildContext,
+ final ContextHolder context) throws FileGeneratorException, IOException {
final var access = new ProjectFileAccess(project, getIdentifier());
// Step one: determine what files are going to be generated
// Step four: submit all code generation tasks (via parallelStream()) and wait for them to complete
sw.reset().start();
- final var result = dirs.values().parallelStream()
- .map(WriteTask::generateFile)
- .collect(Collectors.toList());
- LOG.debug("Generated {} files in {}", result.size(), sw);
-
+ final var outputFiles = dirs.values().parallelStream()
+ .map(task -> {
+ try {
+ return task.generateFile();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to generate " + task.file, e);
+ }
+ })
+ .collect(Collectors.toList());
+ LOG.debug("Generated {} files in {}", outputFiles.size(), sw);
+
+ // Step five: update maven project to include top-level directories
access.updateMavenProject();
- return result;
+
+ // Step six: extract FileState objects while notifying BuildContext of any files which have been changed
+ return outputFiles.stream()
+ .map(output -> {
+ final var state = output.state();
+ if (output.changed()) {
+ buildContext.refresh(new File(state.path()));
+ }
+ return state;
+ })
+ .collect(Collectors.toList());
}
@Override
return MoreObjects.toStringHelper(this).add("generator", generatorName()).add("argument", arg).toString();
}
+ /**
+ * A single file produced by {@link GeneratorTask}. It contains a {@link FileState} and an indication whether the
+ * file has been changed.
+ */
+ private record OutputFile(@NonNull FileState state, boolean changed) {
+ OutputFile {
+ requireNonNull(state);
+ }
+ }
+
private static final class WriteTask {
private final GeneratedFile file;
private final File target;
this.file = requireNonNull(file);
}
- FileState generateFile() {
- final FileState ret;
- try {
- ret = FileState.ofWrittenFile(target, file::writeBody);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to generate file " + target, e);
+ OutputFile generateFile() throws IOException {
+ if (!target.isFile()) {
+ // Target file does not exist: just output body
+ return new OutputFile(FileState.ofWrittenFile(target, file::writeBody), true);
}
- return ret;
+
+ // Acquire existing file state
+ final var existingFile = FileState.ofFile(target);
+
+ // Write out the new output into a temporary file
+ final var tmpFile = FileState.ofWrittenFile(File.createTempFile("gen", null, target.getParentFile()),
+ file::writeBody);
+
+ // If file size and checksum matches just delete our output
+ final var tmpPath = Path.of(tmpFile.path());
+ if (existingFile.size() == tmpFile.size() && existingFile.crc32() == tmpFile.crc32()) {
+ Files.delete(tmpPath);
+ return new OutputFile(existingFile, false);
+ }
+
+ // Mismatch: move the temp file into place
+ Files.move(tmpPath, target.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ return new OutputFile(new FileState(existingFile.path(), tmpFile.size(), tmpFile.crc32()), true);
}
}
}