]> git.donarmstrong.com Git - bamtools.git/blobdiff - src/api/internal/SamHeaderValidator_p.cpp
Cleaned up intra-API includes & moved version numbers to 2.0.0
[bamtools.git] / src / api / internal / SamHeaderValidator_p.cpp
index 4409411bdbba7768afcef34abe06c9c27c794cd5..094e79adb795ecb4a3560fab9700dce91ecbf80d 100644 (file)
@@ -1,28 +1,49 @@
 // ***************************************************************************
 // SamHeaderValidator.cpp (c) 2010 Derek Barnett
 // Marth Lab, Department of Biology, Boston College
-// All rights reserved.
 // ---------------------------------------------------------------------------
-// Last modified: 23 December 2010 (DB)
+// Last modified: 10 October 2011 (DB)
 // ---------------------------------------------------------------------------
 // Provides functionality for validating SamHeader data
 // ***************************************************************************
 
-#include <api/SamConstants.h>
-#include <api/SamHeader.h>
-#include <api/internal/SamHeaderValidator_p.h>
-#include <api/internal/SamHeaderVersion_p.h>
+#include "api/SamConstants.h"
+#include "api/SamHeader.h"
+#include "api/internal/SamHeaderValidator_p.h"
+#include "api/internal/SamHeaderVersion_p.h"
 using namespace BamTools;
 using namespace BamTools::Internal;
 
-#include <iostream>
+#include <cctype>
 #include <set>
 #include <sstream>
-#include <vector>
 using namespace std;
 
-// -------------------------------------------------------------------
-// Allow validation rules to vary between SAM header versions
+// ------------------------
+// static utility methods
+// -------------------------
+
+static
+bool caseInsensitiveCompare(const string& lhs, const string& rhs) {
+
+    // can omit checking chars if lengths not equal
+    const int lhsLength = lhs.length();
+    const int rhsLength = rhs.length();
+    if ( lhsLength != rhsLength )
+        return false;
+
+    // do *basic* toupper checks on each string char's
+    for ( int i = 0; i < lhsLength; ++i ) {
+        if ( toupper( (int)lhs.at(i)) != toupper( (int)rhs.at(i)) )
+            return false;
+    }
+
+    // otherwise OK
+    return true;
+}
+
+// ------------------------------------------------------------------------
+// Allow validation rules to vary, as needed, between SAM header versions
 //
 // use SAM_VERSION_X_Y to tag important changes
 //
@@ -33,108 +54,83 @@ using namespace std;
 //     // use rule introduced with version 2.0
 
 static const SamHeaderVersion SAM_VERSION_1_0 = SamHeaderVersion(1,0);
+static const SamHeaderVersion SAM_VERSION_1_1 = SamHeaderVersion(1,1);
+static const SamHeaderVersion SAM_VERSION_1_2 = SamHeaderVersion(1,2);
 static const SamHeaderVersion SAM_VERSION_1_3 = SamHeaderVersion(1,3);
+static const SamHeaderVersion SAM_VERSION_1_4 = SamHeaderVersion(1,4);
 
-// -----------------------------------------
-// SamHeaderValidatorPrivate implementation
-
-class SamHeaderValidator::SamHeaderValidatorPrivate {
-
-    // ctor & dtor
-    public:
-        SamHeaderValidatorPrivate(const SamHeader& header);
-        ~SamHeaderValidatorPrivate(void) { }
-
-    // 'public' methods
-    public:
-        bool Validate(bool verbose);
-
-    // internal validation methods
-    private:
-
-        // validate header metadata
-        bool ValidateMetadata(void);
-        bool ValidateVersion(void);
-        bool ContainsOnlyDigits(const string& s);
-        bool ValidateSortOrder(void);
-        bool ValidateGroupOrder(void);
-
-        // validate sequence dictionary
-        bool ValidateSequenceDictionary(void);
-        bool ContainsUniqueSequenceNames(void);
-        bool CheckNameFormat(const string& name);
-        bool ValidateSequence(const SamSequence& seq);
-        bool CheckLengthInRange(const string& length);
-
-        // validate read group dictionary
-        bool ValidateReadGroupDictionary(void);
-        bool ValidateReadGroup(const SamReadGroup& rg);
-        bool ContainsUniqueIDsAndPlatformUnits(void);
-        bool CheckReadGroupID(const string& id);
-        bool CheckSequencingTechnology(const string& technology);
-        bool Is454(const string& technology);
-        bool IsHelicos(const string& technology);
-        bool IsIllumina(const string& technology);
-        bool IsPacBio(const string& technology);
-        bool IsSolid(const string& technology);
-
-        // validate program data
-        bool ValidateProgramData(void);
-        bool ContainsUniqueProgramIds(void);
-        bool ValidatePreviousProgramIds(void);
-
-    // error reporting
-    private:
-        void AddError(const string& message);
-        void AddWarning(const string& message);
-        void PrintErrorMessages(void);
-        void PrintWarningMessages(void);
-
-    // data members
-    private:
-        const SamHeader&       m_header;
-        const SamHeaderVersion m_version;
-
-        bool m_isVerboseOutput;
-        const string ERROR_PREFIX;
-        const string WARN_PREFIX;
-        const string NEWLINE;
-        vector<string> m_errorMessages;
-        vector<string> m_warningMessages;
-};
-
-SamHeaderValidator::SamHeaderValidatorPrivate::SamHeaderValidatorPrivate(const SamHeader& header)
+// TODO: This functionality is currently unused.
+//       Make validation "version-aware."
+//
+// ------------------------------------------------------------------------
+
+const string SamHeaderValidator::ERROR_PREFIX = "ERROR: ";
+const string SamHeaderValidator::WARN_PREFIX  = "WARNING: ";
+const string SamHeaderValidator::NEWLINE      = "\n";
+
+SamHeaderValidator::SamHeaderValidator(const SamHeader& header)
     : m_header(header)
-    , m_version( header.Version )
-    , m_isVerboseOutput(false)
-    , ERROR_PREFIX("ERROR: ")
-    , WARN_PREFIX("WARNING: ")
-    , NEWLINE("\n")
 { }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::Validate(bool verbose) {
+SamHeaderValidator::~SamHeaderValidator(void) { }
 
-    // set error reporting mode
-    m_isVerboseOutput = verbose;
+void SamHeaderValidator::AddError(const string& message) {
+    m_errorMessages.push_back(ERROR_PREFIX + message + NEWLINE);
+}
+
+void SamHeaderValidator::AddWarning(const string& message) {
+    m_warningMessages.push_back(WARN_PREFIX + message + NEWLINE);
+}
+
+void SamHeaderValidator::PrintErrorMessages(ostream& stream) {
+
+    // skip if no error messages
+    if ( m_errorMessages.empty() )
+        return;
 
-    // validate header components
+    // print error header line
+    stream << "* SAM header has " << m_errorMessages.size() << " errors:" << endl;
+
+    // print each error message
+    vector<string>::const_iterator errorIter = m_errorMessages.begin();
+    vector<string>::const_iterator errorEnd  = m_errorMessages.end();
+    for ( ; errorIter != errorEnd; ++errorIter )
+        stream << (*errorIter);
+}
+
+void SamHeaderValidator::PrintMessages(ostream& stream) {
+    PrintErrorMessages(stream);
+    PrintWarningMessages(stream);
+}
+
+void SamHeaderValidator::PrintWarningMessages(ostream& stream) {
+
+    // skip if no warning messages
+    if ( m_warningMessages.empty() )
+        return;
+
+    // print warning header line
+    stream << "* SAM header has " << m_warningMessages.size() << " warnings:" << endl;
+
+    // print each warning message
+    vector<string>::const_iterator warnIter = m_warningMessages.begin();
+    vector<string>::const_iterator warnEnd  = m_warningMessages.end();
+    for ( ; warnIter != warnEnd; ++warnIter )
+        stream << (*warnIter);
+}
+
+// entry point for validation
+bool SamHeaderValidator::Validate(void) {
     bool isValid = true;
     isValid &= ValidateMetadata();
     isValid &= ValidateSequenceDictionary();
     isValid &= ValidateReadGroupDictionary();
-    isValid &= ValidateProgramData();
-
-    // report errors if desired
-    if ( m_isVerboseOutput ) {
-        PrintErrorMessages();
-        PrintWarningMessages();
-    }
-
-    // return validation status
+    isValid &= ValidateProgramChain();
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateMetadata(void) {
+// check all SAM header 'metadata'
+bool SamHeaderValidator::ValidateMetadata(void) {
     bool isValid = true;
     isValid &= ValidateVersion();
     isValid &= ValidateSortOrder();
@@ -142,7 +138,8 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateMetadata(void) {
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateVersion(void) {
+// check SAM header version tag
+bool SamHeaderValidator::ValidateVersion(void) {
 
     const string& version = m_header.Version;
 
@@ -181,12 +178,13 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateVersion(void) {
 }
 
 // assumes non-empty input string
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ContainsOnlyDigits(const string& s) {
+bool SamHeaderValidator::ContainsOnlyDigits(const string& s) {
     const size_t nonDigitPosition = s.find_first_not_of(Constants::SAM_DIGITS);
     return ( nonDigitPosition == string::npos ) ;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateSortOrder(void) {
+// validate SAM header sort order tag
+bool SamHeaderValidator::ValidateSortOrder(void) {
 
     const string& sortOrder = m_header.SortOrder;
 
@@ -201,35 +199,40 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateSortOrder(void) {
          sortOrder == Constants::SAM_HD_SORTORDER_QUERYNAME  ||
          sortOrder == Constants::SAM_HD_SORTORDER_UNSORTED
        )
-    { return true; }
+    {
+        return true;
+    }
 
     // otherwise
     AddError("Invalid sort order (SO): " + sortOrder);
     return false;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateGroupOrder(void) {
+// validate SAM header group order tag
+bool SamHeaderValidator::ValidateGroupOrder(void) {
 
     const string& groupOrder = m_header.GroupOrder;
 
     // if no group order, no problem, just return OK
-    if ( groupOrder.empty() ) return true;
+    if ( groupOrder.empty() )
+        return true;
 
     // if group order is valid keyword
     if ( groupOrder == Constants::SAM_HD_GROUPORDER_NONE  ||
          groupOrder == Constants::SAM_HD_GROUPORDER_QUERY ||
          groupOrder == Constants::SAM_HD_GROUPORDER_REFERENCE
        )
-    { return true; }
+    {
+        return true;
+    }
 
     // otherwise
     AddError("Invalid group order (GO): " + groupOrder);
     return false;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateSequenceDictionary(void) {
-
-    // TODO: warn/error if no sequences ?
+// validate SAM header sequence dictionary
+bool SamHeaderValidator::ValidateSequenceDictionary(void) {
 
     bool isValid = true;
 
@@ -249,7 +252,8 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateSequenceDictionary(v
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ContainsUniqueSequenceNames(void) {
+// make sure all SQ names are unique
+bool SamHeaderValidator::ContainsUniqueSequenceNames(void) {
 
     bool isValid = true;
     set<string> sequenceNames;
@@ -261,9 +265,9 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::ContainsUniqueSequenceNames(
     SamSequenceConstIterator seqEnd  = sequences.ConstEnd();
     for ( ; seqIter != seqEnd; ++seqIter ) {
         const SamSequence& seq = (*seqIter);
-        const string& name = seq.Name;
 
         // lookup sequence name
+        const string& name = seq.Name;
         nameIter = sequenceNames.find(name);
 
         // error if found (duplicate entry)
@@ -280,14 +284,16 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::ContainsUniqueSequenceNames(
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateSequence(const SamSequence& seq) {
+// validate SAM header sequence entry
+bool SamHeaderValidator::ValidateSequence(const SamSequence& seq) {
     bool isValid = true;
     isValid &= CheckNameFormat(seq.Name);
     isValid &= CheckLengthInRange(seq.Length);
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::CheckNameFormat(const string& name) {
+// check sequence name is valid format
+bool SamHeaderValidator::CheckNameFormat(const string& name) {
 
     // invalid if name is empty
     if ( name.empty() ) {
@@ -305,7 +311,8 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::CheckNameFormat(const string
     return true;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::CheckLengthInRange(const string& length) {
+// check that sequence length is within accepted range
+bool SamHeaderValidator::CheckLengthInRange(const string& length) {
 
     // invalid if empty
     if ( length.empty() ) {
@@ -328,9 +335,8 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::CheckLengthInRange(const str
     return true;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateReadGroupDictionary(void) {
-
-    // TODO: warn/error if no read groups ?
+// validate SAM header read group dictionary
+bool SamHeaderValidator::ValidateReadGroupDictionary(void) {
 
     bool isValid = true;
 
@@ -350,7 +356,8 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateReadGroupDictionary(
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ContainsUniqueIDsAndPlatformUnits(void) {
+// make sure RG IDs and platform units are unique
+bool SamHeaderValidator::ContainsUniqueIDsAndPlatformUnits(void) {
 
     bool isValid = true;
     set<string> readGroupIds;
@@ -402,14 +409,16 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::ContainsUniqueIDsAndPlatform
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateReadGroup(const SamReadGroup& rg) {
+// validate SAM header read group entry
+bool SamHeaderValidator::ValidateReadGroup(const SamReadGroup& rg) {
     bool isValid = true;
     isValid &= CheckReadGroupID(rg.ID);
     isValid &= CheckSequencingTechnology(rg.SequencingTechnology);
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::CheckReadGroupID(const string& id) {
+// make sure RG ID exists
+bool SamHeaderValidator::CheckReadGroupID(const string& id) {
 
     // invalid if empty
     if ( id.empty() ) {
@@ -421,125 +430,95 @@ bool SamHeaderValidator::SamHeaderValidatorPrivate::CheckReadGroupID(const strin
     return true;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::CheckSequencingTechnology(const string& technology) {
+// make sure RG sequencing tech is one of the accepted keywords
+bool SamHeaderValidator::CheckSequencingTechnology(const string& technology) {
 
     // if no technology provided, no problem, just return OK
-    if ( technology.empty() ) return true;
+    if ( technology.empty() )
+        return true;
 
     // if technology is valid keyword
-    if ( Is454(technology)      ||
-         IsHelicos(technology)  ||
-         IsIllumina(technology) ||
-         IsPacBio(technology)   ||
-         IsSolid(technology)
+    if ( caseInsensitiveCompare(technology, Constants::SAM_RG_SEQTECHNOLOGY_CAPILLARY)  ||
+         caseInsensitiveCompare(technology, Constants::SAM_RG_SEQTECHNOLOGY_HELICOS)    ||
+         caseInsensitiveCompare(technology, Constants::SAM_RG_SEQTECHNOLOGY_ILLUMINA)   ||
+         caseInsensitiveCompare(technology, Constants::SAM_RG_SEQTECHNOLOGY_IONTORRENT) ||
+         caseInsensitiveCompare(technology, Constants::SAM_RG_SEQTECHNOLOGY_LS454)      ||
+         caseInsensitiveCompare(technology, Constants::SAM_RG_SEQTECHNOLOGY_PACBIO)     ||
+         caseInsensitiveCompare(technology, Constants::SAM_RG_SEQTECHNOLOGY_SOLID)
        )
-    { return true; }
+    {
+        return true;
+    }
 
     // otherwise
     AddError("Invalid read group sequencing platform (PL): " + technology);
     return false;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::Is454(const string& technology) {
-    return ( technology == Constants::SAM_RG_SEQTECHNOLOGY_454 ||
-             technology == Constants::SAM_RG_SEQTECHNOLOGY_LS454_LOWER ||
-             technology == Constants::SAM_RG_SEQTECHNOLOGY_LS454_UPPER
-           );
-}
-
-bool SamHeaderValidator::SamHeaderValidatorPrivate::IsHelicos(const string& technology) {
-    return ( technology == Constants::SAM_RG_SEQTECHNOLOGY_HELICOS_LOWER ||
-             technology == Constants::SAM_RG_SEQTECHNOLOGY_HELICOS_UPPER
-           );
-}
-
-bool SamHeaderValidator::SamHeaderValidatorPrivate::IsIllumina(const string& technology) {
-    return ( technology == Constants::SAM_RG_SEQTECHNOLOGY_ILLUMINA_LOWER ||
-             technology == Constants::SAM_RG_SEQTECHNOLOGY_ILLUMINA_UPPER
-           );
-}
-
-bool SamHeaderValidator::SamHeaderValidatorPrivate::IsPacBio(const string& technology) {
-    return ( technology == Constants::SAM_RG_SEQTECHNOLOGY_PACBIO_LOWER ||
-             technology == Constants::SAM_RG_SEQTECHNOLOGY_PACBIO_UPPER
-           );
-}
-
-bool SamHeaderValidator::SamHeaderValidatorPrivate::IsSolid(const string& technology) {
-    return ( technology == Constants::SAM_RG_SEQTECHNOLOGY_SOLID_LOWER ||
-             technology == Constants::SAM_RG_SEQTECHNOLOGY_SOLID_UPPER
-           );
-}
-
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidateProgramData(void) {
+// validate the SAM header "program chain"
+bool SamHeaderValidator::ValidateProgramChain(void) {
     bool isValid = true;
     isValid &= ContainsUniqueProgramIds();
     isValid &= ValidatePreviousProgramIds();
     return isValid;
 }
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ContainsUniqueProgramIds(void) {
-    bool isValid = true;
-    // TODO: once we have ability to handle multiple @PG entries,
-    // check here for duplicate ID's
-    // but for now, just return true
-    return isValid;
-}
+// make sure all PG IDs are unique
+bool SamHeaderValidator::ContainsUniqueProgramIds(void) {
 
-bool SamHeaderValidator::SamHeaderValidatorPrivate::ValidatePreviousProgramIds(void) {
     bool isValid = true;
-    // TODO: check that PP entries are valid later, after we get multiple @PG-entry handling
-    // just return true for now
-    return isValid;
-}
-void SamHeaderValidator::SamHeaderValidatorPrivate::AddError(const string& message) {
-    m_errorMessages.push_back(ERROR_PREFIX + message + NEWLINE);
-}
+    set<string> programIds;
+    set<string>::iterator pgIdIter;
 
-void SamHeaderValidator::SamHeaderValidatorPrivate::AddWarning(const string& message) {
-    m_warningMessages.push_back(WARN_PREFIX + message + NEWLINE);
-}
+    // iterate over program records
+    const SamProgramChain& programs = m_header.Programs;
+    SamProgramConstIterator pgIter = programs.ConstBegin();
+    SamProgramConstIterator pgEnd  = programs.ConstEnd();
+    for ( ; pgIter != pgEnd; ++pgIter ) {
+        const SamProgram& pg = (*pgIter);
 
-void SamHeaderValidator::SamHeaderValidatorPrivate::PrintErrorMessages(void) {
+        // lookup program ID
+        const string& pgId = pg.ID;
+        pgIdIter = programIds.find(pgId);
 
-    // skip if no error messages
-    if ( m_errorMessages.empty() ) return;
+        // error if found (duplicate entry)
+        if ( pgIdIter != programIds.end() ) {
+            AddError("Program ID (ID): " + pgId + " is not unique");
+            isValid = false;
+        }
 
-    // print error header line
-    cerr << "* SAM header has " << m_errorMessages.size() << " errors:" << endl;
+        // otherwise ok, store ID
+        programIds.insert(pgId);
+    }
 
-    // print each error message
-    vector<string>::const_iterator errorIter = m_errorMessages.begin();
-    vector<string>::const_iterator errorEnd  = m_errorMessages.end();
-    for ( ; errorIter != errorEnd; ++errorIter )
-        cerr << (*errorIter);
+    // return validation state
+    return isValid;
 }
 
-void SamHeaderValidator::SamHeaderValidatorPrivate::PrintWarningMessages(void) {
-
-    // skip if no warning messages
-    if ( m_warningMessages.empty() ) return;
-
-    // print warning header line
-    cerr << "* SAM header has " << m_warningMessages.size() << " warnings:" << endl;
-
-    // print each warning message
-    vector<string>::const_iterator warnIter = m_warningMessages.begin();
-    vector<string>::const_iterator warnEnd  = m_warningMessages.end();
-    for ( ; warnIter != warnEnd; ++warnIter )
-        cerr << (*warnIter);
-}
+// make sure that any PP tags present point to existing @PG IDs
+bool SamHeaderValidator::ValidatePreviousProgramIds(void) {
 
-// -----------------------------------
-// SamHeaderValidator implementation
+    bool isValid = true;
 
-SamHeaderValidator::SamHeaderValidator(const BamTools::SamHeader& header)
-    : d( new SamHeaderValidatorPrivate(header) )
-{ }
+    // iterate over program records
+    const SamProgramChain& programs = m_header.Programs;
+    SamProgramConstIterator pgIter = programs.ConstBegin();
+    SamProgramConstIterator pgEnd  = programs.ConstEnd();
+    for ( ; pgIter != pgEnd; ++pgIter ) {
+        const SamProgram& pg = (*pgIter);
+
+        // ignore record for validation if PreviousProgramID is empty
+        const string& ppId = pg.PreviousProgramID;
+        if ( ppId.empty() )
+            continue;
+
+        // see if program "chain" contains an entry for ppId
+        if ( !programs.Contains(ppId) ) {
+            AddError("PreviousProgramID (PP): " + ppId + " is not a known ID");
+            isValid = false;
+        }
+    }
 
-SamHeaderValidator::~SamHeaderValidator(void) {
-    delete d;
-    d = 0;
+    // return validation state
+    return isValid;
 }
-
-bool SamHeaderValidator::Validate(bool verbose) { return d->Validate(verbose); }