Skip to content

Commit

Permalink
Merge pull request #417 from lindenb/xcigar
Browse files Browse the repository at this point in the history
added utilities to Cigar*
  • Loading branch information
Yossi Farjoun committed Jan 6, 2016
2 parents 6e7bd24 + 1589943 commit 2c43dcf
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 8 deletions.
85 changes: 77 additions & 8 deletions src/java/htsjdk/samtools/Cigar.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
Expand All @@ -37,7 +38,7 @@
*
* c.f. http://samtools.sourceforge.net/SAM1.pdf for complete CIGAR specification.
*/
public class Cigar implements Serializable {
public class Cigar implements Serializable, Iterable<CigarElement> {
public static final long serialVersionUID = 1L;

private final List<CigarElement> cigarElements = new ArrayList<CigarElement>();
Expand Down Expand Up @@ -82,6 +83,8 @@ public int getReferenceLength() {
case EQ:
case X:
length += element.getLength();
break;
default: break;
}
}
return length;
Expand All @@ -101,6 +104,8 @@ public int getPaddedReferenceLength() {
case X:
case P:
length += element.getLength();
break;
default: break;
}
}
return length;
Expand Down Expand Up @@ -236,15 +241,15 @@ private static boolean isRealOperator(final CigarOperator op) {
}

private static boolean isInDelOperator(final CigarOperator op) {
return op == CigarOperator.I || op == CigarOperator.D;
return op !=null && op.isIndel();
}

private static boolean isClippingOperator(final CigarOperator op) {
return op == CigarOperator.S || op == CigarOperator.H;
return op !=null && op.isClipping();
}

private static boolean isPaddingOperator(final CigarOperator op) {
return op == CigarOperator.P;
return op !=null && op.isPadding();
}

@Override
Expand All @@ -254,15 +259,79 @@ public boolean equals(final Object o) {

final Cigar cigar = (Cigar) o;

if (cigarElements != null ? !cigarElements.equals(cigar.cigarElements) : cigar.cigarElements != null)
return false;
return cigarElements.equals(cigar.cigarElements);
}

/** build a new Cigar object from a list of cigar operators.
* This can be used if you have the operators associated to
* each base in the read.
*
* e.g: read length =10 with cigar= <code>[M,M,M,M,M,M,M,M,M,M]</code>, here
* fromCigarOperators would generate the cigar '10M'
*
* later the user resolved the 'M' to '=' or 'X', the array is now
*
* <code>[=,=,=,=,=,X,X,=,=,=]</code>
*
* fromCigarOperators would generate the cigar '5M2X3M'
*
* */
public static Cigar fromCigarOperators(final List<CigarOperator> cigarOperators) {
if (cigarOperators == null) throw new IllegalArgumentException("cigarOperators is null");
final List<CigarElement> cigarElementList = new ArrayList<>();
int i = 0;
// find adjacent operators and build list of cigar elements
while (i < cigarOperators.size() ) {
final CigarOperator currentOp = cigarOperators.get(i);
int j = i + 1;
while (j < cigarOperators.size() && cigarOperators.get(j).equals(currentOp)) {
j++;
}
cigarElementList.add(new CigarElement(j - i, currentOp));
i = j;
}
return new Cigar(cigarElementList);
}

/** shortcut to <code>getCigarElements().iterator()</code> */
@Override
public Iterator<CigarElement> iterator() {
return this.getCigarElements().iterator();
}

/** returns true if the cigar string contains the given operator */
public boolean containsOperator(final CigarOperator operator) {
return this.cigarElements.stream().anyMatch( element -> element.getOperator() == operator);
}

/** returns the first cigar element */
public CigarElement getFirstCigarElement() {
return isEmpty() ? null : this.cigarElements.get(0);
}

/** returns the last cigar element */
public CigarElement getLastCigarElement() {
return isEmpty() ? null : this.cigarElements.get(this.numCigarElements() - 1 );
}

/** returns true if the cigar string starts With a clipping operator */
public boolean isLeftClipped() {
return !isEmpty() && isClippingOperator(getFirstCigarElement().getOperator());
}

return true;
/** returns true if the cigar string ends With a clipping operator */
public boolean isRightClipped() {
return !isEmpty() && isClippingOperator(getLastCigarElement().getOperator());
}

/** returns true if the cigar is clipped */
public boolean isClipped() {
return isLeftClipped() || isRightClipped();
}

@Override
public int hashCode() {
return cigarElements != null ? cigarElements.hashCode() : 0;
return cigarElements.hashCode();
}

public String toString() {
Expand Down
5 changes: 5 additions & 0 deletions src/java/htsjdk/samtools/CigarElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,9 @@ public int hashCode() {
result = 31 * result + (operator != null ? operator.hashCode() : 0);
return result;
}

@Override
public String toString() {
return String.valueOf(this.length)+this.operator;
}
}
25 changes: 25 additions & 0 deletions src/java/htsjdk/samtools/CigarOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,31 @@ public static byte enumToCharacter(final CigarOperator e) {
return e.character;
}

/** Returns true if the operator is a clipped (hard or soft) operator */
public boolean isClipping() {
return this == S || this == H;
}

/** Returns true if the operator is a Insertion or Deletion operator */
public boolean isIndel() {
return this == I || this == D;
}

/** Returns true if the operator is a Skipped Region Insertion or Deletion operator */
public boolean isIndelOrSkippedRegion() {
return this == N || isIndel();
}

/** Returns true if the operator is a M, a X or a EQ */
public boolean isAlignment() {
return this == M || this == X || this == EQ;
}

/** Returns true if the operator is a Padding operator */
public boolean isPadding() {
return this == P;
}

/** Returns the cigar operator as it would be seen in a SAM file. */
@Override public String toString() {
return this.string;
Expand Down
24 changes: 24 additions & 0 deletions src/tests/java/htsjdk/samtools/CigarTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.List;

/**
Expand Down Expand Up @@ -91,4 +92,27 @@ public void testNegative(final String cigar, final SAMValidationError.Type type)
Assert.assertEquals(errors.size(), 1, String.format("Got %d error, expected exactly one error.", errors.size()));
Assert.assertEquals(errors.get(0).getType(), type);
}

@Test
public void testMakeCigarFromOperators() {
final List<CigarOperator> cigarOperators = Arrays.asList(
CigarOperator.S,
CigarOperator.M,
CigarOperator.M,
CigarOperator.M,
CigarOperator.I,
CigarOperator.M,
CigarOperator.D,
CigarOperator.M
);
final Cigar cigar = Cigar.fromCigarOperators(cigarOperators);
Assert.assertFalse(cigar.isEmpty());
Assert.assertEquals(cigar.numCigarElements(), 6);
Assert.assertEquals(cigar.toString(),"1S3M1I1M1D1M");
Assert.assertFalse(cigar.containsOperator(CigarOperator.N));
Assert.assertTrue(cigar.containsOperator(CigarOperator.D));
Assert.assertTrue(cigar.isLeftClipped());
Assert.assertFalse(cigar.isRightClipped());
Assert.assertTrue(cigar.isClipped());
}
}

0 comments on commit 2c43dcf

Please sign in to comment.