/*******************************************************************************
* Copyright 2013 EMBL-EBI
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package net.sf.cram.common;
import htsjdk.samtools.Cigar;
import htsjdk.samtools.CigarElement;
import htsjdk.samtools.CigarOperator;
import htsjdk.samtools.SAMFileHeader;
import htsjdk.samtools.SAMFileWriter;
import htsjdk.samtools.SAMFileWriterFactory;
import htsjdk.samtools.SAMRecord;
import htsjdk.samtools.SAMSequenceDictionary;
import htsjdk.samtools.SAMSequenceRecord;
import htsjdk.samtools.SAMTag;
import htsjdk.samtools.SAMTextWriter;
import htsjdk.samtools.SamPairUtil;
import htsjdk.samtools.cram.build.CramIO;
import htsjdk.samtools.cram.common.CramVersions;
import htsjdk.samtools.cram.encoding.readfeatures.ReadFeature;
import htsjdk.samtools.cram.io.InputStreamUtils;
import htsjdk.samtools.cram.structure.CramCompressionRecord;
import htsjdk.samtools.cram.structure.CramHeader;
import htsjdk.samtools.reference.IndexedFastaSequenceFile;
import htsjdk.samtools.reference.ReferenceSequence;
import htsjdk.samtools.reference.ReferenceSequenceFile;
import htsjdk.samtools.reference.ReferenceSequenceFileFactory;
import htsjdk.samtools.seekablestream.SeekableBufferedStream;
import htsjdk.samtools.seekablestream.SeekableFTPStream;
import htsjdk.samtools.seekablestream.SeekableFileStream;
import htsjdk.samtools.seekablestream.SeekableHTTPStream;
import htsjdk.samtools.seekablestream.SeekableStream;
import htsjdk.samtools.seekablestream.UserPasswordInput;
import htsjdk.samtools.util.Log;
import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import net.sf.cram.CramFixHeader;
import cipheronly.CipherInputStream_256;
import com.beust.jcommander.JCommander;
public class Utils {
private static Log log = Log.getInstance(Utils.class);
public static boolean quitOnMissingEOF = Boolean.parseBoolean(System.getProperty("debug.quit-on-missing-eof",
"true"));
public static class Version {
public final int major;
public final int minor;
public final int build;
public Version(int major, int minor, int build) {
this.major = major;
this.minor = minor;
this.build = build;
}
public Version(String version) {
String[] numbers = version.split("[\\.\\-b]");
major = Integer.valueOf(numbers[0]);
minor = Integer.valueOf(numbers[1]);
if (numbers.length > 3)
build = Integer.valueOf(numbers[3]);
else
build = 0;
}
@Override
public String toString() {
if (build > 0)
return String.format("%d.%d-b%d", major, minor, build);
else
return String.format("%d.%d", major, minor);
}
}
public static final Version CRAM_VERSION = getVersion();
public static void reverse(final byte[] array, int offset, int len) {
final int lastIndex = len - 1;
int i, j;
for (i = offset, j = offset + lastIndex; i < j; ++i, --j) {
final byte tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
if (len % 2 == 1) {
array[i] = array[i];
}
}
public static void reverseComplement(final byte[] bases, int offset, int len) {
final int lastIndex = len - 1;
int i, j;
for (i = offset, j = offset + lastIndex; i < j; ++i, --j) {
final byte tmp = complement(bases[i]);
bases[i] = complement(bases[j]);
bases[j] = tmp;
}
if (len % 2 == 1) {
bases[i] = complement(bases[i]);
}
}
public static final byte a = 'a', c = 'c', g = 'g', t = 't', n = 'n', A = 'A', C = 'C', G = 'G', T = 'T', N = 'N';
public static byte complement(final byte b) {
switch (b) {
case a:
return t;
case c:
return g;
case g:
return c;
case t:
return a;
case A:
return T;
case C:
return G;
case G:
return C;
case T:
return A;
default:
return b;
}
}
public static final byte upperCase(byte base) {
return base >= 'a' ? (byte) (base - ('a' - 'A')) : base;
}
public static final byte[] upperCase(byte[] bases) {
for (int i = 0; i < bases.length; i++)
bases[i] = upperCase(bases[i]);
return bases;
}
public static final byte normalizeBase(byte base) {
switch (base) {
case 'a':
case 'A':
return 'A';
case 'c':
case 'C':
return 'C';
case 'g':
case 'G':
return 'G';
case 't':
case 'T':
return 'T';
default:
return 'N';
}
}
public static final byte[] normalizeBases(byte[] bases) {
for (int i = 0; i < bases.length; i++)
bases[i] = normalizeBase(bases[i]);
return bases;
}
public static Byte[] autobox(byte[] array) {
Byte[] newArray = new Byte[array.length];
for (int i = 0; i < array.length; i++)
newArray[i] = array[i];
return newArray;
}
public static Integer[] autobox(int[] array) {
Integer[] newArray = new Integer[array.length];
for (int i = 0; i < array.length; i++)
newArray[i] = array[i];
return newArray;
}
public static void changeReadLength(SAMRecord record, int newLength) {
if (newLength == record.getReadLength())
return;
if (newLength < 1 || newLength >= record.getReadLength())
throw new IllegalArgumentException("Cannot change read length to " + newLength);
List<CigarElement> newCigarElements = new ArrayList<CigarElement>();
int len = 0;
for (CigarElement ce : record.getCigar().getCigarElements()) {
switch (ce.getOperator()) {
case D:
break;
case S:
// dump = true;
// len -= ce.getLength();
// break;
case M:
case I:
case X:
len += ce.getLength();
break;
default:
throw new IllegalArgumentException("Unexpected cigar operator: " + ce.getOperator() + " in cigar "
+ record.getCigarString());
}
if (len <= newLength) {
newCigarElements.add(ce);
continue;
}
CigarElement newCe = new CigarElement(ce.getLength() - (record.getReadLength() - newLength),
ce.getOperator());
if (newCe.getLength() > 0)
newCigarElements.add(newCe);
break;
}
byte[] newBases = new byte[newLength];
System.arraycopy(record.getReadBases(), 0, newBases, 0, newLength);
record.setReadBases(newBases);
byte[] newScores = new byte[newLength];
System.arraycopy(record.getBaseQualities(), 0, newScores, 0, newLength);
record.setCigar(new Cigar(newCigarElements));
}
public static void reversePositionsInRead(CramCompressionRecord record) {
if (record.readFeatures == null || record.readFeatures.isEmpty())
return;
for (ReadFeature f : record.readFeatures)
f.setPosition(record.readLength - f.getPosition() - 1);
Collections.reverse(record.readFeatures);
}
public static byte[] getBasesFromReferenceFile(String referenceFilePath, String seqName, int from, int length) {
ReferenceSequenceFile referenceSequenceFile = ReferenceSequenceFileFactory.getReferenceSequenceFile(new File(
referenceFilePath));
ReferenceSequence sequence = referenceSequenceFile.getSequence(seqName);
byte[] bases = referenceSequenceFile.getSubsequenceAt(sequence.getName(), from, from + length).getBases();
return bases;
}
public static void capitaliseAndCheckBases(byte[] bases, boolean strict) {
for (int i = 0; i < bases.length; i++) {
switch (bases[i]) {
case 'A':
case 'C':
case 'G':
case 'T':
case 'N':
break;
case 'a':
bases[i] = 'A';
break;
case 'c':
bases[i] = 'C';
break;
case 'g':
bases[i] = 'G';
break;
case 't':
bases[i] = 'T';
break;
case 'n':
bases[i] = 'N';
break;
default:
if (strict)
throw new RuntimeException("Illegal base at " + i + ": " + bases[i]);
else
bases[i] = 'N';
break;
}
}
}
/**
* Copied from net.sf.picard.sam.SamPairUtil. This is a more permissive
* version of the method, which does not reset alignment start and reference
* for unmapped reads.
*
* @param rec1
* @param rec2
* @param header
*/
public static void setLooseMateInfo(final SAMRecord rec1, final SAMRecord rec2, final SAMFileHeader header) {
if (rec1.getReferenceName() != SAMRecord.NO_ALIGNMENT_REFERENCE_NAME
&& rec1.getReferenceIndex() == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX)
rec1.setReferenceIndex(header.getSequenceIndex(rec1.getReferenceName()));
if (rec2.getReferenceName() != SAMRecord.NO_ALIGNMENT_REFERENCE_NAME
&& rec2.getReferenceIndex() == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX)
rec2.setReferenceIndex(header.getSequenceIndex(rec2.getReferenceName()));
// If neither read is unmapped just set their mate info
if (!rec1.getReadUnmappedFlag() && !rec2.getReadUnmappedFlag()) {
rec1.setMateReferenceIndex(rec2.getReferenceIndex());
rec1.setMateAlignmentStart(rec2.getAlignmentStart());
rec1.setMateNegativeStrandFlag(rec2.getReadNegativeStrandFlag());
rec1.setMateUnmappedFlag(false);
rec1.setAttribute(SAMTag.MQ.name(), rec2.getMappingQuality());
rec2.setMateReferenceIndex(rec1.getReferenceIndex());
rec2.setMateAlignmentStart(rec1.getAlignmentStart());
rec2.setMateNegativeStrandFlag(rec1.getReadNegativeStrandFlag());
rec2.setMateUnmappedFlag(false);
rec2.setAttribute(SAMTag.MQ.name(), rec1.getMappingQuality());
}
// Else if they're both unmapped set that straight
else if (rec1.getReadUnmappedFlag() && rec2.getReadUnmappedFlag()) {
rec1.setMateNegativeStrandFlag(rec2.getReadNegativeStrandFlag());
rec1.setMateUnmappedFlag(true);
rec1.setAttribute(SAMTag.MQ.name(), null);
rec1.setInferredInsertSize(0);
rec2.setMateNegativeStrandFlag(rec1.getReadNegativeStrandFlag());
rec2.setMateUnmappedFlag(true);
rec2.setAttribute(SAMTag.MQ.name(), null);
rec2.setInferredInsertSize(0);
}
// And if only one is mapped copy it's coordinate information to the
// mate
else {
final SAMRecord mapped = rec1.getReadUnmappedFlag() ? rec2 : rec1;
final SAMRecord unmapped = rec1.getReadUnmappedFlag() ? rec1 : rec2;
mapped.setMateReferenceIndex(unmapped.getReferenceIndex());
mapped.setMateAlignmentStart(unmapped.getAlignmentStart());
mapped.setMateNegativeStrandFlag(unmapped.getReadNegativeStrandFlag());
mapped.setMateUnmappedFlag(true);
mapped.setInferredInsertSize(0);
unmapped.setMateReferenceIndex(mapped.getReferenceIndex());
unmapped.setMateAlignmentStart(mapped.getAlignmentStart());
unmapped.setMateNegativeStrandFlag(mapped.getReadNegativeStrandFlag());
unmapped.setMateUnmappedFlag(false);
unmapped.setInferredInsertSize(0);
}
boolean firstIsFirst = rec1.getAlignmentStart() < rec2.getAlignmentStart();
int insertSize = firstIsFirst ? SamPairUtil.computeInsertSize(rec1, rec2) : SamPairUtil.computeInsertSize(rec2,
rec1);
rec1.setInferredInsertSize(firstIsFirst ? insertSize : -insertSize);
rec2.setInferredInsertSize(firstIsFirst ? -insertSize : insertSize);
}
public static final void setInsertSize(CramCompressionRecord record) {
}
public static int computeInsertSize(CramCompressionRecord firstEnd, CramCompressionRecord secondEnd) {
if (firstEnd.isSegmentUnmapped() || secondEnd.isSegmentUnmapped()) {
return 0;
}
if (firstEnd.sequenceId != secondEnd.sequenceId) {
return 0;
}
final int right = Math.max(Math.max(firstEnd.alignmentStart, firstEnd.getAlignmentEnd()),
Math.max(secondEnd.alignmentStart, secondEnd.getAlignmentEnd()));
final int left = Math.min(Math.min(firstEnd.alignmentStart, firstEnd.getAlignmentEnd()),
Math.min(secondEnd.alignmentStart, secondEnd.getAlignmentEnd()));
final int tlen = right - left + 1;
if (firstEnd.alignmentStart == left) {
if (firstEnd.getAlignmentEnd() != right)
firstEnd.templateSize = tlen;
else if (firstEnd.isFirstSegment())
firstEnd.templateSize = tlen;
else
firstEnd.templateSize = -tlen;
} else {
firstEnd.templateSize = -tlen;
}
if (secondEnd.alignmentStart == left) {
if (secondEnd.getAlignmentEnd() != right)
secondEnd.templateSize = tlen;
else if (secondEnd.isFirstSegment())
secondEnd.templateSize = tlen;
else
secondEnd.templateSize = -tlen;
} else {
secondEnd.templateSize = -tlen;
}
return tlen;
}
public static IndexedFastaSequenceFile createIndexedFastaSequenceFile(File file) throws RuntimeException,
FileNotFoundException {
if (IndexedFastaSequenceFile.canCreateIndexedFastaReader(file)) {
IndexedFastaSequenceFile ifsFile = new IndexedFastaSequenceFile(file);
return ifsFile;
} else
throw new RuntimeException(
"Reference fasta file is not indexed or index file not found. Try executing 'samtools faidx "
+ file.getAbsolutePath() + "'");
}
/**
* A rip off samtools bam_md.c
*
* @param record
* @param ref
* @param calcMD
* @param calcNM
*/
public static void calculateMdAndNmTags(SAMRecord record, byte[] ref, boolean calcMD, boolean calcNM) {
if (!calcMD && !calcNM)
return;
Cigar cigar = record.getCigar();
List<CigarElement> cigarElements = cigar.getCigarElements();
byte[] seq = record.getReadBases();
int start = record.getAlignmentStart() - 1;
int i, x, y, u = 0;
int nm = 0;
StringBuffer str = new StringBuffer();
int size = cigarElements.size();
for (i = y = 0, x = start; i < size; ++i) {
CigarElement ce = cigarElements.get(i);
int j, l = ce.getLength();
CigarOperator op = ce.getOperator();
if (op == CigarOperator.MATCH_OR_MISMATCH || op == CigarOperator.EQ || op == CigarOperator.X) {
for (j = 0; j < l; ++j) {
int z = y + j;
if (ref.length <= x + j)
break; // out of boundary
int c1 = 0;
int c2 = 0;
// try {
c1 = seq[z];
c2 = ref[x + j];
if ((c1 == c2 && c1 != 15 && c2 != 15) || c1 == 0) {
// a match
++u;
} else {
str.append(u);
str.appendCodePoint(ref[x + j]);
u = 0;
++nm;
}
}
if (j < l)
break;
x += l;
y += l;
} else if (op == CigarOperator.DELETION) {
str.append(u);
str.append('^');
for (j = 0; j < l; ++j) {
if (ref[x + j] == 0)
break;
str.appendCodePoint(ref[x + j]);
}
u = 0;
if (j < l)
break;
x += l;
nm += l;
} else if (op == CigarOperator.INSERTION || op == CigarOperator.SOFT_CLIP) {
y += l;
if (op == CigarOperator.INSERTION)
nm += l;
} else if (op == CigarOperator.SKIPPED_REGION) {
x += l;
}
}
str.append(u);
if (calcMD)
record.setAttribute(SAMTag.MD.name(), str.toString());
if (calcNM)
record.setAttribute(SAMTag.NM.name(), nm);
}
public static int[][] sortByFirst(int[] array1, int[] array2) {
int[][] sorted = new int[array1.length][2];
for (int i = 0; i < array1.length; i++) {
sorted[i][0] = array1[i];
sorted[i][1] = array2[i];
}
Arrays.sort(sorted, intArray_2_Comparator);
int[][] result = new int[2][array1.length];
for (int i = 0; i < array1.length; i++) {
result[0][i] = sorted[i][0];
result[1][i] = sorted[i][1];
}
return result;
}
private static Comparator<int[]> intArray_2_Comparator = new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
int result = o1[0] - o2[0];
if (result != 0)
return -result;
return -(o1[1] - o2[1]);
}
};
public static void checkRefMD5(SAMSequenceDictionary d, ReferenceSequenceFile refFile, boolean checkExistingMD5,
boolean failIfMD5Mismatch) throws NoSuchAlgorithmException {
for (SAMSequenceRecord r : d.getSequences()) {
ReferenceSequence sequence = refFile.getSequence(r.getSequenceName());
if (!r.getAttributes().contains(SAMSequenceRecord.MD5_TAG)) {
String md5 = calculateMD5String(sequence.getBases());
r.setAttribute(SAMSequenceRecord.MD5_TAG, md5);
} else {
if (checkExistingMD5) {
String existingMD5 = r.getAttribute(SAMSequenceRecord.MD5_TAG);
String md5 = calculateMD5String(sequence.getBases());
if (!md5.equals(existingMD5)) {
String message = String.format("For sequence %s the md5 %s does not match the actual md5 %s.",
r.getSequenceName(), existingMD5, md5);
if (failIfMD5Mismatch)
throw new RuntimeException(message);
else
log.warn(message);
}
}
}
}
}
public static String calculateMD5String(byte[] data) {
return calculateMD5String(data, 0, data.length);
}
public static String calculateMD5String(byte[] data, int offset, int len) {
byte[] digest = calculateMD5(data, offset, len);
return String.format("%032x", new BigInteger(1, digest));
}
public static byte[] calculateMD5(byte[] data, int offset, int len) {
MessageDigest md5_MessageDigest;
try {
md5_MessageDigest = MessageDigest.getInstance("MD5");
md5_MessageDigest.reset();
md5_MessageDigest.update(data, offset, len);
return md5_MessageDigest.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static boolean isValidSequence(byte[] bases, int checkOnlyThisManyBases) {
for (int i = 0; i < checkOnlyThisManyBases && i < bases.length; i++) {
switch (bases[i]) {
case 'A':
case 'C':
case 'G':
case 'T':
case 'U':
case 'R':
case 'Y':
case 'S':
case 'W':
case 'K':
case 'M':
case 'B':
case 'D':
case 'H':
case 'V':
case 'N':
case '.':
case '-':
break;
default:
return false;
}
}
return true;
}
public static void main(String[] args) throws NoSuchAlgorithmException {
byte b = 'a';
byte u = upperCase((byte) 'a');
System.out.printf("%d=%d, %c\n", b, u, u);
System.out.println(calculateMD5String("363".getBytes()));
System.out.println(calculateMD5String("a".getBytes()));
System.out.println(calculateMD5String("Ჾ蠇".getBytes()));
System.out.println(calculateMD5String("jk8ssl".getBytes()));
System.out.println(calculateMD5String("0".getBytes()));
}
public static SAMFileWriter createSAMTextWriter(SAMFileWriterFactory factoryOrNull, OutputStream os,
SAMFileHeader header, boolean printHeader) throws IOException {
SAMFileWriter writer = null;
if (printHeader) {
if (factoryOrNull == null)
factoryOrNull = new SAMFileWriterFactory();
writer = factoryOrNull.makeSAMWriter(header, true, os);
} else {
SwapOutputStream sos = new SwapOutputStream();
final SAMTextWriter ret = new SAMTextWriter(sos);
ret.setSortOrder(header.getSortOrder(), true);
ret.setHeader(header);
ret.getWriter().flush();
writer = ret;
sos.delegate = os;
}
return writer;
}
private static class SwapOutputStream extends OutputStream {
OutputStream delegate;
@Override
public void write(byte[] b) throws IOException {
if (delegate != null)
delegate.write(b);
}
@Override
public void write(int b) throws IOException {
if (delegate != null)
delegate.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (delegate != null)
delegate.write(b, off, len);
}
@Override
public void flush() throws IOException {
if (delegate != null)
delegate.flush();
}
@Override
public void close() throws IOException {
if (delegate != null)
delegate.close();
}
}
private static Version getVersion() {
String version = CramFixHeader.class.getPackage().getImplementationVersion();
if (version == null)
return new Version(3, 0, 0);
else
return new Version(version);
}
public static int getMajorVersion() {
return CRAM_VERSION.major;
}
public static int getMinorVersion() {
return CRAM_VERSION.minor;
}
public static String join(String[] words, String delimiter) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < words.length; i++) {
sb.append(words[i]);
if (i < words.length - 1)
sb.append(delimiter);
}
return sb.toString();
}
public static String getJavaCommand() {
return System.getProperty("sun.java.command");
}
public static void printUsage(JCommander jc) {
StringBuilder sb = new StringBuilder();
sb.append("\n");
jc.usage(sb);
System.out.println("Version " + CRAM_VERSION.toString());
System.out.println(sb.toString());
}
public static InputStream openInputStreamFromURL(String source) throws SocketException, IOException,
URISyntaxException {
URL url = null;
try {
url = new URL(source);
} catch (MalformedURLException e) {
File file = new File(source);
if (file.exists())
return new SeekableBufferedStream(new SeekableFileStream(file), 8 * 1024);
else
return null;
}
String protocol = url.getProtocol();
if ("ftp".equalsIgnoreCase(protocol))
return new SeekableBufferedStream(new NamedSeekableFTPStream(url));
if ("http".equalsIgnoreCase(protocol))
return new SeekableBufferedStream(new SeekableHTTPStream(url));
if ("file".equalsIgnoreCase(protocol)) {
File file = new File(url.toURI());
return new SeekableBufferedStream(new SeekableFileStream(file), 8 * 1024);
}
throw new RuntimeException("Uknown protocol: " + protocol);
}
private static class NamedSeekableFTPStream extends SeekableFTPStream {
/**
* This class purpose is to preserve and pass the URL string as the
* source.
*/
private URL source;
public NamedSeekableFTPStream(URL url) throws IOException {
super(url);
source = url;
}
public NamedSeekableFTPStream(URL url, UserPasswordInput userPasswordInput) throws IOException {
super(url, userPasswordInput);
source = url;
}
@Override
public String getSource() {
return source.toString();
}
}
public static String getFileName(String urlString) {
URL url = null;
try {
url = new URL(urlString);
return new File(url.getFile()).getName();
} catch (MalformedURLException e) {
return new File(urlString).getName();
}
}
/**
* A convenience method.
* <p>
* If a file is supplied then it will be wrapped into a SeekableStream. If
* file is null, then the fromIS argument will be used or System.in if null.
* Optionally the input can be decrypted using provided password or the
* password read from the console.
* </p>
* The method also checks for EOF marker and raise error if the marker is
* not found for files with version 2.1 or greater. For version below 2.1 a
* warning will CRAM be issued.
*
* @param decrypt
* decrypt the input stream
* @param password
* a password to use for decryption
* @return an InputStream ready to be used for reading CRAM file definition
* @throws IOException
* @throws URISyntaxException
*/
public static InputStream openCramInputStream(String cramURL, boolean decrypt, String password) throws IOException,
URISyntaxException {
InputStream is = null;
if (cramURL == null)
is = new BufferedInputStream(System.in);
else
is = openInputStreamFromURL(cramURL);
if (decrypt) {
char[] pass = null;
if (password == null) {
if (System.console() == null)
throw new RuntimeException("Cannot access console.");
pass = System.console().readPassword();
} else
pass = password.toCharArray();
// TODO: SeekableCipherStream_256 relies on net.sf.samtools package
// which has been renamed. Commenting out this for now:
// if (is instanceof SeekableStream)
// is = new SeekableCipherStream_256((SeekableStream) is, pass, 1,
// 128);
// else
is = new CipherInputStream_256(is, pass, 128).getCipherInputStream();
}
if (is instanceof SeekableStream) {
CramHeader cramHeader = CramIO.readCramHeader(is);
SeekableStream s = (SeekableStream) is;
if (!checkEOF(cramHeader.getVersion(), s))
eofNotFound(cramHeader.getVersion());
s.seek(0);
} else
log.warn("CRAM file/stream completion cannot be verified.");
return is;
}
private static boolean streamEndsWith(SeekableStream seekableStream, byte[] marker) throws IOException {
byte[] tail = new byte[marker.length];
seekableStream.seek(seekableStream.length() - marker.length);
InputStreamUtils.readFully(seekableStream, tail, 0, tail.length);
if (Arrays.equals(tail, marker))
return true;
tail[8] = (byte) (tail[8] | 240);
return Arrays.equals(tail, marker);
}
private static boolean checkEOF(htsjdk.samtools.cram.common.Version version, SeekableStream seekableStream)
throws IOException {
if (version.compatibleWith(CramVersions.CRAM_v3))
return streamEndsWith(seekableStream, CramIO.ZERO_F_EOF_MARKER);
if (version.compatibleWith(CramVersions.CRAM_v2_1))
return streamEndsWith(seekableStream, CramIO.ZERO_B_EOF_MARKER);
return false;
}
private static void eofNotFound(htsjdk.samtools.cram.common.Version version) {
if (version.major >= 2 && version.minor >= 1) {
log.error("Incomplete data: EOF marker not found.");
if (quitOnMissingEOF)
System.exit(1);
} else {
log.warn("EOF marker not found, possibly incomplete file/stream.");
}
}
public final static byte[] readFully(InputStream is, int len) throws IOException {
byte[] b = new byte[len];
int off = 0;
if (len < 0)
throw new IndexOutOfBoundsException();
int n = 0;
while (n < len) {
int count = is.read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
}
return b;
}
public static int readInto(ByteBuffer buf, InputStream inputStream) throws IOException {
int read = 0;
while (buf.hasRemaining()) {
int count = inputStream.read(buf.array(), buf.position(), buf.remaining());
if (count < 0)
throw new EOFException();
read += count;
}
return read;
}
public static char getTagValueType(final Object value) {
if (value instanceof String) {
return 'Z';
} else if (value instanceof Character) {
return 'A';
} else if (value instanceof Float) {
return 'f';
} else if (value instanceof Number) {
if (!(value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long)) {
throw new IllegalArgumentException("Unrecognized tag type " + value.getClass().getName());
}
return getIntegerType(((Number) value).longValue());
} /*
* Note that H tag type is never written anymore, because B style is
* more compact. else if (value instanceof byte[]) { return 'H'; }
*/else if (value instanceof byte[] || value instanceof short[] || value instanceof int[]
|| value instanceof float[]) {
return 'B';
} else {
throw new IllegalArgumentException("When writing BAM, unrecognized tag type " + value.getClass().getName());
}
}
private static final long MAX_INT = Integer.MAX_VALUE;
private static final long MAX_UINT = MAX_INT * 2 + 1;
private static final long MAX_SHORT = Short.MAX_VALUE;
private static final long MAX_USHORT = MAX_SHORT * 2 + 1;
private static final long MAX_BYTE = Byte.MAX_VALUE;
private static final long MAX_UBYTE = MAX_BYTE * 2 + 1;
static private char getIntegerType(final long val) {
if (val > MAX_UINT) {
throw new IllegalArgumentException("Integer attribute value too large: " + val);
}
if (val > MAX_INT) {
return 'I';
}
if (val > MAX_USHORT) {
return 'i';
}
if (val > MAX_SHORT) {
return 'S';
}
if (val > MAX_UBYTE) {
return 's';
}
if (val > MAX_BYTE) {
return 'C';
}
if (val >= Byte.MIN_VALUE) {
return 'c';
}
if (val >= Short.MIN_VALUE) {
return 's';
}
if (val >= Integer.MIN_VALUE) {
return 'i';
}
throw new IllegalArgumentException("Integer attribute value too negative to be encoded in BAM");
}
public static int getTagValueByteSize(final byte type, final Object value) {
switch (type) {
case 'A':
return 1;
case 'I':
return 4;
case 'i':
return 4;
case 's':
return 2;
case 'S':
return 2;
case 'c':
return 1;
case 'C':
return 1;
case 'f':
return 4;
case 'Z':
return ((String) value).length();
case 'B':
if (value instanceof byte[])
return ((byte[]) value).length;
if (value instanceof short[])
return ((short[]) value).length * 2;
if (value instanceof int[])
return ((int[]) value).length * 4;
if (value instanceof float[])
return ((byte[]) value).length * 4;
if (value instanceof long[])
return ((long[]) value).length * 4;
throw new RuntimeException("Unkown tag array class: " + value.getClass());
default:
throw new RuntimeException("Unkown tag type: " + (char) type);
}
}
}