-
Notifications
You must be signed in to change notification settings - Fork 243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding support for 0-length B arrays in SAM files to conform to 1.6 spec #1194
Changes from 12 commits
06dd1c1
744d7c6
306c309
8aabba8
14ff9e4
ef8c7a2
5696fb4
133370a
f7c46fb
ebf496f
9b7476f
37630eb
98ceb53
5d591c3
1d41109
2306e3f
fbfdd79
c1e19ec
8bd2258
31cc9ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,14 @@ public class TextTagCodec { | |
// 3 fields for non-empty strings 2 fields if the string is empty. | ||
private static final int NUM_TAG_FIELDS = 3; | ||
|
||
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; | ||
private static final TagValueAndUnsignedArrayFlag EMPTY_UNSIGNED_BYTE_ARRAY = new TagValueAndUnsignedArrayFlag(EMPTY_BYTE_ARRAY, true); | ||
private static final short[] EMPTY_SHORT_ARRAY = new short[0]; | ||
private static final TagValueAndUnsignedArrayFlag EMPTY_UNSIGNED_SHORT_ARRAY = new TagValueAndUnsignedArrayFlag(EMPTY_SHORT_ARRAY, true); | ||
private static final int[] EMPTY_INT_ARRAY = new int[0]; | ||
private static final TagValueAndUnsignedArrayFlag EMPTY_UNSIGNED_INT_ARRAY = new TagValueAndUnsignedArrayFlag(EMPTY_INT_ARRAY, true); | ||
private static final float[] EMPTY_FLOAT_ARRAY = new float[0]; | ||
|
||
/** | ||
* This is really a local variable of decode(), but allocated here to reduce allocations. | ||
*/ | ||
|
@@ -52,7 +60,7 @@ public class TextTagCodec { | |
/** | ||
* Convert in-memory representation of tag to SAM text representation. | ||
* @param tagName Two-character tag name. | ||
* @param value Tag value as approriate Object subclass. | ||
* @param value Tag value as appropriate Object subclass. | ||
* @return SAM text String representation, i.e. name:type:value | ||
*/ | ||
public String encode(final String tagName, Object value) { | ||
|
@@ -71,7 +79,7 @@ public String encode(final String tagName, Object value) { | |
// H should never happen anymore. | ||
value = StringUtil.bytesToHexString((byte[])value); | ||
} else if (tagType == 'B') { | ||
value = getArrayType(value, false) + "," + encodeArrayValue(value); | ||
value = getArrayType(value, false) + encodeArrayValue(value); | ||
} else if (tagType == 'i') { | ||
final long longVal = ((Number) value).longValue(); | ||
// as the spec says: [-2^31, 2^32) | ||
|
@@ -83,7 +91,7 @@ public String encode(final String tagName, Object value) { | |
return sb.toString(); | ||
} | ||
|
||
private char getArrayType(final Object array, final boolean isUnsigned) { | ||
private static char getArrayType(final Object array, final boolean isUnsigned) { | ||
final char type; | ||
final Class<?> componentType = array.getClass().getComponentType(); | ||
if (componentType == Float.TYPE) { | ||
|
@@ -97,18 +105,18 @@ private char getArrayType(final Object array, final boolean isUnsigned) { | |
return (isUnsigned? Character.toUpperCase(type): type); | ||
} | ||
|
||
private String encodeArrayValue(final Object value) { | ||
final StringBuilder ret = new StringBuilder(Array.get(value, 0).toString()); | ||
private static String encodeArrayValue(final Object value) { | ||
final int length = Array.getLength(value); | ||
for (int i = 1; i < length; ++i) { | ||
final StringBuilder ret = new StringBuilder(); | ||
for (int i = 0; i < length; ++i) { | ||
ret.append(','); | ||
ret.append(Array.get(value, i).toString()); | ||
} | ||
return ret.toString(); | ||
|
||
} | ||
|
||
private long[] widenToUnsigned(final Object array) { | ||
private static long[] widenToUnsigned(final Object array) { | ||
final Class<?> componentType = array.getClass().getComponentType(); | ||
final long mask; | ||
if (componentType == Byte.TYPE) mask = 0xffL; | ||
|
@@ -127,7 +135,7 @@ String encodeUnsignedArray(final String tagName, final Object array) { | |
throw new IllegalArgumentException("Non-array passed to encodeUnsignedArray: " + array.getClass()); | ||
} | ||
final long[] widened = widenToUnsigned(array); | ||
return tagName + ":B:" + getArrayType(array, true) + "," + encodeArrayValue(widened); | ||
return tagName + ":B:" + getArrayType(array, true) + encodeArrayValue(widened); | ||
} | ||
|
||
/** | ||
|
@@ -175,7 +183,7 @@ public Object setValue(final Object o) { | |
}; | ||
} | ||
|
||
private Object convertStringToObject(final String type, final String stringVal) { | ||
private static Object convertStringToObject(final String type, final String stringVal) { | ||
if (type.equals("Z")) { | ||
return stringVal; | ||
} else if (type.equals("A")) { | ||
|
@@ -219,15 +227,20 @@ else if (SAMUtils.isValidUnsignedIntegerAttribute(lValue)) { | |
} | ||
} | ||
|
||
private Object covertStringArrayToObject(final String stringVal) { | ||
private static Object covertStringArrayToObject(final String stringVal) { | ||
final String[] elementTypeAndValue = new String[2]; | ||
if (StringUtil.splitConcatenateExcessTokens(stringVal, elementTypeAndValue, ',') != 2) { | ||
throw new SAMFormatException("Tag of type B should have an element type followed by comma"); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove NL |
||
final int numberOfTokens = StringUtil.splitConcatenateExcessTokens(stringVal, elementTypeAndValue, ','); | ||
|
||
if (elementTypeAndValue[0].length() != 1) { | ||
throw new SAMFormatException("Unrecognized element type for array tag value: " + elementTypeAndValue[0]); | ||
} | ||
|
||
final char elementType = elementTypeAndValue[0].charAt(0); | ||
if (numberOfTokens == 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this be removed and fall through with length==0? |
||
return createEmptyArray(elementType); | ||
} | ||
|
||
final String[] stringValues = elementTypeAndValue[1].split(","); | ||
if (stringValues.length == 0) throw new SAMFormatException("Tag of type B should have at least one element"); | ||
if (elementType == 'f') { | ||
|
@@ -316,6 +329,27 @@ private Object covertStringArrayToObject(final String stringVal) { | |
} | ||
} | ||
|
||
private static Object createEmptyArray(char elementType) { | ||
switch ( elementType ) { | ||
lbergelson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
case 'c': | ||
return EMPTY_BYTE_ARRAY; | ||
case 'C': | ||
return EMPTY_UNSIGNED_BYTE_ARRAY; | ||
case 's': | ||
return EMPTY_SHORT_ARRAY; | ||
case 'S': | ||
return EMPTY_UNSIGNED_SHORT_ARRAY; | ||
case 'i': | ||
return EMPTY_INT_ARRAY; | ||
case 'I': | ||
return EMPTY_UNSIGNED_INT_ARRAY; | ||
case 'f': | ||
//note that F is not a valid option since there is no signed/unsigned float distinction | ||
lbergelson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return EMPTY_FLOAT_ARRAY; | ||
default: { throw new SAMFormatException("Unrecognized array tag element type: " + elementType); } | ||
lbergelson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
Iso8601Date decodeDate(final String dateStr) { | ||
try { | ||
return new Iso8601Date(dateStr); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ | |
|
||
public class SAMTextWriterTest extends HtsjdkTest { | ||
|
||
private SAMRecordSetBuilder getSAMReader(final boolean sortForMe, final SAMFileHeader.SortOrder sortOrder) { | ||
private SAMRecordSetBuilder getSamRecordSet(final boolean sortForMe, final SAMFileHeader.SortOrder sortOrder) { | ||
final SAMRecordSetBuilder ret = new SAMRecordSetBuilder(sortForMe, sortOrder); | ||
ret.addPair("readB", 20, 200, 300); | ||
ret.addPair("readA", 20, 100, 150); | ||
|
@@ -45,7 +45,7 @@ private SAMRecordSetBuilder getSAMReader(final boolean sortForMe, final SAMFileH | |
|
||
@Test | ||
public void testNullHeader() throws Exception { | ||
final SAMRecordSetBuilder recordSetBuilder = getSAMReader(true, SAMFileHeader.SortOrder.coordinate); | ||
final SAMRecordSetBuilder recordSetBuilder = getSamRecordSet(true, SAMFileHeader.SortOrder.coordinate); | ||
for (final SAMRecord rec : recordSetBuilder.getRecords()) { | ||
rec.setHeader(null); | ||
} | ||
|
@@ -77,7 +77,7 @@ private void doTest(final SAMRecordSetBuilder recordSetBuilder) throws Exception | |
} | ||
|
||
private void doTest(final SamFlagField samFlagField) throws Exception { | ||
doTest(getSAMReader(true, SAMFileHeader.SortOrder.coordinate), samFlagField); | ||
doTest(getSamRecordSet(true, SAMFileHeader.SortOrder.coordinate), samFlagField); | ||
} | ||
|
||
private void doTest(final SAMRecordSetBuilder recordSetBuilder, final SamFlagField samFlagField) throws Exception { | ||
|
@@ -128,4 +128,12 @@ private void doTest(final SAMRecordSetBuilder recordSetBuilder, final SamFlagFie | |
Assert.assertFalse(newSAMIt.hasNext()); | ||
inputSAM.close(); | ||
} | ||
|
||
@Test | ||
public void testEmptyArrayAttributeHasNoCommaWhenWrittenToSAM(){ | ||
final SAMFileHeader header = new SAMFileHeader(); | ||
final SAMRecord record = new SAMRecord(header); | ||
record.setAttribute("xa", new int[0]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ARRAY_TAG? |
||
Assert.assertTrue(record.getSAMString().endsWith("xa:B:i\n")); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove nl