package fr.ens.biologie.genomique.eoulsan.util.r;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.*;
import com.google.common.base.Joiner;
import fr.ens.biologie.genomique.eoulsan.EoulsanLogger;
import fr.ens.biologie.genomique.eoulsan.data.DataFile;
import fr.ens.biologie.genomique.eoulsan.data.DataFiles;
import fr.ens.biologie.genomique.eoulsan.util.ProcessUtils;
import fr.ens.biologie.genomique.eoulsan.util.StringUtils;
import fr.ens.biologie.genomique.eoulsan.util.SystemUtils;
/**
* This class define a standard RExecutor using a system process.
* @author Laurent Jourdren
* @since 2.0
*/
public class ProcessRExecutor extends AbstractRExecutor {
public static final String REXECUTOR_NAME = "process";
public static final String RSCRIPT_EXECUTABLE = "Rscript";
protected static final String LANG_ENVIRONMENT_VARIABLE = "LANG";
protected static final String DEFAULT_R_LANG = "C";
private Set<String> filenamesToKeep = new HashSet<>();
@Override
public String getName() {
return REXECUTOR_NAME;
}
@Override
protected void putFile(final DataFile inputFile, final String outputFilename)
throws IOException {
final File outputDir = getOutputDirectory().getAbsoluteFile();
final DataFile outputFile = new DataFile(outputDir, outputFilename);
// Check if the input and output file are the same
if (isSameLocalPath(inputFile, outputFile)) {
return;
}
if (outputFile.exists()) {
throw new IOException("The output file already exists: " + outputFile);
}
if (!inputFile.isLocalFile()
|| inputFile.getCompressionType().isCompressed()) {
// Copy the file if the file is not on local file system or compressed
DataFiles.copy(inputFile, outputFile);
} else {
final File parentDir =
inputFile.toFile().getParentFile().getAbsoluteFile();
if (!parentDir.equals(outputDir)
|| !inputFile.getName().equals(outputFilename)) {
// If the output file is not in the same directory that the original
// file or its filename is different, create a symbolic link
inputFile.symlink(outputFile, true);
} else {
this.filenamesToKeep.add(outputFilename);
}
}
}
@Override
public void writerFile(final String content, final String outputFilename)
throws IOException {
if (content == null) {
throw new NullPointerException("content argument cannot be null");
}
if (outputFilename == null) {
throw new NullPointerException("outputFilename argument cannot be null");
}
final DataFile outputFile =
new DataFile(getOutputDirectory(), outputFilename);
if (outputFile.exists()) {
throw new IOException("The output file already exists: " + outputFile);
}
try (Writer writer = new OutputStreamWriter(outputFile.create())) {
writer.write(content);
}
}
@Override
protected void removeFile(final String filename) throws IOException {
// Avoid removing original input files
if (this.filenamesToKeep.contains(filename)) {
return;
}
final File file = new File(getOutputDirectory(), filename);
if (!file.delete()) {
EoulsanLogger.logWarning("Cannot remove file used by R: " + file);
}
}
/**
* Create R command.
* @param rScriptFile the R script file to execute
* @param sweave true if the script is a Sweave file
* @param scriptArguments script arguments
* @return the R command as a list
*/
protected List<String> createCommand(final File rScriptFile,
final boolean sweave, final String sweaveOuput,
final String... scriptArguments) {
final List<String> result = new ArrayList<>();
result.add(RSCRIPT_EXECUTABLE);
if (sweave) {
result.add("-e");
final StringBuilder sb = new StringBuilder();
sb.append("Sweave(\"");
sb.append(rScriptFile.getAbsolutePath());
sb.append('\"');
if (sweaveOuput != null) {
sb.append(", output=\"");
sb.append(sweaveOuput);
sb.append('\"');
}
sb.append(')');
result.add(sb.toString());
} else {
result.add(rScriptFile.getAbsolutePath());
}
if (scriptArguments != null) {
Collections.addAll(result, scriptArguments);
}
return result;
}
@Override
protected void executeRScript(final File rScriptFile, final boolean sweave,
final String sweaveOuput, final File workflowOutputDir, final String... scriptArguments)
throws IOException {
final List<String> command =
createCommand(rScriptFile, sweave, sweaveOuput, scriptArguments);
// Search the command in The PATH
final File executablePath =
SystemUtils.searchExecutableInPATH(command.get(0));
if (executablePath == null) {
throw new IOException(
"Unable to find executable in the PATH: " + command.get(0));
}
// Update the command with the path of the command
command.set(0, executablePath.getAbsolutePath());
final ProcessBuilder pb = new ProcessBuilder(command);
// Set the LANG to C
pb.environment().put(LANG_ENVIRONMENT_VARIABLE, DEFAULT_R_LANG);
// Set the temporary directory for R
pb.environment().put("TMPDIR", getTemporaryDirectory().getAbsolutePath());
// Redirect stdout and stderr
pb.redirectOutput(changeFileExtension(rScriptFile, ".out"));
pb.redirectErrorStream(true);
ProcessUtils.logEndTime(pb.start(), Joiner.on(' ').join(pb.command()),
System.currentTimeMillis());
}
/**
* Change the extendsion of a file
* @param file the file
* @param newExtension the new extension of the file
* @return a file object
*/
protected static File changeFileExtension(final File file,
final String newExtension) {
if (file == null) {
return null;
}
if (newExtension == null) {
return file;
}
final String newFilename =
StringUtils.filenameWithoutExtension(file.getName()) + newExtension;
return new File(file.getParent(), newFilename);
}
@Override
public void closeConnection() throws IOException {
this.filenamesToKeep.clear();
super.closeConnection();
}
//
// Other methods
//
/**
* Check if two file have the same local path
* @param a first file
* @param b second file
* @return true if the file have the same local file
*/
protected static boolean isSameLocalPath(final DataFile a, final DataFile b) {
if (a.equals(b)) {
return true;
}
if (a.isLocalFile()) {
final File fa = a.toFile().getAbsoluteFile();
final File fb = b.toFile().getAbsoluteFile();
return fa.equals(fb);
}
return false;
}
//
// Constructor
//
/**
* Constructor.
* @param outputDirectory the output directory
* @param temporaryDirectory the temporary directory
* @throws IOException if an error occurs while creating the object
*/
protected ProcessRExecutor(final File outputDirectory,
final File temporaryDirectory) throws IOException {
super(outputDirectory, temporaryDirectory);
}
}