/* * The MIT License * * Copyright (c) 2009 The Broad Institute * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package htsjdk.samtools; import htsjdk.samtools.util.StringLineReader; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Header information from a SAM or BAM file. */ public class SAMFileHeader extends AbstractSAMHeaderRecord { public static final String VERSION_TAG = "VN"; public static final String SORT_ORDER_TAG = "SO"; public static final String GROUP_ORDER_TAG = "GO"; public static final String CURRENT_VERSION = "1.4"; public static final Set<String> ACCEPTABLE_VERSIONS = new HashSet<String>(Arrays.asList("1.0", "1.3", "1.4")); /** * These tags are of known type, so don't need a type field in the text representation. */ public static final Set<String> STANDARD_TAGS = new HashSet<String>(Arrays.asList(VERSION_TAG, SORT_ORDER_TAG, GROUP_ORDER_TAG)); Set<String> getStandardTags() { return STANDARD_TAGS; } /** * Ways in which a SAM or BAM may be sorted. */ public enum SortOrder { unsorted(null), queryname(SAMRecordQueryNameComparator.class), coordinate(SAMRecordCoordinateComparator.class); private final Class<? extends SAMRecordComparator> comparator; SortOrder(final Class<? extends SAMRecordComparator> comparatorClass) { this.comparator = comparatorClass; } /** * @return Comparator class to sort in the specified order, or null if unsorted. */ public Class<? extends SAMRecordComparator> getComparator() { return comparator; } /** * @return Comparator to sort in the specified order, or null if unsorted. */ public SAMRecordComparator getComparatorInstance() { if (comparator != null) { try { final Constructor<? extends SAMRecordComparator> ctor = comparator.getConstructor(); return ctor.newInstance(); } catch (Exception e) { throw new IllegalStateException("Could not instantiate a comparator for sort order: " + this.name(), e); } } return null; } } public enum GroupOrder { none, query, reference } private List<SAMReadGroupRecord> mReadGroups = new ArrayList<SAMReadGroupRecord>(); private List<SAMProgramRecord> mProgramRecords = new ArrayList<SAMProgramRecord>(); private final Map<String, SAMReadGroupRecord> mReadGroupMap = new HashMap<String, SAMReadGroupRecord>(); private final Map<String, SAMProgramRecord> mProgramRecordMap = new HashMap<String, SAMProgramRecord>(); private SAMSequenceDictionary mSequenceDictionary = new SAMSequenceDictionary(); final private List<String> mComments = new ArrayList<String>(); private String textHeader; private final List<SAMValidationError> mValidationErrors = new ArrayList<SAMValidationError>(); public SAMFileHeader() { setAttribute(VERSION_TAG, CURRENT_VERSION); } public String getVersion() { return (String) getAttribute("VN"); } public String getCreator() { return (String) getAttribute("CR"); } public SAMSequenceDictionary getSequenceDictionary() { return mSequenceDictionary; } public List<SAMReadGroupRecord> getReadGroups() { return Collections.unmodifiableList(mReadGroups); } /** * Look up sequence record by name. */ public SAMSequenceRecord getSequence(final String name) { return mSequenceDictionary.getSequence(name); } /** * Look up read group record by name. */ public SAMReadGroupRecord getReadGroup(final String name) { return mReadGroupMap.get(name); } /** * Replace entire sequence dictionary. The given sequence dictionary is stored, not copied. */ public void setSequenceDictionary(final SAMSequenceDictionary sequenceDictionary) { mSequenceDictionary = sequenceDictionary; } public void addSequence(final SAMSequenceRecord sequenceRecord) { mSequenceDictionary.addSequence(sequenceRecord); } /** * Look up a sequence record by index. First sequence in the header is the 0th. * @return The corresponding sequence record, or null if the index is out of range. */ public SAMSequenceRecord getSequence(final int sequenceIndex) { return mSequenceDictionary.getSequence(sequenceIndex); } /** * * @return Sequence index for the given sequence name, or -1 if the name is not found. */ public int getSequenceIndex(final String sequenceName) { return mSequenceDictionary.getSequenceIndex(sequenceName); } /** * Replace entire list of read groups. The given list is stored, not copied. */ public void setReadGroups(final List<SAMReadGroupRecord> readGroups) { mReadGroups = readGroups; mReadGroupMap.clear(); for (final SAMReadGroupRecord readGroupRecord : readGroups) { mReadGroupMap.put(readGroupRecord.getReadGroupId(), readGroupRecord); } } public void addReadGroup(final SAMReadGroupRecord readGroup) { if (mReadGroupMap.containsKey(readGroup.getReadGroupId())) { throw new IllegalArgumentException("Read group with group id " + readGroup.getReadGroupId() + " already exists in SAMFileHeader!"); } mReadGroups.add(readGroup); mReadGroupMap.put(readGroup.getReadGroupId(), readGroup); } public List<SAMProgramRecord> getProgramRecords() { return Collections.unmodifiableList(mProgramRecords); } public void addProgramRecord(final SAMProgramRecord programRecord) { if (mProgramRecordMap.containsKey(programRecord.getProgramGroupId())) { throw new IllegalArgumentException("Program record with group id " + programRecord.getProgramGroupId() + " already exists in SAMFileHeader!"); } this.mProgramRecords.add(programRecord); this.mProgramRecordMap.put(programRecord.getProgramGroupId(), programRecord); } public SAMProgramRecord getProgramRecord(final String pgId) { return this.mProgramRecordMap.get(pgId); } /** * Replace entire list of program records * @param programRecords This list is used directly, not copied. */ public void setProgramRecords(final List<SAMProgramRecord> programRecords) { this.mProgramRecords = programRecords; this.mProgramRecordMap.clear(); for (final SAMProgramRecord programRecord : this.mProgramRecords) { this.mProgramRecordMap.put(programRecord.getProgramGroupId(), programRecord); } } /** * @return a new SAMProgramRecord with an ID guaranteed to not exist in this SAMFileHeader */ public SAMProgramRecord createProgramRecord() { for (int i = 0; i < Integer.MAX_VALUE; ++i) { final String s = Integer.toString(i); if (!this.mProgramRecordMap.containsKey(s)) { final SAMProgramRecord ret = new SAMProgramRecord(s); addProgramRecord(ret); return ret; } } throw new IllegalStateException("Surprising number of SAMProgramRecords"); } public SortOrder getSortOrder() { final String so = getAttribute("SO"); if (so == null || so.equals("unknown")) { return SortOrder.unsorted; } return SortOrder.valueOf((String) so); } public void setSortOrder(final SortOrder so) { setAttribute("SO", so.name()); } public GroupOrder getGroupOrder() { if (getAttribute("GO") == null) { return GroupOrder.none; } return GroupOrder.valueOf((String)getAttribute("GO")); } public void setGroupOrder(final GroupOrder go) { setAttribute("GO", go.name()); } /** * If this SAMHeader was read from a file, this property contains the header * as it appeared in the file, otherwise it is null. Note that this is not a toString() * operation. Changes to the SAMFileHeader object after reading from the file are not reflected in this value. * * In addition this value is only set if one of the following is true: * - The size of the header is < 1,048,576 characters (1MB ascii, 2MB unicode) * - There are either validation or parsing errors associated with the header * * Invalid header lines may appear in value but are not stored in the SAMFileHeader object. */ public String getTextHeader() { return textHeader; } public void setTextHeader(final String textHeader) { this.textHeader = textHeader; } public List<String> getComments() { return Collections.unmodifiableList(mComments); } public void addComment(String comment) { if (!comment.startsWith(SAMTextHeaderCodec.COMMENT_PREFIX)) { comment = SAMTextHeaderCodec.COMMENT_PREFIX + comment; } mComments.add(comment); } /** * Replace existing comments with the contents of the given collection. */ public void setComments(final Collection<String> comments) { mComments.clear(); for (final String comment : comments) { addComment(comment); } } public List<SAMValidationError> getValidationErrors() { return Collections.unmodifiableList(mValidationErrors); } public void addValidationError(final SAMValidationError error) { mValidationErrors.add(error); } /** * Replace list of validation errors with the elements of the given list. */ public void setValidationErrors(final Collection<SAMValidationError> errors) { mValidationErrors.clear(); mValidationErrors.addAll(errors); } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final SAMFileHeader that = (SAMFileHeader) o; if (!attributesEqual(that)) return false; if (mProgramRecords != null ? !mProgramRecords.equals(that.mProgramRecords) : that.mProgramRecords != null) return false; if (mReadGroups != null ? !mReadGroups.equals(that.mReadGroups) : that.mReadGroups != null) return false; if (mSequenceDictionary != null ? !mSequenceDictionary.equals(that.mSequenceDictionary) : that.mSequenceDictionary != null) return false; return true; } @Override public int hashCode() { int result = attributesHashCode(); result = 31 * result + (mSequenceDictionary != null ? mSequenceDictionary.hashCode() : 0); result = 31 * result + (mReadGroups != null ? mReadGroups.hashCode() : 0); result = 31 * result + (mProgramRecords != null ? mProgramRecords.hashCode() : 0); return result; } public final SAMFileHeader clone() { final SAMTextHeaderCodec codec = new SAMTextHeaderCodec(); codec.setValidationStringency(ValidationStringency.SILENT); final StringWriter stringWriter = new StringWriter(); codec.encode(stringWriter, this); return codec.decode(new StringLineReader(stringWriter.toString()), "SAMFileHeader.clone"); } }