/*
* Eoulsan development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public License version 2.1 or
* later and CeCILL-C. This should be distributed with the code.
* If you do not have a copy, see:
*
* http://www.gnu.org/licenses/lgpl-2.1.txt
* http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt
*
* Copyright for this code is held jointly by the Genomic platform
* of the Institut de Biologie de l'École normale supérieure and
* the individual authors. These should be listed in @author doc
* comments.
*
* For more information on the Eoulsan project and its aims,
* or to join the Eoulsan Google group, visit the home page
* at:
*
* http://outils.genomique.biologie.ens.fr/eoulsan
*
*/
package fr.ens.biologie.genomique.eoulsan.bio.readsmappers;
import static com.google.common.base.Preconditions.checkState;
import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger;
import static fr.ens.biologie.genomique.eoulsan.util.FileUtils.checkExistingStandardFile;
import static fr.ens.biologie.genomique.eoulsan.util.Utils.checkNotNull;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Strings;
import fr.ens.biologie.genomique.eoulsan.EoulsanRuntime;
import fr.ens.biologie.genomique.eoulsan.Globals;
import fr.ens.biologie.genomique.eoulsan.bio.FastqFormat;
import fr.ens.biologie.genomique.eoulsan.bio.ReadSequence;
import fr.ens.biologie.genomique.eoulsan.bio.io.FastqReader;
import fr.ens.biologie.genomique.eoulsan.bio.readsmappers.MapperExecutor.Result;
import fr.ens.biologie.genomique.eoulsan.data.DataFile;
import fr.ens.biologie.genomique.eoulsan.io.CompressionType;
import fr.ens.biologie.genomique.eoulsan.util.FileUtils;
import fr.ens.biologie.genomique.eoulsan.util.ReporterIncrementer;
import fr.ens.biologie.genomique.eoulsan.util.StringUtils;
/**
* This class abstract implements a generic Mapper.
* @since 1.0
* @author Laurent Jourdren
* @author Maria Bernard
*/
public abstract class AbstractSequenceReadsMapper
implements SequenceReadsMapper {
private static final String SYNC =
AbstractSequenceReadsMapper.class.getName();
static final String SHORT_INDEX_FLAVOR = "standard";
static final String LARGE_INDEX_FLAVOR = "large-index";
static final String DEFAULT_FLAVOR = SHORT_INDEX_FLAVOR;
private InputStream archiveIndexFileInputStream;
private File archiveIndexDir;
private FastqFormat fastqFormat = FastqFormat.FASTQ_SANGER;
private String mapperVersionToUse = getDefaultPackageVersion();
private String flavorToUse = DEFAULT_FLAVOR;
private String flavor = DEFAULT_FLAVOR;
private boolean useBundledBinaries = true;
private String mapperDockerImage = "";
private int threadsNumber;
private String mapperArguments = null;
private String indexerArguments = null;
private File tempDir = EoulsanRuntime.getSettings().getTempDirectoryFile();
private File executablesTempDir =
EoulsanRuntime.getSettings().getExecutablesTempDirectoryFile();
private boolean multipleInstancesEnabled;
private ReporterIncrementer incrementer;
private String counterGroup;
private boolean binariesReady;
private boolean initialized;
private IOException mappingException;
private MapperExecutor executor;
//
// Binaries management
//
@Override
public boolean isIndexGeneratorOnly() {
return false;
}
/**
* Get the software package of the mapper.
* @return the software package of the mapper
*/
protected String getSoftwarePackage() {
return getMapperName();
}
/**
* Get the default version of the mapper.
* @return the default version of the mapper
*/
protected abstract String getDefaultPackageVersion();
/**
* Get the indexer executable.
* @return the indexer executable
*/
protected abstract String getIndexerExecutable();
/**
* Get the indexer executables.
* @return the indexer executables
*/
protected String[] getIndexerExecutables() {
return new String[] {getIndexerExecutable()};
}
/**
* Get the indexer command.
* @param indexerPathname the path to the indexer
* @param genomePathname the path to the genome
* @return a list that is the command to execute
*/
protected abstract List<String> getIndexerCommand(
final String indexerPathname, final String genomePathname);
@Override
public String getMapperFlavorToUse() {
return this.flavorToUse;
}
@Override
public String getMapperVersionToUse() {
return this.mapperVersionToUse;
}
@Override
public String getMapperFlavor() {
checkIfFlavorExists();
return this.flavor;
}
@Override
public boolean isUseBundledBinaries() {
return this.useBundledBinaries;
}
@Override
public String getMapperDockerImage() {
return this.mapperDockerImage;
}
//
// Getters
//
@Override
public int getThreadsNumber() {
return this.threadsNumber;
}
@Override
public String getMapperArguments() {
return this.mapperArguments;
}
@Override
public String getIndexerArguments() {
return this.indexerArguments;
}
/**
* Get the default mapper arguments.
* @return the default mapper arguments
*/
protected abstract String getDefaultMapperArguments();
@Override
public List<String> getListMapperArguments() {
return getListArguments(getMapperArguments());
}
@Override
public List<String> getListIndexerArguments() {
return getListArguments(getIndexerArguments());
}
/**
* Get Fastq format.
* @return the fastq format
*/
@Override
public FastqFormat getFastqFormat() {
return this.fastqFormat;
}
@Override
public File getTempDirectory() {
return this.tempDir;
}
@Override
public File getExecutablesTempDirectory() {
return this.executablesTempDir;
}
@Override
public boolean isMultipleInstancesAllowed() {
return false;
}
@Override
public boolean isMultipleInstancesEnabled() {
return this.multipleInstancesEnabled;
}
/**
* Convenient method to directly get the absolute path for the temporary
* directory.
* @return the absolute path to the temporary directory as a string
*/
protected String getTempDirectoryPath() {
return getTempDirectory().getAbsolutePath();
}
/**
* Get mapper executor.
* @return the mapper executor
*/
protected MapperExecutor getExecutor() {
return this.executor;
}
//
// Setters
//
@Override
public void setMapperFlavorToUse(final String flavor) {
checkState(!this.binariesReady, "Mapper has been initialized");
this.flavorToUse = Strings.emptyToNull(flavor);
}
@Override
public void setMapperVersionToUse(final String version) {
checkState(!this.binariesReady, "Mapper has been initialized");
this.mapperVersionToUse = Strings.emptyToNull(version) == null
? getDefaultPackageVersion() : version;
}
@Override
public void setUseBundledBinaries(final boolean use) {
checkState(!this.binariesReady, "Mapper has been initialized");
this.useBundledBinaries = use;
}
@Override
public void setMapperDockerImage(final String dockerImage) {
checkState(!this.binariesReady, "Mapper has been initialized");
if (dockerImage == null) {
this.mapperDockerImage = "";
} else {
this.mapperDockerImage = dockerImage.trim();
}
}
@Override
public void setThreadsNumber(final int threadsNumber) {
checkState(!this.initialized, "Mapper has been initialized");
this.threadsNumber = threadsNumber;
}
@Override
public void setMapperArguments(final String arguments) {
checkState(!this.initialized, "Mapper has been initialized");
if (arguments == null) {
this.mapperArguments = "";
} else {
this.mapperArguments = arguments;
}
}
@Override
public void setIndexerArguments(final String arguments) {
if (arguments == null) {
this.indexerArguments = "";
} else {
this.indexerArguments = arguments;
}
}
@Override
public void setTempDirectory(final File tempDirectory) {
checkState(!this.initialized, "Mapper has been initialized");
this.tempDir = tempDirectory;
}
@Override
public void setExecutablesTempDirectory(final File executablesTempDirectory) {
checkState(!this.initialized, "Mapper has been initialized");
this.tempDir = executablesTempDirectory;
}
@Override
public void setFastqFormat(final FastqFormat format) {
checkState(!this.initialized, "Mapper has been initialized");
if (format == null) {
throw new NullPointerException("The FASTQ format is null");
}
this.fastqFormat = format;
}
/**
* Set the "real" flavor of the mapper.
* @param flavor the flavor to set
*/
protected void setFlavor(final String flavor) {
checkState(!this.initialized, "Mapper has been initialized");
if (flavor != null) {
this.flavor = flavor;
}
}
@Override
public void setMultipleInstancesEnabled(final boolean enable) {
checkState(!this.initialized, "Mapper has been initialized");
if (isMultipleInstancesAllowed() && enable) {
this.multipleInstancesEnabled = true;
} else {
this.multipleInstancesEnabled = false;
}
}
//
// Get Mapper version
//
@Override
public final String getMapperVersion() {
// Prepare binaries
try {
prepareBinaries();
} catch (IOException e) {
return null;
}
return internalGetMapperVersion();
}
/**
* Get mapper version.
* @return a string with the version of the mapper
*/
protected abstract String internalGetMapperVersion();
//
// Index creation
//
private static File uncompressGenomeIfNecessary(final File genomeFile,
final File outputDir) throws FileNotFoundException, IOException {
final CompressionType ct =
CompressionType.getCompressionTypeByFilename(genomeFile.getName());
if (ct == CompressionType.NONE) {
return genomeFile;
}
// Define the output filename
final File uncompressFile = new File(outputDir,
StringUtils.filenameWithoutCompressionExtension(genomeFile.getName()));
getLogger()
.fine("Uncompress genome " + genomeFile + " to " + uncompressFile);
// Create input stream
final InputStream in =
ct.createInputStream(FileUtils.createInputStream(genomeFile));
// Create output stream
final OutputStream out = FileUtils.createOutputStream(uncompressFile);
// Uncompress
FileUtils.copy(in, out);
// Return the uncompress file
return uncompressFile;
}
private void makeIndex(final File genomeFile, final File outputDir)
throws IOException {
checkNotNull(genomeFile, "genome file is null");
checkNotNull(outputDir, "output directory is null");
final File unCompressedGenomeFile =
uncompressGenomeIfNecessary(genomeFile, outputDir);
getLogger().fine("Start computing "
+ getMapperName() + " index for " + unCompressedGenomeFile);
final long startTime = System.currentTimeMillis();
final String indexerPath;
synchronized (SYNC) {
indexerPath = install(getIndexerExecutables());
}
if (!outputDir.exists() && !outputDir.mkdir()) {
throw new IOException("Unable to create directory for genome index");
}
final File tmpGenomeFile =
new File(outputDir, unCompressedGenomeFile.getName());
// Create temporary symbolic link for genome
if (!unCompressedGenomeFile.equals(tmpGenomeFile)) {
try {
Files.createSymbolicLink(tmpGenomeFile.toPath(),
unCompressedGenomeFile.toPath());
} catch (IOException e) {
throw new IOException("Unable to create the symbolic link in "
+ tmpGenomeFile + " directory for " + unCompressedGenomeFile);
}
}
// Build the command line and compute the index
final List<String> cmd = new ArrayList<>();
cmd.addAll(getIndexerCommand(indexerPath, tmpGenomeFile.getAbsolutePath()));
getLogger().fine(cmd.toString());
final int exitValue =
this.executor.execute(cmd, tmpGenomeFile.getParentFile(), false, false,
unCompressedGenomeFile, tmpGenomeFile).waitFor();
if (exitValue != 0) {
throw new IOException(
"Bad error result for index creation execution: " + exitValue);
}
// Remove symbolic link
if (!tmpGenomeFile.delete()) {
getLogger().warning("Cannot remove symbolic link while after creating "
+ getMapperName() + " index");
}
final long endTime = System.currentTimeMillis();
getLogger().fine("Create the "
+ getMapperName() + " index in "
+ StringUtils.toTimeHumanReadable(endTime - startTime));
}
@Override
public void makeArchiveIndex(final File genomeFile,
final File archiveOutputFile) throws IOException {
getLogger().fine("Start index computation");
final String indexTmpDirPrefix = Globals.APP_NAME_LOWER_CASE
+ "-" + getMapperName().toLowerCase() + "-genomeindexdir-";
getLogger().fine("Want to create a temporary directory with prefix: "
+ indexTmpDirPrefix + " in " + getTempDirectory());
final File indexTmpDir = Files
.createTempDirectory(getTempDirectory().toPath(), indexTmpDirPrefix)
.toFile();
makeIndex(genomeFile, indexTmpDir);
// Zip index files
FileUtils.createZip(indexTmpDir, archiveOutputFile);
// Remove temporary directory
FileUtils.removeDirectory(indexTmpDir);
getLogger().fine("End index computation");
}
/**
* Create the soap index in a zip archive.
* @param is InputStream to use for the genome file
* @throws IOException if an error occurs while creating the index
*/
@Override
public void makeArchiveIndex(final InputStream is,
final File archiveOutputFile) throws IOException {
checkNotNull(is, "Input steam is null");
checkNotNull(archiveOutputFile, "Archive output file is null");
getLogger().fine("Copy genome to local disk before computing index");
final File genomeTmpFile = File.createTempFile(
Globals.APP_NAME_LOWER_CASE + "-genome", ".fasta", getTempDirectory());
FileUtils.copy(is, FileUtils.createOutputStream(genomeTmpFile));
makeArchiveIndex(genomeTmpFile, archiveOutputFile);
if (!genomeTmpFile.delete()) {
getLogger().warning("Cannot delete temporary index zip file");
}
}
protected String getIndexPath(final File archiveIndexDir,
final String extension, final int extensionLength) throws IOException {
final File[] indexFiles =
FileUtils.listFilesByExtension(archiveIndexDir, extension);
if (indexFiles == null || indexFiles.length == 0) {
throw new IOException("Unable to get index file for "
+ getMapperName() + " with \"" + extension
+ "\" extension in directory: " + archiveIndexDir);
}
if (indexFiles.length > 1) {
throw new IOException("More than one index file for "
+ getMapperName() + " with \"" + extension
+ "\" extension in directory: " + archiveIndexDir);
}
// Get the path to the index
final String bwtFile = indexFiles[0].getAbsolutePath();
return bwtFile.substring(0, bwtFile.length() - extensionLength);
}
private void unzipArchiveIndexFile(final InputStream archiveIndexFile,
final File archiveIndexDir) throws IOException {
final File lockFile =
new File(archiveIndexDir.getAbsoluteFile().getParentFile(),
archiveIndexDir.getName() + ".lock");
final RandomAccessFile lockIs = new RandomAccessFile(lockFile, "rw");
final FileLock lock = lockIs.getChannel().lock();
try {
// Uncompress archive if necessary
if (!archiveIndexDir.exists()) {
if (!archiveIndexDir.mkdir()) {
throw new IOException("Can't create directory for "
+ getMapperName() + " index: " + archiveIndexDir);
}
getLogger().fine("Unzip archiveIndexFile "
+ archiveIndexFile + " in " + archiveIndexDir);
FileUtils.unzip(archiveIndexFile, archiveIndexDir);
}
} finally {
lock.release();
lockIs.close();
lockFile.delete();
}
FileUtils.checkExistingDirectoryFile(archiveIndexDir,
getMapperName() + " index directory");
}
//
// Mapping with File
//
@Override
public final MapperProcess mapSE(final DataFile readsFile)
throws IOException {
checkNotNull(readsFile, "readsFile is null");
if (!readsFile.exists()) {
throw new IOException("readsFile1 not exits");
}
getLogger().fine("FASTQ file to map: " + readsFile);
return mapSE(readsFile.open());
}
@Override
public final MapperProcess mapSE(final File readsFile) throws IOException {
checkNotNull(readsFile, "readsFile is null");
checkExistingStandardFile(readsFile,
"readsFile1 not exits or is not a standard file.");
getLogger().fine("FASTQ file to map: " + readsFile);
return mapSE(new FileInputStream(readsFile));
}
/**
* Map reads of FASTQ file in single end mode.
* @param in FASTQ input stream
* @return an InputStream with SAM data
* @throws IOException if an error occurs while mapping the reads
*/
private MapperProcess mapSE(final InputStream in) throws IOException {
checkNotNull(in, "in argument is null");
// Check if the mapper has been initialized
checkState(this.initialized, "Mapper has not been initialized");
getLogger().fine("Mapping with " + getMapperName() + " in single-end mode");
// Unzip archive index if necessary
unzipArchiveIndexFile(this.archiveIndexFileInputStream,
this.archiveIndexDir);
// Process to mapping
final MapperProcess mapperProcess = mapSE();
// Copy reads file to named pipe
writeFirstPairEntries(in, mapperProcess);
return mapperProcess;
}
@Override
public final MapperProcess mapPE(final DataFile readsFile1,
final DataFile readsFile2) throws IOException {
checkNotNull(readsFile1, "readsFile1 is null");
checkNotNull(readsFile2, "readsFile2 is null");
if (!readsFile1.exists()) {
throw new IOException("readsFile1 not exits");
}
if (!readsFile2.exists()) {
throw new IOException("readsFile1 not exits");
}
getLogger().fine("First pair FASTQ file to map: " + readsFile1);
getLogger().fine("Second pair FASTQ file to map: " + readsFile2);
return mapPE(readsFile1.open(), readsFile2.open());
}
@Override
public final MapperProcess mapPE(final File readsFile1, final File readsFile2)
throws IOException {
checkNotNull(readsFile1, "readsFile1 is null");
checkNotNull(readsFile2, "readsFile2 is null");
checkExistingStandardFile(readsFile1,
"readsFile1 not exits or is not a standard file.");
checkExistingStandardFile(readsFile2,
"readsFile2 not exits or is not a standard file.");
getLogger().fine("First pair FASTQ file: " + readsFile1);
getLogger().fine("Second pair FASTQ file: " + readsFile2);
return mapPE(new FileInputStream(readsFile1),
new FileInputStream(readsFile2));
}
/**
* Map reads of FASTQ file in paired end mode.
* @param in1 FASTQ input file with reads of the first end
* @param in2 FASTQ input file with reads of the first end mapper
* @return an InputStream with SAM data
* @throws IOException if an error occurs while mapping the reads
*/
private MapperProcess mapPE(final InputStream in1, final InputStream in2)
throws IOException {
checkNotNull(in1, "in1 argument is null");
checkNotNull(in2, "in2 argument is null");
// Check if the mapper has been initialized
checkState(this.initialized, "Mapper has not been initialized");
getLogger().fine("Mapping with " + getMapperName() + " in pair-end mode");
checkNotNull(in1, "readsFile1 is null");
checkNotNull(in2, "readsFile2 is null");
// Unzip archive index if necessary
unzipArchiveIndexFile(this.archiveIndexFileInputStream,
this.archiveIndexDir);
// Process to mapping
final MapperProcess mapperProcess = mapPE();
// Copy reads files to named pipes
writeFirstPairEntries(in1, mapperProcess);
writeSecondPairEntries(in2, mapperProcess);
return mapperProcess;
}
/**
* Write first pairs entries to the mapper process
* @param in first pairs FASTQ file
* @param mp mapper process
* @throws FileNotFoundException if the input cannot be found
*/
private void writeFirstPairEntries(final InputStream in,
final MapperProcess mp) throws FileNotFoundException {
checkNotNull(in, "in argument cannot be null");
checkNotNull(mp, "mp argument cannot be null");
final Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
final FastqReader reader = new FastqReader(in);
for (ReadSequence read : reader) {
mp.writeEntry1(read);
}
reader.close();
mp.closeWriter1();
} catch (IOException e) {
mappingException = e;
}
}
}, "Mapper writeFirstPairEntries thread");
t.start();
}
/**
* Write first pairs entries to the mapper process
* @param in first pairs FASTQ file
* @param mp mapper process
* @throws FileNotFoundException if the input cannot be found
*/
private void writeSecondPairEntries(final InputStream in,
final MapperProcess mp) throws FileNotFoundException {
checkNotNull(in, "in argument cannot be null");
checkNotNull(mp, "mp argument cannot be null");
final Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
final FastqReader reader = new FastqReader(in);
for (ReadSequence read : reader) {
mp.writeEntry2(read);
}
reader.close();
mp.closeWriter2();
} catch (IOException e) {
mappingException = e;
}
}
}, "Mapper writeSecondPairEntries thread");
t.start();
}
@Override
public void throwMappingException() throws IOException {
if (this.mappingException != null) {
throw this.mappingException;
}
}
//
// Mapping with streams
//
@Override
public final MapperProcess mapPE() throws IOException {
// Check if the mapper has been initialized
checkState(this.initialized, "Mapper has not been initialized");
getLogger().fine("Mapping with " + getMapperName() + " in single-end mode");
// Unzip archive index if necessary
unzipArchiveIndexFile(this.archiveIndexFileInputStream,
this.archiveIndexDir);
// Process to mapping
final MapperProcess result = internalMapPE(this.archiveIndexDir);
// Set counter
result.setIncrementer(this.incrementer, this.counterGroup);
return result;
}
@Override
public final MapperProcess mapSE() throws IOException {
// Check if the mapper has been initialized
checkState(this.initialized, "Mapper has not been initialized");
getLogger().fine("Mapping with " + getMapperName() + " in single-end mode");
// Unzip archive index if necessary
unzipArchiveIndexFile(this.archiveIndexFileInputStream,
this.archiveIndexDir);
// Process to mapping
final MapperProcess result = internalMapSE(this.archiveIndexDir);
// Set counter
result.setIncrementer(this.incrementer, this.counterGroup);
return result;
}
protected abstract MapperProcess internalMapPE(final File archiveIndex)
throws IOException;
protected abstract MapperProcess internalMapSE(final File archiveIndex)
throws IOException;
//
// Init
//
/**
* Check if the mapper flavor exists.
*/
protected boolean checkIfFlavorExists() {
return true;
}
@Override
public void prepareBinaries() throws IOException {
// Do nothing if binaries has already been prepared
if (this.binariesReady) {
return;
}
// Set the executor to use
if (!this.mapperDockerImage.isEmpty()) {
this.executor =
new DockerMapperExecutor(getMapperDockerImage(), getTempDirectory());
} else if (isUseBundledBinaries()) {
this.executor = new BundledMapperExecutor(getSoftwarePackage(),
getMapperVersionToUse(), getExecutablesTempDirectory());
} else {
this.executor = new PathMapperExecutor();
}
getLogger().fine("Use executor: " + this.executor);
if (!checkIfBinaryExists(getIndexerExecutables())) {
throw new IOException("Unable to find mapper "
+ getMapperName() + " version " + this.mapperVersionToUse
+ " (flavor: "
+ (this.flavorToUse == null ? "not defined" : this.flavorToUse)
+ ")");
}
if (!checkIfFlavorExists()) {
throw new IOException("Unable to find mapper "
+ getMapperName() + " version " + this.mapperVersionToUse
+ " (flavor: "
+ (this.flavorToUse == null ? "not defined" : this.flavorToUse)
+ ")");
}
this.binariesReady = true;
}
@Override
public void init(final DataFile archiveIndexFile, final File archiveIndexDir,
final ReporterIncrementer incrementer, final String counterGroup)
throws IOException {
checkNotNull(archiveIndexFile, "archiveIndexFile is null");
if (!archiveIndexFile.exists()) {
throw new IOException("The archive index file not exits");
}
getLogger().fine("Mapper index archive file: " + archiveIndexFile);
init(archiveIndexFile.open(), archiveIndexDir, incrementer, counterGroup);
}
@Override
public void init(final File archiveIndexFile, final File archiveIndexDir,
final ReporterIncrementer incrementer, final String counterGroup)
throws IOException {
checkNotNull(archiveIndexFile, "archiveIndexFile is null");
checkExistingStandardFile(archiveIndexFile,
"The archive index file not exits or is not a standard file");
getLogger().fine("Mapper index archive file: " + archiveIndexFile);
init(new FileInputStream(archiveIndexFile), archiveIndexDir, incrementer,
counterGroup);
}
@Override
public void init(final InputStream archiveIndexInputStream,
final File archiveIndexDir, final ReporterIncrementer incrementer,
final String counterGroup) throws IOException {
checkState(!this.initialized, "Mapper has already been initialized");
checkNotNull(incrementer, "incrementer is null");
checkNotNull(counterGroup, "counterGroup is null");
checkNotNull(archiveIndexInputStream, "archiveIndexInputStream is null");
checkNotNull(archiveIndexDir, "archiveIndexDir is null");
this.archiveIndexFileInputStream = archiveIndexInputStream;
this.archiveIndexDir = archiveIndexDir;
this.incrementer = incrementer;
this.counterGroup = counterGroup;
// Prepare binaries
prepareBinaries();
this.initialized = true;
}
//
// Utilities methods
//
/**
* Install a list of binaries bundled in the jar in a temporary directory.
* This method automatically use the temporary directory defined in the object
* for the path where to install the binary.
* @param binaryFilenames programs to install
* @return a string with the path of the last installed binary
* @throws IOException if an error occurs while installing binary
*/
protected String install(final String... binaryFilenames) throws IOException {
String result = null;
if (binaryFilenames != null) {
for (String binaryFilename : binaryFilenames) {
result = install(binaryFilename);
}
}
return result;
}
/**
* Install a binary bundled in the jar in a temporary directory. This method
* automatically use the temporary directory defined in the object for the
* path where to install the binary.
* @param binaryFilename program to install
* @return a string with the path of the installed binary
* @throws IOException if an error occurs while installing binary
*/
protected String install(final String binaryFilename) throws IOException {
return this.executor.install(binaryFilename);
}
/**
* Check if binaries bundled in the jar exists.
* @param binaryFilenames program to check
* @return true if the binary exists
*/
protected boolean checkIfBinaryExists(final String... binaryFilenames)
throws IOException {
if (binaryFilenames == null || binaryFilenames.length == 0) {
return false;
}
for (String binaryFilename : binaryFilenames) {
if (!checkIfBinaryExists(binaryFilename)) {
return false;
}
}
return true;
}
/**
* Check if a binary bundled in the jar exists.
* @param binaryFilename program to check
* @return true if the binary exists
*/
protected boolean checkIfBinaryExists(final String binaryFilename)
throws IOException {
return this.executor.isExecutable(binaryFilename);
}
/**
* Convert a string that contains a list of arguments to a list of strings.
* @param s the string to convert
* @return a list of string
*/
private static List<String> getListArguments(final String s) {
if (s == null) {
return Collections.emptyList();
}
// Split the mapper arguments
final String[] tabMapperArguments = s.trim().split(" ");
final List<String> result = new ArrayList<>();
// Keep only non empty arguments
for (String arg : tabMapperArguments) {
if (!arg.isEmpty()) {
result.add(arg);
}
}
return result;
}
/**
* Execute a command and get its output.
* @param command the command to execute
* @return a string with the output
* @throws IOException if an error occurs while executing the command
*/
protected String executeToString(final List<String> command)
throws IOException {
final Result result = this.executor.execute(command, null, true, true);
final StringBuilder sb = new StringBuilder();
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(result.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append('\n');
}
}
return sb.toString();
}
//
// Constructor
//
/**
* Protected constructor.
*/
protected AbstractSequenceReadsMapper() {
setMapperArguments(getDefaultMapperArguments());
}
}