/*
* 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.StringUtil;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A interface representing a collection of (possibly) discontinuous segments in the
* BAM file, possibly representing the results of an index query.
*/
public interface SAMFileSpan extends Cloneable {
/**
* Gets a pointer over the data immediately following this span.
* @return The a pointer to data immediately following this span.
*/
public SAMFileSpan getContentsFollowing();
/**
* Remove all pointers in this file span before the given file span starts.
* @param fileSpan The filespan before which to eliminate.
* @return The portion of the chunk list after the given chunk.
*/
public SAMFileSpan removeContentsBefore(final SAMFileSpan fileSpan);
/**
* Does this file span point to any data, or is it completely empty?
* @return True if the file span is empty, false otherwise.
*/
public boolean isEmpty();
}
/**
* An ordered list of chunks, capable of representing a set of discontiguous
* regions in the BAM file. FileSpans are mutable within the package, but perceived
* as immutable outside the package.
*
* Some operations on FileSpans assume that the spans are sorted. In these cases,
* sort order will be validated.
*
* @author mhanna
* @version 0.1
*/
class BAMFileSpan implements SAMFileSpan, Serializable {
private static final long serialVersionUID = 1L;
/**
* The constituent chunks of this list.
*/
private final List<Chunk> chunks;
/**
* Create a new empty list of chunks.
*/
protected BAMFileSpan() {
this.chunks = new ArrayList<Chunk>();
}
/**
* Convenience constructor to construct a BAM file span from
* a single chunk.
* @param chunk Chunk to use as the sole region in this span.
*/
protected BAMFileSpan(final Chunk chunk) {
this.chunks = new ArrayList<Chunk>();
chunks.add(chunk);
}
/**
* Create a new chunk list from the given list of chunks.
* @param chunks Constituent chunks.
*/
protected BAMFileSpan(final List<Chunk> chunks) {
this.chunks = new ArrayList<Chunk>(chunks);
}
/**
* Does this chunk list map to any position within the BAM file?
* @return True iff the ChunkList points to any data within the BAM.
*/
public boolean isEmpty() {
return chunks.isEmpty();
}
/**
* Deep clone the given chunk list.
* @return A copy of the chunk list.
*/
public BAMFileSpan clone() {
final BAMFileSpan clone = new BAMFileSpan();
for(final Chunk chunk: chunks)
clone.chunks.add(chunk.clone());
return clone;
}
/**
* Remove all chunks in this file span before the given file span starts.
* If a chunk in the chunk list starts before and ends after the given
* chunk, the first portion of the chunk will be deleted.
* @param fileSpan The filespan before which to eliminate.
* @return The portion of the chunk list after the given chunk.
*/
public SAMFileSpan removeContentsBefore(final SAMFileSpan fileSpan) {
if(fileSpan == null)
return clone();
if(!(fileSpan instanceof BAMFileSpan))
throw new SAMException("Unable to compare ");
final BAMFileSpan bamFileSpan = (BAMFileSpan)fileSpan;
if(bamFileSpan.isEmpty())
return clone();
validateSorted();
final BAMFileSpan trimmedChunkList = new BAMFileSpan();
for(final Chunk chunkToTrim: chunks) {
if(chunkToTrim.getChunkEnd() > chunkToTrim.getChunkStart()) {
if(chunkToTrim.getChunkStart() >= bamFileSpan.chunks.get(0).getChunkStart()) {
// This chunk from the list is completely beyond the start of the filtering chunk.
trimmedChunkList.add(chunkToTrim.clone());
}
else {
// This chunk from the list partially overlaps the filtering chunk and must be trimmed.
trimmedChunkList.add(new Chunk(bamFileSpan.chunks.get(0).getChunkStart(),chunkToTrim.getChunkEnd()));
}
}
}
return trimmedChunkList;
}
/**
* Gets a file span over the data immediately following this span.
* @return The a pointer to data immediately following this span.
*/
public SAMFileSpan getContentsFollowing() {
if(chunks.isEmpty())
throw new SAMException("Unable to get the file pointer following this one: no data present.");
validateSorted();
return new BAMFileSpan(new Chunk(chunks.get(chunks.size()-1).getChunkEnd(),Long.MAX_VALUE));
}
/**
* Merge one span into another
*
* @param span - span with chunks to add to this one
*/
public void add(final BAMFileSpan span) {
for (final Chunk c : span.chunks) {
chunks.add(c);
}
}
/**
* Adds a new chunk to this list. Visible only within the BAm.
* @param chunk Chunk to add.
*/
protected void add(final Chunk chunk) {
chunks.add(chunk);
}
/**
* Convert the chunk list to an array of offsets, paired in [start,end) format.
* @return Array of offsets.
*/
protected long[] toCoordinateArray() {
final int count = chunks.size() * 2;
if (count == 0) {
return null;
}
int index = 0;
final long[] result = new long[count];
for (final Chunk chunk : chunks) {
result[index++] = chunk.getChunkStart();
result[index++] = chunk.getChunkEnd();
}
return result;
}
/**
* Find the first offset in the chunk list
* @return The first offset in the span
*/
protected long getFirstOffset() {
final long result = 0;
if (chunks == null){
return result;
}
for (final Chunk chunk : chunks) {
return chunk.getChunkStart();
}
return result;
}
/**
* Gets the constituent chunks stored in this span.
* @return An unmodifiable list of chunks.
*/
protected List<Chunk> getChunks() {
return Collections.unmodifiableList(chunks);
}
/**
* Checks that there is only a single chunk for this span and returns it.
* @return The single chunk stored in this span
*/
protected Chunk getSingleChunk() {
if (chunks.size() != 1){
throw new SAMException("Expecting a single chunk for span. Found " + chunks.size());
}
return chunks.get(0);
}
/**
* The list of chunks is often represented as an array of
* longs where every even-numbered index is a start coordinate
* and every odd-numbered index is a stop coordinate. Convert
* from that format back to a list of chunks.
* @param coordinateArray List of chunks to convert.
* @return A list of chunks.
*/
protected static SAMFileSpan toChunkList(final long[] coordinateArray) {
if(coordinateArray.length % 2 != 0)
throw new SAMException("Data supplied does not appear to be in coordinate array format.");
final BAMFileSpan chunkList = new BAMFileSpan();
for(int i = 0; i < coordinateArray.length; i += 2)
chunkList.add(new Chunk(coordinateArray[i],coordinateArray[i+1]));
chunkList.validateSorted();
return chunkList;
}
/**
* Validates the list of chunks to ensure that they appear in sorted order.
*/
private void validateSorted() {
for(int i = 1; i < chunks.size(); i++) {
if(chunks.get(i).getChunkStart() < chunks.get(i-1).getChunkEnd())
throw new SAMException(String.format("Chunk list is unsorted; chunk %s is before chunk %s",chunks.get(i-1),chunks.get(i)));
}
}
/**
* Creates a string representation of this chunk list.
*/
@Override
public String toString() {
return StringUtil.join(";", chunks);
}
/**
*
* @return A single BAMFileSpan that is an intelligent merge of the input spans, i.e. contiguous, overlapping
* and contained chunks are intelligently merged, and the chunks are sorted.
*/
public static BAMFileSpan merge(final BAMFileSpan[] spans) {
final ArrayList<Chunk> inputChunks = new ArrayList<Chunk>();
for (final BAMFileSpan span : spans) {
if(span != null){
inputChunks.addAll(span.chunks);
}
}
return new BAMFileSpan(Chunk.optimizeChunkList(inputChunks, 0));
}
}