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