]> git.donarmstrong.com Git - bamtools.git/blobdiff - src/api/internal/BamMultiReader_p.cpp
Major update to BamTools version 1.0
[bamtools.git] / src / api / internal / BamMultiReader_p.cpp
index bfec6bbf2581cdf674e7f3386cb0a30462df899b..583085c7cd11782b68337f483113cd8ef702110a 100644 (file)
@@ -3,7 +3,7 @@
 // Marth Lab, Department of Biology, Boston College
 // All rights reserved.
 // ---------------------------------------------------------------------------
-// Last modified: 17 January 2011 (DB)
+// Last modified: 21 March 2011 (DB)
 // ---------------------------------------------------------------------------
 // Functionality for simultaneously reading multiple BAM files
 // *************************************************************************
@@ -40,47 +40,82 @@ BamMultiReaderPrivate::~BamMultiReaderPrivate(void) {
     m_alignments = 0;
 }
 
-// close the BAM files
+// close all BAM files
 void BamMultiReaderPrivate::Close(void) {
+    CloseFiles( Filenames() );
+}
 
-    // clear out alignment cache
-    m_alignments->Clear();
-
-    // iterate over readers
-    vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
-    vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
-    for ( ; readerIter != readerEnd; ++readerIter ) {
+// close requested BAM file
+void BamMultiReaderPrivate::CloseFile(const string& filename) {    
+    vector<string> filenames(1, filename);
+    CloseFiles(filenames);
+}
 
-        // close reader
-        BamReader*    reader    = (*readerIter).first;
-        BamAlignment* alignment = (*readerIter).second;
-        if ( reader ) reader->Close();
+// close requested BAM files
+void BamMultiReaderPrivate::CloseFiles(const vector<string>& filenames) {
 
-        // delete pointers
-        delete reader;
-        reader = 0;
-        delete alignment;
-        alignment = 0;
+    // iterate over filenames
+    vector<string>::const_iterator filesIter = filenames.begin();
+    vector<string>::const_iterator filesEnd  = filenames.end();
+    for ( ; filesIter != filesEnd; ++filesIter ) {
+        const string& filename = (*filesIter);
+        if ( filename.empty() ) continue;
+
+        // iterate over readers
+        vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
+        vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
+        for ( ; readerIter != readerEnd; ++readerIter ) {
+            BamReader* reader = (*readerIter).first;
+            if ( reader == 0 ) continue;
+
+            // if reader matches requested filename
+            if ( reader->GetFilename() == filename ) {
+
+                // remove reader/alignment from alignment cache
+                m_alignments->Remove(reader);
+
+                // close & delete reader
+                reader->Close();
+                delete reader;
+                reader = 0;
+
+                // delete reader's alignment entry
+                BamAlignment* alignment = (*readerIter).second;
+                delete alignment;
+                alignment = 0;
+
+                // remove reader from container
+                m_readers.erase(readerIter);
+
+                // on match, just go on to next filename
+                // (no need to keep looking and iterator is invalid now anyway)
+                break;
+            }
+        }
     }
 
-    // clear out readers
-    m_readers.clear();
-
-    // reset default flags
-    m_isCoreMode = false;
-    m_sortOrder = BamMultiReader::SortedByPosition;
+    // make sure alignment cache is cleared if all readers are now closed
+    if ( m_readers.empty() && m_alignments != 0 )
+        m_alignments->Clear();
 }
 
-// saves index data to BAM index files (".bai"/".bti") where necessary, returns success/fail
-bool BamMultiReaderPrivate::CreateIndexes(bool useStandardIndex) {
+// creates index files for BAM files that don't have them
+bool BamMultiReaderPrivate::CreateIndexes(const BamIndex::IndexType& type) {
 
     bool result = true;
+
+    // iterate over readers
     vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
     vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
     for ( ; readerIter != readerEnd; ++readerIter ) {
         BamReader* reader = (*readerIter).first;
-        result &= reader->CreateIndex(useStandardIndex);
+        if ( reader == 0 ) continue;
+
+        // if reader doesn't have an index, create one
+        if ( !reader->HasIndex() )
+            result &= reader->CreateIndex(type);
     }
+
     return result;
 }
 
@@ -89,7 +124,8 @@ IBamMultiMerger* BamMultiReaderPrivate::CreateMergerForCurrentSortOrder(void) co
         case ( BamMultiReader::SortedByPosition ) : return new PositionMultiMerger;
         case ( BamMultiReader::SortedByReadName ) : return new ReadNameMultiMerger;
         case ( BamMultiReader::Unsorted )         : return new UnsortedMultiMerger;
-        default : //print error
+        default :
+            cerr << "BamMultiReader ERROR: requested sort order is unknown" << endl;
             return 0;
     }
 }
@@ -113,21 +149,50 @@ const string BamMultiReaderPrivate::ExtractReadGroup(const string& headerLine) c
     return readGroup;
 }
 
+const vector<string> BamMultiReaderPrivate::Filenames(void) const {
+
+    // init filename container
+    vector<string> filenames;
+    filenames.reserve( m_readers.size() );
+
+    // iterate over readers
+    vector<ReaderAlignment>::const_iterator readerIter = m_readers.begin();
+    vector<ReaderAlignment>::const_iterator readerEnd  = m_readers.end();
+    for ( ; readerIter != readerEnd; ++readerIter ) {
+        const BamReader* reader = (*readerIter).first;
+        if ( reader == 0 ) continue;
+
+        // store filename if not empty
+        const string filename = reader->GetFilename();
+        if ( !filename.empty() )
+            filenames.push_back( reader->GetFilename() );
+    }
+
+    // return result
+    return filenames;
+}
+
+SamHeader BamMultiReaderPrivate::GetHeader(void) const {
+    string text = GetHeaderText();
+    return SamHeader(text);
+}
+
 // makes a virtual, unified header for all the bam files in the multireader
-const string BamMultiReaderPrivate::GetHeaderText(void) const {
+string BamMultiReaderPrivate::GetHeaderText(void) const {
 
-    // just spit single header out if only have one reader open
+    // if only one reader is open
     if ( m_readers.size() == 1 ) {
 
+        // just return reader's header text
+        const ReaderAlignment& ra = m_readers.front();
+        const BamReader* reader = ra.first;
+        if ( reader ) return reader->GetHeaderText();
 
-        vector<ReaderAlignment>::const_iterator readerBegin = m_readers.begin();
-        const ReaderAlignment& entry = (*readerBegin);
-        const BamReader* reader = entry.first;
-        if ( reader == 0 ) return "";
-        return reader->GetHeaderText();
+        // invalid reader
+        return string();
     }
 
-    string mergedHeader = "";
+    string mergedHeader("");
     map<string, bool> readGroups;
 
     // foreach extraction entry (each BAM file)
@@ -135,10 +200,10 @@ const string BamMultiReaderPrivate::GetHeaderText(void) const {
     vector<ReaderAlignment>::const_iterator readerIter  = readerBegin;
     vector<ReaderAlignment>::const_iterator readerEnd   = m_readers.end();
     for ( ; readerIter != readerEnd; ++readerIter ) {
-
-        // get header from reader
         const BamReader* reader = (*readerIter).first;
         if ( reader == 0 ) continue;
+
+        // get header from reader
         string headerText = reader->GetHeaderText();
         if ( headerText.empty() ) continue;
 
@@ -153,9 +218,10 @@ const string BamMultiReaderPrivate::GetHeaderText(void) const {
 
             // get next line from header, skip if empty
             const string headerLine = (*linesIter);
-            if ( headerLine.empty() ) { continue; }
+            if ( headerLine.empty() ) continue;
 
             // if first file, save HD & SQ entries
+            // TODO: what if first file has empty header, should just check for empty 'mergedHeader' instead ?
             if ( readerIter == readerBegin ) {
                 if ( headerLine.find("@HD") == 0 || headerLine.find("@SQ") == 0) {
                     mergedHeader.append(headerLine.c_str());
@@ -179,8 +245,8 @@ const string BamMultiReaderPrivate::GetHeaderText(void) const {
                     // warn iff we are reading one file and discover duplicated @RG tags in the header
                     // otherwise, we emit no warning, as we might be merging multiple BAM files with identical @RG tags
                     if ( currentFileReadGroups.find(readGroup) != currentFileReadGroups.end() ) {
-                        cerr << "WARNING: duplicate @RG tag " << readGroup
-                            << " entry in header of " << reader->GetFilename() << endl;
+                        cerr << "BamMultiReader WARNING: duplicate @RG tag " << readGroup
+                             << " entry in header of " << reader->GetFilename() << endl;
                     }
                 }
             }
@@ -193,198 +259,344 @@ const string BamMultiReaderPrivate::GetHeaderText(void) const {
 
 // get next alignment among all files
 bool BamMultiReaderPrivate::GetNextAlignment(BamAlignment& al) {
-    return LoadNextAlignment(al, false);
+    m_isCoreMode = false;
+    return LoadNextAlignment(al);
 }
 
 // get next alignment among all files without parsing character data from alignments
 bool BamMultiReaderPrivate::GetNextAlignmentCore(BamAlignment& al) {
-    return LoadNextAlignment(al, true);
+    m_isCoreMode = true;
+    return LoadNextAlignment(al);
 }
 
 // ---------------------------------------------------------------------------------------
 //
 // NB: The following GetReferenceX() functions assume that we have identical
-// references for all BAM files.  We enforce this by invoking the above
-// validation function (ValidateReaders) to verify that our reference data
-// is the same across all files on Open, so we will not encounter a situation
-// in which there is a mismatch and we are still live.
+// references for all BAM files.  We enforce this by invoking the
+// ValidateReaders() method to verify that our reference data is the same
+// across all files on Open - so we will not encounter a situation in which
+// there is a mismatch and we are still live.
 //
 // ---------------------------------------------------------------------------------------
 
 // returns the number of reference sequences
-const int BamMultiReaderPrivate::GetReferenceCount(void) const {
-    const ReaderAlignment& firstReader = m_readers.front();
-    const BamReader* reader = firstReader.first;
+int BamMultiReaderPrivate::GetReferenceCount(void) const {
+
+    // handle empty multireader
+    if ( m_readers.empty() )
+        return 0;
+
+    // return reference count from first reader
+    const ReaderAlignment& ra = m_readers.front();
+    const BamReader* reader = ra.first;
     if ( reader ) return reader->GetReferenceCount();
-    else return 0;
+
+    // invalid reader
+    return 0;
 }
 
 // returns vector of reference objects
 const RefVector BamMultiReaderPrivate::GetReferenceData(void) const {
-    const ReaderAlignment& firstReader = m_readers.front();
-    const BamReader* reader = firstReader.first;
+
+    // handle empty multireader
+    if ( m_readers.empty() )
+        return RefVector();
+
+    // return reference data from first BamReader
+    const ReaderAlignment& ra = m_readers.front();
+    const BamReader* reader = ra.first;
     if ( reader ) return reader->GetReferenceData();
-    else return RefVector();
+
+    // invalid reader
+    return RefVector();
 }
 
 // returns refID from reference name
-const int BamMultiReaderPrivate::GetReferenceID(const string& refName) const {
-    const ReaderAlignment& firstReader = m_readers.front();
-    const BamReader* reader = firstReader.first;
+int BamMultiReaderPrivate::GetReferenceID(const string& refName) const {
+
+    // handle empty multireader
+    if ( m_readers.empty() )
+        return -1;
+
+    // return reference ID from first BamReader
+    const ReaderAlignment& ra = m_readers.front();
+    const BamReader* reader = ra.first;
     if ( reader ) return reader->GetReferenceID(refName);
-    else return -1; // ERROR case - how to report
-}
 
+    // invalid reader
+    return -1;
+}
 // ---------------------------------------------------------------------------------------
 
 // checks if any readers still have alignments
-bool BamMultiReaderPrivate::HasOpenReaders(void) {
-    return ( m_alignments->Size() > 0 );
+bool BamMultiReaderPrivate::HasAlignmentData(void) const {
+    if ( m_alignments == 0 )
+        return false;
+    return !m_alignments->IsEmpty();
 }
 
-// returns whether underlying BAM readers ALL have an index loaded
+// returns true if all readers have index data available
 // this is useful to indicate whether Jump() or SetRegion() are possible
-bool BamMultiReaderPrivate::IsIndexLoaded(void) const {
-    bool ok = true;
+bool BamMultiReaderPrivate::HasIndexes(void) const {
+
+    // handle empty multireader
+    if ( m_readers.empty() )
+        return false;
+
+    bool result = true;
+
+    // iterate over readers
+    vector<ReaderAlignment>::const_iterator readerIter = m_readers.begin();
+    vector<ReaderAlignment>::const_iterator readerEnd  = m_readers.end();
+    for ( ; readerIter != readerEnd; ++readerIter ) {
+        const BamReader* reader = (*readerIter).first;
+        if ( reader  == 0 ) continue;
+
+        // see if current reader has index data
+        result &= reader->HasIndex();
+    }
+
+    return result;
+}
+
+// returns true if multireader has open readers
+bool BamMultiReaderPrivate::HasOpenReaders(void) {
+
+    // iterate over readers
     vector<ReaderAlignment>::const_iterator readerIter = m_readers.begin();
     vector<ReaderAlignment>::const_iterator readerEnd  = m_readers.end();
     for ( ; readerIter != readerEnd; ++readerIter ) {
         const BamReader* reader = (*readerIter).first;
-        if ( reader ) ok &= reader->IsIndexLoaded();
+        if ( reader == 0 ) continue;
+
+        // return true whenever an open reader is found
+        if ( reader->IsOpen() ) return true;
     }
-    return ok;
+
+    // no readers open
+    return false;
 }
 
-// jumps to specified region(refID, leftBound) in BAM files, returns success/fail
+// performs random-access jump using (refID, position) as a left-bound
 bool BamMultiReaderPrivate::Jump(int refID, int position) {
 
-    bool ok = true;
+    // NB: While it may make sense to track readers in which we can
+    // successfully Jump, in practice a failure of Jump means "no
+    // alignments here."  It makes sense to simply accept the failure,
+    // UpdateAlignments(), and continue.
+
+    // iterate over readers
     vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
     vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
     for ( ; readerIter != readerEnd; ++readerIter ) {
         BamReader* reader = (*readerIter).first;
         if ( reader == 0 ) continue;
 
-        ok &= reader->Jump(refID, position);
-        if ( !ok ) {
-            cerr << "ERROR: could not jump " << reader->GetFilename()
+        // attempt jump() on each
+        if ( !reader->Jump(refID, position) ) {
+            cerr << "BamMultiReader ERROR: could not jump " << reader->GetFilename()
                  << " to " << refID << ":" << position << endl;
-            exit(1);
         }
     }
 
-    if (ok) UpdateAlignments();
-    return ok;
+    // update alignment cache & return success
+    UpdateAlignmentCache();
+    return true;
 }
 
-bool BamMultiReaderPrivate::LoadNextAlignment(BamAlignment& al, bool coreMode) {
+bool BamMultiReaderPrivate::LoadNextAlignment(BamAlignment& al) {
 
     // bail out if no more data to process
-    if ( !HasOpenReaders() ) return false;
+    if ( !HasAlignmentData() )
+        return false;
 
     // "pop" next alignment and reader
     ReaderAlignment nextReaderAlignment = m_alignments->TakeFirst();
-    BamReader*    reader    = nextReaderAlignment.first;
+    BamReader* reader = nextReaderAlignment.first;
     BamAlignment* alignment = nextReaderAlignment.second;
 
-    // save it by copy to our argument
-    al = BamAlignment(*alignment);
+    // store cached alignment into destination parameter (by copy)
+    al = *alignment;
 
     // peek to next alignment & store in cache
-    m_isCoreMode = coreMode;
-    SaveNextAlignment(reader,alignment);
+    SaveNextAlignment(reader, alignment);
 
     // return success
     return true;
 }
 
-// opens BAM files
-bool BamMultiReaderPrivate::Open(const vector<string>& filenames,
-                                 bool openIndexes,
-                                 bool coreMode,
-                                 bool preferStandardIndex)
-{
-    // store core mode flag
-    m_isCoreMode = coreMode;
-
-    // first clear out any prior alignment cache prior data
-    if ( m_alignments ) {
-        m_alignments->Clear();
-        delete m_alignments;
-        m_alignments = 0;
+// locate (& load) index files for BAM readers that don't already have one loaded
+bool BamMultiReaderPrivate::LocateIndexes(const BamIndex::IndexType& preferredType) {
+
+    bool result = true;
+
+    // iterate over readers
+    vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
+    vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
+    for ( ; readerIter != readerEnd; ++readerIter ) {
+        BamReader* reader = (*readerIter).first;
+        if ( reader == 0 ) continue;
+
+        // if reader has no index, try to locate one
+        if ( !reader->HasIndex() )
+            result &= reader->LocateIndex(preferredType);
     }
 
-    // create alignment cache based on sorting mode
-    m_alignments = CreateMergerForCurrentSortOrder();
-    if ( m_alignments == 0 ) return false;
+    return result;
+}
+
+// opens BAM files
+bool BamMultiReaderPrivate::Open(const vector<string>& filenames) {
+
+    // create alignment cache if neccessary
+    if ( m_alignments == 0 ) {
+        m_alignments = CreateMergerForCurrentSortOrder();
+        if ( m_alignments == 0 ) return false;
+    }
 
     // iterate over filenames
     vector<string>::const_iterator filenameIter = filenames.begin();
     vector<string>::const_iterator filenameEnd  = filenames.end();
     for ( ; filenameIter != filenameEnd; ++filenameIter ) {
-        const string filename = (*filenameIter);
+        const string& filename = (*filenameIter);
+        if ( filename.empty() ) continue;
 
-        bool openedOk = true;
-        BamReader* reader = new BamReader;
-        openedOk = reader->Open(filename, "", openIndexes, preferStandardIndex);
+        // attempt to open BamReader on filename
+        BamReader* reader = OpenReader(filename);
+        if ( reader == 0 ) continue;
 
-        // if file opened ok
-        if ( openedOk ) {
+        // store reader with new alignment
+        m_readers.push_back( make_pair(reader, new BamAlignment) );
+    }
 
-            // try to read first alignment
-            bool fileOk = true;
-            BamAlignment* alignment = new BamAlignment;
-            fileOk &= ( coreMode ? reader->GetNextAlignmentCore(*alignment)
-                                 : reader->GetNextAlignment(*alignment) );
+    // validate & rewind any opened readers, also refreshes alignment cache
+    if ( !m_readers.empty() ) {
+        ValidateReaders();
+        Rewind();
+    }
 
-            if ( fileOk ) {
+    // return success
+    return true;
+}
 
-                m_readers.push_back( make_pair(reader, alignment) );
-                m_alignments->Add( make_pair(reader, alignment) );
+bool BamMultiReaderPrivate::OpenFile(const std::string& filename) {
+    vector<string> filenames(1, filename);
+    return Open(filenames);
+}
 
-            } else {
-                cerr << "WARNING: could not read first alignment in "
-                     << filename << ", ignoring file" << endl;
+bool BamMultiReaderPrivate::OpenIndexes(const vector<string>& indexFilenames) {
 
-                // if only file available & could not be read, return failure
-                if ( filenames.size() == 1 )
-                    return false;
-            }
+    // TODO: This needs to be cleaner - should not assume same order.
+    //       And either way, shouldn't start at first reader.  Should start at
+    //       first reader without an index?
+
+    // make sure same number of index filenames as readers
+    if ( m_readers.size() != indexFilenames.size() || !indexFilenames.empty() )
+        return false;
+
+    // init result flag
+    bool result = true;
+
+    // iterate over BamReaders
+    vector<string>::const_iterator indexFilenameIter = indexFilenames.begin();
+    vector<string>::const_iterator indexFilenameEnd  = indexFilenames.end();
+    vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
+    vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
+    for ( ; readerIter != readerEnd; ++readerIter ) {
+        BamReader* reader = (*readerIter).first;
+
+        // open index filename on reader
+        if ( reader ) {
+            const string& indexFilename = (*indexFilenameIter);
+            result &= reader->OpenIndex(indexFilename);
+        }
+
+        // increment filename iterator, skip if no more index files to open
+        if ( ++indexFilenameIter == indexFilenameEnd )
+            break;
+    }
+
+    // TODO: validation ??
+
+    // return success/fail
+    return result;
+}
+
+BamReader* BamMultiReaderPrivate::OpenReader(const std::string& filename) {
+
+    // create new BamReader
+    BamReader* reader = new BamReader;
+
+    // if reader opens OK
+    if ( reader->Open(filename) ) {
+
+        // attempt to read first alignment (sanity check)
+        // if ok, then return BamReader pointer
+        BamAlignment al;
+        if ( reader->GetNextAlignmentCore(al) )
+            return reader;
+
+        // could not read alignment
+        else {
+            cerr << "BamMultiReader WARNING: Could not read first alignment from "
+                 << filename << ", ignoring file" << endl;
         }
+    }
 
-        // TODO; any further error handling when openedOK is false ??
-        else return false;
+    // reader could not open
+    else {
+        cerr << "BamMultiReader WARNING: Could not open: "
+              << filename << ", ignoring file" << endl;
     }
 
-    // files opened ok, at least one alignment could be read,
-    // now need to check that all files use same reference data
-    ValidateReaders();
-    return true;
+    // if we get here, there was a problem with this BAM file (opening or reading)
+    // clean up memory allocation & return null pointer
+    delete reader;
+    return 0;
 }
 
 // print associated filenames to stdout
 void BamMultiReaderPrivate::PrintFilenames(void) const {
+    const vector<string>& filenames = Filenames();
+    vector<string>::const_iterator filenameIter = filenames.begin();
+    vector<string>::const_iterator filenameEnd  = filenames.end();
+    for ( ; filenameIter != filenameEnd; ++filenameIter )
+        cout << (*filenameIter) << endl;
+}
 
-    vector<ReaderAlignment>::const_iterator readerIter = m_readers.begin();
-    vector<ReaderAlignment>::const_iterator readerEnd  = m_readers.end();
-    for ( ; readerIter != readerEnd; ++readerIter ) {
-        const BamReader* reader = (*readerIter).first;
-        if ( reader == 0 ) continue;
-        cout << reader->GetFilename() << endl;
+// returns BAM file pointers to beginning of alignment data & resets alignment cache
+bool BamMultiReaderPrivate::Rewind(void) {
+
+    // clear out alignment cache
+    m_alignments->Clear();
+
+    // attempt to rewind files
+    if ( !RewindReaders() ) {
+        cerr << "BamMultiReader ERROR: could not rewind file(s) successfully";
+        return false;
     }
+
+    // reset cache & return success
+    UpdateAlignmentCache();
+    return true;
 }
 
 // returns BAM file pointers to beginning of alignment data
-bool BamMultiReaderPrivate::Rewind(void) {
+bool BamMultiReaderPrivate::RewindReaders(void) {
 
     bool result = true;
+
+    // iterate over readers
     vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
     vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
     for ( ; readerIter != readerEnd; ++readerIter ) {
         BamReader* reader = (*readerIter).first;
         if ( reader == 0 ) continue;
+
+        // attempt rewind on BamReader
         result &= reader->Rewind();
     }
+
     return result;
 }
 
@@ -404,13 +616,16 @@ void BamMultiReaderPrivate::SaveNextAlignment(BamReader* reader, BamAlignment* a
 }
 
 // sets the index caching mode on the readers
-void BamMultiReaderPrivate::SetIndexCacheMode(const BamIndex::BamIndexCacheMode mode) {
+void BamMultiReaderPrivate::SetIndexCacheMode(const BamIndex::IndexCacheMode mode) {
 
+    // iterate over readers
     vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
     vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
     for ( ; readerIter != readerEnd; ++readerIter ) {
         BamReader* reader = (*readerIter).first;
         if ( reader == 0 ) continue;
+
+        // set reader's index cache mode
         reader->SetIndexCacheMode(mode);
     }
 }
@@ -422,19 +637,23 @@ bool BamMultiReaderPrivate::SetRegion(const BamRegion& region) {
     // alignments here."  It makes sense to simply accept the failure,
     // UpdateAlignments(), and continue.
 
+    // iterate over alignments
     vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
     vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
     for ( ; readerIter != readerEnd; ++readerIter ) {
         BamReader* reader = (*readerIter).first;
         if ( reader == 0 ) continue;
+
+        // attempt to set BamReader's region of interest
         if ( !reader->SetRegion(region) ) {
-            cerr << "ERROR: could not jump " << reader->GetFilename() << " to "
+            cerr << "BamMultiReader ERROR: could not jump " << reader->GetFilename() << " to "
                  << region.LeftRefID  << ":" << region.LeftPosition   << ".."
                  << region.RightRefID << ":" << region.RightPosition  << endl;
         }
     }
 
-    UpdateAlignments();
+    // update alignment cache & return success
+    UpdateAlignmentCache();
     return true;
 }
 
@@ -452,7 +671,7 @@ void BamMultiReaderPrivate::SetSortOrder(const BamMultiReader::SortOrder& order)
 
     // copy old cache contents to new cache
     while ( m_alignments->Size() > 0 ) {
-        ReaderAlignment value = m_alignments->TakeFirst();
+        ReaderAlignment value = m_alignments->TakeFirst(); // retrieves & 'pops'
         newAlignmentCache->Add(value);
     }
 
@@ -461,33 +680,44 @@ void BamMultiReaderPrivate::SetSortOrder(const BamMultiReader::SortOrder& order)
     m_alignments = newAlignmentCache;
 }
 
+// splits the entire header into a list of strings
+const vector<string> BamMultiReaderPrivate::SplitHeaderText(const string& headerText) const {
+
+    stringstream header(headerText);
+    string item;
+
+    vector<string> lines;
+    while ( getline(header, item) )
+        lines.push_back(item);
+    return lines;
+}
+
 // updates our alignment cache
-void BamMultiReaderPrivate::UpdateAlignments(void) {
+void BamMultiReaderPrivate::UpdateAlignmentCache(void) {
+
+    // skip if invalid alignment cache
+    if ( m_alignments == 0 ) return;
 
     // clear the cache
     m_alignments->Clear();
 
+    // seed cache with fully-populated alignments
+    // further updates will fill with full/core-only as requested
+    m_isCoreMode = false;
+
     // iterate over readers
     vector<ReaderAlignment>::iterator readerIter = m_readers.begin();
     vector<ReaderAlignment>::iterator readerEnd  = m_readers.end();
     for ( ; readerIter != readerEnd; ++readerIter ) {
         BamReader* reader = (*readerIter).first;
         BamAlignment* alignment = (*readerIter).second;
-        if ( reader == 0 ) continue;
+        if ( reader == 0 || alignment == 0 ) continue;
+
+        // save next alignment from each reader in cache
         SaveNextAlignment(reader, alignment);
     }
 }
 
-// splits the entire header into a list of strings
-const vector<string> BamMultiReaderPrivate::SplitHeaderText(const string& headerText) const {
-    stringstream header(headerText);
-    vector<string> lines;
-    string item;
-    while ( getline(header, item) )
-        lines.push_back(item);
-    return lines;
-}
-
 // ValidateReaders checks that all the readers point to BAM files representing
 // alignments against the same set of reference sequences, and that the
 // sequences are identically ordered.  If these checks fail the operation of
@@ -496,7 +726,7 @@ void BamMultiReaderPrivate::ValidateReaders(void) const {
 
     // retrieve first reader data
     const BamReader* firstReader = m_readers.front().first;
-    if ( firstReader == 0 ) return; // signal error?
+    if ( firstReader == 0 ) return;
     const RefVector firstReaderRefData = firstReader->GetReferenceData();
     const int firstReaderRefCount = firstReader->GetReferenceCount();
     const int firstReaderRefSize = firstReaderRefData.size();
@@ -508,7 +738,7 @@ void BamMultiReaderPrivate::ValidateReaders(void) const {
 
         // get current reader data
         BamReader* reader = (*readerIter).first;
-        if ( reader == 0 ) continue; // error?
+        if ( reader == 0 ) continue;
         const RefVector currentReaderRefData = reader->GetReferenceData();
         const int currentReaderRefCount = reader->GetReferenceCount();
         const int currentReaderRefSize  = currentReaderRefData.size();
@@ -522,7 +752,7 @@ void BamMultiReaderPrivate::ValidateReaders(void) const {
         if ( (currentReaderRefCount != firstReaderRefCount) ||
              (firstReaderRefSize    != currentReaderRefSize) )
         {
-            cerr << "ERROR: mismatched number of references in " << reader->GetFilename()
+            cerr << "BamMultiReader ERROR: mismatched number of references in " << reader->GetFilename()
                  << " expected " << firstReaderRefCount
                  << " reference sequences but only found " << currentReaderRefCount << endl;
             exit(1);
@@ -531,7 +761,6 @@ void BamMultiReaderPrivate::ValidateReaders(void) const {
         // this will be ok; we just checked above that we have identically-sized sets of references
         // here we simply check if they are all, in fact, equal in content
         while ( firstRefIter != firstRefEnd ) {
-
             const RefData& firstRef   = (*firstRefIter);
             const RefData& currentRef = (*currentRefIter);
 
@@ -539,8 +768,10 @@ void BamMultiReaderPrivate::ValidateReaders(void) const {
             if ( (firstRef.RefName   != currentRef.RefName) ||
                  (firstRef.RefLength != currentRef.RefLength) )
             {
-                cerr << "ERROR: mismatched references found in " << reader->GetFilename()
+                cerr << "BamMultiReader ERROR: mismatched references found in " << reader->GetFilename()
                      << " expected: " << endl;
+
+                // print first reader's reference data
                 RefVector::const_iterator refIter = firstReaderRefData.begin();
                 RefVector::const_iterator refEnd  = firstReaderRefData.end();
                 for ( ; refIter != refEnd; ++refIter ) {
@@ -549,6 +780,8 @@ void BamMultiReaderPrivate::ValidateReaders(void) const {
                 }
 
                 cerr << "but found: " << endl;
+
+                // print current reader's reference data
                 refIter = currentReaderRefData.begin();
                 refEnd  = currentReaderRefData.end();
                 for ( ; refIter != refEnd; ++refIter ) {