org.opendaylight.yangtools.yang2sources.plugin.GeneratorTask Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yang-maven-plugin Show documentation
Show all versions of yang-maven-plugin Show documentation
This plugin is a wrapper for "yang to source code" generation.
It can be configured by a set of third-party code generators and resource providers.
For further info see available goals.
Sample usage:
TODO: add sample usage when finished
/*
* Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.yangtools.yang2sources.plugin;
import static java.util.Objects.requireNonNull;
import com.google.common.base.MoreObjects;
import com.google.common.base.Stopwatch;
import com.google.common.collect.MultimapBuilder;
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.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.plugin.generator.api.FileGeneratorFactory;
import org.opendaylight.yangtools.plugin.generator.api.GeneratedFile;
import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.plexus.build.incremental.BuildContext;
final class GeneratorTask implements Identifiable {
private static final Logger LOG = LoggerFactory.getLogger(GeneratorTask.class);
private final YangParserConfiguration parserConfig;
private final FileGeneratorArg arg;
private final FileGenerator gen;
GeneratorTask(final FileGeneratorFactory factory, final FileGeneratorArg arg) throws FileGeneratorException {
this.arg = requireNonNull(arg);
gen = factory.newFileGenerator(arg.getConfiguration());
parserConfig = switch (gen.importResolutionMode()) {
case REVISION_EXACT_OR_LATEST -> YangParserConfiguration.DEFAULT;
};
}
@Override
public String getIdentifier() {
return arg.getIdentifier();
}
@NonNull YangParserConfiguration parserConfig() {
return parserConfig;
}
@NonNull FileGeneratorArg arg() {
return arg;
}
@NonNull String generatorName() {
return gen.getClass().getName();
}
/**
* Create a new {@link GeneratorTask} which will work in scope of specified {@link MavenProject} with the effective
* 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
*/
@NonNull List execute(final MavenProject project, final BuildContext buildContext,
final ContextHolder context) throws FileGeneratorException {
final var access = new ProjectFileAccess(project, getIdentifier());
// Step one: determine what files are going to be generated
final var sw = Stopwatch.createStarted();
final var generatedFiles = gen.generateFiles(context.getContext(), context.getYangModules(), context);
LOG.info("{}: Defined {} files in {}", getIdentifier(), generatedFiles.size(), sw);
// Step two: create generation tasks for each target file and group them by parent directory
sw.reset().start();
final var dirs = MultimapBuilder.hashKeys().arrayListValues().build();
for (var cell : generatedFiles.cellSet()) {
final GeneratedFile file = cell.getValue();
final String relativePath = cell.getColumnKey().getPath();
final File target;
switch (file.getLifecycle()) {
case PERSISTENT:
target = new File(access.persistentPath(cell.getRowKey()), relativePath);
if (target.exists()) {
LOG.debug("Skipping existing persistent {}", target);
continue;
}
break;
case TRANSIENT:
target = new File(access.transientPath(cell.getRowKey()), relativePath);
break;
default:
throw new FileGeneratorException("Unsupported file type in " + file);
}
dirs.put(target.getParentFile(), new WriteTask(target, cell.getValue()));
}
LOG.info("Sorted {} files into {} directories in {}", dirs.size(), dirs.keySet().size(), sw);
// Step three: submit parent directory creation tasks (via parallelStream()) and wait for them to complete
sw.reset().start();
dirs.keySet().parallelStream().forEach(path -> {
try {
Files.createDirectories(path.toPath());
} catch (IOException e) {
throw new IllegalStateException("Failed to create " + path, e);
}
});
LOG.debug("Parent directories created in {}", sw);
// Step four: submit all code generation tasks (via parallelStream()) and wait for them to complete
sw.reset().start();
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();
// 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
public String toString() {
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;
WriteTask(final File target, final GeneratedFile file) {
this.target = requireNonNull(target);
this.file = requireNonNull(file);
}
OutputFile generateFile() throws IOException {
if (!target.isFile()) {
// Target file does not exist: just output body
return new OutputFile(FileState.ofWrittenFile(target, file::writeBody), true);
}
// 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);
}
}
}