/*
* The MIT License
*
* Copyright (c) 2009 The Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package htsjdk.samtools;
import htsjdk.samtools.util.BlockCompressedOutputStream;
import htsjdk.samtools.util.IOUtil;
import htsjdk.samtools.util.Md5CalculatingOutputStream;
import htsjdk.samtools.util.RuntimeIOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Create a SAMFileWriter for writing SAM or BAM.
*/
public class SAMFileWriterFactory {
private static boolean defaultCreateIndexWhileWriting = Defaults.CREATE_INDEX;
private boolean createIndex = defaultCreateIndexWhileWriting ;
private static boolean defaultCreateMd5File = Defaults.CREATE_MD5;
private boolean createMd5File = defaultCreateMd5File;
private boolean useAsyncIo = Defaults.USE_ASYNC_IO;
private int asyncOutputBufferSize = AsyncSAMFileWriter.DEFAULT_QUEUE_SIZE;
private int bufferSize = Defaults.BUFFER_SIZE;
private File tmpDir;
private Integer maxRecordsInRam;
/** Sets the default for whether to create md5Files for BAM files this factory. */
public static void setDefaultCreateMd5File(final boolean createMd5File) {
defaultCreateMd5File = createMd5File;
}
/** Sets whether to create md5Files for BAMs from this factory. */
public SAMFileWriterFactory setCreateMd5File(final boolean createMd5File) {
this.createMd5File = createMd5File;
return this;
}
/**
* Sets the default for subsequent SAMFileWriterFactories
* that do not specify whether to create an index.
* If a BAM (not SAM) file is created, the setting is true, and the file header specifies coordinate order,
* then a BAM index file will be written along with the BAM file.
*
* @param setting whether to attempt to create a BAM index while creating the BAM file
*/
public static void setDefaultCreateIndexWhileWriting(final boolean setting) {
defaultCreateIndexWhileWriting = setting;
}
/**
* Convenience method allowing newSAMFileWriterFactory().setCreateIndex(true);
* Equivalent to SAMFileWriterFactory.setDefaultCreateIndexWhileWriting(true); newSAMFileWriterFactory();
* If a BAM (not SAM) file is created, the setting is true, and the file header specifies coordinate order,
* then a BAM index file will be written along with the BAM file.
*
* @param setting whether to attempt to create a BAM index while creating the BAM file.
* @return this factory object
*/
public SAMFileWriterFactory setCreateIndex(final boolean setting){
this.createIndex = setting;
return this;
}
/**
* Before creating a writer that is not presorted, this method may be called in order to override
* the default number of SAMRecords stored in RAM before spilling to disk
* (c.f. SAMFileWriterImpl.MAX_RECORDS_IN_RAM). When writing very large sorted SAM files, you may need
* call this method in order to avoid running out of file handles. The RAM available to the JVM may need
* to be increased in order to hold the specified number of records in RAM. This value affects the number
* of records stored in subsequent calls to one of the make...() methods.
*
* @param maxRecordsInRam Number of records to store in RAM before spilling to temporary file when
* creating a sorted SAM or BAM file.
*/
public SAMFileWriterFactory setMaxRecordsInRam(final int maxRecordsInRam) {
this.maxRecordsInRam = maxRecordsInRam;
return this;
}
/**
* Turn on or off the use of asynchronous IO for writing output SAM and BAM files. If true then
* each SAMFileWriter creates a dedicated thread which is used for compression and IO activities.
*/
public SAMFileWriterFactory setUseAsyncIo(final boolean useAsyncIo) {
this.useAsyncIo = useAsyncIo;
return this;
}
/**
* If and only if using asynchronous IO then sets the maximum number of records that can be buffered per
* SAMFileWriter before producers will block when trying to write another SAMRecord.
*/
public SAMFileWriterFactory setAsyncOutputBufferSize(final int asyncOutputBufferSize) {
this.asyncOutputBufferSize = asyncOutputBufferSize;
return this;
}
/**
* Controls size of write buffer.
* Default value: [[htsjdk.samtools.Defaults#BUFFER_SIZE]]
*
*/
public SAMFileWriterFactory setBufferSize(final int bufferSize) {
this.bufferSize = bufferSize;
return this;
}
/**
* Set the temporary directory to use when sort data.
* @param tmpDir Path to the temporary directory
*/
public SAMFileWriterFactory setTempDirectory(final File tmpDir) {
this.tmpDir = tmpDir;
return this;
}
/**
* Create a BAMFileWriter that is ready to receive SAMRecords. Uses default compression level.
* @param header entire header. Sort order is determined by the sortOrder property of this arg.
* @param presorted if true, SAMRecords must be added to the SAMFileWriter in order that agrees with header.sortOrder.
* @param outputFile where to write the output.
*/
public SAMFileWriter makeBAMWriter(final SAMFileHeader header, final boolean presorted, final File outputFile) {
return makeBAMWriter(header, presorted, outputFile, BlockCompressedOutputStream.getDefaultCompressionLevel());
}
/**
*
* Create a BAMFileWriter that is ready to receive SAMRecords.
* @param header entire header. Sort order is determined by the sortOrder property of this arg.
* @param presorted if true, SAMRecords must be added to the SAMFileWriter in order that agrees with header.sortOrder.
* @param outputFile where to write the output.
* @param compressionLevel Override default compression level with the given value, between 0 (fastest) and 9 (smallest).
*/
public SAMFileWriter makeBAMWriter(final SAMFileHeader header, final boolean presorted, final File outputFile,
final int compressionLevel) {
try {
final boolean createMd5File = this.createMd5File && IOUtil.isRegularPath(outputFile);
if (this.createMd5File && !createMd5File) {
System.err.println("Cannot create MD5 file for BAM because output file is not a regular file: " + outputFile.getAbsolutePath());
}
OutputStream os = IOUtil.maybeBufferOutputStream(new FileOutputStream(outputFile, false), bufferSize);
if (createMd5File) os = new Md5CalculatingOutputStream(os, new File(outputFile.getAbsolutePath() + ".md5"));
final BAMFileWriter ret = new BAMFileWriter(os, outputFile, compressionLevel);
final boolean createIndex = this.createIndex && IOUtil.isRegularPath(outputFile);
if (this.createIndex && !createIndex) {
System.err.println("Cannot create index for BAM because output file is not a regular file: " + outputFile.getAbsolutePath());
}
if (this.tmpDir!=null) ret.setTempDirectory(this.tmpDir);
initializeBAMWriter(ret, header, presorted, createIndex);
if (this.useAsyncIo) return new AsyncSAMFileWriter(ret, this.asyncOutputBufferSize);
else return ret;
}
catch (final IOException ioe) {
throw new RuntimeIOException("Error opening file: " + outputFile.getAbsolutePath());
}
}
private void initializeBAMWriter(final BAMFileWriter writer, final SAMFileHeader header, final boolean presorted, final boolean createIndex) {
writer.setSortOrder(header.getSortOrder(), presorted);
if (maxRecordsInRam != null) {
writer.setMaxRecordsInRam(maxRecordsInRam);
}
writer.setHeader(header);
if (createIndex && writer.getSortOrder().equals(SAMFileHeader.SortOrder.coordinate)){
writer.enableBamIndexConstruction();
}
}
/**
* Create a SAMTextWriter that is ready to receive SAMRecords.
* @param header entire header. Sort order is determined by the sortOrder property of this arg.
* @param presorted if true, SAMRecords must be added to the SAMFileWriter in order that agrees with header.sortOrder.
* @param outputFile where to write the output.
*/
public SAMFileWriter makeSAMWriter(final SAMFileHeader header, final boolean presorted, final File outputFile) {
try {
final SAMTextWriter ret = this.createMd5File
? new SAMTextWriter(new Md5CalculatingOutputStream(new FileOutputStream(outputFile, false),
new File(outputFile.getAbsolutePath() + ".md5")))
: new SAMTextWriter(outputFile);
ret.setSortOrder(header.getSortOrder(), presorted);
if (maxRecordsInRam != null) {
ret.setMaxRecordsInRam(maxRecordsInRam);
}
ret.setHeader(header);
if (this.useAsyncIo) return new AsyncSAMFileWriter(ret, this.asyncOutputBufferSize);
else return ret;
}
catch (final IOException ioe) {
throw new RuntimeIOException("Error opening file: " + outputFile.getAbsolutePath());
}
}
/**
* Create a SAMTextWriter for writing to a stream that is ready to receive SAMRecords.
* This method does not support the creation of an MD5 file
*
* @param header entire header. Sort order is determined by the sortOrder property of this arg.
* @param presorted if true, SAMRecords must be added to the SAMFileWriter in order that agrees with header.sortOrder.
* @param stream the stream to write records to. Note that this method does not buffer the stream, so the
* caller must buffer if desired. Note that PrintStream is buffered.
*/
public SAMFileWriter makeSAMWriter(final SAMFileHeader header, final boolean presorted, final OutputStream stream) {
return initWriter(header, presorted, false, new SAMTextWriter(stream));
}
/**
* Create a BAMFileWriter for writing to a stream that is ready to receive SAMRecords.
* This method does not support the creation of an MD5 file
*
* @param header entire header. Sort order is determined by the sortOrder property of this arg.
* @param presorted if true, SAMRecords must be added to the SAMFileWriter in order that agrees with header.sortOrder.
* @param stream the stream to write records to. Note that this method does not buffer the stream, so the
* caller must buffer if desired. Note that PrintStream is buffered.
*/
public SAMFileWriter makeBAMWriter(final SAMFileHeader header, final boolean presorted, final OutputStream stream) {
return initWriter(header, presorted, true, new BAMFileWriter(stream, null));
}
/**
* Initialize SAMTextWriter or a BAMFileWriter and possibly wrap in AsyncSAMFileWriter
*
* @param header entire header. Sort order is determined by the sortOrder property of this arg.
* @param presorted if true, SAMRecords must be added to the SAMFileWriter in order that agrees with header.sortOrder.
* @param binary do we want to generate a BAM or a SAM
* @param writer SAM or BAM writer to initialize and maybe wrap.
*/
private SAMFileWriter initWriter(final SAMFileHeader header, final boolean presorted, final boolean binary,
final SAMFileWriterImpl writer) {
writer.setSortOrder(header.getSortOrder(), presorted);
if (maxRecordsInRam != null) {
writer.setMaxRecordsInRam(maxRecordsInRam);
}
writer.setHeader(header);
if (this.useAsyncIo) return new AsyncSAMFileWriter(writer, this.asyncOutputBufferSize);
else return writer;
}
/**
* Create either a SAM or a BAM writer based on examination of the outputFile extension.
* @param header entire header. Sort order is determined by the sortOrder property of this arg.
* @param presorted presorted if true, SAMRecords must be added to the SAMFileWriter in order that agrees with header.sortOrder.
* @param outputFile where to write the output. Must end with .sam or .bam.
* @return SAM or BAM writer based on file extension of outputFile.
*/
public SAMFileWriter makeSAMOrBAMWriter(final SAMFileHeader header, final boolean presorted, final File outputFile) {
final String filename = outputFile.getName();
if (filename.endsWith(BamFileIoUtils.BAM_FILE_EXTENSION)) {
return makeBAMWriter(header, presorted, outputFile);
}
if (filename.endsWith(".sam")) {
return makeSAMWriter(header, presorted, outputFile);
}
return makeBAMWriter(header, presorted, outputFile);
}
}