package htsjdk.samtools;
import htsjdk.samtools.util.BinaryCodec;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
* Adapted from HTSJDK Binary BAMIndexWriter,
* See https://github.com/samtools/htsjdk/blob/master/src/java/htsjdk/samtools/BinaryBAMIndexWriter.java
* Only writes header for the first reference.
*/
public class BinaryBAMShardIndexWriter implements BAMIndexWriter {
private final BinaryCodec codec;
/**
* @param nRef Number of reference sequences. If zero is passed then header is not written.
* This is useful in sharded writing as we only want the header written for the first shard.
*
* @param output BAM index output stream. This stream will be closed when BinaryBAMIndexWriter.close() is called.
*/
public BinaryBAMShardIndexWriter(final int nRef, final OutputStream output) {
try {
codec = new BinaryCodec(output);
if (nRef > 0) {
writeHeader(nRef);
}
} catch (final Exception e) {
throw new SAMException("Exception opening output stream", e);
}
}
/**
* Write this content as binary output
*/
@Override
public void writeReference(final BAMIndexContent content) {
if (content == null) {
writeNullContent();
return;
}
// write bins
final BAMIndexContent.BinList bins = content.getBins();
final int size = bins == null ? 0 : content.getNumberOfNonNullBins();
if (size == 0) {
writeNullContent();
return;
}
//final List<Chunk> chunks = content.getMetaData() == null ? null
// : content.getMetaData().getMetaDataChunks();
final BAMIndexMetaData metaData = content.getMetaData();
codec.writeInt(size + ((metaData != null)? 1 : 0 ));
// codec.writeInt(size);
for (final Bin bin : bins) { // note, bins will always be sorted
if (bin.getBinNumber() == GenomicIndexUtil.MAX_BINS)
continue;
writeBin(bin);
}
// write metadata "bin" and chunks
if (metaData != null)
writeChunkMetaData(metaData);
// write linear index
final LinearIndex linearIndex = content.getLinearIndex();
final long[] entries = linearIndex == null ? null : linearIndex.getIndexEntries();
final int indexStart = linearIndex == null ? 0 : linearIndex.getIndexStart();
final int n_intv = entries == null ? indexStart : entries.length + indexStart;
codec.writeInt(n_intv);
if (entries == null) {
return;
}
// since indexStart is usually 0, this is usually a no-op
for (int i = 0; i < indexStart; i++) {
codec.writeLong(0);
}
for (int k = 0; k < entries.length; k++) {
codec.writeLong(entries[k]);
}
try {
codec.getOutputStream().flush();
} catch (final IOException e) {
throw new SAMException("IOException in BinaryBAMIndexWriter reference " + content.getReferenceSequence(), e);
}
}
/**
* Writes out the count of records without coordinates
*
* @param count
*/
@Override
public void writeNoCoordinateRecordCount(final Long count) {
codec.writeLong(count == null ? 0 : count);
}
/**
* Any necessary processing at the end of the file
*/
@Override
public void close() {
codec.close();
}
private void writeBin(final Bin bin) {
final int binNumber = bin.getBinNumber();
if (binNumber >= GenomicIndexUtil.MAX_BINS){
throw new SAMException("Unexpected bin number when writing bam index " + binNumber);
}
codec.writeInt(binNumber);
if (bin.getChunkList() == null){
codec.writeInt(0);
return;
}
final List<Chunk> chunkList = bin.getChunkList();
final int n_chunk = chunkList.size();
codec.writeInt(n_chunk);
for (final Chunk c : chunkList) {
codec.writeLong(c.getChunkStart());
codec.writeLong(c.getChunkEnd());
}
}
/**
* Write the meta data represented by the chunkLists associated with bin MAX_BINS 37450
*
* @param metaData information describing numAligned records, numUnAligned, etc
*/
private void writeChunkMetaData(final BAMIndexMetaData metaData) {
codec.writeInt(GenomicIndexUtil.MAX_BINS);
final int nChunk = 2;
codec.writeInt(nChunk);
codec.writeLong(metaData.getFirstOffset());
codec.writeLong(metaData.getLastOffset());
codec.writeLong(metaData.getAlignedRecordCount());
codec.writeLong(metaData.getUnalignedRecordCount());
}
private void writeHeader(int nRef) {
// magic string
final byte[] magic = BAMFileConstants.BAM_INDEX_MAGIC;
codec.writeBytes(magic);
codec.writeInt(nRef);
}
private void writeNullContent() {
codec.writeLong(0); // 0 bins , 0 intv
}
}