1 package org.broadinstitute.hellbender.utils.read; 2 3 import htsjdk.samtools.*; 4 import htsjdk.samtools.util.Locatable; 5 import org.apache.commons.lang.ArrayUtils; 6 import org.broadinstitute.hellbender.exceptions.GATKException; 7 import org.broadinstitute.hellbender.utils.Utils; 8 9 import java.io.Serializable; 10 import java.nio.charset.Charset; 11 import java.util.Arrays; 12 import java.util.Collections; 13 import java.util.List; 14 import java.util.Objects; 15 16 /** 17 * Implementation of the {@link GATKRead} interface for the {@link SAMRecord} class. 18 * 19 * This adapter wraps a {@link SAMRecord} without making a copy, so construction is cheap, 20 * but care must be exercised if the underlying read has been exposed somewhere before 21 * wrapping. 22 */ 23 public class SAMRecordToGATKReadAdapter implements GATKRead, Serializable { 24 private static final long serialVersionUID = 1L; 25 26 private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); 27 28 private final SAMRecord samRecord; 29 30 private transient Integer cachedSoftStart = null; 31 private transient Integer cachedSoftEnd = null; 32 private transient Integer cachedAdaptorBoundary = null; 33 private transient Integer cachedCigarLength = null; 34 clearCachedValues()35 private void clearCachedValues() { 36 cachedSoftStart = null; 37 cachedSoftEnd = null; 38 cachedAdaptorBoundary = null; 39 cachedCigarLength = null; 40 } 41 getFlags()42 public int getFlags() { 43 return samRecord.getFlags(); 44 } 45 SAMRecordToGATKReadAdapter( final SAMRecord samRecord )46 public SAMRecordToGATKReadAdapter( final SAMRecord samRecord ) { 47 this.samRecord = samRecord; 48 } 49 50 /** 51 * Produces a SAMRecordToGATKReadAdapter wrapping the provided SAMRecord, 52 * and nulls out the header in the encapsulated read. This is useful for 53 * Spark tools, in order to avoid serializing the SAMFileHeader for each 54 * record. 55 * 56 * @param samRecord read to adapt (header will be stripped) 57 * @return SAMRecordToGATKReadAdapter wrapping the headerless samRecord 58 */ headerlessReadAdapter( final SAMRecord samRecord )59 public static SAMRecordToGATKReadAdapter headerlessReadAdapter( final SAMRecord samRecord ) { 60 samRecord.setHeaderStrict(null); 61 return new SAMRecordToGATKReadAdapter(samRecord); 62 } 63 64 @Override getName()65 public String getName() { 66 return samRecord.getReadName(); 67 } 68 69 @Override setName( final String name )70 public void setName( final String name ) { 71 clearCachedValues(); 72 samRecord.setReadName(name); 73 } 74 75 @Override getContig()76 public String getContig() { 77 if ( isUnmapped() ) { 78 return null; 79 } 80 81 // Guaranteed not to be null or SAMRecord.NO_ALIGNMENT_REFERENCE_NAME due to the isUnmapped() check above 82 return samRecord.getReferenceName(); 83 } 84 85 @Override getStart()86 public int getStart() { 87 if ( isUnmapped() ) { 88 return ReadConstants.UNSET_POSITION; 89 } 90 91 // Guaranteed not to be SAMRecord.NO_ALIGNMENT_START due to the isUnmapped() check above 92 return samRecord.getAlignmentStart(); 93 } 94 95 @Override getEnd()96 public int getEnd() { 97 if ( isUnmapped() ) { 98 return ReadConstants.UNSET_POSITION; 99 } 100 101 // Guaranteed not to be SAMRecord.NO_ALIGNMENT_START due to the isUnmapped() check above 102 return samRecord.getAlignmentEnd(); 103 } 104 105 @Override setPosition( final String contig, final int start )106 public void setPosition( final String contig, final int start ) { 107 assertPositionIsValid(contig, start); 108 109 clearCachedValues(); 110 samRecord.setReferenceName(contig); 111 samRecord.setAlignmentStart(start); 112 samRecord.setReadUnmappedFlag(false); 113 } 114 115 @Override setPosition( final Locatable locatable )116 public void setPosition( final Locatable locatable ) { 117 if ( locatable == null ) { 118 throw new IllegalArgumentException("Cannot set read position to null"); 119 } 120 121 setPosition(locatable.getContig(), locatable.getStart()); 122 } 123 124 @Override getAssignedContig()125 public String getAssignedContig() { 126 return samRecord.getReferenceName(); 127 } 128 129 @Override getAssignedStart()130 public int getAssignedStart() { 131 return samRecord.getAlignmentStart(); 132 } 133 134 @Override getUnclippedStart()135 public int getUnclippedStart() { 136 if ( isUnmapped() ) { 137 return ReadConstants.UNSET_POSITION; 138 } 139 140 return samRecord.getUnclippedStart(); 141 } 142 143 @Override getUnclippedEnd()144 public int getUnclippedEnd() { 145 if ( isUnmapped() ) { 146 return ReadConstants.UNSET_POSITION; 147 } 148 149 return samRecord.getUnclippedEnd(); 150 } 151 152 @Override getSoftStart()153 public int getSoftStart() { 154 if ( cachedSoftStart == null ) { 155 cachedSoftStart = ReadUtils.getSoftStart(this); 156 } 157 158 return cachedSoftStart; 159 } 160 161 @Override getSoftEnd()162 public int getSoftEnd() { 163 if ( cachedSoftEnd == null ) { 164 cachedSoftEnd = ReadUtils.getSoftEnd(this); 165 } 166 167 return cachedSoftEnd; 168 } 169 170 @Override getAdaptorBoundary()171 public int getAdaptorBoundary() { 172 if ( cachedAdaptorBoundary == null ) { 173 cachedAdaptorBoundary = ReadUtils.getAdaptorBoundary(this); 174 } 175 return cachedAdaptorBoundary; 176 } 177 178 @Override getMateContig()179 public String getMateContig() { 180 if ( mateIsUnmapped() ) { 181 return null; 182 } 183 184 return samRecord.getMateReferenceName(); 185 } 186 187 @Override getMateStart()188 public int getMateStart() { 189 if ( mateIsUnmapped() ) { 190 return ReadConstants.UNSET_POSITION; 191 } 192 193 return samRecord.getMateAlignmentStart(); 194 } 195 196 @Override setMatePosition( final String contig, final int start )197 public void setMatePosition( final String contig, final int start ) { 198 assertPositionIsValid(contig, start); 199 200 clearCachedValues(); 201 202 // Calling this method has the additional effect of marking the read as paired 203 setIsPaired(true); 204 205 samRecord.setMateReferenceName(contig); 206 samRecord.setMateAlignmentStart(start); 207 samRecord.setMateUnmappedFlag(false); 208 } 209 assertPositionIsValid(final String contig, final int start)210 private static void assertPositionIsValid(final String contig, final int start) { 211 if ( contig == null || contig.equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME) || start < 1 ) { 212 throw new IllegalArgumentException("contig must be non-null and not equal to " + SAMRecord.NO_ALIGNMENT_REFERENCE_NAME + ", and start must be >= 1" 213 + "\nContig: " + contig + "\nStart: " + start); 214 } 215 } 216 217 @Override setMatePosition( final Locatable locatable )218 public void setMatePosition( final Locatable locatable ) { 219 Utils.nonNull(locatable, "Cannot set mate position to null"); 220 221 setMatePosition(locatable.getContig(), locatable.getStart()); 222 } 223 224 @Override getFragmentLength()225 public int getFragmentLength() { 226 return samRecord.getInferredInsertSize(); 227 } 228 229 @Override setFragmentLength( final int fragmentLength )230 public void setFragmentLength( final int fragmentLength ) { 231 clearCachedValues(); 232 233 // May be negative if mate maps to lower position than read 234 samRecord.setInferredInsertSize(fragmentLength); 235 } 236 237 @Override getMappingQuality()238 public int getMappingQuality() { 239 return samRecord.getMappingQuality() != SAMRecord.NO_MAPPING_QUALITY ? samRecord.getMappingQuality() : ReadConstants.NO_MAPPING_QUALITY; 240 } 241 242 @Override setMappingQuality( final int mappingQuality )243 public void setMappingQuality( final int mappingQuality ) { 244 if ( mappingQuality < 0 || mappingQuality > 255 ) { 245 throw new IllegalArgumentException("mapping quality must be >= 0 and <= 255 \nMappingQuality: " + mappingQuality); 246 } 247 248 clearCachedValues(); 249 samRecord.setMappingQuality(mappingQuality); 250 } 251 252 @Override getBases()253 public byte[] getBases() { 254 final byte[] bases = samRecord.getReadBases(); 255 256 // Make a defensive copy to protect against direct modification of the returned array 257 return bases != null && bases.length > 0 ? Arrays.copyOf(bases, bases.length) : ArrayUtils.EMPTY_BYTE_ARRAY; 258 } 259 260 @Override getBasesNoCopy()261 public byte[] getBasesNoCopy() { 262 final byte[] bases = samRecord.getReadBases(); 263 return bases != null ? bases : ArrayUtils.EMPTY_BYTE_ARRAY; 264 } 265 266 //Overridden default method to avoid a call to getBases which makes a copy of data 267 //Bounds checking is the caller's responsibility, as it's too expensive in this hotspot method 268 @Override getBase(final int i)269 public byte getBase(final int i){ 270 final byte[] bases = samRecord.getReadBases(); 271 return bases[i]; 272 } 273 274 @Override getLength()275 public int getLength() { 276 final byte[] bases = samRecord.getReadBases(); 277 return bases == null ? 0 : bases.length; 278 } 279 280 @Override setBases( final byte[] bases )281 public void setBases( final byte[] bases ) { 282 clearCachedValues(); 283 samRecord.setReadBases(bases); 284 } 285 286 @Override getBaseQualities()287 public byte[] getBaseQualities() { 288 final byte[] baseQualities = samRecord.getBaseQualities(); 289 290 // Make a defensive copy to protect against direct modification of the returned array 291 return baseQualities != null && baseQualities.length > 0 ? Arrays.copyOf(baseQualities, baseQualities.length) : ArrayUtils.EMPTY_BYTE_ARRAY; 292 } 293 294 @Override getBaseQualitiesNoCopy()295 public byte[] getBaseQualitiesNoCopy() { 296 final byte[] baseQualities = samRecord.getBaseQualities(); 297 return baseQualities != null ? baseQualities : ArrayUtils.EMPTY_BYTE_ARRAY; 298 } 299 300 @Override getBaseQualityCount()301 public int getBaseQualityCount(){ 302 final byte[] baseQualities = samRecord.getBaseQualities(); 303 return baseQualities == null ? 0 : baseQualities.length; 304 } 305 306 //Overridden default method to avoid a call to getBaseQualities which makes a copy of data 307 //Bounds checking is the caller's responsibility, as it's too expensive in this hotspot method 308 @Override getBaseQuality(final int i)309 public byte getBaseQuality(final int i) { 310 final byte[] baseQualities = samRecord.getBaseQualities(); 311 return baseQualities[i]; 312 } 313 314 @Override setBaseQualities( final byte[] baseQualities )315 public void setBaseQualities( final byte[] baseQualities ) { 316 if ( baseQualities != null ) { 317 for ( byte b : baseQualities ) { 318 if ( b < 0 ) { 319 throw new IllegalArgumentException("Base quality score " + b + " is invalid"); 320 } 321 } 322 } 323 324 clearCachedValues(); 325 samRecord.setBaseQualities(baseQualities); 326 } 327 328 @Override getCigar()329 public Cigar getCigar() { 330 // Make a defensive copy before returning to guard against modification of the return value, 331 // since Cigar is a mutable type: 332 return samRecord.getCigar() != null ? new Cigar(samRecord.getCigar().getCigarElements()) : new Cigar(); 333 } 334 335 /** 336 * This implementation does not make a new Cigar object but instead provides 337 * an unmodifiable view of the underlying list of CigarElements. 338 * This is done to reduce the amount of object allocation. 339 */ 340 @Override getCigarElements()341 public List<CigarElement> getCigarElements(){ 342 //Cigar.getCigarElements returns an unmodifiable list so we don't wrap it again 343 return samRecord.getCigar() == null ? Collections.emptyList() : samRecord.getCigar().getCigarElements(); 344 } 345 346 /** 347 * This implementation avoids the creation of the unmodifiable view of the underlying list of CigarElements 348 * and simply retrieves the element that is requested. 349 * 350 * Bounds checking is the caller's responsibility, as it's too expensive in this hotspot method. 351 */ 352 @Override getCigarElement(final int index)353 public CigarElement getCigarElement(final int index) { 354 return samRecord.getCigar().getCigarElement(index); 355 } 356 357 /** 358 * This implementation saves time by not making an unmodifiable view of the list of 359 * elements but returns the length of the list directly (or 0 if there's no cigar). 360 */ 361 @Override numCigarElements()362 public int numCigarElements() { 363 // It's surprising and bizarre, but profiling reveals that caching the cigar length 364 // actually helps performance in some cases (eg., the HaplotypeCaller) 365 if ( cachedCigarLength == null ) { 366 cachedCigarLength = samRecord.getCigar() == null ? 0 : samRecord.getCigarLength(); 367 } 368 return cachedCigarLength; 369 } 370 371 @Override setCigar( final Cigar cigar )372 public void setCigar( final Cigar cigar ) { 373 clearCachedValues(); 374 samRecord.setCigar(cigar); 375 } 376 377 @Override setCigar( final String cigarString )378 public void setCigar( final String cigarString ) { 379 clearCachedValues(); 380 samRecord.setCigarString(cigarString); 381 } 382 383 @Override getReadGroup()384 public String getReadGroup() { 385 // May return null 386 return (String)samRecord.getAttribute(SAMTag.RG.getBinaryTag()); 387 } 388 389 @Override setReadGroup( final String readGroupID )390 public void setReadGroup( final String readGroupID ) { 391 clearCachedValues(); 392 samRecord.setAttribute(SAMTag.RG.name(), readGroupID); 393 } 394 395 @Override isPaired()396 public boolean isPaired() { 397 return samRecord.getReadPairedFlag(); 398 } 399 400 @Override setIsPaired( final boolean isPaired )401 public void setIsPaired( final boolean isPaired ) { 402 clearCachedValues(); 403 404 samRecord.setReadPairedFlag(isPaired); 405 if ( ! isPaired ) { 406 samRecord.setProperPairFlag(false); 407 } 408 } 409 410 @Override isProperlyPaired()411 public boolean isProperlyPaired() { 412 return isPaired() && samRecord.getProperPairFlag(); 413 } 414 415 @Override setIsProperlyPaired( final boolean isProperlyPaired )416 public void setIsProperlyPaired( final boolean isProperlyPaired ) { 417 clearCachedValues(); 418 419 if ( isProperlyPaired ) { 420 setIsPaired(true); 421 } 422 423 samRecord.setProperPairFlag(isProperlyPaired); 424 } 425 426 @Override isUnmapped()427 public boolean isUnmapped() { 428 return samRecord.getReadUnmappedFlag() || 429 samRecord.getReferenceName() == null || samRecord.getReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME) || 430 samRecord.getAlignmentStart() == SAMRecord.NO_ALIGNMENT_START; 431 } 432 433 @Override isUnplaced()434 public boolean isUnplaced() { 435 return samRecord.getReferenceName() == null || samRecord.getReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME) || 436 samRecord.getAlignmentStart() == SAMRecord.NO_ALIGNMENT_START; 437 } 438 439 @Override setIsUnmapped()440 public void setIsUnmapped() { 441 clearCachedValues(); 442 443 samRecord.setReadUnmappedFlag(true); 444 } 445 446 447 @Override setIsUnplaced()448 public void setIsUnplaced() { 449 clearCachedValues(); 450 451 samRecord.setReadUnmappedFlag(true); 452 samRecord.setReferenceIndex(SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX); 453 samRecord.setAlignmentStart(SAMRecord.NO_ALIGNMENT_START); 454 samRecord.setMappingQuality(SAMRecord.NO_MAPPING_QUALITY); 455 } 456 457 @Override mateIsUnmapped()458 public boolean mateIsUnmapped() { 459 Utils.validate(isPaired(), "Cannot get mate information for an unpaired read"); 460 461 return samRecord.getMateUnmappedFlag() || 462 samRecord.getMateReferenceName() == null || samRecord.getMateReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME) || 463 samRecord.getMateAlignmentStart() == SAMRecord.NO_ALIGNMENT_START; 464 } 465 466 @Override mateIsUnplaced()467 public boolean mateIsUnplaced() { 468 Utils.validate(isPaired(), "Cannot get mate information for an unpaired read"); 469 470 return samRecord.getMateReferenceName() == null || samRecord.getMateReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME) || 471 samRecord.getMateAlignmentStart() == SAMRecord.NO_ALIGNMENT_START; 472 } 473 474 @Override setMateIsUnmapped()475 public void setMateIsUnmapped() { 476 clearCachedValues(); 477 478 // Calling this method has the side effect of marking the read as paired. 479 setIsPaired(true); 480 481 samRecord.setMateUnmappedFlag(true); 482 } 483 484 @Override setMateIsUnplaced()485 public void setMateIsUnplaced() { 486 clearCachedValues(); 487 488 // Calling this method has the side effect of marking the read as paired. 489 setIsPaired(true); 490 491 samRecord.setMateUnmappedFlag(true); 492 samRecord.setMateNegativeStrandFlag(false); 493 samRecord.setMateReferenceIndex(SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX); 494 samRecord.setMateAlignmentStart(SAMRecord.NO_ALIGNMENT_START); 495 } 496 497 @Override isReverseStrand()498 public boolean isReverseStrand() { 499 return samRecord.getReadNegativeStrandFlag(); 500 } 501 502 @Override setIsReverseStrand( final boolean isReverseStrand )503 public void setIsReverseStrand( final boolean isReverseStrand ) { 504 clearCachedValues(); 505 506 samRecord.setReadNegativeStrandFlag(isReverseStrand); 507 } 508 509 @Override mateIsReverseStrand()510 public boolean mateIsReverseStrand() { 511 Utils.validate(isPaired(), "Cannot get mate information for an unpaired read"); 512 513 return samRecord.getMateNegativeStrandFlag(); 514 } 515 516 @Override setMateIsReverseStrand( final boolean mateIsReverseStrand )517 public void setMateIsReverseStrand( final boolean mateIsReverseStrand ) { 518 clearCachedValues(); 519 520 // Calling this method has the side effect of marking the read as paired. 521 setIsPaired(true); 522 523 samRecord.setMateNegativeStrandFlag(mateIsReverseStrand); 524 } 525 526 @Override isFirstOfPair()527 public boolean isFirstOfPair() { 528 return isPaired() && samRecord.getFirstOfPairFlag(); 529 } 530 531 @Override setIsFirstOfPair()532 public void setIsFirstOfPair() { 533 clearCachedValues(); 534 535 // Calling this method has the side effect of marking the read as paired. 536 setIsPaired(true); 537 538 samRecord.setFirstOfPairFlag(true); 539 samRecord.setSecondOfPairFlag(false); 540 } 541 542 @Override isSecondOfPair()543 public boolean isSecondOfPair() { 544 return isPaired() && samRecord.getSecondOfPairFlag(); 545 } 546 547 @Override setIsSecondOfPair()548 public void setIsSecondOfPair() { 549 clearCachedValues(); 550 551 // Calling this method has the side effect of marking the read as paired. 552 setIsPaired(true); 553 554 samRecord.setSecondOfPairFlag(true); 555 samRecord.setFirstOfPairFlag(false); 556 } 557 558 @Override isSecondaryAlignment()559 public boolean isSecondaryAlignment() { 560 return samRecord.isSecondaryAlignment(); 561 } 562 563 @Override setIsSecondaryAlignment( final boolean isSecondaryAlignment )564 public void setIsSecondaryAlignment( final boolean isSecondaryAlignment ) { 565 clearCachedValues(); 566 567 samRecord.setSecondaryAlignment(isSecondaryAlignment); 568 } 569 570 @Override isSupplementaryAlignment()571 public boolean isSupplementaryAlignment() { 572 return samRecord.getSupplementaryAlignmentFlag(); 573 } 574 575 @Override setIsSupplementaryAlignment( final boolean isSupplementaryAlignment )576 public void setIsSupplementaryAlignment( final boolean isSupplementaryAlignment ) { 577 clearCachedValues(); 578 579 samRecord.setSupplementaryAlignmentFlag(isSupplementaryAlignment); 580 } 581 582 @Override failsVendorQualityCheck()583 public boolean failsVendorQualityCheck() { 584 return samRecord.getReadFailsVendorQualityCheckFlag(); 585 } 586 587 @Override setFailsVendorQualityCheck( final boolean failsVendorQualityCheck )588 public void setFailsVendorQualityCheck( final boolean failsVendorQualityCheck ) { 589 clearCachedValues(); 590 591 samRecord.setReadFailsVendorQualityCheckFlag(failsVendorQualityCheck); 592 } 593 594 @Override isDuplicate()595 public boolean isDuplicate() { 596 return samRecord.getDuplicateReadFlag(); 597 } 598 599 @Override setIsDuplicate( final boolean isDuplicate )600 public void setIsDuplicate( final boolean isDuplicate ) { 601 clearCachedValues(); 602 603 samRecord.setDuplicateReadFlag(isDuplicate); 604 } 605 606 @Override hasAttribute( final String attributeName )607 public boolean hasAttribute( final String attributeName ) { 608 ReadUtils.assertAttributeNameIsLegal(attributeName); 609 return samRecord.getAttribute(attributeName) != null; 610 } 611 612 @Override getAttributeAsInteger( final String attributeName )613 public Integer getAttributeAsInteger( final String attributeName ) { 614 ReadUtils.assertAttributeNameIsLegal(attributeName); 615 final Object attributeValue = samRecord.getAttribute(attributeName); 616 617 if ( attributeValue == null ) { 618 return null; 619 } 620 else if ( attributeValue instanceof Integer ) { 621 return (Integer)attributeValue; 622 } 623 else { 624 try { 625 return Integer.parseInt(attributeValue.toString()); 626 } 627 catch ( NumberFormatException e ) { 628 throw new GATKException.ReadAttributeTypeMismatch(attributeName, "integer", e); 629 } 630 } 631 } 632 633 @Override getAttributeAsString( final String attributeName )634 public String getAttributeAsString( final String attributeName ) { 635 ReadUtils.assertAttributeNameIsLegal(attributeName); 636 final Object attributeValue = samRecord.getAttribute(attributeName); 637 if ( attributeValue instanceof byte[]) { 638 // in case that the attribute is a byte[] array, the toString method will format it as name@hashCode 639 // for a good representation of the byte[] as String, it encodes the bytes with the default charset (UTF-8) 640 final byte[] val = (byte[]) attributeValue; 641 return (val.length == 0) ? "" : new String(val, DEFAULT_CHARSET); 642 } 643 // otherwise, just use the toString() method unless it is null 644 return attributeValue != null ? attributeValue.toString() : null; 645 } 646 647 @Override getAttributeAsByteArray( final String attributeName )648 public byte[] getAttributeAsByteArray( final String attributeName ) { 649 ReadUtils.assertAttributeNameIsLegal(attributeName); 650 final Object attributeValue = samRecord.getAttribute(attributeName); 651 652 if ( attributeValue == null ) { 653 return null; 654 } 655 else if ( attributeValue instanceof byte[] ) { 656 // In the case where the attribute value is already a byte[], make a defensive 657 // copy before returning to guard against modification of the return value. 658 final byte[] ret = (byte[])attributeValue; 659 return Arrays.copyOf(ret, ret.length); 660 } 661 else if ( attributeValue instanceof String ) { 662 return ((String)attributeValue).getBytes(DEFAULT_CHARSET); 663 } 664 else { 665 throw new GATKException.ReadAttributeTypeMismatch(attributeName, "byte array"); 666 } 667 } 668 669 @Override setAttribute( final String attributeName, final Integer attributeValue )670 public void setAttribute( final String attributeName, final Integer attributeValue ) { 671 ReadUtils.assertAttributeNameIsLegal(attributeName); 672 673 clearCachedValues(); 674 samRecord.setAttribute(attributeName, attributeValue); 675 } 676 677 @Override setAttribute( final String attributeName, final String attributeValue )678 public void setAttribute( final String attributeName, final String attributeValue ) { 679 ReadUtils.assertAttributeNameIsLegal(attributeName); 680 681 clearCachedValues(); 682 samRecord.setAttribute(attributeName, attributeValue); 683 } 684 685 @Override setAttribute( final String attributeName, final byte[] attributeValue )686 public void setAttribute( final String attributeName, final byte[] attributeValue ) { 687 ReadUtils.assertAttributeNameIsLegal(attributeName); 688 689 clearCachedValues(); 690 samRecord.setAttribute(attributeName, attributeValue); 691 } 692 693 @Override clearAttribute( final String attributeName )694 public void clearAttribute( final String attributeName ) { 695 ReadUtils.assertAttributeNameIsLegal(attributeName); 696 697 clearCachedValues(); 698 samRecord.setAttribute(attributeName, null); 699 } 700 701 @Override clearAttributes()702 public void clearAttributes() { 703 clearCachedValues(); 704 705 samRecord.clearAttributes(); 706 } 707 708 @Override clearTransientAttribute( final String attributeName )709 public void clearTransientAttribute( final String attributeName ) { 710 clearCachedValues(); 711 samRecord.removeTransientAttribute(attributeName); 712 } 713 714 @Override copy()715 public GATKRead copy() { 716 // Produces a shallow but "safe to use" copy. 717 return new SAMRecordToGATKReadAdapter(ReadUtils.cloneSAMRecord(samRecord)); 718 } 719 720 @Override deepCopy()721 public GATKRead deepCopy() { 722 // Produces a true deep copy. 723 return new SAMRecordToGATKReadAdapter(samRecord.deepCopy()); 724 } 725 726 @Override getSAMString()727 public String getSAMString() { 728 return samRecord.getSAMString(); 729 } 730 731 @Override reverseComplement()732 public void reverseComplement() { 733 clearCachedValues(); 734 735 samRecord.reverseComplement(true); 736 } 737 738 @Override convertToSAMRecord( final SAMFileHeader header )739 public SAMRecord convertToSAMRecord( final SAMFileHeader header ) { 740 samRecord.setHeaderStrict(header); 741 return samRecord; 742 } 743 getEncapsulatedSamRecord()744 public SAMRecord getEncapsulatedSamRecord() { 745 return samRecord; 746 } 747 748 @Override equals(Object o)749 public boolean equals(Object o) { 750 if (this == o) return true; 751 if (o == null || getClass() != o.getClass()) return false; 752 753 SAMRecordToGATKReadAdapter that = (SAMRecordToGATKReadAdapter) o; 754 755 return Objects.equals(samRecord, that.samRecord); 756 757 } 758 759 @Override hashCode()760 public int hashCode() { 761 return Objects.hashCode(samRecord); 762 } 763 764 @Override toString()765 public String toString() { 766 return commonToString(); 767 } 768 hasHeader()769 public boolean hasHeader() { 770 return samRecord.getHeader() != null; 771 } 772 setHeader(SAMFileHeader header)773 public void setHeader(SAMFileHeader header) { 774 clearCachedValues(); 775 776 samRecord.setHeaderStrict(header); 777 } 778 779 /** 780 * This is used to access the transient attribute store in the underlying SAMRecord. 781 * 782 * NOTE: This is an advanced use case for SAMRecord and you should probably use setAttribute() instead 783 * @param key key whose value is to be retrived 784 */ getTransientAttribute(Object key)785 public Object getTransientAttribute(Object key) { 786 return samRecord.getTransientAttribute(key); 787 } 788 789 /** 790 * This is used to access the transient attribute store in the underlying SAMRecord. This is used to store temporary 791 * attributes that will not be serialized and that do not trigger the SAMRecord to parse the attributes if they are not needed. 792 * 793 * NOTE: This is an advanced use case for SAMRecord and you should probably use setAttribute() instead 794 * @param key key under which the value will be stored 795 * @param value value to be keyed 796 */ setTransientAttribute(Object key, Object value)797 public void setTransientAttribute(Object key, Object value) { 798 samRecord.setTransientAttribute(key, value); 799 } 800 } 801