/* * The MIT License (MIT) * * Copyright (c) 2007-2015 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 org.broad.igv.sam.reader; import htsjdk.samtools.SamFileHeaderMerger; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.util.CloseableIterator; import org.apache.log4j.Logger; import org.broad.igv.feature.genome.Genome; import org.broad.igv.feature.genome.GenomeManager; import org.broad.igv.sam.Alignment; import java.io.IOException; import java.util.*; /** * Performs a logical merge of bam files. * <p/> * User: jrobinso * Date: Apr 25, 2010 */ public class MergedAlignmentReader implements AlignmentReader { private static Logger log = Logger.getLogger(MergedAlignmentReader.class); List<AlignmentReader> readers; List<String> sequenceNames; Map<String, Integer> chrNameIndex; SAMFileHeader header; public MergedAlignmentReader(List<AlignmentReader> readers) throws IOException { this.readers = readers; loadSequenceNames(); } public CloseableIterator<Alignment> iterator() { return new MergedFileIterator(); } public CloseableIterator<Alignment> query(String chr, int start, int end, boolean contained) throws IOException { return new MergedFileIterator(chr, start, end, contained); } public void close() throws IOException { for (AlignmentReader reader : readers) { reader.close(); } } public List<String> getSequenceNames() { return sequenceNames; } public Set<String> getPlatforms() { Set<String> platforms = new HashSet<String>(); for (AlignmentReader reader : readers) { Set<String> plf = reader.getPlatforms(); if(plf != null){ platforms.addAll(plf); } } return platforms; } public SAMFileHeader getFileHeader(){ if(this.header == null){ this.header = loadHeaders(); } return this.header; } /** * Return the merged list of all sequence names, maintaining order. * * @return */ public void loadSequenceNames() throws IOException { // Use a set for quick comparison LinkedHashSet<String> names = new LinkedHashSet<String>(50); for (AlignmentReader reader : readers) { names.addAll(reader.getSequenceNames()); } sequenceNames = new ArrayList<String>(names); Genome genome = GenomeManager.getInstance().getCurrentGenome(); chrNameIndex = new HashMap<String, Integer>(sequenceNames.size()); for (int i = 0; i < sequenceNames.size(); i++) { final String seqName = sequenceNames.get(i); String chr = genome == null ? seqName : genome.getCanonicalChrName(seqName); chrNameIndex.put(chr, i); } } private SAMFileHeader loadHeaders(){ List<SAMFileHeader> headersList = new ArrayList<SAMFileHeader>(); SAMFileHeader.SortOrder sortOrder = null; for(AlignmentReader reader: readers){ SAMFileHeader curHeader = reader.getFileHeader(); if(curHeader != null) { headersList.add(curHeader); sortOrder = curHeader.getSortOrder(); } } if(sortOrder != null){ SamFileHeaderMerger headerMerger = new SamFileHeaderMerger(sortOrder, headersList, true); return headerMerger.getMergedHeader(); } return null; } public boolean hasIndex() { return readers.iterator().next().hasIndex(); } public class MergedFileIterator implements CloseableIterator<Alignment> { List<CloseableIterator<Alignment>> allIterators = new ArrayList(); PriorityQueue<RecordIterWrapper> iteratorQueue; public MergedFileIterator() { try { create(null, -1, -1, false); } catch (IOException e) { log.error(e.getMessage(), e); } } public MergedFileIterator(String chr, int start, int end, boolean contained) throws IOException { create(chr, start, end, contained); } private void create(String chr, int start, int end, boolean contained) throws IOException { iteratorQueue = new PriorityQueue(readers.size(), new AlignmentStartComparator()); boolean iterate = (start == end) && (start == -1); for (AlignmentReader reader : readers) { CloseableIterator<Alignment> iter; if (iterate) { iter = reader.iterator(); } else { iter = reader.query(chr, start, end, contained); } allIterators.add(iter); if (iter.hasNext()) { iteratorQueue.add(new RecordIterWrapper(iter)); } } } public boolean hasNext() { return iteratorQueue.size() > 0; } public Alignment next() { RecordIterWrapper wrapper = iteratorQueue.poll(); Alignment next = wrapper.advance(); if (wrapper.hasNext()) { iteratorQueue.add(wrapper); } return next; } public void remove() { throw new UnsupportedOperationException("Remove not implemented"); } public void close() { for (CloseableIterator<Alignment> iter : allIterators) { iter.close(); } allIterators.clear(); iteratorQueue.clear(); } class RecordIterWrapper { Alignment nextRecord; CloseableIterator<Alignment> iterator; RecordIterWrapper(CloseableIterator<Alignment> iter) { this.iterator = iter; nextRecord = (iterator.hasNext() ? iterator.next() : null); } Alignment advance() { Alignment tmp = nextRecord; nextRecord = (iterator.hasNext() ? iterator.next() : null); return tmp; } boolean hasNext() { return nextRecord != null; } void close() { if (iterator != null) { iterator.close(); iterator = null; } } } class AlignmentStartComparator implements Comparator<RecordIterWrapper> { public int compare(RecordIterWrapper wrapper1, RecordIterWrapper wrapper2) { Alignment a1 = wrapper1.nextRecord; Alignment a2 = wrapper2.nextRecord; Integer idx1 = chrNameIndex.get(a1.getChr()); Integer idx2 = chrNameIndex.get(a2.getChr()); if(idx1==null) idx1 = Integer.MAX_VALUE; if(idx2== null) idx2 = Integer.MAX_VALUE; // Put these records at the end. if (idx1 > idx2) { return 1; } else if (idx1 < idx2) { return -1; } else { return a1.getAlignmentStart() - a2.getAlignmentStart(); } } } } }