From 96662d8d1b7837365809442fdc85dd4ce3b92604 Mon Sep 17 00:00:00 2001 From: hanna Date: Thu, 18 Mar 2010 17:43:42 +0000 Subject: [PATCH] Moving from GATK dependencies on isolated classes checked into the GATK codebase to a dependency on a jar file compiled from my private picard branch. git-svn-id: file:///humgen/gsa-scr1/gsa-engineering/svn_contents/trunk@3034 348d0f76-0448-11de-a6fe-93d51630548a --- .../sam/ComparableSamRecordIterator.java | 92 - .../picard/sam/MergingSamRecordIterator.java | 243 --- java/src/net/sf/samtools/BAMFileIndex2.java | 391 ----- java/src/net/sf/samtools/BAMFileReader2.java | 664 ------- java/src/net/sf/samtools/Bin.java | 69 - java/src/net/sf/samtools/Chunk.java | 114 -- java/src/net/sf/samtools/LinearIndex.java | 24 - java/src/net/sf/samtools/SAMFileReader2.java | 191 -- java/src/net/sf/samtools/SAMRecord.java | 1558 ----------------- ...=> picard-private-parts-1333-sharding.jar} | Bin 272644 -> 284842 bytes ...=> picard-private-parts-1333-sharding.xml} | 2 +- .../repository/net.sf/picard-1.12.256.jar | Bin 273761 -> 0 bytes .../repository/net.sf/picard-1.12.256.xml | 3 - .../net.sf/picard-1.16.359-sharding.jar | Bin 0 -> 407397 bytes .../net.sf/picard-1.16.359-sharding.xml | 3 + settings/repository/net.sf/sam-1.12.256.xml | 3 - ...1.12.256.jar => sam-1.16.359-sharding.jar} | Bin 437962 -> 482295 bytes .../net.sf/sam-1.16.359-sharding.xml | 3 + 18 files changed, 7 insertions(+), 3353 deletions(-) delete mode 100644 java/src/net/sf/picard/sam/ComparableSamRecordIterator.java delete mode 100644 java/src/net/sf/picard/sam/MergingSamRecordIterator.java delete mode 100644 java/src/net/sf/samtools/BAMFileIndex2.java delete mode 100644 java/src/net/sf/samtools/BAMFileReader2.java delete mode 100644 java/src/net/sf/samtools/Bin.java delete mode 100644 java/src/net/sf/samtools/Chunk.java delete mode 100644 java/src/net/sf/samtools/LinearIndex.java delete mode 100644 java/src/net/sf/samtools/SAMFileReader2.java delete mode 100644 java/src/net/sf/samtools/SAMRecord.java rename settings/repository/edu.mit.broad/{picard-private-parts-1198.jar => picard-private-parts-1333-sharding.jar} (60%) rename settings/repository/edu.mit.broad/{picard-private-parts-1198.xml => picard-private-parts-1333-sharding.xml} (55%) delete mode 100644 settings/repository/net.sf/picard-1.12.256.jar delete mode 100644 settings/repository/net.sf/picard-1.12.256.xml create mode 100644 settings/repository/net.sf/picard-1.16.359-sharding.jar create mode 100644 settings/repository/net.sf/picard-1.16.359-sharding.xml delete mode 100644 settings/repository/net.sf/sam-1.12.256.xml rename settings/repository/net.sf/{sam-1.12.256.jar => sam-1.16.359-sharding.jar} (59%) create mode 100644 settings/repository/net.sf/sam-1.16.359-sharding.xml diff --git a/java/src/net/sf/picard/sam/ComparableSamRecordIterator.java b/java/src/net/sf/picard/sam/ComparableSamRecordIterator.java deleted file mode 100644 index eb1101eaa..000000000 --- a/java/src/net/sf/picard/sam/ComparableSamRecordIterator.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 net.sf.picard.sam; - -import net.sf.picard.util.PeekableIterator; - -import java.util.Comparator; -import java.util.Iterator; - -import net.sf.samtools.SAMRecord; -import net.sf.samtools.SAMFileReader; -import net.sf.samtools.util.CloseableIterator; - -/** - * Iterator for SAM records that implements comparable to enable sorting of iterators. - * The comparison is performed by comparing the next record in the iterator to the next - * record in another iterator and returning the ordering between those SAM records. - */ -class ComparableSamRecordIterator extends PeekableIterator implements Comparable { - private final CloseableIterator iterator; - private final Comparator comparator; - - /** - * Constructs a wrapping iterator around the given iterator that will be able - * to compare itself to other ComparableSamRecordIterators using the given comparator. - * - * @param iterator the wrapped iterator. - * @param comparator the Comparator to use to provide ordering fo SAMRecords - */ - public ComparableSamRecordIterator(final CloseableIterator iterator, final Comparator comparator) { - super(iterator); - this.iterator = iterator; - this.comparator = comparator; - } - - public CloseableIterator getWrappedIterator() { - return iterator; - } - - /** - * Compares this iterator to another comparable iterator based on the next record - * available in each iterator. If the two comparable iterators have different - * comparator types internally an exception is thrown. - * - * @param that another iterator to compare to - * @return a negative, 0 or positive number as described in the Comparator interface - */ - public int compareTo(final ComparableSamRecordIterator that) { - if (this.comparator.getClass() != that.comparator.getClass()) { - throw new IllegalStateException("Attempt to compare two ComparableSAMRecordIterators that " + - "have different orderings internally"); - } - - final SAMRecord record = this.peek(); - final SAMRecord record2 = that.peek(); - return comparator.compare(record, record2); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - return compareTo((ComparableSamRecordIterator)o) == 0; - } - - @Override - public int hashCode() { - throw new UnsupportedOperationException("ComparableSamRecordIterator should not be hashed because it can change value"); - } -} diff --git a/java/src/net/sf/picard/sam/MergingSamRecordIterator.java b/java/src/net/sf/picard/sam/MergingSamRecordIterator.java deleted file mode 100644 index 6160d1301..000000000 --- a/java/src/net/sf/picard/sam/MergingSamRecordIterator.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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 net.sf.picard.sam; - -import net.sf.picard.PicardException; - -import java.util.*; -import java.lang.reflect.Constructor; - -import net.sf.samtools.*; -import net.sf.samtools.util.CloseableIterator; - -/** - * Provides an iterator interface for merging multiple underlying iterators into a single - * iterable stream. The underlying iterators/files must all have the same sort order unless - * the requested output format is unsorted, in which case any combination is valid. - */ -public class MergingSamRecordIterator implements CloseableIterator { - private final PriorityQueue pq; - private final SamFileHeaderMerger samHeaderMerger; - private final SAMFileHeader.SortOrder sortOrder; - - /** - * Maps iterators back to the readers from which they are derived. - */ - private final Map,SAMFileReader> iteratorToSourceMap = new HashMap,SAMFileReader>(); - - /** - * Constructs a new merging iterator with the same set of readers and sort order as - * provided by the header merger parameter. - * @param headerMerger The merged header and contents of readers. - * @param forcePresorted True to ensure that the iterator checks the headers of the readers for appropriate sort order. - */ - public MergingSamRecordIterator(final SamFileHeaderMerger headerMerger, final boolean forcePresorted) { - this(headerMerger,createWholeFileIterators(headerMerger.getReaders()),forcePresorted); - } - - /** - * Constructs a new merging iterator with a given merged header and a subset of readers. - * @param headerMerger The merged header and contents of readers. - * @param readerToIteratorMap A mapping of reader to iterator. - * @param forcePresorted True to ensure that the iterator checks the headers of the readers for appropriate sort order. - */ - public MergingSamRecordIterator(final SamFileHeaderMerger headerMerger, final Map> readerToIteratorMap, final boolean forcePresorted) { - this.samHeaderMerger = headerMerger; - this.sortOrder = headerMerger.getMergedHeader().getSortOrder(); - final SAMRecordComparator comparator = getComparator(); - - final Collection readers = headerMerger.getReaders(); - this.pq = new PriorityQueue(readers.size()); - - for(final SAMFileReader reader: readerToIteratorMap.keySet()) { - if (!forcePresorted && this.sortOrder != SAMFileHeader.SortOrder.unsorted && - reader.getFileHeader().getSortOrder() != this.sortOrder){ - throw new PicardException("Files are not compatible with sort order"); - } - - final ComparableSamRecordIterator iterator = new ComparableSamRecordIterator(readerToIteratorMap.get(reader),comparator); - addIfNotEmpty(iterator); - iteratorToSourceMap.put(iterator.getWrappedIterator(),reader); - } - } - - /** - * For each reader, derive an iterator that can walk the entire file and associate that back to - * @param readers The readers from which to derive iterators. - * @return A map of reader to its associated iterator. - */ - private static Map> createWholeFileIterators(Collection readers) { - Map> readerToIteratorMap = new HashMap>(); - for(final SAMFileReader reader: readers) - readerToIteratorMap.put(reader,reader.iterator()); - return readerToIteratorMap; - } - - /** - * Close down all open iterators. - */ - public void close() { - // Iterators not in the priority queue have already been closed; only close down the iterators that are still in the priority queue. - for(CloseableIterator iterator: pq) - iterator.close(); - } - - /** Returns true if any of the underlying iterators has more records, otherwise false. */ - public boolean hasNext() { - return !this.pq.isEmpty(); - } - - /** Returns the next record from the top most iterator during merging. */ - public SAMRecord next() { - final ComparableSamRecordIterator iterator = this.pq.poll(); - final SAMRecord record = iterator.next(); - addIfNotEmpty(iterator); - record.setHeader(this.samHeaderMerger.getMergedHeader()); - - // Fix the read group if needs be - if (this.samHeaderMerger.hasReadGroupCollisions()) { - final String oldGroupId = (String) record.getAttribute(ReservedTagConstants.READ_GROUP_ID); - if (oldGroupId != null ) { - final String newGroupId = this.samHeaderMerger.getReadGroupId(iteratorToSourceMap.get(iterator.getWrappedIterator()), oldGroupId); - record.setAttribute(ReservedTagConstants.READ_GROUP_ID, newGroupId); - } - } - - // Fix the program group if needs be - if (this.samHeaderMerger.hasProgramGroupCollisions()) { - final String oldGroupId = (String) record.getAttribute(ReservedTagConstants.PROGRAM_GROUP_ID); - if (oldGroupId != null ) { - final String newGroupId = this.samHeaderMerger.getProgramGroupId(iteratorToSourceMap.get(iterator.getWrappedIterator()), oldGroupId); - record.setAttribute(ReservedTagConstants.PROGRAM_GROUP_ID, newGroupId); - } - } - - // Fix up the sequence indexes if needs be - if (this.samHeaderMerger.hasMergedSequenceDictionary()) { - if (record.getReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { - record.setReferenceIndex(this.samHeaderMerger.getMergedSequenceIndex(iteratorToSourceMap.get(iterator.getWrappedIterator()),record.getReferenceIndex())); - } - - if (record.getReadPairedFlag() && record.getMateReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { - record.setMateReferenceIndex(this.samHeaderMerger.getMergedSequenceIndex(iteratorToSourceMap.get(iterator.getWrappedIterator()), record.getMateReferenceIndex())); - } - } - - return record; - } - - /** - * Adds iterator to priority queue. If the iterator has more records it is added - * otherwise it is closed and not added. - */ - private void addIfNotEmpty(final ComparableSamRecordIterator iterator) { - if (iterator.hasNext()) { - pq.offer(iterator); - } - else { - iterator.close(); - } - } - - /** Unsupported operation. */ - public void remove() { - throw new UnsupportedOperationException("MergingSAMRecorderIterator.remove()"); - } - - /** - * Get the right comparator for a given sort order (coordinate, alphabetic). In the - * case of "unsorted" it will return a comparator that gives an arbitrary but reflexive - * ordering. - */ - private SAMRecordComparator getComparator() { - // For unsorted build a fake comparator that compares based on object ID - if (this.sortOrder == SAMFileHeader.SortOrder.unsorted) { - return new SAMRecordComparator() { - public int fileOrderCompare(final SAMRecord lhs, final SAMRecord rhs) { - return System.identityHashCode(lhs) - System.identityHashCode(rhs); - } - - public int compare(final SAMRecord lhs, final SAMRecord rhs) { - return fileOrderCompare(lhs, rhs); - } - }; - } - if (samHeaderMerger.hasMergedSequenceDictionary() && sortOrder.equals(SAMFileHeader.SortOrder.coordinate)) { - return new MergedSequenceDictionaryCoordinateOrderComparator(); - } - - // Otherwise try and figure out what kind of comparator to return and build it - final Class type = this.sortOrder.getComparator(); - - try { - final Constructor ctor = type.getConstructor(); - return ctor.newInstance(); - } - catch (Exception e) { - throw new PicardException("Could not instantiate a comparator for sort order: " + this.sortOrder, e); - } - } - - /** Returns the merged header that the merging iterator is working from. */ - public SAMFileHeader getMergedHeader() { - return this.samHeaderMerger.getMergedHeader(); - } - - /** - * Ugh. Basically does a regular coordinate compare, but looks up the sequence indices in the merged - * sequence dictionary. I hate the fact that this extends SAMRecordCoordinateComparator, but it avoids - * more copy & paste. - */ - private class MergedSequenceDictionaryCoordinateOrderComparator extends SAMRecordCoordinateComparator { - - public int fileOrderCompare(final SAMRecord samRecord1, final SAMRecord samRecord2) { - final int referenceIndex1 = getReferenceIndex(samRecord1); - final int referenceIndex2 = getReferenceIndex(samRecord2); - if (referenceIndex1 != referenceIndex2) { - if (referenceIndex1 == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { - return 1; - } else if (referenceIndex2 == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { - return -1; - } else { - return referenceIndex1 - referenceIndex2; - } - } - if (referenceIndex1 == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { - // Both are unmapped. - return 0; - } - return samRecord1.getAlignmentStart() - samRecord2.getAlignmentStart(); - } - - private int getReferenceIndex(final SAMRecord samRecord) { - if (samRecord.getReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { - return samHeaderMerger.getMergedSequenceIndex(samRecord.getHeader(), samRecord.getReferenceIndex()); - } - if (samRecord.getMateReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { - return samHeaderMerger.getMergedSequenceIndex(samRecord.getHeader(), samRecord.getMateReferenceIndex()); - } - return SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX; - } - } -} diff --git a/java/src/net/sf/samtools/BAMFileIndex2.java b/java/src/net/sf/samtools/BAMFileIndex2.java deleted file mode 100644 index 1def4e694..000000000 --- a/java/src/net/sf/samtools/BAMFileIndex2.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * 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 net.sf.samtools; - - -import net.sf.samtools.util.RuntimeIOException; - -import java.io.*; -import java.nio.*; -import java.nio.channels.*; -import java.util.*; - -/** - * Class for reading BAM file indexes. - */ -public class BAMFileIndex2 extends BAMFileIndex -{ - /** - * Reports the total amount of genomic data that any bin can index. - */ - private static final int BIN_SPAN = 512*1024*1024; - - /** - * Reports the maximum number of bins in a BAM file index, based on the the pseudocode - * in section 1.2 of the BAM spec. - */ - private static final int MAX_BINS = 37450; // =(8^6-1)/7+1 - - private static final int BAM_LIDX_SHIFT = 14; - - /** - * What is the starting bin for each level? - */ - private static final int[] LEVEL_STARTS = {0,1,9,73,585,4681}; - - /** - * A mapping of reference sequence index to list of bins. - */ - protected final SortedMap referenceToBins = new TreeMap(); - - /** - * A mapping of reference sequence index to linear indices. - */ - protected final SortedMap referenceToLinearIndices = new TreeMap(); - - /** - * A mapping from bin to the chunks contained in that bin. - */ - protected final SortedMap> binToChunks = new TreeMap>(); - - protected BAMFileIndex2(final File file) { - super(file); - loadIndex(file); - } - - /** - * Get the number of levels employed by this index. - * @return Number of levels in this index. - */ - protected int getNumIndexLevels() { - return LEVEL_STARTS.length; - } - - /** - * Gets the level associated with the given bin number. - * @param binNumber The bin number for which to determine the level. - * @return the level associated with the given bin number. - */ - protected int getLevelForBinNumber(final int binNumber) { - if(binNumber >= MAX_BINS) - throw new SAMException("Tried to get level for invalid bin."); - for(int i = getNumIndexLevels()-1; i >= 0; i--) { - if(binNumber >= LEVEL_STARTS[i]) - return i; - } - throw new SAMException("Unable to find correct bin for bin number "+binNumber); - } - - /** - * Gets the first locus that this bin can index into. - * @param bin The bin to test. - * @return The last position that the given bin can represent. - */ - protected int getFirstLocusInBin(final Bin bin) { - final int level = getLevelForBinNumber(bin.binNumber); - final int levelStart = LEVEL_STARTS[level]; - final int levelSize = ((level==getNumIndexLevels()-1) ? MAX_BINS-1 : LEVEL_STARTS[level+1]) - levelStart; - return (bin.binNumber - levelStart)*(BIN_SPAN/levelSize)+1; - } - - /** - * Gets the last locus that this bin can index into. - * @param bin The bin to test. - * @return The last position that the given bin can represent. - */ - protected int getLastLocusInBin(final Bin bin) { - final int level = getLevelForBinNumber(bin.binNumber); - final int levelStart = LEVEL_STARTS[level]; - final int levelSize = ((level==getNumIndexLevels()-1) ? MAX_BINS-1 : LEVEL_STARTS[level+1]) - levelStart; - return (bin.binNumber-levelStart+1)*(BIN_SPAN/levelSize); - } - - /** - * Completely load the index into memory. - * @param file File to load. - */ - private void loadIndex(final File file) { - FileInputStream fileStream; - FileChannel fileChannel; - MappedByteBuffer fileBuffer; - - try { - fileStream = new FileInputStream(file); - fileChannel = fileStream.getChannel(); - fileBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0L, fileChannel.size()); - fileBuffer.order(ByteOrder.LITTLE_ENDIAN); - } catch (IOException exc) { - throw new RuntimeIOException(exc.getMessage(), exc); - } - - try { - final byte[] buffer = new byte[4]; - readBytes(fileBuffer,buffer); - if (!Arrays.equals(buffer, BAMFileConstants.BAM_INDEX_MAGIC)) { - throw new RuntimeException("Invalid file header in BAM index " + file + - ": " + new String(buffer)); - } - - final int sequenceCount = readInteger(fileBuffer); - for(int sequence = 0; sequence < sequenceCount; sequence++) { - final int binCount = readInteger(fileBuffer); - final Bin[] bins = new Bin[binCount]; - for(int bin = 0; bin < binCount; bin++) { - List chunkList = new ArrayList(); - final int indexBin = readInteger(fileBuffer); - final int nChunks = readInteger(fileBuffer); - for (int ci = 0; ci < nChunks; ci++) { - final long chunkBegin = readLong(fileBuffer); - final long chunkEnd = readLong(fileBuffer); - chunkList.add(new Chunk(chunkBegin, chunkEnd)); - } - bins[bin] = new Bin(sequence,indexBin); - binToChunks.put(bins[bin],chunkList); - } - referenceToBins.put(sequence,bins); - - int linearIndexSize = readInteger(fileBuffer); - long[] linearIndex = new long[linearIndexSize]; - for(int indexEntry = 0; indexEntry < linearIndexSize; indexEntry++) - linearIndex[indexEntry] = readLong(fileBuffer); - - referenceToLinearIndices.put(sequence,new LinearIndex(sequence,linearIndex)); - } - } - finally { - try { - fileChannel.close(); - fileStream.close(); - } catch (IOException exc) { - throw new RuntimeIOException(exc.getMessage(), exc); - } - } - } - - /** - * Perform an overlapping query of all bins bounding the given location. - * @param bin The bin over which to perform an overlapping query. - * @return The file pointers - */ - long[] getFilePointersBounding(final Bin bin) { - if(bin == null) - return null; - - final int referenceSequence = bin.referenceSequence; - final Bin[] allBins = referenceToBins.get(referenceSequence); - - final int binLevel = getLevelForBinNumber(bin.binNumber); - final int firstLocusInBin = getFirstLocusInBin(bin); - - List binTree = new ArrayList(); - binTree.add(bin); - - int currentBinLevel = binLevel; - while(--currentBinLevel >= 0) { - final int binStart = LEVEL_STARTS[currentBinLevel]; - final int binWidth = BIN_SPAN/(LEVEL_STARTS[currentBinLevel+1]-LEVEL_STARTS[currentBinLevel]); - final int binNumber = firstLocusInBin/binWidth + binStart; - for(Bin referenceBin: allBins) { - if(binNumber == referenceBin.binNumber) - binTree.add(referenceBin); - } - } - - List chunkList = new ArrayList(); - for(Bin coveringBin: binTree) { - for(Chunk chunk: binToChunks.get(coveringBin)) - chunkList.add(chunk.clone()); - } - - final int start = getFirstLocusInBin(bin)-1; - final int regionLinearBin = start >> BAM_LIDX_SHIFT; - LinearIndex index = referenceToLinearIndices.get(referenceSequence); - long minimumOffset = 0; - if (regionLinearBin < index.indexEntries.length) - minimumOffset = index.indexEntries[regionLinearBin]; - - chunkList = optimizeChunkList(chunkList, minimumOffset); - return convertToArray(chunkList); - } - - /** - * Get list of regions of BAM file that may contain SAMRecords for the given range - * @param referenceIndex sequence of desired SAMRecords - * @param startPos 1-based start of the desired interval, inclusive - * @param endPos 1-based end of the desired interval, inclusive - * @return array of pairs of virtual file positions. Each pair is the first and last - * virtual file position in a range that can be scanned to find SAMRecords that overlap the given - * positions. The last position in each pair is a virtual file pointer to the first SAMRecord beyond - * the range that may contain the indicated SAMRecords. - */ - long[] getFilePointersContaining(final int referenceIndex, final int startPos, final int endPos) { - List bins = getBinsContaining(referenceIndex,startPos,endPos); - // System.out.println("# Sequence target TID: " + referenceIndex); - if (bins == null) { - return null; - } - - List chunkList = new ArrayList(); - for(Bin bin: bins) { - for(Chunk chunk: binToChunks.get(bin)) - chunkList.add(chunk.clone()); - } - - if (chunkList.isEmpty()) { - return null; - } - - final int start = (startPos <= 0) ? 0 : startPos-1; - final int regionLinearBin = start >> BAM_LIDX_SHIFT; - // System.out.println("# regionLinearBin: " + regionLinearBin); - LinearIndex index = referenceToLinearIndices.get(referenceIndex); - long minimumOffset = 0; - if (regionLinearBin < index.indexEntries.length) - minimumOffset = index.indexEntries[regionLinearBin]; - chunkList = optimizeChunkList(chunkList, minimumOffset); - return convertToArray(chunkList); - } - - /** - * Get a list of bins in the BAM file that may contain SAMRecords for the given range. - * @param referenceIndex sequence of desired SAMRecords - * @param startPos 1-based start of the desired interval, inclusive - * @param endPos 1-based end of the desired interval, inclusive - * @return a list of bins that contain relevant data. - */ - List getBinsContaining(final int referenceIndex, final int startPos, final int endPos) { - List filteredBins = new ArrayList(); - - if (referenceIndex >= referenceToBins.size()) { - return null; - } - - final BitSet regionBins = regionToBins(startPos, endPos); - if (regionBins == null) { - return null; - } - - Bin[] bins = referenceToBins.get(referenceIndex); - - for(Bin bin: bins) { - if (regionBins.get(bin.binNumber)) - filteredBins.add(bin); - } - - return filteredBins; - } - - /** - * Use to get close to the unmapped reads at the end of a BAM file. - * @return The file offset of the first record in the last linear bin, or -1 - * if there are no elements in linear bins (i.e. no mapped reads). - */ - long getStartOfLastLinearBin() { - LinearIndex lastLinearIndex = referenceToLinearIndices.get(referenceToLinearIndices.lastKey()); - return lastLinearIndex.indexEntries[lastLinearIndex.indexEntries.length-1]; - } - - private List optimizeChunkList(final List chunkList, final long minimumOffset) { - Chunk lastChunk = null; - Collections.sort(chunkList); - final List result = new ArrayList(); - for (final Chunk chunk : chunkList) { - if (chunk.getChunkEnd() <= minimumOffset) { - continue; - } - if (result.isEmpty()) { - result.add(chunk); - lastChunk = chunk; - continue; - } - // Coalesce chunks that are in adjacent file blocks. - // This is a performance optimization. - final long lastFileBlock = getFileBlock(lastChunk.getChunkEnd()); - final long chunkFileBlock = getFileBlock(chunk.getChunkStart()); - if (chunkFileBlock - lastFileBlock > 1) { - result.add(chunk); - lastChunk = chunk; - } else { - if (chunk.getChunkEnd() > lastChunk.getChunkEnd()) { - lastChunk.setChunkEnd(chunk.getChunkEnd()); - } - } - } - return result; - } - - private long[] convertToArray(final List chunkList) { - final int count = chunkList.size() * 2; - if (count == 0) { - return null; - } - int index = 0; - final long[] result = new long[count]; - for (final Chunk chunk : chunkList) { - result[index++] = chunk.getChunkStart(); - result[index++] = chunk.getChunkEnd(); - } - return result; - } - - /** - * Get candidate bins for the specified region - * @param startPos 1-based start of target region, inclusive. - * @param endPos 1-based end of target region, inclusive. - * @return bit set for each bin that may contain SAMRecords in the target region. - */ - protected BitSet regionToBins(final int startPos, final int endPos) { - final int maxPos = 0x1FFFFFFF; - final int start = (startPos <= 0) ? 0 : (startPos-1) & maxPos; - final int end = (endPos <= 0) ? maxPos : (endPos-1) & maxPos; - if (start > end) { - return null; - } - int k; - final BitSet bitSet = new BitSet(MAX_BINS); - bitSet.set(0); - for (k = LEVEL_STARTS[1] + (start>>26); k <= LEVEL_STARTS[1] + (end>>26); ++k) bitSet.set(k); - for (k = LEVEL_STARTS[2] + (start>>23); k <= LEVEL_STARTS[2] + (end>>23); ++k) bitSet.set(k); - for (k = LEVEL_STARTS[3] + (start>>20); k <= LEVEL_STARTS[3] + (end>>20); ++k) bitSet.set(k); - for (k = LEVEL_STARTS[4] + (start>>17); k <= LEVEL_STARTS[4] + (end>>17); ++k) bitSet.set(k); - for (k = LEVEL_STARTS[5] + (start>>14); k <= LEVEL_STARTS[5] + (end>>14); ++k) bitSet.set(k); - return bitSet; - } - - private long getFileBlock(final long bgzfOffset) { - return ((bgzfOffset >> 16L) & 0xFFFFFFFFFFFFL); - } - - private void readBytes(MappedByteBuffer source, final byte[] target) { - source.get(target); - } - - private int readInteger(MappedByteBuffer source) { - return source.getInt(); - } - - private long readLong(MappedByteBuffer source) { - return source.getLong(); - } -} diff --git a/java/src/net/sf/samtools/BAMFileReader2.java b/java/src/net/sf/samtools/BAMFileReader2.java deleted file mode 100644 index 0509b8df6..000000000 --- a/java/src/net/sf/samtools/BAMFileReader2.java +++ /dev/null @@ -1,664 +0,0 @@ -/* - * 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 net.sf.samtools; - - -import net.sf.samtools.util.BinaryCodec; -import net.sf.samtools.util.BlockCompressedInputStream; -import net.sf.samtools.util.CloseableIterator; -import net.sf.samtools.util.StringLineReader; -import net.sf.samtools.SAMFileReader.ValidationStringency; - -import java.io.*; -import java.util.*; -import java.net.URL; - -/** - * Internal class for reading and querying BAM files. - */ -class BAMFileReader2 - extends SAMFileReader.ReaderImplementation { - // True if reading from a File rather than an InputStream - private boolean mIsSeekable = false; - // For converting bytes into other primitive types - private BinaryCodec mStream = null; - // Underlying compressed data stream. - private final BlockCompressedInputStream mCompressedInputStream; - private SAMFileReader mFileReader = null; - private SAMFileHeader mFileHeader = null; - // Populated if the file is seekable and an index exists - private BAMFileIndex2 mFileIndex = null; - private long mFirstRecordPointer = 0; - private CloseableIterator mCurrentIterator = null; - // If true, all SAMRecords are fully decoded as they are read. - private final boolean eagerDecode; - // For error-checking. - private ValidationStringency mValidationStringency; - - /** - * Prepare to read BAM from a stream (not seekable) - * @param stream source of bytes. - * @param eagerDecode if true, decode all BAM fields as reading rather than lazily. - * @param validationStringency Controls how to handle invalidate reads or header lines. - */ - BAMFileReader2(final InputStream stream, final boolean eagerDecode, final ValidationStringency validationStringency) - throws IOException { - mIsSeekable = false; - mCompressedInputStream = new BlockCompressedInputStream(stream); - mStream = new BinaryCodec(new DataInputStream(mCompressedInputStream)); - this.eagerDecode = eagerDecode; - this.mValidationStringency = validationStringency; - readHeader(null); - } - - /** - * Prepare to read BAM from a file (seekable) - * @param file source of bytes. - * @param eagerDecode if true, decode all BAM fields as reading rather than lazily. - * @param validationStringency Controls how to handle invalidate reads or header lines. - */ - BAMFileReader2(final File file, final boolean eagerDecode, final ValidationStringency validationStringency) - throws IOException { - this(new BlockCompressedInputStream(file), eagerDecode, file.getAbsolutePath(), validationStringency); - } - - - BAMFileReader2(final URL url, final boolean eagerDecode, final ValidationStringency validationStringency) - throws IOException { - this(new BlockCompressedInputStream(url), eagerDecode, url.toString(), validationStringency); - } - - private BAMFileReader2(final BlockCompressedInputStream compressedInputStream, final boolean eagerDecode, - final String source, final ValidationStringency validationStringency) - throws IOException { - mIsSeekable = true; - mCompressedInputStream = compressedInputStream; - mStream = new BinaryCodec(new DataInputStream(mCompressedInputStream)); - this.eagerDecode = eagerDecode; - this.mValidationStringency = validationStringency; - readHeader(source); - mFirstRecordPointer = mCompressedInputStream.getFilePointer(); - } - - /** - * Sets the reader reading this file. - * @param reader The source reader. - */ - void setReader(SAMFileReader reader) { - mFileReader = reader; - } - - void close() { - if (mStream != null) { - mStream.close(); - } - mStream = null; - mFileHeader = null; - mFileIndex = null; - } - - /** - * @return the file index, if one exists, else null. - */ - BAMFileIndex2 getFileIndex() { - return mFileIndex; - } - - void setFileIndex(final BAMFileIndex2 fileIndex) { - mFileIndex = fileIndex; - } - - SAMFileHeader getFileHeader() { - return mFileHeader; - } - - /** - * Set error-checking level for subsequent SAMRecord reads. - */ - void setValidationStringency(final SAMFileReader.ValidationStringency validationStringency) { - this.mValidationStringency = validationStringency; - } - - SAMFileReader.ValidationStringency getValidationStringency() { - return this.mValidationStringency; - } - - /** - * Prepare to iterate through the SAMRecords in file order. - * Only a single iterator on a BAM file can be extant at a time. If getIterator() or a query method has been called once, - * that iterator must be closed before getIterator() can be called again. - * A somewhat peculiar aspect of this method is that if the file is not seekable, a second call to - * getIterator() begins its iteration where the last one left off. That is the best that can be - * done in that situation. - */ - CloseableIterator getIterator() { - if (mStream == null) { - throw new IllegalStateException("File reader is closed"); - } - if (mCurrentIterator != null) { - throw new IllegalStateException("Iteration in progress"); - } - if (mIsSeekable) { - try { - mCompressedInputStream.seek(mFirstRecordPointer); - } catch (IOException exc) { - throw new RuntimeException(exc.getMessage(), exc); - } - } - mCurrentIterator = new BAMFileIterator(); - return mCurrentIterator; - } - - CloseableIterator getIterator(List chunks) { - if (mStream == null) { - throw new IllegalStateException("File reader is closed"); - } - if (mCurrentIterator != null) { - throw new IllegalStateException("Iteration in progress"); - } - if (mIsSeekable) { - try { - mCompressedInputStream.seek(mFirstRecordPointer); - } catch (IOException exc) { - throw new RuntimeException(exc.getMessage(), exc); - } - } - - // Create an iterator over the given chunk boundaries. - mCurrentIterator = new BAMFileIndexIterator(Chunk.toCoordinateArray(chunks)); - return mCurrentIterator; - } - - public List getOverlappingBins(final String sequence, final int start, final int end) { - List bins = Collections.emptyList(); - - final SAMFileHeader fileHeader = getFileHeader(); - int referenceIndex = fileHeader.getSequenceIndex(sequence); - if (referenceIndex != -1) { - final BAMFileIndex2 fileIndex = getFileIndex(); - bins = fileIndex.getBinsContaining(referenceIndex, start, end); - } - - return bins; - } - - public List getFilePointersBounding(final Bin bin) { - final BAMFileIndex2 fileIndex = getFileIndex(); - long[] filePointers = fileIndex.getFilePointersBounding(bin); - return (filePointers != null) ? Chunk.toChunkList(filePointers) : Collections.emptyList(); - } - - public Long getFilePointer() { - return mCompressedInputStream.getFilePointer(); - } - - /** - * Prepare to iterate through the SAMRecords that match the given interval. - * Only a single iterator on a BAMFile can be extant at a time. The previous one must be closed - * before calling any of the methods that return an iterator. - * - * Note that an unmapped SAMRecord may still have a reference name and an alignment start for sorting - * purposes (typically this is the coordinate of its mate), and will be found by this method if the coordinate - * matches the specified interval. - * - * Note that this method is not necessarily efficient in terms of disk I/O. The index does not have perfect - * resolution, so some SAMRecords may be read and then discarded because they do not match the specified interval. - * - * @param sequence Reference sequence sought. - * @param start Desired SAMRecords must overlap or be contained in the interval specified by start and end. - * A value of zero implies the start of the reference sequence. - * @param end A value of zero implies the end of the reference sequence. - * @param contained If true, the alignments for the SAMRecords must be completely contained in the interval - * specified by start and end. If false, the SAMRecords need only overlap the interval. - * @return Iterator for the matching SAMRecords - */ - CloseableIterator query(final String sequence, final int start, final int end, final boolean contained) { - if (mStream == null) { - throw new IllegalStateException("File reader is closed"); - } - if (mCurrentIterator != null) { - throw new IllegalStateException("Iteration in progress"); - } - if (!mIsSeekable) { - throw new UnsupportedOperationException("Cannot query stream-based BAM file"); - } - if (mFileIndex == null) { - throw new IllegalStateException("No BAM file index is available"); - } - mCurrentIterator = createIndexIterator(sequence, start, end, contained? QueryType.CONTAINED: QueryType.OVERLAPPING); - return mCurrentIterator; - } - - /** - * Prepare to iterate through the SAMRecords with the given alignment start. - * Only a single iterator on a BAMFile can be extant at a time. The previous one must be closed - * before calling any of the methods that return an iterator. - * - * Note that an unmapped SAMRecord may still have a reference name and an alignment start for sorting - * purposes (typically this is the coordinate of its mate), and will be found by this method if the coordinate - * matches the specified interval. - * - * Note that this method is not necessarily efficient in terms of disk I/O. The index does not have perfect - * resolution, so some SAMRecords may be read and then discarded because they do not match the specified interval. - * - * @param sequence Reference sequence sought. - * @param start Alignment start sought. - * @return Iterator for the matching SAMRecords. - */ - CloseableIterator queryAlignmentStart(final String sequence, final int start) { - if (mStream == null) { - throw new IllegalStateException("File reader is closed"); - } - if (mCurrentIterator != null) { - throw new IllegalStateException("Iteration in progress"); - } - if (!mIsSeekable) { - throw new UnsupportedOperationException("Cannot query stream-based BAM file"); - } - if (mFileIndex == null) { - throw new IllegalStateException("No BAM file index is available"); - } - mCurrentIterator = createIndexIterator(sequence, start, -1, QueryType.STARTING_AT); - return mCurrentIterator; - } - - public CloseableIterator queryUnmapped() { - if (mStream == null) { - throw new IllegalStateException("File reader is closed"); - } - if (mCurrentIterator != null) { - throw new IllegalStateException("Iteration in progress"); - } - if (!mIsSeekable) { - throw new UnsupportedOperationException("Cannot query stream-based BAM file"); - } - if (mFileIndex == null) { - throw new IllegalStateException("No BAM file index is available"); - } - try { - final long startOfLastLinearBin = mFileIndex.getStartOfLastLinearBin(); - if (startOfLastLinearBin != -1) { - mCompressedInputStream.seek(startOfLastLinearBin); - } else { - // No mapped reads in file, just start at the first read in file. - mCompressedInputStream.seek(mFirstRecordPointer); - } - mCurrentIterator = new BAMFileIndexUnmappedIterator(); - return mCurrentIterator; - } catch (IOException e) { - throw new RuntimeException("IOException seeking to unmapped reads", e); - } - } - - /** - * Reads the header from the file or stream - * @param source Note that this is used only for reporting errors. - */ - private void readHeader(final String source) - throws IOException { - - final byte[] buffer = new byte[4]; - mStream.readBytes(buffer); - if (!Arrays.equals(buffer, BAMFileConstants.BAM_MAGIC)) { - throw new IOException("Invalid BAM file header"); - } - - final int headerTextLength = mStream.readInt(); - final String textHeader = mStream.readString(headerTextLength); - final SAMTextHeaderCodec headerCodec = new SAMTextHeaderCodec(); - headerCodec.setValidationStringency(mValidationStringency); - mFileHeader = headerCodec.decode(new StringLineReader(textHeader), - source); - - final int sequenceCount = mStream.readInt(); - if (mFileHeader.getSequenceDictionary().size() > 0) { - // It is allowed to have binary sequences but no text sequences, so only validate if both are present - if (sequenceCount != mFileHeader.getSequenceDictionary().size()) { - throw new SAMFormatException("Number of sequences in text header (" + - mFileHeader.getSequenceDictionary().size() + - ") != number of sequences in binary header (" + sequenceCount + ") for file " + source); - } - for (int i = 0; i < sequenceCount; i++) { - final SAMSequenceRecord binarySequenceRecord = readSequenceRecord(source); - final SAMSequenceRecord sequenceRecord = mFileHeader.getSequence(i); - if (!sequenceRecord.getSequenceName().equals(binarySequenceRecord.getSequenceName())) { - throw new SAMFormatException("For sequence " + i + ", text and binary have different names in file " + - source); - } - if (sequenceRecord.getSequenceLength() != binarySequenceRecord.getSequenceLength()) { - throw new SAMFormatException("For sequence " + i + ", text and binary have different lengths in file " + - source); - } - } - } else { - // If only binary sequences are present, copy them into mFileHeader - final List sequences = new ArrayList(sequenceCount); - for (int i = 0; i < sequenceCount; i++) { - sequences.add(readSequenceRecord(source)); - } - mFileHeader.setSequenceDictionary(new SAMSequenceDictionary(sequences)); - } - } - - /** - * Reads a single binary sequence record from the file or stream - * @param source Note that this is used only for reporting errors. - */ - private SAMSequenceRecord readSequenceRecord(final String source) { - final int nameLength = mStream.readInt(); - if (nameLength <= 1) { - throw new SAMFormatException("Invalid BAM file header: missing sequence name in file " + source); - } - final String sequenceName = mStream.readString(nameLength - 1); - // Skip the null terminator - mStream.readByte(); - final int sequenceLength = mStream.readInt(); - return new SAMSequenceRecord(sequenceName, sequenceLength); - } - - /** - * Iterator for non-indexed sequential iteration through all SAMRecords in file. - * Starting point of iteration is wherever current file position is when the iterator is constructed. - */ - private class BAMFileIterator implements CloseableIterator { - private SAMRecord mNextRecord = null; - private final BAMRecordCodec bamRecordCodec = new BAMRecordCodec(getFileHeader()); - private long samRecordIndex = 0; // Records at what position (counted in records) we are at in the file - - BAMFileIterator() { - this(true); - } - - /** - * @param advance Trick to enable subclass to do more setup before advancing - */ - BAMFileIterator(final boolean advance) { - this.bamRecordCodec.setInputStream(BAMFileReader2.this.mStream.getInputStream()); - - if (advance) { - advance(); - } - } - - public void close() { - if (this != mCurrentIterator) { - throw new IllegalStateException("Attempt to close non-current iterator"); - } - mCurrentIterator = null; - } - - public boolean hasNext() { - return (mNextRecord != null); - } - - public SAMRecord next() { - final SAMRecord result = mNextRecord; - advance(); - return result; - } - - public void remove() { - throw new UnsupportedOperationException("Not supported: remove"); - } - - void advance() { - try { - long startCoordinate = mCompressedInputStream.getFilePointer(); - mNextRecord = getNextRecord(); - long stopCoordinate = mCompressedInputStream.getFilePointer(); - - if (mNextRecord != null) { - ++this.samRecordIndex; - // Because some decoding is done lazily, the record needs to remember the validation stringency. - mNextRecord.setReader(mFileReader); - mNextRecord.setValidationStringency(mValidationStringency); - mNextRecord.setCoordinates(new Chunk(startCoordinate,stopCoordinate)); - - if (mValidationStringency != ValidationStringency.SILENT) { - final List validationErrors = mNextRecord.isValid(); - SAMUtils.processValidationErrors(validationErrors, - this.samRecordIndex, BAMFileReader2.this.getValidationStringency()); - } - } - if (eagerDecode && mNextRecord != null) { - mNextRecord.eagerDecode(); - } - } catch (IOException exc) { - throw new RuntimeException(exc.getMessage(), exc); - } - } - - /** - * Read the next record from the input stream. - */ - SAMRecord getNextRecord() throws IOException { - return bamRecordCodec.decode(); - } - - /** - * @return The record that will be return by the next call to next() - */ - protected SAMRecord peek() { - return mNextRecord; - } - } - - enum QueryType {CONTAINED, OVERLAPPING, STARTING_AT} - - /** - * Creates an iterator over indexed data in the specified range. - * @param sequence Sequence to which to constrain the data. - * @param start Starting position within the above sequence to which the data should be constrained. - * @param end Ending position within the above sequence to which the data should be constrained.s - * @param queryType Type of query. Useful for establishing the boundary rules. - * @return An iterator over the requested data. - */ - private CloseableIterator createIndexIterator(final String sequence, - final int start, - final int end, - final QueryType queryType) { - long[] filePointers = null; - - // Hit the index to determine the chunk boundaries for the required data. - final SAMFileHeader fileHeader = getFileHeader(); - int referenceIndex = fileHeader.getSequenceIndex(sequence); - if (referenceIndex != -1) { - final BAMFileIndex2 fileIndex = getFileIndex(); - filePointers = fileIndex.getFilePointersContaining(referenceIndex, start, end); - } - - // Create an iterator over the above chunk boundaries. - BAMFileIndexIterator iterator = new BAMFileIndexIterator(filePointers); - - // Add some preprocessing filters for edge-case reads that don't fit into this - // query type. - return new BAMQueryFilteringIterator(iterator,sequence,start,end,queryType); - } - - private class BAMFileIndexIterator - extends BAMFileIterator { - - private long[] mFilePointers = null; - private int mFilePointerIndex = 0; - private long mFilePointerLimit = -1; - - BAMFileIndexIterator(final long[] filePointers) { - super(false); // delay advance() until after construction - mFilePointers = filePointers; - advance(); - } - - SAMRecord getNextRecord() - throws IOException { - while (true) { - // Advance to next file block if necessary - while (mCompressedInputStream.getFilePointer() >= mFilePointerLimit) { - if (mFilePointers == null || - mFilePointerIndex >= mFilePointers.length) { - return null; - } - final long startOffset = mFilePointers[mFilePointerIndex++]; - final long endOffset = mFilePointers[mFilePointerIndex++]; - mCompressedInputStream.seek(startOffset); - mFilePointerLimit = endOffset; - } - // Pull next record from stream - return super.getNextRecord(); - } - } - } - - /** - * A decorating iterator that filters out records that are outside the bounds of the - * given query parameters. - */ - private class BAMQueryFilteringIterator implements CloseableIterator { - /** - * The wrapped iterator. - */ - private final CloseableIterator wrappedIterator; - - /** - * The next record to be returned. Will be null if no such record exists. - */ - private SAMRecord nextRead; - - private final int mReferenceIndex; - private final int mRegionStart; - private final int mRegionEnd; - private final QueryType mQueryType; - - public BAMQueryFilteringIterator(final CloseableIterator iterator,final String sequence, final int start, final int end, final QueryType queryType) { - this.wrappedIterator = iterator; - final SAMFileHeader fileHeader = getFileHeader(); - mReferenceIndex = fileHeader.getSequenceIndex(sequence); - mRegionStart = start; - if (queryType == QueryType.STARTING_AT) { - mRegionEnd = mRegionStart; - } else { - mRegionEnd = (end <= 0) ? Integer.MAX_VALUE : end; - } - mQueryType = queryType; - nextRead = advance(); - } - - /** - * Returns true if a next element exists; false otherwise. - */ - public boolean hasNext() { - return nextRead != null; - } - - /** - * Gets the next record from the given iterator. - * @return The next SAM record in the iterator. - */ - public SAMRecord next() { - if(!hasNext()) - throw new NoSuchElementException("BAMQueryFilteringIterator: no next element available"); - final SAMRecord currentRead = nextRead; - nextRead = advance(); - return currentRead; - } - - /** - * Closes down the existing iterator. - */ - public void close() { - if (this != mCurrentIterator) { - throw new IllegalStateException("Attempt to close non-current iterator"); - } - mCurrentIterator = null; - } - - /** - * @throws UnsupportedOperationException always. - */ - public void remove() { - throw new UnsupportedOperationException("Not supported: remove"); - } - - SAMRecord advance() { - while (true) { - // Pull next record from stream - if(!wrappedIterator.hasNext()) - return null; - - final SAMRecord record = wrappedIterator.next(); - // If beyond the end of this reference sequence, end iteration - final int referenceIndex = record.getReferenceIndex(); - if (referenceIndex != mReferenceIndex) { - if (referenceIndex < 0 || - referenceIndex > mReferenceIndex) { - return null; - } - // If before this reference sequence, continue - continue; - } - if (mRegionStart == 0 && mRegionEnd == Integer.MAX_VALUE) { - // Quick exit to avoid expensive alignment end calculation - return record; - } - final int alignmentStart = record.getAlignmentStart(); - // If read is unmapped but has a coordinate, return it if the coordinate is within - // the query region, regardless of whether the mapped mate will be returned. - final int alignmentEnd; - if (mQueryType == QueryType.STARTING_AT) { - alignmentEnd = -1; - } else { - alignmentEnd = (record.getAlignmentEnd() != SAMRecord.NO_ALIGNMENT_START? - record.getAlignmentEnd(): alignmentStart); - } - - if (alignmentStart > mRegionEnd) { - // If scanned beyond target region, end iteration - return null; - } - // Filter for overlap with region - if (mQueryType == QueryType.CONTAINED) { - if (alignmentStart >= mRegionStart && alignmentEnd <= mRegionEnd) { - return record; - } - } else if (mQueryType == QueryType.OVERLAPPING) { - if (alignmentEnd >= mRegionStart && alignmentStart <= mRegionEnd) { - return record; - } - } else { - if (alignmentStart == mRegionStart) { - return record; - } - } - } - } - } - - private class BAMFileIndexUnmappedIterator extends BAMFileIterator { - private BAMFileIndexUnmappedIterator() { - while (this.hasNext() && peek().getReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { - advance(); - } - } - } - -} diff --git a/java/src/net/sf/samtools/Bin.java b/java/src/net/sf/samtools/Bin.java deleted file mode 100644 index c3f1a6e50..000000000 --- a/java/src/net/sf/samtools/Bin.java +++ /dev/null @@ -1,69 +0,0 @@ -package net.sf.samtools; - -import java.util.List; - -/** - * An individual bin in a BAM file. - * - * @author mhanna - * @version 0.1 - */ -public class Bin implements Comparable { - /** - * The reference sequence associated with this bin. - */ - public final int referenceSequence; - - /** - * The number of this bin within the BAM file. - */ - public final int binNumber; - - public Bin(int referenceSequence, int binNumber) { - this.referenceSequence = referenceSequence; - this.binNumber = binNumber; - } - - /** - * See whether two bins are equal. If the ref seq and the bin number - * are equal, assume equality of the chunk list. - * @param other The other Bin to which to compare this. - * @return True if the two bins are equal. False otherwise. - */ - @Override - public boolean equals(Object other) { - if(other == null) return false; - if(!(other instanceof Bin)) return false; - - Bin otherBin = (Bin)other; - return this.referenceSequence == otherBin.referenceSequence && this.binNumber == otherBin.binNumber; - } - - /** - * Compute a unique hash code for the given reference sequence and bin number. - * @return A unique hash code. - */ - @Override - public int hashCode() { - return ((Integer)referenceSequence).hashCode() ^ ((Integer)binNumber).hashCode(); - } - - /** - * Compare two bins to see what ordering they should appear in. - * @param other Other bin to which this bin should be compared. - * @return -1 if this < other, 0 if this == other, 1 if this > other. - */ - @Override - public int compareTo(Object other) { - if(other == null) - throw new ClassCastException("Cannot compare to a null object"); - Bin otherBin = (Bin)other; - - // Check the reference sequences first. - if(this.referenceSequence != otherBin.referenceSequence) - return ((Integer)referenceSequence).compareTo(otherBin.referenceSequence); - - // Then check the bin ordering. - return ((Integer)binNumber).compareTo(otherBin.binNumber); - } -} diff --git a/java/src/net/sf/samtools/Chunk.java b/java/src/net/sf/samtools/Chunk.java deleted file mode 100644 index a9750ce28..000000000 --- a/java/src/net/sf/samtools/Chunk.java +++ /dev/null @@ -1,114 +0,0 @@ -package net.sf.samtools; - -import net.sf.picard.PicardException; - -import java.util.List; -import java.util.ArrayList; - -/** - * Represents a chunk stolen from the BAM file. Originally a private static inner class within - * BAMFileIndex; now breaking it out so that the sharding system can use it. - * - * @author mhanna - * @version 0.1 - */ -public class Chunk implements Cloneable,Comparable { - - private long mChunkStart; - private long mChunkEnd; - - public Chunk(final long start, final long end) { - mChunkStart = start; - mChunkEnd = end; - } - - protected Chunk clone() { - return new Chunk(mChunkStart,mChunkEnd); - } - - public long getChunkStart() { - return mChunkStart; - } - - public void setChunkStart(final long value) { - mChunkStart = value; - } - - public long getChunkEnd() { - return mChunkEnd; - } - - public void setChunkEnd(final long value) { - mChunkEnd = value; - } - - /** - * 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. - */ - public static List toChunkList(long[] coordinateArray) { - if(coordinateArray.length % 2 != 0) - throw new PicardException("Data supplied does not appear to be in coordinate array format."); - - // TODO: possibly also check for monotonically increasing; this seems to be an implicit requirement of this format. - List chunkList = new ArrayList(); - for(int i = 0; i < coordinateArray.length; i += 2) - chunkList.add(new Chunk(coordinateArray[i],coordinateArray[i+1])); - - return chunkList; - } - - /** - * 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. - * @param chunks List of chunks to convert. - * @return A long array of the format described above. - */ - public static long[] toCoordinateArray(List chunks) { - long[] coordinateArray = new long[chunks.size()*2]; - int position = 0; - for(Chunk chunk: chunks) { - coordinateArray[position++] = chunk.getChunkStart(); - coordinateArray[position++] = chunk.getChunkEnd(); - } - return coordinateArray; - } - - public int compareTo(final Chunk chunk) { - int result = Long.signum(mChunkStart - chunk.mChunkStart); - if (result == 0) { - result = Long.signum(mChunkEnd - chunk.mChunkEnd); - } - return result; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final Chunk chunk = (Chunk) o; - - if (mChunkEnd != chunk.mChunkEnd) return false; - if (mChunkStart != chunk.mChunkStart) return false; - - return true; - } - - @Override - public int hashCode() { - int result = (int) (mChunkStart ^ (mChunkStart >>> 32)); - result = 31 * result + (int) (mChunkEnd ^ (mChunkEnd >>> 32)); - return result; - } - - @Override - public String toString() { - return String.format("%d:%d-%d:%d",mChunkStart >> 16,mChunkStart & 0xFFFF,mChunkEnd >> 16,mChunkEnd & 0xFFFF); - } -} diff --git a/java/src/net/sf/samtools/LinearIndex.java b/java/src/net/sf/samtools/LinearIndex.java deleted file mode 100644 index 37d454b58..000000000 --- a/java/src/net/sf/samtools/LinearIndex.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.sf.samtools; - -/** - * The linear index associated with a given reference in a BAM index. - * - * @author mhanna - * @version 0.1 - */ -public class LinearIndex { - /** - * The reference sequence number for this linear index. - */ - public final int referenceSequence; - - /** - * The linear index entries within this bin. - */ - public final long[] indexEntries; - - public LinearIndex(final int referenceSequence, final long[] indexEntries) { - this.referenceSequence = referenceSequence; - this.indexEntries = indexEntries; - } -} diff --git a/java/src/net/sf/samtools/SAMFileReader2.java b/java/src/net/sf/samtools/SAMFileReader2.java deleted file mode 100644 index 40fb13c08..000000000 --- a/java/src/net/sf/samtools/SAMFileReader2.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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 net.sf.samtools; - -import net.sf.samtools.util.CloseableIterator; - -import java.io.*; -import java.util.List; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.InvocationTargetException; - -import org.broadinstitute.sting.utils.JVMUtils; -import org.broadinstitute.sting.utils.StingException; - -/** - * Class for reading and querying SAM/BAM files. Delegates to appropriate concrete implementation. - */ -public class SAMFileReader2 extends SAMFileReader { - private final File sourceFile; - - /** - * Prepare to read a SAM or BAM file. If the given file is a BAM, and has a companion BAI index file - */ - public SAMFileReader2(final File file) { - this(file, null, false); - } - - /** - * Read a SAM or BAM file, possibly with an index file if present. - * If the given file is a BAM, and an index is present, indexed query will be allowed. - * - * @param file SAM or BAM. - * @param eagerDecode if true, decode SAM record entirely when reading it. - */ - public SAMFileReader2(final File file, final boolean eagerDecode) { - this(file,null,eagerDecode); - } - - /** - * Read a SAM or BAM file, possibly with an index file. If the given file is a BAM, and an index is present, - * indexed query will be allowed. - * - * @param file SAM or BAM. - * @param indexFile Location of index file, or null in order to use the default index file (if present). - * @param eagerDecode eagerDecode if true, decode SAM record entirely when reading it. - */ - public SAMFileReader2(final File file, File indexFile, final boolean eagerDecode){ - super(file,indexFile,eagerDecode); - this.sourceFile = file; - close(); - - try { - BAMFileReader2 reader = new BAMFileReader2(file,eagerDecode,getDefaultValidationStringency()); - reader.setReader(this); - JVMUtils.setFieldValue(getField("mReader"),this,reader); - - if(indexFile != null || findIndexFileFromParent(file) != null) { - BAMFileIndex2 index = new BAMFileIndex2(indexFile != null ? indexFile : findIndexFileFromParent(file)); - reader.setFileIndex(index); - JVMUtils.setFieldValue(getField("mFileIndex"),this,index); - } - } - catch(IOException ex) { - throw new StingException("Unable to load BAM file: " + file,ex); - } - } - - /** - * Get the number of levels employed by this index. - * @return Number of levels in this index. - */ - public int getNumIndexLevels() { - final BAMFileIndex2 fileIndex = (BAMFileIndex2)JVMUtils.getFieldValue(getField("mFileIndex"),this); - if(fileIndex == null) - throw new SAMException("Unable to determine number of index levels; BAM file index is not present."); - return fileIndex.getNumIndexLevels(); - } - - /** - * Gets the level associated with the given bin number. - * @param bin The bin for which to determine the level. - * @return the level associated with the given bin number. - */ - public int getLevelForBin(final Bin bin) { - final BAMFileIndex2 fileIndex = (BAMFileIndex2)JVMUtils.getFieldValue(getField("mFileIndex"),this); - if(fileIndex == null) - throw new SAMException("Unable to determine number of index levels; BAM file index is not present."); - return fileIndex.getLevelForBinNumber(bin.binNumber); - } - - /** - * Gets the first locus that this bin can index into. - * @param bin The bin to test. - * @return The last position that the given bin can represent. - */ - public int getFirstLocusInBin(final Bin bin) { - final BAMFileIndex2 fileIndex = (BAMFileIndex2)JVMUtils.getFieldValue(getField("mFileIndex"),this); - if(fileIndex == null) - throw new SAMException("Unable to determine number of index levels; BAM file index is not present."); - return fileIndex.getFirstLocusInBin(bin); - } - - /** - * Gets the last locus that this bin can index into. - * @param bin The bin to test. - * @return The last position that the given bin can represent. - */ - public int getLastLocusInBin(final Bin bin) { - final BAMFileIndex2 fileIndex = (BAMFileIndex2)JVMUtils.getFieldValue(getField("mFileIndex"),this); - if(fileIndex == null) - throw new SAMException("Unable to determine number of index levels; BAM file index is not present."); - return fileIndex.getLastLocusInBin(bin); - } - - /** - * Iterate through the given chunks in the file. - * @param chunks List of chunks for which to retrieve data. - * @return An iterator over the given chunks. - */ - public CloseableIterator iterator(List chunks) { - // TODO: Add sanity checks so that we're not doing this against an unsupported BAM file. - BAMFileReader2 reader = (BAMFileReader2)JVMUtils.getFieldValue(getField("mReader"),this); - return reader.getIterator(chunks); - } - - public List getOverlappingBins(final String sequence, final int start, final int end) { - // TODO: Add sanity checks so that we're not doing this against an unsupported BAM file. - BAMFileReader2 reader = (BAMFileReader2)JVMUtils.getFieldValue(getField("mReader"),this); - return reader.getOverlappingBins(sequence,start,end); - } - - public List getFilePointersBounding(final Bin bin) { - // TODO: Add sanity checks so that we're not doing this against an unsupported BAM file. - BAMFileReader2 reader = (BAMFileReader2)JVMUtils.getFieldValue(getField("mReader"),this); - return reader.getFilePointersBounding(bin); - } - - public Chunk getCurrentPosition() { - // TODO: Add sanity checks so that we're not doing this against an unsupported BAM file. - BAMFileReader2 reader = (BAMFileReader2)JVMUtils.getFieldValue(getField("mReader"),this); - return new Chunk(reader.getFilePointer(),Long.MAX_VALUE); - } - - private Field getField(String fieldName) { - try { - return getClass().getSuperclass().getDeclaredField(fieldName); - } - catch(NoSuchFieldException ex) { - throw new StingException("Unable to load field: " + fieldName); - } - } - - private File findIndexFileFromParent(File bamFile) { - try { - Method method = getClass().getSuperclass().getDeclaredMethod("findIndexFile",File.class); - method.setAccessible(true); - return (File)method.invoke(this,bamFile); - } - catch(IllegalAccessException ex) { - throw new StingException("Unable to run method findIndexFile",ex); - } - catch(InvocationTargetException ex) { - throw new StingException("Unable to run method findIndexFile",ex); - } - catch(NoSuchMethodException ex) { - throw new StingException("Unable to run method findIndexFile",ex); - } - } -} diff --git a/java/src/net/sf/samtools/SAMRecord.java b/java/src/net/sf/samtools/SAMRecord.java deleted file mode 100644 index d21a6b7f0..000000000 --- a/java/src/net/sf/samtools/SAMRecord.java +++ /dev/null @@ -1,1558 +0,0 @@ -/* - * 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 net.sf.samtools; - - -import net.sf.samtools.util.StringUtil; - -import java.util.*; - -/** - * Java binding for a SAM file record. c.f. http://samtools.sourceforge.net/SAM1.pdf - * - * The presence of reference name/reference index and alignment start - * do not necessarily mean that a read is aligned. Those values may merely be set to force a SAMRecord - * to appear in a certain place in the sort order. The readUnmappedFlag must be checked to determine whether - * or not a read is mapped. Only if the readUnmappedFlag is false can the reference name/index and alignment start - * be interpretted as indicating an actual alignment position. - * - * Likewise, presence of mate reference name/index and mate alignment start do not necessarily mean that the - * mate is aligned. These may be set for an unaligned mate if the mate has been forced into a particular place - * in the sort order per the above paragraph. Only if the mateUnmappedFlag is false can the mate reference name/index - * and mate alignment start be interpretted as indicating the actual alignment position of the mate. - * - * Note also that there are a number of getters & setters that are linked, i.e. they present different representations - * of the same underlying data. In these cases there is typically a representation that is preferred because it - * ought to be faster than some other representation. The following are the preferred representations: - * - * getReadNameLength() is preferred to getReadName().length() - * get/setReadBases() is preferred to get/setReadString() - * get/setBaseQualities() is preferred to get/setBaseQualityString() - * get/setReferenceIndex() is preferred to get/setReferenceName() - * get/setMateReferenceIndex() is preferred to get/setMateReferenceName() - * getCigarLength() is preferred to getCigar().getNumElements() - * get/setCigar() is preferred to get/setCigarString() - * - * Note that setIndexingBin() need not be called when writing SAMRecords. It will be computed as necessary. It is only - * present as an optimization in the event that the value is already known and need not be computed. - * - * setHeader() need not be called when writing SAMRecords. It may be convenient to call it, however, because - * get/setReferenceIndex() and get/setMateReferenceIndex() must have access to the SAM header, either as an argument - * or previously passed to setHeader(). - * - * setHeader() is called by the SAM reading code, so the get/setReferenceIndex() and get/setMateReferenceIndex() - * methods will have access to the sequence dictionary. - * - * Some of the get() methods return values that are mutable, due to the limitations of Java. A caller should - * never change the value returned by a get() method. If you want to change the value of some attribute of a - * SAMRecord, create a new value object and call the appropriate set() method. - */ -public class SAMRecord implements Cloneable -{ - /** - * Alignment score for a good alignment, but where computing a Phred-score is not feasible. - */ - public static final int UNKNOWN_MAPPING_QUALITY = 255; - - /** - * Alignment score for an unaligned read. - */ - public static final int NO_MAPPING_QUALITY = 0; - - /** - * If a read has this reference name, it is unaligned, but not all unaligned reads have - * this reference name (see above). - */ - public static final String NO_ALIGNMENT_REFERENCE_NAME = "*"; - - /** - * If a read has this reference index, it is unaligned, but not all unaligned reads have - * this reference index (see above). - */ - public static final int NO_ALIGNMENT_REFERENCE_INDEX = -1; - - /** - * Cigar string for an unaligned read. - */ - public static final String NO_ALIGNMENT_CIGAR = "*"; - - /** - * If a read has reference name "*", it will have this value for position. - */ - public static final int NO_ALIGNMENT_START = 0; - - /** - * This should rarely be used, since a read with no sequence doesn't make much sense. - */ - public static final byte[] NULL_SEQUENCE = new byte[0]; - - public static final String NULL_SEQUENCE_STRING = "*"; - - /** - * This should rarely be used, since all reads should have quality scores. - */ - public static final byte[] NULL_QUALS = new byte[0]; - public static final String NULL_QUALS_STRING = "*"; - - /** - * abs(insertSize) must be <= this - */ - public static final int MAX_INSERT_SIZE = 1<<29; - - /** - * It is not necessary in general to use the flag constants, because there are getters - * & setters that handles these symbolically. - */ - private static final int READ_PAIRED_FLAG = 0x1; - private static final int PROPER_PAIR_FLAG = 0x2; - private static final int READ_UNMAPPED_FLAG = 0x4; - private static final int MATE_UNMAPPED_FLAG = 0x8; - private static final int READ_STRAND_FLAG = 0x10; - private static final int MATE_STRAND_FLAG = 0x20; - private static final int FIRST_OF_PAIR_FLAG = 0x40; - private static final int SECOND_OF_PAIR_FLAG = 0x80; - private static final int NOT_PRIMARY_ALIGNMENT_FLAG = 0x100; - private static final int READ_FAILS_VENDOR_QUALITY_CHECK_FLAG = 0x200; - private static final int DUPLICATE_READ_FLAG = 0x400; - - - private String mReadName = null; - private byte[] mReadBases = NULL_SEQUENCE; - private byte[] mBaseQualities = NULL_QUALS; - private String mReferenceName = NO_ALIGNMENT_REFERENCE_NAME; - private int mAlignmentStart = NO_ALIGNMENT_START; - private transient int mAlignmentEnd = NO_ALIGNMENT_START; - private int mMappingQuality = NO_MAPPING_QUALITY; - private String mCigarString = NO_ALIGNMENT_CIGAR; - private Cigar mCigar = null; - private List mAlignmentBlocks = null; - private int mFlags = 0; - private String mMateReferenceName = NO_ALIGNMENT_REFERENCE_NAME; - private int mMateAlignmentStart = 0; - private int mInferredInsertSize = 0; - private List mAttributes = null; - private Integer mReferenceIndex = null; - private Integer mMateReferenceIndex = null; - private Integer mIndexingBin = null; - - /** - * Some attributes (e.g. CIGAR) are not decoded immediately. Use this to decide how to validate when decoded. - */ - private SAMFileReader.ValidationStringency mValidationStringency = SAMFileReader.ValidationStringency.SILENT; - - private SAMFileReader mReader = null; - private SAMFileHeader mHeader = null; - - /** - * Where is this chunk located on the file system? - */ - private Chunk coordinates; - - public SAMRecord(final SAMFileHeader header) { - mHeader = header; - } - - public String getReadName() { - return mReadName; - } - - /** - * This method is preferred over getReadName().length(), because for BAMRecord - * it may be faster. - * @return length not including a null terminator. - */ - public int getReadNameLength() { - return mReadName.length(); - } - - public void setReadName(final String value) { - mReadName = value; - } - - /** - * @return read sequence as a string of ACGTN=. - */ - public String getReadString() { - final byte[] readBases = getReadBases(); - if (readBases.length == 0) { - return NULL_SEQUENCE_STRING; - } - return StringUtil.bytesToString(readBases); - } - - public void setReadString(final String value) { - if (NULL_SEQUENCE_STRING.equals(value)) { - mReadBases = NULL_SEQUENCE; - } else { - final byte[] bases = StringUtil.stringToBytes(value); - SAMUtils.normalizeBases(bases); - setReadBases(bases); - } - } - - - /** - * Do not modify the value returned by this method. If you want to change the bases, create a new - * byte[] and call setReadBases() or call setReadString(). - * @return read sequence as ASCII bytes ACGTN=. - */ - public byte[] getReadBases() { - return mReadBases; - } - - public void setReadBases(final byte[] value) { - mReadBases = value; - } - - /** - * This method is preferred over getReadBases().length, because for BAMRecord it may be faster. - * @return number of bases in the read. - */ - public int getReadLength() { - return getReadBases().length; - } - - /** - * @return Base qualities, encoded as a FASTQ string. - */ - public String getBaseQualityString() { - if (Arrays.equals(NULL_QUALS, getBaseQualities())) { - return NULL_QUALS_STRING; - } - return SAMUtils.phredToFastq(getBaseQualities()); - } - - public void setBaseQualityString(final String value) { - if (NULL_QUALS_STRING.equals(value)) { - setBaseQualities(NULL_QUALS); - } else { - setBaseQualities(SAMUtils.fastqToPhred(value)); - } - } - - /** - * Do not modify the value returned by this method. If you want to change the qualities, create a new - * byte[] and call setBaseQualities() or call setBaseQualityString(). - * @return Base qualities, as binary phred scores (not ASCII). - */ - public byte[] getBaseQualities() { - return mBaseQualities; - } - - public void setBaseQualities(final byte[] value) { - mBaseQualities = value; - } - - /** - * If the original base quality scores have been store in the "OQ" tag will return the numeric - * score as a byte[] - */ - public byte[] getOriginalBaseQualities() { - final String oqString = (String) getAttribute("OQ"); - if (oqString != null && oqString.length() > 0) { - return SAMUtils.fastqToPhred(oqString); - } - else { - return null; - } - } - - /** - * Sets the original base quality scores into the "OQ" tag as a String. Supplied value should be - * as phred-scaled numeric qualities. - */ - public void setOriginalBaseQualities(final byte[] oq) { - setAttribute("OQ", SAMUtils.phredToFastq(oq)); - } - - private static boolean hasReferenceName(final Integer referenceIndex, final String referenceName) { - return (referenceIndex != null && referenceIndex != NO_ALIGNMENT_REFERENCE_INDEX) || - !NO_ALIGNMENT_REFERENCE_NAME.equals(referenceName); - } - - /** - * @return true if this SAMRecord has a reference, either as a String or index (or both). - */ - private boolean hasReferenceName() { - return hasReferenceName(mReferenceIndex, mReferenceName); - } - - /** - * @return true if this SAMRecord has a mate reference, either as a String or index (or both). - */ - private boolean hasMateReferenceName() { - return hasReferenceName(mMateReferenceIndex, mMateReferenceName); - } - - /** - * @return Reference name, or null if record has no reference. - */ - public String getReferenceName() { - return mReferenceName; - } - - public void setReferenceName(final String value) { - mReferenceName = value.intern(); - mReferenceIndex = null; - } - - /** - * @return index of the reference sequence for this read in the sequence dictionary, or -1 - * if read has no reference sequence set, or if a String reference name is not found in the sequence index.. - */ - public Integer getReferenceIndex() { - if (mReferenceIndex == null) { - if (mReferenceName == null) { - mReferenceIndex = NO_ALIGNMENT_REFERENCE_INDEX; - } else if (NO_ALIGNMENT_REFERENCE_NAME.equals(mReferenceName)) { - mReferenceIndex = NO_ALIGNMENT_REFERENCE_INDEX; - } else { - mReferenceIndex = mHeader.getSequenceIndex(mReferenceName); - } - } - return mReferenceIndex; - } - - /** - * @param referenceIndex Must either equal -1 (indicating no reference), or exist in the sequence dictionary - * in the header associated with this record. - */ - public void setReferenceIndex(final int referenceIndex) { - mReferenceIndex = referenceIndex; - if (mReferenceIndex == NO_ALIGNMENT_REFERENCE_INDEX) { - mReferenceName = NO_ALIGNMENT_REFERENCE_NAME; - } else { - mReferenceName = mHeader.getSequence(referenceIndex).getSequenceName(); - } - } - - /** - * @return Mate reference name, or null if one is not assigned. - */ - public String getMateReferenceName() { - return mMateReferenceName; - } - - public void setMateReferenceName(final String mateReferenceName) { - this.mMateReferenceName = mateReferenceName.intern(); - mMateReferenceIndex = null; - } - - /** - * @return index of the reference sequence for this read's mate in the sequence dictionary, or -1 - * if mate has no reference sequence set. - */ - public Integer getMateReferenceIndex() { - if (mMateReferenceIndex == null) { - if (mMateReferenceName == null) { - mMateReferenceIndex = NO_ALIGNMENT_REFERENCE_INDEX; - } else if (NO_ALIGNMENT_REFERENCE_NAME.equals(mMateReferenceName)){ - mMateReferenceIndex = NO_ALIGNMENT_REFERENCE_INDEX; - } else { - mMateReferenceIndex = mHeader.getSequenceIndex(mMateReferenceName); - } - } - return mMateReferenceIndex; - } - - /** - * @param referenceIndex Must either equal -1 (indicating no reference), or exist in the sequence dictionary - * in the header associated with this record. - */ - public void setMateReferenceIndex(final int referenceIndex) { - mMateReferenceIndex = referenceIndex; - if (mMateReferenceIndex == NO_ALIGNMENT_REFERENCE_INDEX) { - mMateReferenceName = NO_ALIGNMENT_REFERENCE_NAME; - } else { - mMateReferenceName = mHeader.getSequence(referenceIndex).getSequenceName(); - } - } - - /** - * @return 1-based inclusive leftmost position of the clippped sequence, or 0 if there is no position. - */ - public int getAlignmentStart() { - return mAlignmentStart; - } - - /** - * @param value 1-based inclusive leftmost position of the clippped sequence, or 0 if there is no position. - */ - public void setAlignmentStart(final int value) { - mAlignmentStart = value; - // Clear cached alignment end - mAlignmentEnd = NO_ALIGNMENT_START; - // Change to alignmentStart could change indexing bin - setIndexingBin(null); - } - - /** - * @return 1-based inclusive rightmost position of the clippped sequence, or 0 read if unmapped. - */ - public int getAlignmentEnd() { - if (getReadUnmappedFlag()) { - return NO_ALIGNMENT_START; - } - else if (this.mAlignmentEnd == NO_ALIGNMENT_START) { - this.mAlignmentEnd = mAlignmentStart + getCigar().getReferenceLength() - 1; - } - - return this.mAlignmentEnd; - } - - /** - * @return the alignment start (1-based, inclusive) adjusted for clipped bases. For example if the read - * has an alignment start of 100 but the first 4 bases were clipped (hard or soft clipped) - * then this method will return 96. - * - * Invalid to call on an unmapped read. - */ - public int getUnclippedStart() { - int pos = getAlignmentStart(); - - for (final CigarElement cig : getCigar().getCigarElements()) { - final CigarOperator op = cig.getOperator(); - if (op == CigarOperator.SOFT_CLIP || op == CigarOperator.HARD_CLIP) { - pos -= cig.getLength(); - } - else { - break; - } - } - - return pos; - } - - /** - * @return the alignment end (1-based, inclusive) adjusted for clipped bases. For example if the read - * has an alignment end of 100 but the last 7 bases were clipped (hard or soft clipped) - * then this method will return 107. - * - * Invalid to call on an unmapped read. - */ - public int getUnclippedEnd() { - int pos = getAlignmentEnd(); - final List cigs = getCigar().getCigarElements(); - for (int i=cigs.size() - 1; i>=0; --i) { - final CigarElement cig = cigs.get(i); - final CigarOperator op = cig.getOperator(); - - if (op == CigarOperator.SOFT_CLIP || op == CigarOperator.HARD_CLIP) { - pos += cig.getLength(); - } - else { - break; - } - } - - return pos; - } - - /** - * Unsupported. This property is derived from alignment start and CIGAR. - */ - public void setAlignmentEnd(final int value) { - throw new UnsupportedOperationException("Not supported: setAlignmentEnd"); - } - - /** - * @return 1-based inclusive leftmost position of the clippped mate sequence, or 0 if there is no position. - */ - public int getMateAlignmentStart() { - return mMateAlignmentStart; - } - - public void setMateAlignmentStart(final int mateAlignmentStart) { - this.mMateAlignmentStart = mateAlignmentStart; - } - - /** - * @return insert size (difference btw 5' end of read & 5' end of mate), if possible, else 0. - * Negative if mate maps to lower position than read. - */ - public int getInferredInsertSize() { - return mInferredInsertSize; - } - - public void setInferredInsertSize(final int inferredInsertSize) { - this.mInferredInsertSize = inferredInsertSize; - } - - /** - * @return phred scaled mapping quality. 255 implies valid mapping but quality is hard to compute. - */ - public int getMappingQuality() { - return mMappingQuality; - } - - public void setMappingQuality(final int value) { - mMappingQuality = value; - } - - public String getCigarString() { - if (mCigarString == null && getCigar() != null) { - mCigarString = TextCigarCodec.getSingleton().encode(getCigar()); - } - return mCigarString; - } - - public void setCigarString(final String value) { - mCigarString = value; - mCigar = null; - mAlignmentBlocks = null; - // Clear cached alignment end - mAlignmentEnd = NO_ALIGNMENT_START; - // Change to cigar could change alignmentEnd, and thus indexing bin - setIndexingBin(null); - } - - /** - * Do not modify the value returned by this method. If you want to change the Cigar, create a new - * Cigar and call setCigar() or call setCigarString() - * @return Cigar object for the read, or null if there is none. - */ - public Cigar getCigar() { - if (mCigar == null && mCigarString != null) { - mCigar = TextCigarCodec.getSingleton().decode(mCigarString); - if (getValidationStringency() != SAMFileReader.ValidationStringency.SILENT && !this.getReadUnmappedFlag()) { - // Don't know line number, and don't want to force read name to be decoded. - SAMUtils.processValidationErrors(validateCigar(-1L), -1L, getValidationStringency()); - } - } - return mCigar; - } - - /** - * This method is preferred over getCigar().getNumElements(), because for BAMRecord it may be faster. - * @return number of cigar elements (number + operator) in the cigar string. - */ - public int getCigarLength() { - return getCigar().numCigarElements(); - } - - public void setCigar(final Cigar cigar) { - initializeCigar(cigar); - // Change to cigar could change alignmentEnd, and thus indexing bin - setIndexingBin(null); - } - - /** - * For setting the Cigar string when BAMRecord has decoded it. Use this rather than setCigar() - * so that indexing bin doesn't get clobbered. - */ - protected void initializeCigar(final Cigar cigar) { - this.mCigar = cigar; - mCigarString = null; - mAlignmentBlocks = null; - // Clear cached alignment end - mAlignmentEnd = NO_ALIGNMENT_START; - } - - /** - * Get the SAMReadGroupRecord for this SAMRecord. - * @return The SAMReadGroupRecord from the SAMFileHeader for this SAMRecord, or null if - * 1) this record has no RG tag, or 2) the header doesn't contain the read group with - * the given ID. - * @throws NullPointerException if this.getHeader() returns null. - * @throws ClassCastException if RG tag does not have a String value. - */ - public SAMReadGroupRecord getReadGroup() { - final String rgId = (String)getAttribute(SAMTagUtil.getSingleton().RG); - if (rgId == null) { - return null; - } - return getHeader().getReadGroup(rgId); - } - - /** - * It is preferrable to use the get*Flag() methods that handle the flag word symbolically. - */ - public int getFlags() { - return mFlags; - } - - public void setFlags(final int value) { - mFlags = value; - // Could imply change to readUnmapped flag, which could change indexing bin - setIndexingBin(null); - } - - /** - * the read is paired in sequencing, no matter whether it is mapped in a pair. - */ - public boolean getReadPairedFlag() { - return (mFlags & READ_PAIRED_FLAG) != 0; - } - - private void requireReadPaired() { - if (!getReadPairedFlag()) { - throw new IllegalStateException("Inappropriate call if not paired read"); - } - } - - /** - * the read is mapped in a proper pair (depends on the protocol, normally inferred during alignment). - */ - public boolean getProperPairFlag() { - requireReadPaired(); - return getProperPairFlagUnchecked(); - } - - private boolean getProperPairFlagUnchecked() { - return (mFlags & PROPER_PAIR_FLAG) != 0; - } - - /** - * the query sequence itself is unmapped. - */ - public boolean getReadUnmappedFlag() { - return (mFlags & READ_UNMAPPED_FLAG) != 0; - } - - /** - * the mate is unmapped. - */ - public boolean getMateUnmappedFlag() { - requireReadPaired(); - return getMateUnmappedFlagUnchecked(); - } - - private boolean getMateUnmappedFlagUnchecked() { - return (mFlags & MATE_UNMAPPED_FLAG) != 0; - } - - /** - * strand of the query (false for forward; true for reverse strand). - */ - public boolean getReadNegativeStrandFlag() { - return (mFlags & READ_STRAND_FLAG) != 0; - } - - /** - * strand of the mate (false for forward; true for reverse strand). - */ - public boolean getMateNegativeStrandFlag() { - requireReadPaired(); - return getMateNegativeStrandFlagUnchecked(); - } - - private boolean getMateNegativeStrandFlagUnchecked() { - return (mFlags & MATE_STRAND_FLAG) != 0; - } - - /** - * the read is the first read in a pair. - */ - public boolean getFirstOfPairFlag() { - requireReadPaired(); - return getFirstOfPairFlagUnchecked(); - } - - private boolean getFirstOfPairFlagUnchecked() { - return (mFlags & FIRST_OF_PAIR_FLAG) != 0; - } - - /** - * the read is the second read in a pair. - */ - public boolean getSecondOfPairFlag() { - requireReadPaired(); - return getSecondOfPairFlagUnchecked(); - } - - private boolean getSecondOfPairFlagUnchecked() { - return (mFlags & SECOND_OF_PAIR_FLAG) != 0; - } - - /** - * the alignment is not primary (a read having split hits may have multiple primary alignment records). - */ - public boolean getNotPrimaryAlignmentFlag() { - return (mFlags & NOT_PRIMARY_ALIGNMENT_FLAG) != 0; - } - - /** - * the read fails platform/vendor quality checks. - */ - public boolean getReadFailsVendorQualityCheckFlag() { - return (mFlags & READ_FAILS_VENDOR_QUALITY_CHECK_FLAG) != 0; - } - - /** - * the read is either a PCR duplicate or an optical duplicate. - */ - public boolean getDuplicateReadFlag() { - return (mFlags & DUPLICATE_READ_FLAG) != 0; - } - - /** - * the read is paired in sequencing, no matter whether it is mapped in a pair. - */ - public void setReadPairedFlag(final boolean flag) { - setFlag(flag, READ_PAIRED_FLAG); - } - - /** - * the read is mapped in a proper pair (depends on the protocol, normally inferred during alignment). - */ - public void setProperPairFlag(final boolean flag) { - setFlag(flag, PROPER_PAIR_FLAG); - } - - /** - * the query sequence itself is unmapped. - */ - public void setReadUmappedFlag(final boolean flag) { - setFlag(flag, READ_UNMAPPED_FLAG); - // Change to readUnmapped could change indexing bin - setIndexingBin(null); - } - - /** - * the mate is unmapped. - */ - public void setMateUnmappedFlag(final boolean flag) { - setFlag(flag, MATE_UNMAPPED_FLAG); - } - - /** - * strand of the query (false for forward; true for reverse strand). - */ - public void setReadNegativeStrandFlag(final boolean flag) { - setFlag(flag, READ_STRAND_FLAG); - } - - /** - * strand of the mate (false for forward; true for reverse strand). - */ - public void setMateNegativeStrandFlag(final boolean flag) { - setFlag(flag, MATE_STRAND_FLAG); - } - - /** - * the read is the first read in a pair. - */ - public void setFirstOfPairFlag(final boolean flag) { - setFlag(flag, FIRST_OF_PAIR_FLAG); - } - - /** - * the read is the second read in a pair. - */ - public void setSecondOfPairFlag(final boolean flag) { - setFlag(flag, SECOND_OF_PAIR_FLAG); - } - - /** - * the alignment is not primary (a read having split hits may have multiple primary alignment records). - */ - public void setNotPrimaryAlignmentFlag(final boolean flag) { - setFlag(flag, NOT_PRIMARY_ALIGNMENT_FLAG); - } - - /** - * the read fails platform/vendor quality checks. - */ - public void setReadFailsVendorQualityCheckFlag(final boolean flag) { - setFlag(flag, READ_FAILS_VENDOR_QUALITY_CHECK_FLAG); - } - - /** - * the read is either a PCR duplicate or an optical duplicate. - */ - public void setDuplicateReadFlag(final boolean flag) { - setFlag(flag, DUPLICATE_READ_FLAG); - } - - private void setFlag(final boolean flag, final int bit) { - if (flag) { - mFlags |= bit; - } else { - mFlags &= ~bit; - } - } - - public SAMFileReader.ValidationStringency getValidationStringency() { - return mValidationStringency; - } - - /** - * Control validation of lazily-decoded elements. - */ - public void setValidationStringency(final SAMFileReader.ValidationStringency validationStringency) { - this.mValidationStringency = validationStringency; - } - - /** - * Get the value for a SAM tag. - * WARNING: Some value types (e.g. byte[]) are mutable. It is dangerous to change one of these values in - * place, because some SAMRecord implementations keep track of when attributes have been changed. If you - * want to change an attribute value, call setAttribute() to replace the value. - * - * @param tag Two-character tag name. - * @return Appropriately typed tag value, or null if the requested tag is not present. - */ - public final Object getAttribute(final String tag) { - return getAttribute(SAMTagUtil.getSingleton().makeBinaryTag(tag)); - } - - /** - * Get the tag value and attempt to coerce it into the requested type. - * @param tag The requested tag. - * @return The value of a tag, converted into an Integer if possible. - * @throws RuntimeException If the value is not an integer type, or will not fit in an Integer. - */ - public final Integer getIntegerAttribute(final String tag) { - final Object val = getAttribute(tag); - if (val == null) return null; - if (val instanceof Integer) { - return (Integer)val; - } - if (!(val instanceof Number)) { - throw new RuntimeException("Value for tag " + tag + " is not Number: " + val.getClass()); - } - final long longVal = ((Number)val).longValue(); - if (longVal < Integer.MIN_VALUE || longVal > Integer.MAX_VALUE) { - throw new RuntimeException("Value for tag " + tag + " is not in Integer range: " + longVal); - } - return (int)longVal; - } - - /** - * Get the tag value and attempt to coerce it into the requested type. - * @param tag The requested tag. - * @return The value of a tag, converted into a Short if possible. - * @throws RuntimeException If the value is not an integer type, or will not fit in a Short. - */ - public final Short getShortAttribute(final String tag) { - final Object val = getAttribute(tag); - if (val == null) return null; - if (val instanceof Short) { - return (Short)val; - } - if (!(val instanceof Number)) { - throw new RuntimeException("Value for tag " + tag + " is not Number: " + val.getClass()); - } - final long longVal = ((Number)val).longValue(); - if (longVal < Short.MIN_VALUE || longVal > Short.MAX_VALUE) { - throw new RuntimeException("Value for tag " + tag + " is not in Short range: " + longVal); - } - return (short)longVal; - } - - /** - * Get the tag value and attempt to coerce it into the requested type. - * @param tag The requested tag. - * @return The value of a tag, converted into a Byte if possible. - * @throws RuntimeException If the value is not an integer type, or will not fit in a Byte. - */ - public final Byte getByteAttribute(final String tag) { - final Object val = getAttribute(tag); - if (val == null) return null; - if (val instanceof Byte) { - return (Byte)val; - } - if (!(val instanceof Number)) { - throw new RuntimeException("Value for tag " + tag + " is not Number: " + val.getClass()); - } - final long longVal = ((Number)val).longValue(); - if (longVal < Byte.MIN_VALUE || longVal > Byte.MAX_VALUE) { - throw new RuntimeException("Value for tag " + tag + " is not in Short range: " + longVal); - } - return (byte)longVal; - } - - public final String getStringAttribute(final String tag) { - final Object val = getAttribute(tag); - if (val == null) return null; - if (val instanceof String) { - return (String)val; - } - throw new SAMException("Value for tag " + tag + " is not a String: " + val.getClass()); - } - - public final Character getCharacterAttribute(final String tag) { - final Object val = getAttribute(tag); - if (val == null) return null; - if (val instanceof Character) { - return (Character)val; - } - throw new SAMException("Value for tag " + tag + " is not a Character: " + val.getClass()); - } - - public final Float getFloatAttribute(final String tag) { - final Object val = getAttribute(tag); - if (val == null) return null; - if (val instanceof Float) { - return (Float)val; - } - throw new SAMException("Value for tag " + tag + " is not a Float: " + val.getClass()); - } - - public final byte[] getByteArrayAttribute(final String tag) { - final Object val = getAttribute(tag); - if (val == null) return null; - if (val instanceof byte[]) { - return (byte[])val; - } - throw new SAMException("Value for tag " + tag + " is not a byte[]: " + val.getClass()); - } - - protected Object getAttribute(final short tag) { - if (mAttributes == null) { - return null; - } - for (final SAMBinaryTagAndValue tagAndValue : mAttributes) { - if (tagAndValue.tag == tag) { - return tagAndValue.value; - } - } - return null; - } - - /** - * Set a named attribute onto the SAMRecord. Passing a null value causes the attribute to be cleared. - * @param tag two-character tag name. See http://samtools.sourceforge.net/SAM1.pdf for standard and user-defined tags. - * @param value Supported types are String, Char, Integer, Float, byte[]. - * If value == null, tag is cleared. - * - * Byte and Short are allowed but discouraged. If written to a SAM file, these will be converted to Integer, - * whereas if written to BAM, getAttribute() will return as Byte or Short, respectively. - * - * Long with value between 0 and MAX_UINT is allowed for BAM but discouraged. Attempting to write such a value - * to SAM will cause an exception to be thrown. - */ - final public void setAttribute(final String tag, final Object value) { - setAttribute(SAMTagUtil.getSingleton().makeBinaryTag(tag), value); - } - - protected void setAttribute(final short tag, final Object value) { - if (value != null && - !(value instanceof Byte || value instanceof Short || value instanceof Integer || - value instanceof String || value instanceof Character || value instanceof Float || - value instanceof byte[])) { - throw new SAMException("Attribute type " + value.getClass() + " not supported. Tag: " + - SAMTagUtil.getSingleton().makeStringTag(tag)); - } - if (mAttributes == null) { - mAttributes = new ArrayList(); - } - int i; - for (i = 0; i < mAttributes.size(); ++i) { - if (mAttributes.get(i).tag == tag) { - break; - } - } - if (i < mAttributes.size()) { - if (value != null) { - mAttributes.set(i, new SAMBinaryTagAndValue(tag, value)); - } else { - mAttributes.remove(i); - } - } else if (value != null) { - mAttributes.add(new SAMBinaryTagAndValue(tag, value)); - } - } - - /** - * Removes all attributes. - */ - public void clearAttributes() { - mAttributes.clear(); - } - - /** - * Replace any existing attributes with the given list. Does not copy the list - * but installs it directly. - */ - protected void setAttributes(final List attributes) { - mAttributes = attributes; - } - /** - * @return List of all tags on this record. Returns null if there are no tags. - */ - protected List getBinaryAttributes() { - if (mAttributes == null || mAttributes.isEmpty()) { - return Collections.emptyList(); - } - return Collections.unmodifiableList(mAttributes); - } - - /** - * Tag name and value of an attribute, for getAttributes() method. - */ - public static class SAMTagAndValue { - public final String tag; - public final Object value; - - public SAMTagAndValue(final String tag, final Object value) { - this.tag = tag; - this.value = value; - } - } - - /** - * @return list of {tag, value} tuples - */ - public final List getAttributes() { - final List binaryAttributes = getBinaryAttributes(); - final List ret = new ArrayList(binaryAttributes.size()); - for (final SAMBinaryTagAndValue tagAndValue : binaryAttributes) { - ret.add(new SAMTagAndValue(SAMTagUtil.getSingleton().makeStringTag(tagAndValue.tag), - tagAndValue.value)); - } - return ret; - } - - Integer getIndexingBin() { - return mIndexingBin; - } - - /** - * Used internally when writing BAMRecords. - * @param mIndexingBin c.f. http://samtools.sourceforge.net/SAM1.pdf - */ - void setIndexingBin(final Integer mIndexingBin) { - this.mIndexingBin = mIndexingBin; - } - - /** - * Does not change state of this. - * @return indexing bin based on alignment start & end. - */ - int computeIndexingBin() { - // reg2bin has zero-based, half-open API - final int alignmentStart = getAlignmentStart()-1; - int alignmentEnd = getAlignmentEnd(); - if (alignmentEnd <= 0) { - // If alignment end cannot be determined (e.g. because this read is not really aligned), - // then treat this as a one base alignment for indexing purposes. - alignmentEnd = alignmentStart + 1; - } - return SAMUtils.reg2bin(alignmentStart, alignmentEnd); - } - - public SAMFileHeader getHeader() { - return mHeader; - } - - /** - * Setting header into SAMRecord facilitates conversion btw reference sequence names and indices - * @param header contains sequence dictionary for this SAMRecord - */ - public void setHeader(final SAMFileHeader header) { - this.mHeader = header; - } - - /** - * If this record has a valid binary representation of the variable-length portion of a binary record stored, - * return that byte array, otherwise return null. This will never be true for SAMRecords. It will be true - * for BAMRecords that have not been eagerDecoded(), and for which none of the data in the variable-length - * portion has been changed. - */ - public byte[] getVariableBinaryRepresentation() { - return null; - } - - /** - * Depending on the concrete implementation, the binary file size of attributes may be known without - * computing them all. - * @return binary file size of attribute, if known, else -1 - */ - public int getAttributesBinarySize() { - return -1; - } - - public String format() { - final StringBuilder buffer = new StringBuilder(); - addField(buffer, getReadName(), null, null); - addField(buffer, getFlags(), null, null); - addField(buffer, getReferenceName(), null, "*"); - addField(buffer, getAlignmentStart(), 0, "*"); - addField(buffer, getMappingQuality(), 0, "0"); - addField(buffer, getCigarString(), null, "*"); - addField(buffer, getMateReferenceName(), null, "*"); - addField(buffer, getMateAlignmentStart(), 0, "*"); - addField(buffer, getInferredInsertSize(), 0, "*"); - addField(buffer, getReadString(), null, "*"); - addField(buffer, getBaseQualityString(), null, "*"); - if (mAttributes != null) { - for (final SAMBinaryTagAndValue entry : getBinaryAttributes()) { - addField(buffer, formatTagValue(entry.tag, entry.value)); - } - } - return buffer.toString(); - } - - private void addField(final StringBuilder buffer, final Object value, final Object defaultValue, final String defaultString) { - if (safeEquals(value, defaultValue)) { - addField(buffer, defaultString); - } else if (value == null) { - addField(buffer, ""); - } else { - addField(buffer, value.toString()); - } - } - - private void addField(final StringBuilder buffer, final String field) { - if (buffer.length() > 0) { - buffer.append('\t'); - } - buffer.append(field); - } - - private String formatTagValue(final short tag, final Object value) { - final String tagString = SAMTagUtil.getSingleton().makeStringTag(tag); - if (value == null || value instanceof String) { - return tagString + ":Z:" + value; - } else if (value instanceof Integer || value instanceof Long || - value instanceof Short || value instanceof Byte) { - return tagString + ":i:" + value; - } else if (value instanceof Character) { - return tagString + ":A:" + value; - } else if (value instanceof Float) { - return tagString + ":f:" + value; - } else if (value instanceof byte[]) { - return tagString + ":H:" + StringUtil.bytesToHexString((byte[]) value); - } else { - throw new RuntimeException("Unexpected value type for tag " + tagString + - ": " + value + " of class " + value.getClass().getName()); - } - } - - private boolean safeEquals(final Object o1, final Object o2) { - if (o1 == o2) { - return true; - } else if (o1 == null || o2 == null) { - return false; - } else { - return o1.equals(o2); - } - } - - /** - * Force all lazily-initialized data members to be initialized. If a subclass overrides this method, - * typically it should also call super method. - */ - protected void eagerDecode() { - getCigar(); - getCigarString(); - } - - /** - * Returns blocks of the read sequence that have been aligned directly to the - * reference sequence. Note that clipped portions of the read and inserted and - * deleted bases (vs. the reference) are not represented in the alignment blocks. - */ - public List getAlignmentBlocks() { - if (this.mAlignmentBlocks != null) return this.mAlignmentBlocks; - - final Cigar cigar = getCigar(); - if (cigar == null) return Collections.emptyList(); - - - final List alignmentBlocks = new ArrayList(); - int readBase = 1; - int refBase = getAlignmentStart(); - - for (final CigarElement e : cigar.getCigarElements()) { - switch (e.getOperator()) { - case H : break; // ignore hard clips - case P : break; // ignore pads - case S : readBase += e.getLength(); break; // soft clip read bases - case N : refBase += e.getLength(); break; // reference skip - case D : refBase += e.getLength(); break; - case I : readBase += e.getLength(); break; - case M : - case EQ : - case X : - final int length = e.getLength(); - alignmentBlocks.add(new AlignmentBlock(readBase, refBase, length)); - readBase += length; - refBase += length; - break; - default : throw new IllegalStateException("Case statement didn't deal with cigar op: " + e.getOperator()); - } - } - this.mAlignmentBlocks = Collections.unmodifiableList(alignmentBlocks); - - return this.mAlignmentBlocks; - } - - /** - * Run all validations of CIGAR. These include validation that the CIGAR makes sense independent of - * placement, plus validation that CIGAR + placement yields all bases with M operator within the range of the reference. - * @param recordNumber For error reporting. -1 if not known. - * @return List of errors, or null if no errors. - */ - public List validateCigar(final long recordNumber) { - List ret = null; - - if (getValidationStringency() != SAMFileReader.ValidationStringency.SILENT && !this.getReadUnmappedFlag()) { - // Don't know line number, and don't want to force read name to be decoded. - ret = getCigar().isValid(getReadName(), recordNumber); - if (getReferenceIndex() != NO_ALIGNMENT_REFERENCE_INDEX) { - final SAMSequenceRecord sequence = getHeader().getSequence(getReferenceIndex()); - final int referenceSequenceLength = sequence.getSequenceLength(); - for (final AlignmentBlock alignmentBlock : getAlignmentBlocks()) { - if (alignmentBlock.getReferenceStart() + alignmentBlock.getLength() - 1 > referenceSequenceLength) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.CIGAR_MAPS_OFF_REFERENCE, - "CIGAR M operator maps off end of reference", getReadName(), recordNumber)); - break; - } - } - } - } - return ret; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (!(o instanceof SAMRecord)) return false; - - final SAMRecord samRecord = (SAMRecord) o; - - // First check all the elements that do not require decoding - if (mAlignmentStart != samRecord.mAlignmentStart) return false; - if (mFlags != samRecord.mFlags) return false; - if (mInferredInsertSize != samRecord.mInferredInsertSize) return false; - if (mMappingQuality != samRecord.mMappingQuality) return false; - if (mMateAlignmentStart != samRecord.mMateAlignmentStart) return false; - if (mIndexingBin != null ? !mIndexingBin.equals(samRecord.mIndexingBin) : samRecord.mIndexingBin != null) - return false; - if (mMateReferenceIndex != null ? !mMateReferenceIndex.equals(samRecord.mMateReferenceIndex) : samRecord.mMateReferenceIndex != null) - return false; - if (mReferenceIndex != null ? !mReferenceIndex.equals(samRecord.mReferenceIndex) : samRecord.mReferenceIndex != null) - return false; - - eagerDecode(); - samRecord.eagerDecode(); - - if (mAttributes != null ? !mAttributes.equals(samRecord.mAttributes) : samRecord.mAttributes != null) - return false; - if (!Arrays.equals(mBaseQualities, samRecord.mBaseQualities)) return false; - if (mCigar != null ? !mCigar.equals(samRecord.mCigar) : samRecord.mCigar != null) - return false; - if (mMateReferenceName != null ? !mMateReferenceName.equals(samRecord.mMateReferenceName) : samRecord.mMateReferenceName != null) - return false; - if (!Arrays.equals(mReadBases, samRecord.mReadBases)) return false; - if (mReadName != null ? !mReadName.equals(samRecord.mReadName) : samRecord.mReadName != null) return false; - if (mReferenceName != null ? !mReferenceName.equals(samRecord.mReferenceName) : samRecord.mReferenceName != null) - return false; - - return true; - } - - @Override - public int hashCode() { - eagerDecode(); - int result = mReadName != null ? mReadName.hashCode() : 0; - result = 31 * result + (mReadBases != null ? Arrays.hashCode(mReadBases) : 0); - result = 31 * result + (mBaseQualities != null ? Arrays.hashCode(mBaseQualities) : 0); - result = 31 * result + (mReferenceName != null ? mReferenceName.hashCode() : 0); - result = 31 * result + mAlignmentStart; - result = 31 * result + mMappingQuality; - result = 31 * result + (mCigarString != null ? mCigarString.hashCode() : 0); - result = 31 * result + mFlags; - result = 31 * result + (mMateReferenceName != null ? mMateReferenceName.hashCode() : 0); - result = 31 * result + mMateAlignmentStart; - result = 31 * result + mInferredInsertSize; - result = 31 * result + (mAttributes != null ? mAttributes.hashCode() : 0); - result = 31 * result + (mReferenceIndex != null ? mReferenceIndex.hashCode() : 0); - result = 31 * result + (mMateReferenceIndex != null ? mMateReferenceIndex.hashCode() : 0); - result = 31 * result + (mIndexingBin != null ? mIndexingBin.hashCode() : 0); - return result; - } - - /** - * Perform various validations of SAMRecord. - * Note that this method deliberately returns null rather than Collections.emptyList() if there - * are no validation errors, because callers tend to assume that if a non-null list is returned, it is modifiable. - * @return null if valid. If invalid, returns a list of error messages. - */ - public List isValid() { - // ret is only instantiate if there are errors to report, in order to reduce GC in the typical case - // in which everything is valid. It's ugly, but more efficient. - ArrayList ret = null; - if (!getReadPairedFlag()) { - if (getProperPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_PROPER_PAIR, "Proper pair flag should not be set for unpaired read.", getReadName())); - } - if (getMateUnmappedFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_MATE_UNMAPPED, "Mate unmapped flag should not be set for unpaired read.", getReadName())); - } - if (getMateNegativeStrandFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_MATE_NEG_STRAND, "Mate negative strand flag should not be set for unpaired read.", getReadName())); - } - if (getFirstOfPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_FIRST_OF_PAIR, "First of pair flag should not be set for unpaired read.", getReadName())); - } - if (getSecondOfPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_SECOND_OF_PAIR, "First of pair flag should not be set for unpaired read.", getReadName())); - } - if (getMateReferenceIndex() != NO_ALIGNMENT_REFERENCE_INDEX) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_MATE_REF_INDEX, "MRNM should not be set for unpaired read.", getReadName())); - } - } else { - final List errors = isValidReferenceIndexAndPosition(mMateReferenceIndex, mMateReferenceName, - getMateAlignmentStart(), true); - if (errors != null) { - if (ret == null) ret = new ArrayList(); - ret.addAll(errors); - } - if (!hasMateReferenceName() && !getMateUnmappedFlag()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_MATE_UNMAPPED, "Mapped mate should have mate reference name", getReadName())); - } -/* - TODO: PIC-97 This validation should be enabled, but probably at this point there are too many - BAM files that have the proper pair flag set when read or mate is unmapped. - if (getMateUnmappedFlag() && getProperPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_PROPER_PAIR, "Proper pair flag should not be set for unpaired read.", getReadName())); - } -*/ - } - if (getInferredInsertSize() > MAX_INSERT_SIZE || getInferredInsertSize() < -MAX_INSERT_SIZE) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_INSERT_SIZE, "Insert size out of range", getReadName())); - } - if (getReadUnmappedFlag()) { - if (getNotPrimaryAlignmentFlag()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_NOT_PRIM_ALIGNMENT, "Not primary alignment flag should not be set for unmapped read.", getReadName())); - } - if (getMappingQuality() != 0) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_MAPPING_QUALITY, "MAPQ must should be 0 for unmapped read.", getReadName())); - } - if (getCigarLength() != 0) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_CIGAR, "CIGAR should have zero elements for unmapped read.", getReadName())); - } -/* - TODO: PIC-97 This validation should be enabled, but probably at this point there are too many - BAM files that have the proper pair flag set when read or mate is unmapped. - if (getProperPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_PROPER_PAIR, "Proper pair flag should not be set for unmapped read.", getReadName())); - } -*/ - } else { - if (getMappingQuality() >= 256) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_MAPPING_QUALITY, "MAPQ should be < 256.", getReadName())); - } - if (getCigarLength() == 0) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_CIGAR, "CIGAR should have > zero elements for mapped read.", getReadName())); - } - if (getHeader().getSequenceDictionary().size() == 0) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.MISSING_SEQUENCE_DICTIONARY, "Empty sequence dictionary.", getReadName())); - } - if (!hasReferenceName()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_READ_UNMAPPED, "Mapped read should have valid reference name", getReadName())); - } -/* - Oops! We know this is broken in older BAM files, so this having this validation will cause all sorts of - problems! - if (getIndexingBin() != null && getIndexingBin() != computeIndexingBin()) { - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_INDEXING_BIN, - "Indexing bin (" + getIndexingBin() + ") does not agree with computed value (" + computeIndexingBin() + ")", - getReadName())); - - } -*/ - } - // Validate the RG ID is found in header - final String rgId = (String)getAttribute(SAMTagUtil.getSingleton().RG); - if (rgId != null && getHeader().getReadGroup(rgId) == null) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.READ_GROUP_NOT_FOUND, - "RG ID on SAMRecord not found in header: " + rgId, getReadName())); - } - final List errors = isValidReferenceIndexAndPosition(mReferenceIndex, mReferenceName, getAlignmentStart(), false); - if (errors != null) { - if (ret == null) ret = new ArrayList(); - ret.addAll(errors); - } - if (this.getReadLength() == 0) { - String cq = (String)getAttribute(SAMTagUtil.getSingleton().CQ); - String cs = (String)getAttribute(SAMTagUtil.getSingleton().CS); - if (cq == null || cq.length() == 0 || cs == null || cs.length() == 0) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.EMPTY_READ, - "Zero-length read without CS or CQ tag", getReadName())); - } else if (!getReadUnmappedFlag()) { - boolean hasIndel = false; - for (CigarElement cigarElement : getCigar().getCigarElements()) { - if (cigarElement.getOperator() == CigarOperator.DELETION || - cigarElement.getOperator() == CigarOperator.INSERTION) { - hasIndel = true; - break; - } - } - if (!hasIndel) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.EMPTY_READ, - "Colorspace read with zero-length bases but no indel", getReadName())); - } - } - } - if (this.getReadLength() != getBaseQualities().length && !Arrays.equals(getBaseQualities(), NULL_QUALS)) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.MISMATCH_READ_LENGTH_AND_QUALS_LENGTH, - "Read length does not match quals length", getReadName())); - } - if (ret == null || ret.size() == 0) { - return null; - } - return ret; - } - - /** - * Gets the reader that read this SAM file. - * @return - */ - public SAMFileReader getReader() { - return mReader; - } - - /** - * Sets the reader that read this SAM file. Protected access only. - * @param reader Reader which retrieved this file. - */ - protected void setReader(SAMFileReader reader) { - mReader = reader; - } - - /** - * Gets the position of this record, stored on the filesystem. - * @return Chunk indicating the physical position. - */ - public Chunk getCoordinates() { - return coordinates; - } - - /** - * Internal function: sets the coordin - * @param coordinates Where this file is actually sitting on the disk. - */ - protected void setCoordinates(final Chunk coordinates) { - this.coordinates = coordinates; - } - - private List isValidReferenceIndexAndPosition(final Integer referenceIndex, final String referenceName, - final int alignmentStart, final boolean isMate) { - final boolean hasReference = hasReferenceName(referenceIndex, referenceName); - - // ret is only instantiate if there are errors to report, in order to reduce GC in the typical case - // in which everything is valid. It's ugly, but more efficient. - ArrayList ret = null; - if (!hasReference) { - if (alignmentStart != 0) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_ALIGNMENT_START, buildMessage("Alignment start should be 0 because reference name = *.", isMate), getReadName())); - } - } else { - if (alignmentStart == 0) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_ALIGNMENT_START, buildMessage("Alignment start should != 0 because reference name != *.", isMate), getReadName())); - } - - if (getHeader().getSequenceDictionary().size() > 0) { - final SAMSequenceRecord sequence = - (referenceIndex != null? getHeader().getSequence(referenceIndex): getHeader().getSequence(referenceName)); - if (sequence == null) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_REFERENCE_INDEX, buildMessage("Reference sequence not found in sequence dictionary.", isMate), getReadName())); - } else { - if (alignmentStart > sequence.getSequenceLength()) { - if (ret == null) ret = new ArrayList(); - ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_ALIGNMENT_START, buildMessage("Alignment start (" + alignmentStart + ") must be <= reference sequence length (" + - sequence.getSequenceLength() + ") on reference " + sequence.getSequenceName(), isMate), getReadName())); - } - } - } - } - return ret; - } - - private String buildMessage(final String baseMessage, final boolean isMate) { - return isMate ? "Mate " + baseMessage : baseMessage; - } - - /** - * Note that this does a shallow copy of everything, except for the attribute list, for which a copy of the list - * is made, but the attributes themselves are copied by reference. This should be safe because callers should - * never modify a mutable value returned by any of the get() methods anyway. - */ - @Override - public Object clone() throws CloneNotSupportedException { - final SAMRecord newRecord = (SAMRecord)super.clone(); - if (mAttributes != null) { - newRecord.mAttributes = (ArrayList)((ArrayList)mAttributes).clone(); - } - return newRecord; - } - - /** Simple toString() that gives a little bit of useful info about the read. */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(64); - builder.append(getReadName()); - if (getReadPairedFlag()) { - if (getFirstOfPairFlag()) { - builder.append(" 1/2"); - } - else { - builder.append(" 2/2"); - } - } - - builder.append(" "); - builder.append(String.valueOf(getReadLength())); - builder.append("b"); - - if (getReadUnmappedFlag()) { - builder.append(" unmapped read."); - } - else { - builder.append(" aligned read."); - } - - return builder.toString(); - } -} - diff --git a/settings/repository/edu.mit.broad/picard-private-parts-1198.jar b/settings/repository/edu.mit.broad/picard-private-parts-1333-sharding.jar similarity index 60% rename from settings/repository/edu.mit.broad/picard-private-parts-1198.jar rename to settings/repository/edu.mit.broad/picard-private-parts-1333-sharding.jar index b46954c67425507cb1ae2b8bab414d16cb31461e..9ce577e803649585758bb26f68142fc1f48e63a0 100644 GIT binary patch delta 103976 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