From 1259ac536ff7e8f4121041da754cb79c855f0633 Mon Sep 17 00:00:00 2001 From: Ekaterina Kazachkova Date: Tue, 31 Jan 2017 15:04:22 +0300 Subject: [PATCH 01/59] fixes for issue #547 --- src/main/java/htsjdk/samtools/CRAMFileReader.java | 12 ++--- src/main/java/htsjdk/samtools/CRAMIterator.java | 4 +- .../java/htsjdk/samtools/CRAMFileReaderTest.java | 2 +- .../java/htsjdk/samtools/SamReaderSortTest.java | 58 ++++++++++++++++++++-- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/main/java/htsjdk/samtools/CRAMFileReader.java b/src/main/java/htsjdk/samtools/CRAMFileReader.java index 9a29d367f..a7a40889c 100644 --- a/src/main/java/htsjdk/samtools/CRAMFileReader.java +++ b/src/main/java/htsjdk/samtools/CRAMFileReader.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright 2013 EMBL-EBI + * Copyright 2013-2016 EMBL-EBI * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -466,9 +466,8 @@ void enableFileSource(final SamReader reader, final boolean enabled) { iterator.setFileSource(enabled ? reader : null); } - private class CRAMIntervalIterator - extends BAMQueryMultipleIntervalsIteratorFilter - implements SAMRecordIterator { + private class CRAMIntervalIterator extends BAMQueryMultipleIntervalsIteratorFilter + implements CloseableIterator { // the granularity of this iterator is the container, so the records returned // by it must still be filtered to find those matching the filter criteria @@ -507,11 +506,6 @@ public CRAMIntervalIterator(final QueryInterval[] queries, final boolean contain } @Override - public SAMRecordIterator assertSorted(final SortOrder sortOrder) { - return null; - } - - @Override public void close() { if (unfilteredIterator != null) { unfilteredIterator.close(); diff --git a/src/main/java/htsjdk/samtools/CRAMIterator.java b/src/main/java/htsjdk/samtools/CRAMIterator.java index f8179e689..33492df69 100644 --- a/src/main/java/htsjdk/samtools/CRAMIterator.java +++ b/src/main/java/htsjdk/samtools/CRAMIterator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright 2013 EMBL-EBI + * Copyright 2013-2016 EMBL-EBI * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -292,7 +292,7 @@ public void close() { @Override public SAMRecordIterator assertSorted(final SortOrder sortOrder) { - throw new RuntimeException("Not implemented."); + return SamReader.AssertingIterator.of(this).assertSorted(sortOrder); } public SamReader getFileSource() { diff --git a/src/test/java/htsjdk/samtools/CRAMFileReaderTest.java b/src/test/java/htsjdk/samtools/CRAMFileReaderTest.java index 3fcb3bdc9..9e36c3ccc 100644 --- a/src/test/java/htsjdk/samtools/CRAMFileReaderTest.java +++ b/src/test/java/htsjdk/samtools/CRAMFileReaderTest.java @@ -82,7 +82,7 @@ public void testCRAMReader2ReferenceRequired() { @Test(description = "Test CRAMReader 2 input required", expectedExceptions = IllegalArgumentException.class) public void testCRAMReader2_InputRequired() { File file = null; - InputStream bis = null; + InputStream bis = null; new CRAMFileReader(file, bis, createReferenceSource()); } diff --git a/src/test/java/htsjdk/samtools/SamReaderSortTest.java b/src/test/java/htsjdk/samtools/SamReaderSortTest.java index 584410fd0..e7484c787 100755 --- a/src/test/java/htsjdk/samtools/SamReaderSortTest.java +++ b/src/test/java/htsjdk/samtools/SamReaderSortTest.java @@ -3,7 +3,7 @@ /* * The MIT License * - * Copyright (c) 2009 The Broad Institute + * Copyright (c) 2009-2016 The Broad Institute * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,7 @@ * THE SOFTWARE. */ +import htsjdk.samtools.cram.ref.ReferenceSource; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -37,9 +38,15 @@ */ public class SamReaderSortTest { - public static final String COORDINATE_SORTED_FILE = "src/test/resources/htsjdk/samtools/coordinate_sorted.sam"; - public static final String QUERYNAME_SORTED_FILE = "src/test/resources/htsjdk/samtools/queryname_sorted.sam"; - public static final String QUERYNAME_SORTED_NO_HEADER_SORT = "src/test/resources/htsjdk/samtools/unsorted.sam"; + private static final String COORDINATE_SORTED_FILE = "src/test/resources/htsjdk/samtools/coordinate_sorted.sam"; + private static final String QUERYNAME_SORTED_FILE = "src/test/resources/htsjdk/samtools/queryname_sorted.sam"; + private static final String QUERYNAME_SORTED_NO_HEADER_SORT = "src/test/resources/htsjdk/samtools/unsorted.sam"; + private static final String CRAM_FILE = "src/test/resources/htsjdk/samtools/cram_query_sorted.cram"; + private static final String CRAM_REFERENCE = "src/test/resources/htsjdk/samtools/cram_query_sorted.fasta"; + private static final String CRAM_FILE_COORDINATE = "src/test/resources/htsjdk/samtools/cram/ce#tag_depadded.2.1.cram"; + private static final String CRAM_REFERENCE_COORDINATE = "src/test/resources/htsjdk/samtools/cram/ce.fa"; + private static final String CRAM_FILE_UNSORTED = "src/test/resources/htsjdk/samtools/cram/xx#unsorted.3.0.cram"; + private static final String CRAM_REFERENCE_UNSORTED = "src/test/resources/htsjdk/samtools/cram/xx.fa"; @Test(expectedExceptions = IllegalStateException.class) public void testSortsDisagree() throws Exception { @@ -93,6 +100,49 @@ public void testSortAssertionFails(String file, SAMFileHeader.SortOrder order) t } } + private CRAMFileReader getCramFileReader(String file, String fileReference) { + final ReferenceSource referenceSource = new ReferenceSource(new File(fileReference)); + return new CRAMFileReader(new File(file), referenceSource); + } + + @Test(dataProvider = "sortsCramWithoutIndex") + public void testCramSort(String file, String fileReference, SAMFileHeader.SortOrder order) throws Exception { + final CRAMFileReader cramFileReader = getCramFileReader(file, fileReference); + final SAMRecordIterator samRecordIterator = cramFileReader.getIterator().assertSorted(order); + Assert.assertTrue(samRecordIterator.hasNext()); + while (samRecordIterator.hasNext()) { + Assert.assertNotNull(samRecordIterator.next()); + } + } + + @Test(dataProvider = "sortsFailCramWithoutIndex", expectedExceptions = IllegalStateException.class) + public void testCramSortFail(String file, String fileReference, SAMFileHeader.SortOrder order) throws Exception { + final CRAMFileReader cramFileReader = getCramFileReader(file, fileReference); + final SAMRecordIterator samRecordIterator = cramFileReader.getIterator().assertSorted(order); + Assert.assertTrue(samRecordIterator.hasNext()); + while (samRecordIterator.hasNext()) { + Assert.assertNotNull(samRecordIterator.next()); + } + } + + @DataProvider(name = "sortsFailCramWithoutIndex") + public Object[][] getSortsFailCramWithoutIndex() { + return new Object[][]{ + {CRAM_FILE, CRAM_REFERENCE, SAMFileHeader.SortOrder.coordinate}, + {CRAM_FILE_COORDINATE, CRAM_REFERENCE_COORDINATE, SAMFileHeader.SortOrder.queryname}, + {CRAM_FILE_UNSORTED, CRAM_REFERENCE_UNSORTED, SAMFileHeader.SortOrder.coordinate} + }; + } + + @DataProvider(name = "sortsCramWithoutIndex") + public Object[][] getSortsCramWithoutIndex() { + return new Object[][]{ + {CRAM_FILE, CRAM_REFERENCE, SAMFileHeader.SortOrder.queryname}, + {CRAM_FILE_COORDINATE, CRAM_REFERENCE_COORDINATE, SAMFileHeader.SortOrder.coordinate}, + {CRAM_FILE_UNSORTED, CRAM_REFERENCE_UNSORTED, SAMFileHeader.SortOrder.unsorted} + }; + } + @DataProvider(name = "invalidSorts") public Object[][] getInvalidSorts() { return new Object[][]{ From cabe78f67663fce537241f3c3a337bc9459a681e Mon Sep 17 00:00:00 2001 From: Louis Bergelson Date: Tue, 21 Feb 2017 14:54:40 -0500 Subject: [PATCH 02/59] add a coverage drop threshold to limit codecov failure spam (#803) * add a coverage drop threshold to limit codecov failure spam * making codcov.yml a hidden file --- codecov.yml => .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename codecov.yml => .codecov.yml (94%) diff --git a/codecov.yml b/.codecov.yml similarity index 94% rename from codecov.yml rename to .codecov.yml index 98fb05f4c..7167553f5 100644 --- a/codecov.yml +++ b/.codecov.yml @@ -10,7 +10,7 @@ coverage: project: default: target: auto - threshold: null + threshold: .01 branches: null patch: From 912c28bec415c430b43515652ccaf13222b07e7b Mon Sep 17 00:00:00 2001 From: Louis Bergelson Date: Tue, 21 Feb 2017 18:15:13 -0500 Subject: [PATCH 03/59] automatically adding @Override annotations (#729) added the @Override annotation everywhere it was missing using autoinspection --- .../java/htsjdk/samtools/AbstractBAMFileIndex.java | 3 +++ .../java/htsjdk/samtools/AsyncSAMFileWriter.java | 2 ++ src/main/java/htsjdk/samtools/BAMFileReader.java | 19 +++++++++++++++++++ src/main/java/htsjdk/samtools/BAMFileSpan.java | 4 ++++ src/main/java/htsjdk/samtools/BAMFileWriter.java | 4 ++++ src/main/java/htsjdk/samtools/BAMIndex.java | 1 + src/main/java/htsjdk/samtools/BAMIndexWriter.java | 1 + src/main/java/htsjdk/samtools/BAMRecord.java | 1 + src/main/java/htsjdk/samtools/BAMRecordCodec.java | 5 +++++ src/main/java/htsjdk/samtools/Bin.java | 1 + src/main/java/htsjdk/samtools/BinList.java | 4 ++++ .../java/htsjdk/samtools/BinaryBAMIndexWriter.java | 3 +++ .../java/htsjdk/samtools/BinningIndexContent.java | 4 ++++ .../java/htsjdk/samtools/CachingBAMFileIndex.java | 4 ++++ src/main/java/htsjdk/samtools/Chunk.java | 2 ++ .../htsjdk/samtools/ComparableSamRecordIterator.java | 1 + .../htsjdk/samtools/CoordinateSortedPairInfoMap.java | 5 +++++ .../htsjdk/samtools/DefaultSAMRecordFactory.java | 2 ++ .../java/htsjdk/samtools/DiskBasedBAMFileIndex.java | 2 ++ .../java/htsjdk/samtools/DuplicateSetIterator.java | 4 ++++ .../htsjdk/samtools/MergingSamRecordIterator.java | 7 +++++++ src/main/java/htsjdk/samtools/QueryInterval.java | 1 + src/main/java/htsjdk/samtools/SAMFileHeader.java | 2 ++ src/main/java/htsjdk/samtools/SAMFileWriter.java | 1 + src/main/java/htsjdk/samtools/SAMFileWriterImpl.java | 4 ++++ src/main/java/htsjdk/samtools/SAMProgramRecord.java | 2 ++ .../java/htsjdk/samtools/SAMReadGroupRecord.java | 2 ++ .../samtools/SAMRecordCoordinateComparator.java | 2 ++ .../samtools/SAMRecordDuplicateComparator.java | 2 ++ .../samtools/SAMRecordQueryNameComparator.java | 2 ++ .../java/htsjdk/samtools/SAMRecordSetBuilder.java | 5 +++++ src/main/java/htsjdk/samtools/SAMSequenceRecord.java | 2 ++ src/main/java/htsjdk/samtools/SAMTextReader.java | 20 ++++++++++++++++++++ src/main/java/htsjdk/samtools/SAMTextWriter.java | 4 ++++ .../java/htsjdk/samtools/SamFileHeaderMerger.java | 3 +++ src/main/java/htsjdk/samtools/SamFileValidator.java | 15 +++++++++++++++ src/main/java/htsjdk/samtools/SamPairUtil.java | 3 +++ src/main/java/htsjdk/samtools/SamReader.java | 6 ++++++ src/main/java/htsjdk/samtools/TextTagCodec.java | 3 +++ .../java/htsjdk/samtools/TextualBAMIndexWriter.java | 3 +++ .../samtools/cram/encoding/ByteArrayLenEncoding.java | 2 ++ .../cram/encoding/ByteArrayStopEncoding.java | 2 ++ .../cram/encoding/ExternalByteArrayEncoding.java | 2 ++ .../samtools/cram/encoding/ExternalByteEncoding.java | 2 ++ .../cram/encoding/ExternalIntegerEncoding.java | 2 ++ .../samtools/cram/encoding/ExternalLongEncoding.java | 2 ++ .../cram/encoding/GolombRiceIntegerCodec.java | 1 + .../samtools/cram/encoding/huffman/HuffmanTree.java | 1 + .../cram/encoding/readfeatures/BaseQualityScore.java | 1 + .../cram/encoding/readfeatures/HardClip.java | 2 ++ .../cram/encoding/readfeatures/InsertBase.java | 2 ++ .../cram/encoding/readfeatures/Insertion.java | 2 ++ .../samtools/cram/encoding/readfeatures/Padding.java | 2 ++ .../cram/encoding/readfeatures/ReadBase.java | 1 + .../samtools/cram/encoding/readfeatures/RefSkip.java | 2 ++ .../cram/encoding/readfeatures/SoftClip.java | 2 ++ .../cram/encoding/readfeatures/Substitution.java | 2 ++ .../htsjdk/samtools/cram/io/CountingInputStream.java | 8 ++++++++ .../samtools/cram/io/DefaultBitInputStream.java | 4 ++++ .../samtools/cram/io/DefaultBitOutputStream.java | 4 ++++ .../htsjdk/samtools/cram/ref/ReferenceSource.java | 1 + src/main/java/htsjdk/samtools/fastq/FastqReader.java | 4 ++++ src/main/java/htsjdk/samtools/fastq/FastqWriter.java | 1 + .../java/htsjdk/samtools/filter/AggregateFilter.java | 2 ++ .../java/htsjdk/samtools/filter/AlignedFilter.java | 2 ++ .../htsjdk/samtools/filter/DuplicateReadFilter.java | 2 ++ .../filter/FailsVendorReadQualityFilter.java | 2 ++ .../htsjdk/samtools/filter/FilteringSamIterator.java | 4 ++++ .../java/htsjdk/samtools/filter/IntervalFilter.java | 2 ++ .../samtools/filter/IntervalKeepPairFilter.java | 2 ++ .../samtools/filter/NotPrimaryAlignmentFilter.java | 2 ++ .../java/htsjdk/samtools/filter/ReadNameFilter.java | 2 ++ .../samtools/filter/SecondaryAlignmentFilter.java | 2 ++ .../filter/SecondaryOrSupplementaryFilter.java | 2 ++ .../htsjdk/samtools/filter/SolexaNoiseFilter.java | 2 ++ src/main/java/htsjdk/samtools/filter/TagFilter.java | 2 ++ .../samtools/filter/WholeReadClippedFilter.java | 1 + .../java/htsjdk/samtools/metrics/StringHeader.java | 1 + .../java/htsjdk/samtools/metrics/VersionHeader.java | 1 + .../reference/AbstractFastaSequenceFile.java | 4 ++++ .../htsjdk/samtools/reference/FastaSequenceFile.java | 3 +++ .../samtools/reference/FastaSequenceIndex.java | 1 + .../samtools/reference/IndexedFastaSequenceFile.java | 6 ++++++ .../samtools/reference/ReferenceSequenceFile.java | 1 + .../reference/ReferenceSequenceFileWalker.java | 1 + .../seekablestream/SeekableBufferedStream.java | 6 ++++++ .../samtools/seekablestream/SeekableFTPStream.java | 4 ++++ .../samtools/seekablestream/SeekableFileStream.java | 7 +++++++ .../samtools/seekablestream/SeekableHTTPStream.java | 7 +++++++ .../samtools/seekablestream/SeekableStream.java | 2 ++ src/main/java/htsjdk/samtools/sra/SRALazyRecord.java | 1 + .../htsjdk/samtools/util/AbstractAsyncWriter.java | 2 ++ .../java/htsjdk/samtools/util/AbstractLocusInfo.java | 2 ++ .../htsjdk/samtools/util/AbstractLocusIterator.java | 5 +++++ src/main/java/htsjdk/samtools/util/AsciiWriter.java | 3 +++ .../util/AsyncBlockCompressedInputStream.java | 1 + src/main/java/htsjdk/samtools/util/BinaryCodec.java | 1 + .../samtools/util/BlockCompressedInputStream.java | 5 +++++ .../samtools/util/BlockCompressedOutputStream.java | 1 + .../htsjdk/samtools/util/BufferedLineReader.java | 4 ++++ .../java/htsjdk/samtools/util/CloseableIterator.java | 1 + .../htsjdk/samtools/util/DelegatingIterator.java | 4 ++++ .../java/htsjdk/samtools/util/DiskBackedQueue.java | 3 +++ .../htsjdk/samtools/util/EdgingRecordAndOffset.java | 2 ++ .../java/htsjdk/samtools/util/FastLineReader.java | 1 + .../samtools/util/FileAppendStreamLRUCache.java | 2 ++ src/main/java/htsjdk/samtools/util/IOUtil.java | 1 + src/main/java/htsjdk/samtools/util/Interval.java | 1 + src/main/java/htsjdk/samtools/util/IntervalList.java | 2 ++ .../util/IntervalListReferenceSequenceMask.java | 4 ++++ src/main/java/htsjdk/samtools/util/IntervalTree.java | 13 +++++++++++++ .../java/htsjdk/samtools/util/IntervalTreeMap.java | 19 +++++++++++++++++++ src/main/java/htsjdk/samtools/util/Iso8601Date.java | 1 + src/main/java/htsjdk/samtools/util/LineReader.java | 1 + .../java/htsjdk/samtools/util/LocusComparator.java | 1 + src/main/java/htsjdk/samtools/util/LocusImpl.java | 2 ++ .../samtools/util/Md5CalculatingInputStream.java | 9 +++++++++ .../samtools/util/Md5CalculatingOutputStream.java | 5 +++++ src/main/java/htsjdk/samtools/util/PeekIterator.java | 3 +++ .../java/htsjdk/samtools/util/PeekableIterator.java | 4 ++++ .../htsjdk/samtools/util/PositionalOutputStream.java | 4 ++++ .../samtools/util/QualityEncodingDetector.java | 3 +++ .../util/SamRecordIntervalIteratorFactory.java | 4 ++++ .../java/htsjdk/samtools/util/SortingCollection.java | 14 ++++++++++++++ .../htsjdk/samtools/util/SortingLongCollection.java | 1 + .../java/htsjdk/samtools/util/StringLineReader.java | 4 ++++ .../util/WholeGenomeReferenceSequenceMask.java | 4 ++++ src/main/java/htsjdk/tribble/FeatureReader.java | 1 + src/main/java/htsjdk/tribble/SimpleFeature.java | 3 +++ src/main/java/htsjdk/tribble/TribbleException.java | 1 + src/main/java/htsjdk/tribble/bed/FullBEDFeature.java | 1 + .../java/htsjdk/tribble/bed/SimpleBEDFeature.java | 10 ++++++++++ .../java/htsjdk/tribble/index/AbstractIndex.java | 9 +++++++++ .../htsjdk/tribble/index/DynamicIndexCreator.java | 2 ++ src/main/java/htsjdk/tribble/index/IndexFactory.java | 3 +++ .../java/htsjdk/tribble/index/interval/Interval.java | 1 + .../tribble/index/interval/IntervalIndexCreator.java | 2 ++ .../tribble/index/interval/IntervalTreeIndex.java | 6 ++++++ .../htsjdk/tribble/index/linear/LinearIndex.java | 7 +++++++ .../tribble/index/linear/LinearIndexCreator.java | 2 ++ .../java/htsjdk/tribble/readers/AsciiLineReader.java | 2 ++ src/main/java/htsjdk/tribble/readers/LineReader.java | 1 + .../tribble/readers/LongLineBufferedReader.java | 8 ++++++++ .../tribble/readers/PositionalBufferedStream.java | 3 +++ .../tribble/readers/TabixIteratorLineReader.java | 2 ++ .../java/htsjdk/tribble/readers/TabixReader.java | 1 + src/main/java/htsjdk/tribble/util/HTTPHelper.java | 5 +++++ .../tribble/util/LittleEndianOutputStream.java | 2 ++ src/main/java/htsjdk/tribble/util/TabixUtils.java | 1 + .../java/htsjdk/variant/variantcontext/Allele.java | 1 + .../htsjdk/variant/variantcontext/FastGenotype.java | 1 + .../java/htsjdk/variant/variantcontext/JEXLMap.java | 12 ++++++++++++ .../variant/variantcontext/VariantContext.java | 2 ++ .../variant/variantcontext/VariantJEXLContext.java | 3 +++ .../writer/AsyncVariantContextWriter.java | 2 ++ .../variantcontext/writer/BCF2FieldWriter.java | 1 + .../writer/IndexingVariantContextWriter.java | 3 +++ .../writer/SortingVariantContextWriter.java | 1 + .../writer/SortingVariantContextWriterBase.java | 1 + .../variantcontext/writer/VariantContextWriter.java | 1 + .../java/htsjdk/variant/vcf/AbstractVCFCodec.java | 3 +++ src/main/java/htsjdk/variant/vcf/VCF3Codec.java | 2 ++ src/main/java/htsjdk/variant/vcf/VCFCodec.java | 1 + .../htsjdk/variant/vcf/VCFCompoundHeaderLine.java | 2 ++ src/main/java/htsjdk/variant/vcf/VCFFileReader.java | 6 ++++-- src/main/java/htsjdk/variant/vcf/VCFHeaderLine.java | 1 + .../htsjdk/variant/vcf/VCFHeaderLineTranslator.java | 2 ++ .../java/htsjdk/variant/vcf/VCFSimpleHeaderLine.java | 2 ++ .../MergingSamRecordIteratorGroupCollisionTest.java | 16 ++++++++++++++++ .../java/htsjdk/samtools/SamReaderFactoryTest.java | 3 +++ src/test/java/htsjdk/samtools/SamReaderTest.java | 2 ++ .../java/htsjdk/samtools/ValidateSamFileTest.java | 6 ++++++ .../util/BlockCompressedOutputStreamTest.java | 1 + .../htsjdk/samtools/util/DiskBackedQueueTest.java | 3 +++ .../htsjdk/samtools/util/SortingCollectionTest.java | 10 ++++++++++ 175 files changed, 583 insertions(+), 2 deletions(-) diff --git a/src/main/java/htsjdk/samtools/AbstractBAMFileIndex.java b/src/main/java/htsjdk/samtools/AbstractBAMFileIndex.java index 6bf28ef29..724e73c62 100644 --- a/src/main/java/htsjdk/samtools/AbstractBAMFileIndex.java +++ b/src/main/java/htsjdk/samtools/AbstractBAMFileIndex.java @@ -88,6 +88,7 @@ protected AbstractBAMFileIndex(final File file, final SAMSequenceDictionary dict /** * Close this index and release any associated resources. */ + @Override public void close() { mIndexBuffer.close(); } @@ -170,6 +171,7 @@ public int getNumberOfReferences() { * @return The file offset of the first record in the last linear bin, or -1 * if there are no elements in linear bins (i.e. no mapped reads). */ + @Override public long getStartOfLastLinearBin() { seek(4); @@ -206,6 +208,7 @@ public long getStartOfLastLinearBin() { * @param reference the reference of interest * @return meta data for the reference */ + @Override public BAMIndexMetaData getMetaData(final int reference) { seek(4); diff --git a/src/main/java/htsjdk/samtools/AsyncSAMFileWriter.java b/src/main/java/htsjdk/samtools/AsyncSAMFileWriter.java index ab5b8d0b1..1a860f29b 100644 --- a/src/main/java/htsjdk/samtools/AsyncSAMFileWriter.java +++ b/src/main/java/htsjdk/samtools/AsyncSAMFileWriter.java @@ -48,11 +48,13 @@ public void setProgressLogger(final ProgressLoggerInterface progress) { * Adds an alignment to the queue to be written. Will re-throw any exception that was received when * writing prior record(s) to the underlying SAMFileWriter. */ + @Override public void addAlignment(final SAMRecord alignment) { write(alignment); } /** Returns the SAMFileHeader from the underlying SAMFileWriter. */ + @Override public SAMFileHeader getFileHeader() { return this.underlyingWriter.getFileHeader(); } diff --git a/src/main/java/htsjdk/samtools/BAMFileReader.java b/src/main/java/htsjdk/samtools/BAMFileReader.java index 3026eaeda..1d9110ad6 100644 --- a/src/main/java/htsjdk/samtools/BAMFileReader.java +++ b/src/main/java/htsjdk/samtools/BAMFileReader.java @@ -341,6 +341,7 @@ static long findVirtualOffsetOfFirstRecord(final File bam) throws IOException { * If true, writes the source of every read into the source SAMRecords. * @param enabled true to write source information into each SAMRecord. */ + @Override void enableFileSource(final SamReader reader, final boolean enabled) { this.mReader = enabled ? reader : null; } @@ -349,6 +350,7 @@ void enableFileSource(final SamReader reader, final boolean enabled) { * If true, uses the caching version of the index reader. * @param enabled true to use the caching version of the reader. */ + @Override protected void enableIndexCaching(final boolean enabled) { if(mIndex != null) throw new SAMException("Unable to turn on index caching; index file has already been loaded."); @@ -360,6 +362,7 @@ protected void enableIndexCaching(final boolean enabled) { * This is slower but more scalable when accessing large numbers of BAM files sequentially. * @param enabled True to use memory mapping, false to use regular I/O. */ + @Override protected void enableIndexMemoryMapping(final boolean enabled) { if (mIndex != null) { throw new SAMException("Unable to change index memory mapping; index file has already been loaded."); @@ -381,6 +384,7 @@ protected void enableIndexMemoryMapping(final boolean enabled) { /** * @return true if ths is a BAM file, and has an index */ + @Override public boolean hasIndex() { return mIsSeekable && ((mIndexFile != null) || (mIndexStream != null)); } @@ -389,6 +393,7 @@ public boolean hasIndex() { * Retrieves the index for the given file type. Ensure that the index is of the specified type. * @return An index of the given type. */ + @Override public BAMIndex getIndex() { if(!hasIndex()) throw new SAMException("No index is available for this BAM file."); @@ -425,6 +430,7 @@ public void close() { mIndex = null; } + @Override public SAMFileHeader getFileHeader() { return mFileHeader; } @@ -432,10 +438,12 @@ public SAMFileHeader getFileHeader() { /** * Set error-checking level for subsequent SAMRecord reads. */ + @Override void setValidationStringency(final ValidationStringency validationStringency) { this.mValidationStringency = validationStringency; } + @Override public ValidationStringency getValidationStringency() { return this.mValidationStringency; } @@ -448,6 +456,7 @@ public ValidationStringency getValidationStringency() { * getIterator() begins its iteration where the last one left off. That is the best that can be * done in that situation. */ + @Override public CloseableIterator getIterator() { if (mStream == null) { throw new IllegalStateException("File reader is closed"); @@ -552,6 +561,7 @@ public SAMFileSpan getFilePointerSpanningReads() { * @return Iterator for the matching SAMRecords * @see QueryInterval#optimizeIntervals(QueryInterval[]) */ + @Override public CloseableIterator query(final QueryInterval[] intervals, final boolean contained) { if (mStream == null) { throw new IllegalStateException("File reader is closed"); @@ -582,6 +592,7 @@ public SAMFileSpan getFilePointerSpanningReads() { * @param start Alignment start sought. * @return Iterator for the matching SAMRecords. */ + @Override public CloseableIterator queryAlignmentStart(final String sequence, final int start) { if (mStream == null) { throw new IllegalStateException("File reader is closed"); @@ -608,6 +619,7 @@ public SAMFileSpan getFilePointerSpanningReads() { * * @return Iterator for the matching SAMRecords. */ + @Override public CloseableIterator queryUnmapped() { if (mStream == null) { throw new IllegalStateException("File reader is closed"); @@ -710,6 +722,7 @@ private static SAMSequenceRecord readSequenceRecord(final BinaryCodec stream, fi private boolean isClosed = false; + @Override public void close() { if (!isClosed) { if (mCurrentIterator != null && this != mCurrentIterator) { @@ -724,6 +737,7 @@ protected void assertOpen() { if (isClosed) throw new AssertionError("Iterator has been closed"); } + @Override public void remove() { throw new UnsupportedOperationException("Not supported: remove"); } @@ -770,11 +784,13 @@ public SAMRecord next() { } } + @Override public boolean hasNext() { assertOpen(); return (mNextRecord != null); } + @Override public SAMRecord next() { assertOpen(); final SAMRecord result = mNextRecord; @@ -947,6 +963,7 @@ public static BAMFileSpan getFileSpan(QueryInterval[] intervals, BAMIndex fileIn advance(); } + @Override SAMRecord getNextRecord() throws IOException { // Advance to next file block if necessary @@ -989,6 +1006,7 @@ public BAMQueryFilteringIterator(final CloseableIterator iterator, /** * Returns true if a next element exists; false otherwise. */ + @Override public boolean hasNext() { assertOpen(); return mNextRecord != null; @@ -998,6 +1016,7 @@ public boolean hasNext() { * Gets the next record from the given iterator. * @return The next SAM record in the iterator. */ + @Override public SAMRecord next() { if(!hasNext()) throw new NoSuchElementException("BAMQueryFilteringIterator: no next element available"); diff --git a/src/main/java/htsjdk/samtools/BAMFileSpan.java b/src/main/java/htsjdk/samtools/BAMFileSpan.java index 485f69dcf..d99760d2a 100644 --- a/src/main/java/htsjdk/samtools/BAMFileSpan.java +++ b/src/main/java/htsjdk/samtools/BAMFileSpan.java @@ -78,6 +78,7 @@ public BAMFileSpan(final List chunks) { * Does this chunk list map to any position within the BAM file? * @return True iff the ChunkList points to any data within the BAM. */ + @Override public boolean isEmpty() { return chunks.isEmpty(); } @@ -86,6 +87,7 @@ public boolean isEmpty() { * Deep clone the given chunk list. * @return A copy of the chunk list. */ + @Override public BAMFileSpan clone() { final BAMFileSpan clone = new BAMFileSpan(); for(final Chunk chunk: chunks) @@ -100,6 +102,7 @@ public BAMFileSpan clone() { * @param fileSpan The filespan before which to eliminate. * @return A new BAMFileSpan which contains the portion of the chunk list after the given chunk. */ + @Override public SAMFileSpan removeContentsBefore(final SAMFileSpan fileSpan) { if(fileSpan == null) return clone(); @@ -174,6 +177,7 @@ public SAMFileSpan removeContentsAfter(final SAMFileSpan fileSpan) { * Gets a file span over the data immediately following this span. * @return The a pointer to data immediately following this span. */ + @Override public SAMFileSpan getContentsFollowing() { if(chunks.isEmpty()) throw new SAMException("Unable to get the file pointer following this one: no data present."); diff --git a/src/main/java/htsjdk/samtools/BAMFileWriter.java b/src/main/java/htsjdk/samtools/BAMFileWriter.java index f6a474e2d..fc766ae7d 100644 --- a/src/main/java/htsjdk/samtools/BAMFileWriter.java +++ b/src/main/java/htsjdk/samtools/BAMFileWriter.java @@ -115,6 +115,7 @@ private BAMIndexer createBamIndex(final String path) { } } + @Override protected void writeAlignment(final SAMRecord alignment) { prepareToWriteAlignments(); @@ -135,10 +136,12 @@ protected void writeAlignment(final SAMRecord alignment) { } } + @Override protected void writeHeader(final String textHeader) { writeHeader(outputBinaryCodec, getFileHeader(), textHeader); } + @Override protected void finish() { outputBinaryCodec.close(); try { @@ -151,6 +154,7 @@ protected void finish() { } /** @return absolute path, or null if this writer does not correspond to a file. */ + @Override protected String getFilename() { return outputBinaryCodec.getOutputFileName(); } diff --git a/src/main/java/htsjdk/samtools/BAMIndex.java b/src/main/java/htsjdk/samtools/BAMIndex.java index 3663df9d0..62c69c79c 100644 --- a/src/main/java/htsjdk/samtools/BAMIndex.java +++ b/src/main/java/htsjdk/samtools/BAMIndex.java @@ -63,5 +63,6 @@ /** * Close the index and release any associated resources. */ + @Override void close(); } diff --git a/src/main/java/htsjdk/samtools/BAMIndexWriter.java b/src/main/java/htsjdk/samtools/BAMIndexWriter.java index b036b684d..aafcb5fbf 100644 --- a/src/main/java/htsjdk/samtools/BAMIndexWriter.java +++ b/src/main/java/htsjdk/samtools/BAMIndexWriter.java @@ -49,6 +49,7 @@ /** * Any necessary processing at the end of the file */ + @Override public void close(); } \ No newline at end of file diff --git a/src/main/java/htsjdk/samtools/BAMRecord.java b/src/main/java/htsjdk/samtools/BAMRecord.java index c45566f08..672e802c3 100644 --- a/src/main/java/htsjdk/samtools/BAMRecord.java +++ b/src/main/java/htsjdk/samtools/BAMRecord.java @@ -113,6 +113,7 @@ protected BAMRecord(final SAMFileHeader header, /** * Force all the lazily-initialized attributes to be decoded. */ + @Override protected void eagerDecode() { getReadName(); getCigar(); diff --git a/src/main/java/htsjdk/samtools/BAMRecordCodec.java b/src/main/java/htsjdk/samtools/BAMRecordCodec.java index dc1ca8196..5b0a408f6 100644 --- a/src/main/java/htsjdk/samtools/BAMRecordCodec.java +++ b/src/main/java/htsjdk/samtools/BAMRecordCodec.java @@ -49,6 +49,7 @@ public BAMRecordCodec(final SAMFileHeader header, final SAMRecordFactory factory this.samRecordFactory = factory; } + @Override public BAMRecordCodec clone() { // Do not clone the references to codecs, as they must be distinct for each instance. return new BAMRecordCodec(this.header, this.samRecordFactory); @@ -56,6 +57,7 @@ public BAMRecordCodec clone() { /** Sets the output stream that records will be written to. */ + @Override public void setOutputStream(final OutputStream os) { this.binaryCodec.setOutputStream(os); } @@ -67,6 +69,7 @@ public void setOutputStream(final OutputStream os, final String filename) { } /** Sets the input stream that records will be read from. */ + @Override public void setInputStream(final InputStream is) { this.binaryCodec.setInputStream(is); } @@ -85,6 +88,7 @@ public void setInputStream(final InputStream is, final String filename) { * * @param alignment Record to be written. */ + @Override public void encode(final SAMRecord alignment) { // Compute block size, as it is the first element of the file representation of SAMRecord final int readLength = alignment.getReadLength(); @@ -171,6 +175,7 @@ public void encode(final SAMRecord alignment) { * @return null if no more records. Should throw exception if EOF is encountered in the middle of * a record. */ + @Override public SAMRecord decode() { int recordLength = 0; try { diff --git a/src/main/java/htsjdk/samtools/Bin.java b/src/main/java/htsjdk/samtools/Bin.java index 1ac572400..f199d0a87 100644 --- a/src/main/java/htsjdk/samtools/Bin.java +++ b/src/main/java/htsjdk/samtools/Bin.java @@ -105,6 +105,7 @@ public boolean containsChunks() { * @param other Other bin to which this bin should be compared. * @return -1 if this < other, 0 if this == other, 1 if this > other. */ + @Override public int compareTo(final Bin other) { if(other == null) throw new ClassCastException("Cannot compare to a null object"); diff --git a/src/main/java/htsjdk/samtools/BinList.java b/src/main/java/htsjdk/samtools/BinList.java index e7107d44f..2111ba403 100644 --- a/src/main/java/htsjdk/samtools/BinList.java +++ b/src/main/java/htsjdk/samtools/BinList.java @@ -60,6 +60,7 @@ protected BinList(final int referenceSequence, final BitSet bins) { * Gets an iterator over all selected bins. * @return An iterator over all selected bins. */ + @Override public Iterator iterator() { return new BinIterator(); } @@ -95,6 +96,7 @@ public BinIterator() { * Are there more bins in this set, waiting to be returned? * @return True if more bins are remaining. */ + @Override public boolean hasNext() { return nextBin >= 0; } @@ -103,6 +105,7 @@ public boolean hasNext() { * Gets the next bin in the provided BinList. * @return the next available bin in the BinList. */ + @Override public Bin next() { if(!hasNext()) throw new NoSuchElementException("This BinIterator is currently empty"); @@ -111,6 +114,7 @@ public Bin next() { return new Bin(referenceSequence,currentBin); } + @Override public void remove() { throw new UnsupportedOperationException("Unable to remove from a bin iterator"); } diff --git a/src/main/java/htsjdk/samtools/BinaryBAMIndexWriter.java b/src/main/java/htsjdk/samtools/BinaryBAMIndexWriter.java index 35a22f7ac..5719aecf5 100644 --- a/src/main/java/htsjdk/samtools/BinaryBAMIndexWriter.java +++ b/src/main/java/htsjdk/samtools/BinaryBAMIndexWriter.java @@ -78,6 +78,7 @@ public BinaryBAMIndexWriter(final int nRef, final OutputStream output) { /** * Write this content as binary output */ + @Override public void writeReference(final BAMIndexContent content) { if (content == null) { @@ -147,6 +148,7 @@ public void writeReference(final BAMIndexContent content) { * * @param count */ + @Override public void writeNoCoordinateRecordCount(final Long count) { codec.writeLong(count == null ? 0 : count); } @@ -154,6 +156,7 @@ public void writeNoCoordinateRecordCount(final Long count) { /** * Any necessary processing at the end of the file */ + @Override public void close() { codec.close(); } diff --git a/src/main/java/htsjdk/samtools/BinningIndexContent.java b/src/main/java/htsjdk/samtools/BinningIndexContent.java index 9e32601c2..124353e27 100644 --- a/src/main/java/htsjdk/samtools/BinningIndexContent.java +++ b/src/main/java/htsjdk/samtools/BinningIndexContent.java @@ -171,6 +171,7 @@ int getNumberOfNonNullBins() { /** * @return An iterator over all non-empty bins. */ + @Override public Iterator iterator() { return new BinIterator(); } @@ -190,6 +191,7 @@ public BinIterator() { * * @return True if more bins are remaining. */ + @Override public boolean hasNext() { while (nextBin <= maxBinNumber) { if (getBin(nextBin) != null) return true; @@ -203,6 +205,7 @@ public boolean hasNext() { * * @return the next available bin in the BinList. */ + @Override public Bin next() { if (!hasNext()) throw new NoSuchElementException("This BinIterator is currently empty"); @@ -211,6 +214,7 @@ public Bin next() { return result; } + @Override public void remove() { throw new UnsupportedOperationException("Unable to remove from a bin iterator"); } diff --git a/src/main/java/htsjdk/samtools/CachingBAMFileIndex.java b/src/main/java/htsjdk/samtools/CachingBAMFileIndex.java index 8010ce59e..5597832c2 100644 --- a/src/main/java/htsjdk/samtools/CachingBAMFileIndex.java +++ b/src/main/java/htsjdk/samtools/CachingBAMFileIndex.java @@ -61,6 +61,7 @@ public CachingBAMFileIndex(final File file, final SAMSequenceDictionary dictiona * in a range that can be scanned to find SAMRecords that overlap the given positions. * May return null if there is no content overlapping the region. */ + @Override public BAMFileSpan getSpanOverlapping(final int referenceIndex, final int startPos, final int endPos) { final BAMIndexContent queryResults = getQueryResults(referenceIndex); @@ -80,6 +81,7 @@ public BAMFileSpan getSpanOverlapping(final int referenceIndex, final int startP * @param endPos 1-based end of the desired interval, inclusive * @return a list of bins that contain relevant data. */ + @Override public BinList getBinsOverlapping(final int referenceIndex, final int startPos, final int endPos) { final BitSet regionBins = GenomicIndexUtil.regionToBins(startPos, endPos); if (regionBins == null) { @@ -93,6 +95,7 @@ public BinList getBinsOverlapping(final int referenceIndex, final int startPos, * @param bin The bin over which to perform an overlapping query. * @return The file pointers */ + @Override public BAMFileSpan getSpanOverlapping(final Bin bin) { if(bin == null) return null; @@ -138,6 +141,7 @@ public BAMFileSpan getSpanOverlapping(final Bin bin) { * @param referenceIndex The reference to load. CachingBAMFileIndex only stores index data for entire references. * @return The index information for this reference. */ + @Override protected BAMIndexContent getQueryResults(final int referenceIndex) { // WeakHashMap is a bit weird in that its lookups are done via equals() equality, but expirations must be // handled via == equality. This implementation jumps through a few hoops to make sure that == equality still diff --git a/src/main/java/htsjdk/samtools/Chunk.java b/src/main/java/htsjdk/samtools/Chunk.java index 0d77b0cd3..dbe27c64d 100644 --- a/src/main/java/htsjdk/samtools/Chunk.java +++ b/src/main/java/htsjdk/samtools/Chunk.java @@ -38,6 +38,7 @@ public Chunk(final long start, final long end) { mChunkEnd = end; } + @Override public Chunk clone() { return new Chunk(mChunkStart,mChunkEnd); } @@ -58,6 +59,7 @@ protected void setChunkEnd(final long value) { mChunkEnd = value; } + @Override public int compareTo(final Chunk chunk) { int result = Long.signum(mChunkStart - chunk.mChunkStart); if (result == 0) { diff --git a/src/main/java/htsjdk/samtools/ComparableSamRecordIterator.java b/src/main/java/htsjdk/samtools/ComparableSamRecordIterator.java index 06186a1d0..cb2da892c 100644 --- a/src/main/java/htsjdk/samtools/ComparableSamRecordIterator.java +++ b/src/main/java/htsjdk/samtools/ComparableSamRecordIterator.java @@ -63,6 +63,7 @@ public SamReader getReader() { * @param that another iterator to compare to * @return a negative, 0 or positive number as described in the Comparator interface */ + @Override public int compareTo(final ComparableSamRecordIterator that) { if (this.comparator.getClass() != that.comparator.getClass()) { throw new IllegalStateException("Attempt to compare two ComparableSAMRecordIterators that " + diff --git a/src/main/java/htsjdk/samtools/CoordinateSortedPairInfoMap.java b/src/main/java/htsjdk/samtools/CoordinateSortedPairInfoMap.java index d892d655a..37c200cc5 100644 --- a/src/main/java/htsjdk/samtools/CoordinateSortedPairInfoMap.java +++ b/src/main/java/htsjdk/samtools/CoordinateSortedPairInfoMap.java @@ -202,6 +202,7 @@ public int sizeInRam() { * or removed from map when iteration is in progress, nor may a second iteration be started. * Iterator must be closed in order to allow normal access to the map. */ + @Override public CloseableIterator> iterator() { if (iterationInProgress) throw new IllegalStateException("Cannot be called when iteration is in progress"); iterationInProgress = true; @@ -238,11 +239,13 @@ private void createIteratorForMapInRam() { currentReferenceIterator = mapInRam.entrySet().iterator(); } + @Override public void close() { closed = true; iterationInProgress = false; } + @Override public boolean hasNext() { if (closed) throw new IllegalStateException("Iterator has been closed"); if (currentReferenceIterator != null && !currentReferenceIterator.hasNext()) @@ -250,6 +253,7 @@ public boolean hasNext() { return currentReferenceIterator != null; } + @Override public Map.Entry next() { if (closed) throw new IllegalStateException("Iterator has been closed"); if (!hasNext()) throw new NoSuchElementException(); @@ -258,6 +262,7 @@ public boolean hasNext() { return ret; } + @Override public void remove() { throw new UnsupportedOperationException(); } diff --git a/src/main/java/htsjdk/samtools/DefaultSAMRecordFactory.java b/src/main/java/htsjdk/samtools/DefaultSAMRecordFactory.java index 7e3848e3d..707cc6ec1 100644 --- a/src/main/java/htsjdk/samtools/DefaultSAMRecordFactory.java +++ b/src/main/java/htsjdk/samtools/DefaultSAMRecordFactory.java @@ -14,6 +14,7 @@ public static DefaultSAMRecordFactory getInstance() { } /** Create a new SAMRecord to be filled in */ + @Override public SAMRecord createSAMRecord(final SAMFileHeader header) { return new SAMRecord(header); } @@ -23,6 +24,7 @@ public SAMRecord createSAMRecord(final SAMFileHeader header) { * any value other than NO_ALIGNMENT_REFERENCE_INDEX, the values must be resolvable against the sequence * dictionary in the header argument. */ + @Override public BAMRecord createBAMRecord (final SAMFileHeader header, final int referenceSequenceIndex, final int alignmentStart, diff --git a/src/main/java/htsjdk/samtools/DiskBasedBAMFileIndex.java b/src/main/java/htsjdk/samtools/DiskBasedBAMFileIndex.java index b5d6f597a..1eddddde3 100644 --- a/src/main/java/htsjdk/samtools/DiskBasedBAMFileIndex.java +++ b/src/main/java/htsjdk/samtools/DiskBasedBAMFileIndex.java @@ -56,6 +56,7 @@ public DiskBasedBAMFileIndex(final File file, final SAMSequenceDictionary dictio * positions. The last position in each pair is a virtual file pointer to the first SAMRecord beyond * the range that may contain the indicated SAMRecords. */ + @Override public BAMFileSpan getSpanOverlapping(final int referenceIndex, final int startPos, final int endPos) { final BAMIndexContent queryResults = query(referenceIndex,startPos,endPos); @@ -69,6 +70,7 @@ public BAMFileSpan getSpanOverlapping(final int referenceIndex, final int startP return new BAMFileSpan(chunkList); } + @Override protected BAMIndexContent getQueryResults(final int reference){ throw new UnsupportedOperationException(); // todo: there ought to be a way to support this using the first startPos for the reference and the last diff --git a/src/main/java/htsjdk/samtools/DuplicateSetIterator.java b/src/main/java/htsjdk/samtools/DuplicateSetIterator.java index 9a0c6f108..f3b9b072a 100644 --- a/src/main/java/htsjdk/samtools/DuplicateSetIterator.java +++ b/src/main/java/htsjdk/samtools/DuplicateSetIterator.java @@ -120,6 +120,7 @@ public void setScoringStrategy(final DuplicateScoringStrategy.ScoringStrategy sc this.comparator.setScoringStrategy(scoringStrategy); } + @Override public DuplicateSet next() { DuplicateSet duplicateSet = null; @@ -161,12 +162,15 @@ public DuplicateSet next() { return duplicateSet; } + @Override public void close() { wrappedIterator.close(); } + @Override public boolean hasNext() { return (!duplicateSet.isEmpty() || wrappedIterator.hasNext()); } // Does nothing! + @Override public void remove() { } } diff --git a/src/main/java/htsjdk/samtools/MergingSamRecordIterator.java b/src/main/java/htsjdk/samtools/MergingSamRecordIterator.java index a294752de..45d002e3e 100644 --- a/src/main/java/htsjdk/samtools/MergingSamRecordIterator.java +++ b/src/main/java/htsjdk/samtools/MergingSamRecordIterator.java @@ -107,6 +107,7 @@ private void startIterationIfRequired() { /** * Close down all open iterators. */ + @Override public void close() { // Iterators not in the priority queue have already been closed; only close down the iterators that are still in the priority queue. for (CloseableIterator iterator : pq) @@ -114,12 +115,14 @@ public void close() { } /** Returns true if any of the underlying iterators has more records, otherwise false. */ + @Override public boolean hasNext() { startIterationIfRequired(); return !this.pq.isEmpty(); } /** Returns the next record from the top most iterator during merging. */ + @Override public SAMRecord next() { startIterationIfRequired(); @@ -163,6 +166,7 @@ private void addIfNotEmpty(final ComparableSamRecordIterator iterator) { } /** Unsupported operation. */ + @Override public void remove() { throw new UnsupportedOperationException("MergingSAMRecorderIterator.remove()"); } @@ -176,10 +180,12 @@ private SAMRecordComparator getComparator() { // For unsorted build a fake comparator that compares based on object ID if (this.sortOrder == SAMFileHeader.SortOrder.unsorted) { return new SAMRecordComparator() { + @Override public int fileOrderCompare(final SAMRecord lhs, final SAMRecord rhs) { return System.identityHashCode(lhs) - System.identityHashCode(rhs); } + @Override public int compare(final SAMRecord lhs, final SAMRecord rhs) { return fileOrderCompare(lhs, rhs); } @@ -206,6 +212,7 @@ public SAMFileHeader getMergedHeader() { private class MergedSequenceDictionaryCoordinateOrderComparator extends SAMRecordCoordinateComparator implements Serializable { private static final long serialVersionUID = 1L; + @Override public int fileOrderCompare(final SAMRecord samRecord1, final SAMRecord samRecord2) { final int referenceIndex1 = getReferenceIndex(samRecord1); final int referenceIndex2 = getReferenceIndex(samRecord2); diff --git a/src/main/java/htsjdk/samtools/QueryInterval.java b/src/main/java/htsjdk/samtools/QueryInterval.java index bdfb52c37..581e0f648 100644 --- a/src/main/java/htsjdk/samtools/QueryInterval.java +++ b/src/main/java/htsjdk/samtools/QueryInterval.java @@ -29,6 +29,7 @@ public QueryInterval(final int referenceIndex, final int start, final int end) { } + @Override public int compareTo(final QueryInterval other) { int comp = this.referenceIndex - other.referenceIndex; if (comp != 0) return comp; diff --git a/src/main/java/htsjdk/samtools/SAMFileHeader.java b/src/main/java/htsjdk/samtools/SAMFileHeader.java index 47543c2a6..41eed4a4c 100644 --- a/src/main/java/htsjdk/samtools/SAMFileHeader.java +++ b/src/main/java/htsjdk/samtools/SAMFileHeader.java @@ -56,6 +56,7 @@ public static final Set STANDARD_TAGS = new HashSet(Arrays.asList(VERSION_TAG, SORT_ORDER_TAG, GROUP_ORDER_TAG)); + @Override Set getStandardTags() { return STANDARD_TAGS; } @@ -353,6 +354,7 @@ public int hashCode() { return result; } + @Override public final SAMFileHeader clone() { final SAMTextHeaderCodec codec = new SAMTextHeaderCodec(); codec.setValidationStringency(ValidationStringency.SILENT); diff --git a/src/main/java/htsjdk/samtools/SAMFileWriter.java b/src/main/java/htsjdk/samtools/SAMFileWriter.java index fe99591f0..24936a0c1 100644 --- a/src/main/java/htsjdk/samtools/SAMFileWriter.java +++ b/src/main/java/htsjdk/samtools/SAMFileWriter.java @@ -46,5 +46,6 @@ /** * Must be called to flush or file will likely be defective. */ + @Override void close(); } diff --git a/src/main/java/htsjdk/samtools/SAMFileWriterImpl.java b/src/main/java/htsjdk/samtools/SAMFileWriterImpl.java index 130ecea4a..5e0ecdb45 100644 --- a/src/main/java/htsjdk/samtools/SAMFileWriterImpl.java +++ b/src/main/java/htsjdk/samtools/SAMFileWriterImpl.java @@ -75,6 +75,7 @@ public static int getDefaultMaxRecordsInRam() { * Sets the progress logger used by this implementation. Setting this lets this writer emit log * messages as SAM records in a SortingCollection are being written to disk. */ + @Override public void setProgressLogger(final ProgressLoggerInterface progress) { this.progressLogger = progress; } @@ -153,6 +154,7 @@ public void setHeader(final SAMFileHeader header) } } + @Override public SAMFileHeader getFileHeader() { return header; } @@ -180,6 +182,7 @@ private SAMRecordComparator makeComparator() { * @throws IllegalArgumentException if the record's reference or mate reference indices cannot be * resolved against the writer's header using the current reference and mate reference names */ + @Override public void addAlignment(final SAMRecord alignment) { alignment.setHeaderStrict(header); // re-establish the record header and resolve reference indices @@ -206,6 +209,7 @@ private void assertPresorted(final SAMRecord alignment) { /** * Must be called or else file will likely be defective. */ + @Override public final void close() { if (!isClosed) { diff --git a/src/main/java/htsjdk/samtools/SAMProgramRecord.java b/src/main/java/htsjdk/samtools/SAMProgramRecord.java index 3bbecf90d..91d0dac44 100644 --- a/src/main/java/htsjdk/samtools/SAMProgramRecord.java +++ b/src/main/java/htsjdk/samtools/SAMProgramRecord.java @@ -57,6 +57,7 @@ public SAMProgramRecord(final String id, SAMProgramRecord srcProgramRecord) { } } + @Override public String getId() { return getProgramGroupId(); } @@ -126,6 +127,7 @@ public int hashCode() { return result; } + @Override Set getStandardTags() { return STANDARD_TAGS; } diff --git a/src/main/java/htsjdk/samtools/SAMReadGroupRecord.java b/src/main/java/htsjdk/samtools/SAMReadGroupRecord.java index fd81852a0..bae3c4f62 100644 --- a/src/main/java/htsjdk/samtools/SAMReadGroupRecord.java +++ b/src/main/java/htsjdk/samtools/SAMReadGroupRecord.java @@ -74,6 +74,7 @@ public SAMReadGroupRecord(final String id, final SAMReadGroupRecord srcProgramRe } } + @Override public String getId() { return getReadGroupId(); } public String getReadGroupId() { return mReadGroupId; } @@ -158,6 +159,7 @@ public int hashCode() { return mReadGroupId.hashCode(); } + @Override Set getStandardTags() { return STANDARD_TAGS; } diff --git a/src/main/java/htsjdk/samtools/SAMRecordCoordinateComparator.java b/src/main/java/htsjdk/samtools/SAMRecordCoordinateComparator.java index e8887bc46..fe054b40b 100644 --- a/src/main/java/htsjdk/samtools/SAMRecordCoordinateComparator.java +++ b/src/main/java/htsjdk/samtools/SAMRecordCoordinateComparator.java @@ -43,6 +43,7 @@ public class SAMRecordCoordinateComparator implements SAMRecordComparator, Serializable { private static final long serialVersionUID = 1L; + @Override public int compare(final SAMRecord samRecord1, final SAMRecord samRecord2) { int cmp = fileOrderCompare(samRecord1, samRecord2); if (cmp != 0) { @@ -83,6 +84,7 @@ private int compareInts(int i1, int i2) { * * @return negative if samRecord1 < samRecord2, 0 if equal, else positive */ + @Override public int fileOrderCompare(final SAMRecord samRecord1, final SAMRecord samRecord2) { if (null == samRecord1.getHeader() || null == samRecord2.getHeader()) { diff --git a/src/main/java/htsjdk/samtools/SAMRecordDuplicateComparator.java b/src/main/java/htsjdk/samtools/SAMRecordDuplicateComparator.java index 4ed2bb52d..436ba3c0a 100644 --- a/src/main/java/htsjdk/samtools/SAMRecordDuplicateComparator.java +++ b/src/main/java/htsjdk/samtools/SAMRecordDuplicateComparator.java @@ -220,6 +220,7 @@ private boolean pairedEndAndBothMapped(final SAMRecord record) { * If both reads are paired and both ends mapped, always prefer the first end over the second end. This is needed to * properly choose the first end for optical duplicate identification when both ends are mapped to the same position etc. */ + @Override public int compare(final SAMRecord samRecord1, final SAMRecord samRecord2) { populateTransientAttributes(samRecord1, samRecord2); int cmp; @@ -357,6 +358,7 @@ public int duplicateSetCompare(final SAMRecord samRecord1, final SAMRecord samRe /** * Less stringent than duplicateSetCompare, such that two records are equal enough such that their ordering in a sorted SAM file would be arbitrary. */ + @Override public int fileOrderCompare(final SAMRecord samRecord1, final SAMRecord samRecord2) { return fileOrderCompare(samRecord1, samRecord2, false, true); } diff --git a/src/main/java/htsjdk/samtools/SAMRecordQueryNameComparator.java b/src/main/java/htsjdk/samtools/SAMRecordQueryNameComparator.java index 7fd97f5b5..d2f7cdea9 100644 --- a/src/main/java/htsjdk/samtools/SAMRecordQueryNameComparator.java +++ b/src/main/java/htsjdk/samtools/SAMRecordQueryNameComparator.java @@ -31,6 +31,7 @@ public class SAMRecordQueryNameComparator implements SAMRecordComparator, Serializable { private static final long serialVersionUID = 1L; + @Override public int compare(final SAMRecord samRecord1, final SAMRecord samRecord2) { int cmp = fileOrderCompare(samRecord1, samRecord2); if (cmp != 0) { @@ -75,6 +76,7 @@ public int compare(final SAMRecord samRecord1, final SAMRecord samRecord2) { * * @return negative if samRecord1 < samRecord2, 0 if equal, else positive */ + @Override public int fileOrderCompare(final SAMRecord samRecord1, final SAMRecord samRecord2) { return compareReadNames(samRecord1.getReadName(), samRecord2.getReadName()); } diff --git a/src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java b/src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java index 2af91c30f..60aae473a 100644 --- a/src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java +++ b/src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java @@ -193,16 +193,21 @@ public void addRecord(final SAMRecord record) { } /** Returns a CloseableIterator over the collection of SAMRecords. */ + @Override public CloseableIterator iterator() { return new CloseableIterator() { private final Iterator iterator = records.iterator(); + @Override public void close() { /** Do nothing. */} + @Override public boolean hasNext() { return this.iterator.hasNext(); } + @Override public SAMRecord next() { return this.iterator.next(); } + @Override public void remove() { this.iterator.remove(); } }; } diff --git a/src/main/java/htsjdk/samtools/SAMSequenceRecord.java b/src/main/java/htsjdk/samtools/SAMSequenceRecord.java index 6bca979cc..8e7ccf295 100644 --- a/src/main/java/htsjdk/samtools/SAMSequenceRecord.java +++ b/src/main/java/htsjdk/samtools/SAMSequenceRecord.java @@ -194,10 +194,12 @@ public int hashCode() { return mSequenceName != null ? mSequenceName.hashCode() : 0; } + @Override Set getStandardTags() { return STANDARD_TAGS; } + @Override public final SAMSequenceRecord clone() { final SAMSequenceRecord ret = new SAMSequenceRecord(this.mSequenceName, this.mSequenceLength); ret.mSequenceIndex = this.mSequenceIndex; diff --git a/src/main/java/htsjdk/samtools/SAMTextReader.java b/src/main/java/htsjdk/samtools/SAMTextReader.java index 3968f1cc9..62f871752 100644 --- a/src/main/java/htsjdk/samtools/SAMTextReader.java +++ b/src/main/java/htsjdk/samtools/SAMTextReader.java @@ -79,22 +79,27 @@ public SAMTextReader(final InputStream stream, final File file, final Validation * * @param enabled true to write source information into each SAMRecord. */ + @Override public void enableFileSource(final SamReader reader, final boolean enabled) { this.mParentReader = enabled ? reader : null; } + @Override void enableIndexCaching(final boolean enabled) { throw new UnsupportedOperationException("Cannot enable index caching for a SAM text reader"); } + @Override void enableIndexMemoryMapping(final boolean enabled) { throw new UnsupportedOperationException("Cannot enable index memory mapping for a SAM text reader"); } + @Override void enableCrcChecking(final boolean enabled) { // Do nothing - this has no meaning for SAM reading } + @Override void setSAMRecordFactory(final SAMRecordFactory factory) { this.samRecordFactory = factory; } @@ -104,14 +109,17 @@ void setSAMRecordFactory(final SAMRecordFactory factory) { return SamReader.Type.SAM_TYPE; } + @Override public boolean hasIndex() { return false; } + @Override public BAMIndex getIndex() { throw new UnsupportedOperationException(); } + @Override public void close() { if (mReader != null) { try { @@ -122,14 +130,17 @@ public void close() { } } + @Override public SAMFileHeader getFileHeader() { return mFileHeader; } + @Override public ValidationStringency getValidationStringency() { return validationStringency; } + @Override public void setValidationStringency(final ValidationStringency stringency) { this.validationStringency = stringency; } @@ -141,6 +152,7 @@ public void setValidationStringency(final ValidationStringency stringency) { * * @return Iterator of SAMRecords in file order. */ + @Override public CloseableIterator getIterator() { if (mReader == null) { throw new IllegalStateException("File reader is closed"); @@ -158,6 +170,7 @@ public void setValidationStringency(final ValidationStringency stringency) { * @param fileSpan The file span. * @return An iterator over the given file span. */ + @Override public CloseableIterator getIterator(final SAMFileSpan fileSpan) { throw new UnsupportedOperationException("Cannot directly iterate over regions within SAM text files."); } @@ -167,6 +180,7 @@ public void setValidationStringency(final ValidationStringency stringency) { * * @return An pointer to the first read in the file. */ + @Override public SAMFileSpan getFilePointerSpanningReads() { throw new UnsupportedOperationException("Cannot retrieve file pointers within SAM text files."); } @@ -186,10 +200,12 @@ public SAMFileSpan getFilePointerSpanningReads() { /** * Unsupported for SAM text files. */ + @Override public CloseableIterator queryAlignmentStart(final String sequence, final int start) { throw new UnsupportedOperationException("Cannot query SAM text files"); } + @Override public CloseableIterator queryUnmapped() { throw new UnsupportedOperationException("Cannot query SAM text files"); } @@ -220,14 +236,17 @@ private RecordIterator() { } } + @Override public void close() { SAMTextReader.this.close(); } + @Override public boolean hasNext() { return mCurrentLine != null; } + @Override public SAMRecord next() { if (!hasNext()) { throw new IllegalStateException("Cannot call next() on exhausted iterator"); @@ -239,6 +258,7 @@ public SAMRecord next() { } } + @Override public void remove() { throw new UnsupportedOperationException("Not supported: remove"); } diff --git a/src/main/java/htsjdk/samtools/SAMTextWriter.java b/src/main/java/htsjdk/samtools/SAMTextWriter.java index 0786d670c..70dd4a229 100644 --- a/src/main/java/htsjdk/samtools/SAMTextWriter.java +++ b/src/main/java/htsjdk/samtools/SAMTextWriter.java @@ -122,6 +122,7 @@ public SAMTextWriter(final OutputStream stream, final SamFlagField samFlagFieldO * * @param alignment SAMRecord. */ + @Override public void writeAlignment(final SAMRecord alignment) { try { out.write(alignment.getReadName()); @@ -188,6 +189,7 @@ static synchronized String getSAMString(final SAMRecord alignment) { * * @param textHeader String containing the text to write. */ + @Override public void writeHeader(final String textHeader) { try { out.write(textHeader); @@ -199,6 +201,7 @@ public void writeHeader(final String textHeader) { /** * Do any required flushing here. */ + @Override public void finish() { try { out.close(); @@ -212,6 +215,7 @@ public void finish() { * * @return Output filename, or null if there isn't one. */ + @Override public String getFilename() { if (file == null) { return null; diff --git a/src/main/java/htsjdk/samtools/SamFileHeaderMerger.java b/src/main/java/htsjdk/samtools/SamFileHeaderMerger.java index b3f588caa..d3cf16ada 100644 --- a/src/main/java/htsjdk/samtools/SamFileHeaderMerger.java +++ b/src/main/java/htsjdk/samtools/SamFileHeaderMerger.java @@ -98,6 +98,7 @@ //HeaderRecordFactory that creates SAMReadGroupRecord instances. private static final HeaderRecordFactory READ_GROUP_RECORD_FACTORY = new HeaderRecordFactory() { + @Override public SAMReadGroupRecord createRecord(final String id, final SAMReadGroupRecord srcReadGroupRecord) { return new SAMReadGroupRecord(id, srcReadGroupRecord); } @@ -105,6 +106,7 @@ public SAMReadGroupRecord createRecord(final String id, final SAMReadGroupRecord //HeaderRecordFactory that creates SAMProgramRecord instances. private static final HeaderRecordFactory PROGRAM_RECORD_FACTORY = new HeaderRecordFactory() { + @Override public SAMProgramRecord createRecord(final String id, final SAMProgramRecord srcProgramRecord) { return new SAMProgramRecord(id, srcProgramRecord); } @@ -112,6 +114,7 @@ public SAMProgramRecord createRecord(final String id, final SAMProgramRecord src //comparator used to sort lists of program group and read group records private static final Comparator RECORD_ID_COMPARATOR = new Comparator() { + @Override public int compare(final AbstractSAMHeaderRecord o1, final AbstractSAMHeaderRecord o2) { return o1.getId().compareTo(o2.getId()); } diff --git a/src/main/java/htsjdk/samtools/SamFileValidator.java b/src/main/java/htsjdk/samtools/SamFileValidator.java index e40bfe94f..c774b6fd9 100644 --- a/src/main/java/htsjdk/samtools/SamFileValidator.java +++ b/src/main/java/htsjdk/samtools/SamFileValidator.java @@ -750,6 +750,7 @@ private void validateMateFields(final PairEndInfo end1, final PairEndInfo end2, PairEndInfo remove(int mateReferenceIndex, String key); + @Override CloseableIterator> iterator(); } @@ -757,14 +758,17 @@ private void validateMateFields(final PairEndInfo end1, final PairEndInfo end2, private final CoordinateSortedPairInfoMap onDiskMap = new CoordinateSortedPairInfoMap(maxTempFiles, new Codec()); + @Override public void put(int mateReferenceIndex, String key, PairEndInfo value) { onDiskMap.put(mateReferenceIndex, key, value); } + @Override public PairEndInfo remove(int mateReferenceIndex, String key) { return onDiskMap.remove(mateReferenceIndex, key); } + @Override public CloseableIterator> iterator() { return onDiskMap.iterator(); } @@ -773,14 +777,17 @@ public PairEndInfo remove(int mateReferenceIndex, String key) { private DataInputStream in; private DataOutputStream out; + @Override public void setOutputStream(final OutputStream os) { this.out = new DataOutputStream(os); } + @Override public void setInputStream(final InputStream is) { this.in = new DataInputStream(is); } + @Override public void encode(final String key, final PairEndInfo record) { try { out.writeUTF(key); @@ -802,6 +809,7 @@ public void encode(final String key, final PairEndInfo record) { } } + @Override public Map.Entry decode() { try { final String key = in.readUTF(); @@ -838,31 +846,38 @@ public void encode(final String key, final PairEndInfo record) { private static class InMemoryPairEndInfoMap implements PairEndInfoMap { private final Map map = new HashMap(); + @Override public void put(int mateReferenceIndex, String key, PairEndInfo value) { if (mateReferenceIndex != value.mateReferenceIndex) throw new IllegalArgumentException("mateReferenceIndex does not agree with PairEndInfo"); map.put(key, value); } + @Override public PairEndInfo remove(int mateReferenceIndex, String key) { return map.remove(key); } + @Override public CloseableIterator> iterator() { final Iterator> it = map.entrySet().iterator(); return new CloseableIterator>() { + @Override public void close() { // do nothing } + @Override public boolean hasNext() { return it.hasNext(); } + @Override public Map.Entry next() { return it.next(); } + @Override public void remove() { it.remove(); } diff --git a/src/main/java/htsjdk/samtools/SamPairUtil.java b/src/main/java/htsjdk/samtools/SamPairUtil.java index ee1707bd5..4849850ec 100644 --- a/src/main/java/htsjdk/samtools/SamPairUtil.java +++ b/src/main/java/htsjdk/samtools/SamPairUtil.java @@ -424,6 +424,7 @@ public SetMateInfoIterator(final Iterator iterator, final boolean set */ public long getNumMateCigarsAdded() { return this.numMateCigarsAdded; } + @Override public boolean hasNext() { return (!records.isEmpty() || super.hasNext()); } @@ -495,12 +496,14 @@ private void advance() { } } + @Override public SAMRecord next() { advance(); if (records.isEmpty()) throw new IllegalStateException("Unexpectedly found an empty record list"); return this.records.poll(); } + @Override public SAMRecord peek() { advance(); if (records.isEmpty()) throw new IllegalStateException("Unexpectedly found an empty record list"); diff --git a/src/main/java/htsjdk/samtools/SamReader.java b/src/main/java/htsjdk/samtools/SamReader.java index 0c551a045..08f93ec17 100644 --- a/src/main/java/htsjdk/samtools/SamReader.java +++ b/src/main/java/htsjdk/samtools/SamReader.java @@ -164,6 +164,7 @@ public String toString() { * Only a single open iterator on a SAM or BAM file may be extant at any one time. If you want to start * a second iteration, the first one must be closed first. */ + @Override public SAMRecordIterator iterator(); /** @@ -558,6 +559,7 @@ public AssertingIterator(final CloseableIterator iterator) { wrappedIterator = iterator; } + @Override public SAMRecordIterator assertSorted(final SAMFileHeader.SortOrder sortOrder) { if (sortOrder == null || sortOrder == SAMFileHeader.SortOrder.unsorted) { @@ -569,6 +571,7 @@ public SAMRecordIterator assertSorted(final SAMFileHeader.SortOrder sortOrder) { return this; } + @Override public SAMRecord next() { final SAMRecord result = wrappedIterator.next(); if (comparator != null) { @@ -591,10 +594,13 @@ public SAMRecord next() { return result; } + @Override public void close() { wrappedIterator.close(); } + @Override public boolean hasNext() { return wrappedIterator.hasNext(); } + @Override public void remove() { wrappedIterator.remove(); } } diff --git a/src/main/java/htsjdk/samtools/TextTagCodec.java b/src/main/java/htsjdk/samtools/TextTagCodec.java index 60363e160..40dc8ac73 100644 --- a/src/main/java/htsjdk/samtools/TextTagCodec.java +++ b/src/main/java/htsjdk/samtools/TextTagCodec.java @@ -158,14 +158,17 @@ public String encodeUntypedTag(final String tagName, final Object value) { final String stringVal = numFields == TextTagCodec.NUM_TAG_FIELDS ? fields[2] : ""; final Object val = convertStringToObject(type, stringVal); return new Map.Entry() { + @Override public String getKey() { return key; } + @Override public Object getValue() { return val; } + @Override public Object setValue(final Object o) { throw new UnsupportedOperationException(); } diff --git a/src/main/java/htsjdk/samtools/TextualBAMIndexWriter.java b/src/main/java/htsjdk/samtools/TextualBAMIndexWriter.java index d79027069..da418fd2d 100644 --- a/src/main/java/htsjdk/samtools/TextualBAMIndexWriter.java +++ b/src/main/java/htsjdk/samtools/TextualBAMIndexWriter.java @@ -68,6 +68,7 @@ private void writeHeader() { /** * Write this content as human-readable text */ + @Override public void writeReference(final BAMIndexContent content) { final int reference = content.getReferenceSequence(); @@ -172,6 +173,7 @@ private void writeNullContent(final int reference) { * * @param noCoordinateCount the count of records seen with no coordinate positions in the start coordinate */ + @Override public void writeNoCoordinateRecordCount(final Long noCoordinateCount) { pw.println("No Coordinate Count=" + noCoordinateCount); } @@ -179,6 +181,7 @@ public void writeNoCoordinateRecordCount(final Long noCoordinateCount) { /** * Any necessary processing at the end of the file */ + @Override public void close() { pw.close(); } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/ByteArrayLenEncoding.java b/src/main/java/htsjdk/samtools/cram/encoding/ByteArrayLenEncoding.java index 0c76a5b6e..0c4557793 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/ByteArrayLenEncoding.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/ByteArrayLenEncoding.java @@ -60,6 +60,7 @@ public static EncodingParams toParam(final EncodingParams lenParams, return new EncodingParams(ID, byteArrayOutputStream.toByteArray()); } + @Override public byte[] toByteArray() { final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try { @@ -78,6 +79,7 @@ public static EncodingParams toParam(final EncodingParams lenParams, return byteArrayOutputStream.toByteArray(); } + @Override public void fromByteArray(final byte[] data) { final ByteBuffer buffer = ByteBuffer.wrap(data); diff --git a/src/main/java/htsjdk/samtools/cram/encoding/ByteArrayStopEncoding.java b/src/main/java/htsjdk/samtools/cram/encoding/ByteArrayStopEncoding.java index c46d96754..c62334d6a 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/ByteArrayStopEncoding.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/ByteArrayStopEncoding.java @@ -56,6 +56,7 @@ public static EncodingParams toParam(final byte stopByte, final int externalId) return new EncodingParams(ID, e.toByteArray()); } + @Override public byte[] toByteArray() { final ByteBuffer buf = ByteBuffer.allocate(1024); buf.order(ByteOrder.LITTLE_ENDIAN); @@ -69,6 +70,7 @@ public static EncodingParams toParam(final byte stopByte, final int externalId) return array; } + @Override public void fromByteArray(final byte[] data) { final ByteBuffer buf = ByteBuffer.wrap(data); buf.order(ByteOrder.LITTLE_ENDIAN); diff --git a/src/main/java/htsjdk/samtools/cram/encoding/ExternalByteArrayEncoding.java b/src/main/java/htsjdk/samtools/cram/encoding/ExternalByteArrayEncoding.java index 2fc707c5f..107a484e1 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/ExternalByteArrayEncoding.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/ExternalByteArrayEncoding.java @@ -38,10 +38,12 @@ public static EncodingParams toParam(final int contentId) { return new EncodingParams(encodingId, e.toByteArray()); } + @Override public byte[] toByteArray() { return ITF8.writeUnsignedITF8(contentId); } + @Override public void fromByteArray(final byte[] data) { contentId = ITF8.readUnsignedITF8(data); } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/ExternalByteEncoding.java b/src/main/java/htsjdk/samtools/cram/encoding/ExternalByteEncoding.java index 0fed72059..75a63ccd6 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/ExternalByteEncoding.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/ExternalByteEncoding.java @@ -38,10 +38,12 @@ public static EncodingParams toParam(final int contentId) { return new EncodingParams(encodingId, externalByteEncoding.toByteArray()); } + @Override public byte[] toByteArray() { return ITF8.writeUnsignedITF8(contentId); } + @Override public void fromByteArray(final byte[] data) { contentId = ITF8.readUnsignedITF8(data); } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/ExternalIntegerEncoding.java b/src/main/java/htsjdk/samtools/cram/encoding/ExternalIntegerEncoding.java index a7c573668..1f0ecba69 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/ExternalIntegerEncoding.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/ExternalIntegerEncoding.java @@ -38,10 +38,12 @@ public static EncodingParams toParam(final int contentId) { return new EncodingParams(encodingId, externalIntegerEncoding.toByteArray()); } + @Override public byte[] toByteArray() { return ITF8.writeUnsignedITF8(contentId); } + @Override public void fromByteArray(final byte[] data) { contentId = ITF8.readUnsignedITF8(data); } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/ExternalLongEncoding.java b/src/main/java/htsjdk/samtools/cram/encoding/ExternalLongEncoding.java index 402cea888..b3ba54ef6 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/ExternalLongEncoding.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/ExternalLongEncoding.java @@ -38,10 +38,12 @@ public static EncodingParams toParam(final int contentId) { return new EncodingParams(encodingId, externalLongEncoding.toByteArray()); } + @Override public byte[] toByteArray() { return ITF8.writeUnsignedITF8(contentId); } + @Override public void fromByteArray(final byte[] data) { contentId = ITF8.readUnsignedITF8(data); } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/GolombRiceIntegerCodec.java b/src/main/java/htsjdk/samtools/cram/encoding/GolombRiceIntegerCodec.java index e5962a152..579f28b77 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/GolombRiceIntegerCodec.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/GolombRiceIntegerCodec.java @@ -38,6 +38,7 @@ public GolombRiceIntegerCodec(final int offset, final int log2m) { mask = ~(~0 << log2m); } + @Override public final Integer read(final BitInputStream bitInputStream) throws IOException { int unary = 0; diff --git a/src/main/java/htsjdk/samtools/cram/encoding/huffman/HuffmanTree.java b/src/main/java/htsjdk/samtools/cram/encoding/huffman/HuffmanTree.java index 43500c4d3..bd4316d23 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/huffman/HuffmanTree.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/huffman/HuffmanTree.java @@ -24,6 +24,7 @@ frequency = freq; } + @Override public int compareTo(@SuppressWarnings("NullableProblems") final HuffmanTree tree) { return frequency - tree.frequency; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/BaseQualityScore.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/BaseQualityScore.java index 41a69d27f..07ee30502 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/BaseQualityScore.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/BaseQualityScore.java @@ -44,6 +44,7 @@ public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/HardClip.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/HardClip.java index 3c3c7ad04..0e5678bb4 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/HardClip.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/HardClip.java @@ -41,10 +41,12 @@ public byte getOperator() { return operator; } + @Override public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/InsertBase.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/InsertBase.java index 597041337..d4a611e8d 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/InsertBase.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/InsertBase.java @@ -42,10 +42,12 @@ public byte getOperator() { return operator; } + @Override public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Insertion.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Insertion.java index e0182c312..2055ba0fd 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Insertion.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Insertion.java @@ -42,10 +42,12 @@ public byte getOperator() { return operator; } + @Override public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Padding.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Padding.java index 85e90fdf0..f9a201f2d 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Padding.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Padding.java @@ -42,10 +42,12 @@ public byte getOperator() { return operator; } + @Override public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/ReadBase.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/ReadBase.java index 73ae20818..f56d6775a 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/ReadBase.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/ReadBase.java @@ -46,6 +46,7 @@ public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/RefSkip.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/RefSkip.java index 1b99f0969..e9e5ae37e 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/RefSkip.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/RefSkip.java @@ -42,10 +42,12 @@ public byte getOperator() { return operator; } + @Override public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/SoftClip.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/SoftClip.java index b142595dd..7eaac6727 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/SoftClip.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/SoftClip.java @@ -51,10 +51,12 @@ public byte getOperator() { return operator; } + @Override public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Substitution.java b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Substitution.java index b2ed5de62..bc84b5aa8 100644 --- a/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Substitution.java +++ b/src/main/java/htsjdk/samtools/cram/encoding/readfeatures/Substitution.java @@ -58,10 +58,12 @@ public byte getOperator() { return operator; } + @Override public int getPosition() { return position; } + @Override public void setPosition(final int position) { this.position = position; } diff --git a/src/main/java/htsjdk/samtools/cram/io/CountingInputStream.java b/src/main/java/htsjdk/samtools/cram/io/CountingInputStream.java index b5e564206..41cb22aef 100644 --- a/src/main/java/htsjdk/samtools/cram/io/CountingInputStream.java +++ b/src/main/java/htsjdk/samtools/cram/io/CountingInputStream.java @@ -37,42 +37,50 @@ public int read() throws IOException { return delegate.read(); } + @Override public int read(@SuppressWarnings("NullableProblems") final byte[] b) throws IOException { final int read = delegate.read(b); count += read; return read; } + @Override public int read(@SuppressWarnings("NullableProblems") final byte[] b, final int off, final int length) throws IOException { final int read = delegate.read(b, off, length); count += read; return read; } + @Override public long skip(final long n) throws IOException { final long skipped = delegate.skip(n); count += skipped; return skipped; } + @Override public int available() throws IOException { return delegate.available(); } + @Override public void close() throws IOException { if (delegate != null) delegate.close(); } + @Override public void mark(final int readLimit) { delegate.mark(readLimit); } + @Override public void reset() throws IOException { delegate.reset(); count = 0; } + @Override public boolean markSupported() { return delegate.markSupported(); } diff --git a/src/main/java/htsjdk/samtools/cram/io/DefaultBitInputStream.java b/src/main/java/htsjdk/samtools/cram/io/DefaultBitInputStream.java index 519cf9da3..fef9e2b08 100644 --- a/src/main/java/htsjdk/samtools/cram/io/DefaultBitInputStream.java +++ b/src/main/java/htsjdk/samtools/cram/io/DefaultBitInputStream.java @@ -41,6 +41,7 @@ public DefaultBitInputStream(final InputStream in) { this.throwEOF = true; } + @Override public final boolean readBit() throws IOException { if (--nofBufferedBits >= 0) return ((byteBuffer >>> nofBufferedBits) & 1) == 1; @@ -55,6 +56,7 @@ public final boolean readBit() throws IOException { return ((byteBuffer >>> 7) & 1) == 1; } + @Override public final int readBits(int n) throws IOException { if (n == 0) return 0; @@ -77,6 +79,7 @@ private static int rightBits(final int n, final int x) { return x & ((1 << n) - 1); } + @Override public final long readLongBits(int n) throws IOException { if (n > 64) throw new RuntimeException("More then 64 bits are requested in one read from bit stream."); @@ -108,6 +111,7 @@ public final long readLongBits(int n) throws IOException { return x | (byteBuffer >>> nofBufferedBits); } + @Override public void reset() { nofBufferedBits = 0; byteBuffer = 0; diff --git a/src/main/java/htsjdk/samtools/cram/io/DefaultBitOutputStream.java b/src/main/java/htsjdk/samtools/cram/io/DefaultBitOutputStream.java index 2d702ee16..95d6789a8 100644 --- a/src/main/java/htsjdk/samtools/cram/io/DefaultBitOutputStream.java +++ b/src/main/java/htsjdk/samtools/cram/io/DefaultBitOutputStream.java @@ -53,6 +53,7 @@ public String toString() { + Integer.toBinaryString(bufferByte).substring(0, bufferedNumberOfBits); } + @Override public void write(final long bitContainer, final int nofBits) throws IOException { if (nofBits == 0) return; @@ -95,6 +96,7 @@ void write_int_LSB_0(final int value, final int nofBitsToWrite) throws IOExcepti } } + @Override public void write(final int bitContainer, final int nofBits) throws IOException { write_int_LSB_0(bitContainer, nofBits); } @@ -109,6 +111,7 @@ private void writeByte(final int value) throws IOException { } } + @Override public void write(byte bitContainer, final int nofBits) throws IOException { if (nofBits < 0 || nofBits > 8) throw new IOException("Expecting 0 to 8 bits."); @@ -145,6 +148,7 @@ public void write(final boolean bit) throws IOException { write(bit ? (byte) 1 : (byte) 0, 1); } + @Override public void write(final boolean bit, final long repeat) throws IOException { for (long i = 0; i < repeat; i++) write(bit); diff --git a/src/main/java/htsjdk/samtools/cram/ref/ReferenceSource.java b/src/main/java/htsjdk/samtools/cram/ref/ReferenceSource.java index e73fb4155..b162c9412 100644 --- a/src/main/java/htsjdk/samtools/cram/ref/ReferenceSource.java +++ b/src/main/java/htsjdk/samtools/cram/ref/ReferenceSource.java @@ -133,6 +133,7 @@ public void clearCache() { return bases; } + @Override public synchronized byte[] getReferenceBases(final SAMSequenceRecord record, final boolean tryNameVariants) { { // check cache by sequence name: diff --git a/src/main/java/htsjdk/samtools/fastq/FastqReader.java b/src/main/java/htsjdk/samtools/fastq/FastqReader.java index 8086dfaee..7988712f3 100755 --- a/src/main/java/htsjdk/samtools/fastq/FastqReader.java +++ b/src/main/java/htsjdk/samtools/fastq/FastqReader.java @@ -128,8 +128,10 @@ private FastqRecord readNextRecord() { } } + @Override public boolean hasNext() { return nextRecord != null; } + @Override public FastqRecord next() { if (!hasNext()) { throw new NoSuchElementException("next() called when !hasNext()"); @@ -139,6 +141,7 @@ public FastqRecord next() { return rec; } + @Override public void remove() { throw new UnsupportedOperationException("Unsupported operation"); } /** @@ -146,6 +149,7 @@ public FastqRecord next() { * start iteration from the beginning of the file. Developers should probably not call iterator() * directly. It is provided so that this class can be used in Java for-each loop. */ + @Override public Iterator iterator() { return this; } public int getLineNumber() { return line ; } diff --git a/src/main/java/htsjdk/samtools/fastq/FastqWriter.java b/src/main/java/htsjdk/samtools/fastq/FastqWriter.java index e37aec57d..3b2a1b688 100644 --- a/src/main/java/htsjdk/samtools/fastq/FastqWriter.java +++ b/src/main/java/htsjdk/samtools/fastq/FastqWriter.java @@ -9,5 +9,6 @@ */ public interface FastqWriter extends Closeable { void write(final FastqRecord rec); + @Override void close(); } diff --git a/src/main/java/htsjdk/samtools/filter/AggregateFilter.java b/src/main/java/htsjdk/samtools/filter/AggregateFilter.java index f396c593f..62b804b79 100644 --- a/src/main/java/htsjdk/samtools/filter/AggregateFilter.java +++ b/src/main/java/htsjdk/samtools/filter/AggregateFilter.java @@ -51,6 +51,7 @@ public AggregateFilter(final List filters) { * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches at least one filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { for (final SamRecordFilter filter : filters) { if (filter.filterOut(record)) { @@ -68,6 +69,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { for (final SamRecordFilter filter : filters) { if (filter.filterOut(first, second)) { diff --git a/src/main/java/htsjdk/samtools/filter/AlignedFilter.java b/src/main/java/htsjdk/samtools/filter/AlignedFilter.java index c70453d00..cebdc0b95 100644 --- a/src/main/java/htsjdk/samtools/filter/AlignedFilter.java +++ b/src/main/java/htsjdk/samtools/filter/AlignedFilter.java @@ -45,6 +45,7 @@ public AlignedFilter(final boolean includeAligned) { * * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { if (includeAligned) { if (!record.getReadUnmappedFlag()) { @@ -68,6 +69,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { if (includeAligned) { diff --git a/src/main/java/htsjdk/samtools/filter/DuplicateReadFilter.java b/src/main/java/htsjdk/samtools/filter/DuplicateReadFilter.java index c79b3ccfd..2fe773f11 100644 --- a/src/main/java/htsjdk/samtools/filter/DuplicateReadFilter.java +++ b/src/main/java/htsjdk/samtools/filter/DuplicateReadFilter.java @@ -34,6 +34,7 @@ * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { return record.getDuplicateReadFlag(); } @@ -46,6 +47,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { throw new UnsupportedOperationException("Paired DuplicateReadFilter filter not implemented!"); } diff --git a/src/main/java/htsjdk/samtools/filter/FailsVendorReadQualityFilter.java b/src/main/java/htsjdk/samtools/filter/FailsVendorReadQualityFilter.java index 7c6825cba..661286df3 100644 --- a/src/main/java/htsjdk/samtools/filter/FailsVendorReadQualityFilter.java +++ b/src/main/java/htsjdk/samtools/filter/FailsVendorReadQualityFilter.java @@ -38,6 +38,7 @@ * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { return record.getReadFailsVendorQualityCheckFlag(); } @@ -50,6 +51,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { // if either fails, exclude them both return (first.getReadFailsVendorQualityCheckFlag() || second.getReadFailsVendorQualityCheckFlag()); diff --git a/src/main/java/htsjdk/samtools/filter/FilteringSamIterator.java b/src/main/java/htsjdk/samtools/filter/FilteringSamIterator.java index 7ac1c0aaa..a70156ad6 100644 --- a/src/main/java/htsjdk/samtools/filter/FilteringSamIterator.java +++ b/src/main/java/htsjdk/samtools/filter/FilteringSamIterator.java @@ -87,6 +87,7 @@ public FilteringSamIterator(final Iterator iterator, final SamRecordF * * @return true if the iteration has more elements. Otherwise returns false. */ + @Override public boolean hasNext() { return next != null; } @@ -98,6 +99,7 @@ public boolean hasNext() { * @throws java.util.NoSuchElementException * */ + @Override public SAMRecord next() { if (next == null) { throw new NoSuchElementException("Iterator has no more elements."); @@ -112,10 +114,12 @@ public SAMRecord next() { * * @throws UnsupportedOperationException */ + @Override public void remove() { throw new UnsupportedOperationException("Remove() not supported by FilteringSamIterator"); } + @Override public void close() { CloserUtil.close(iterator); } diff --git a/src/main/java/htsjdk/samtools/filter/IntervalFilter.java b/src/main/java/htsjdk/samtools/filter/IntervalFilter.java index ff3620ae9..ef5c98a3f 100644 --- a/src/main/java/htsjdk/samtools/filter/IntervalFilter.java +++ b/src/main/java/htsjdk/samtools/filter/IntervalFilter.java @@ -65,6 +65,7 @@ public IntervalFilter(final List intervals, final SAMFileHeader samHea * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { while (currentInterval != null && (currentSequenceIndex < record.getReferenceIndex() || @@ -93,6 +94,7 @@ private void advanceInterval() { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { // This can never be implemented because if the bam is coordinate sorted, // which it has to be for this filter, it will never get both the first and second reads together diff --git a/src/main/java/htsjdk/samtools/filter/IntervalKeepPairFilter.java b/src/main/java/htsjdk/samtools/filter/IntervalKeepPairFilter.java index 5a7961bbb..c4e01aae2 100644 --- a/src/main/java/htsjdk/samtools/filter/IntervalKeepPairFilter.java +++ b/src/main/java/htsjdk/samtools/filter/IntervalKeepPairFilter.java @@ -65,6 +65,7 @@ public IntervalKeepPairFilter(final List intervals) { * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { if (record.isSecondaryOrSupplementary()) { return true; @@ -102,6 +103,7 @@ private boolean hasOverlaps(final String refSequence, final int start, final int * * @return true if both SAMRecords do not overlap the interval list */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { return filterOut(first) && filterOut(second); } diff --git a/src/main/java/htsjdk/samtools/filter/NotPrimaryAlignmentFilter.java b/src/main/java/htsjdk/samtools/filter/NotPrimaryAlignmentFilter.java index 0f2364c92..cda45e045 100644 --- a/src/main/java/htsjdk/samtools/filter/NotPrimaryAlignmentFilter.java +++ b/src/main/java/htsjdk/samtools/filter/NotPrimaryAlignmentFilter.java @@ -35,6 +35,7 @@ * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { return record.getNotPrimaryAlignmentFlag(); } @@ -47,6 +48,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { // if either fails, exclude them both return (first.getNotPrimaryAlignmentFlag() || second.getNotPrimaryAlignmentFlag()); diff --git a/src/main/java/htsjdk/samtools/filter/ReadNameFilter.java b/src/main/java/htsjdk/samtools/filter/ReadNameFilter.java index e4b2a20d9..94a4397a8 100644 --- a/src/main/java/htsjdk/samtools/filter/ReadNameFilter.java +++ b/src/main/java/htsjdk/samtools/filter/ReadNameFilter.java @@ -79,6 +79,7 @@ public ReadNameFilter(final Set readNameFilterSet, final boolean include * * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { if (includeReads) { if (readNameFilterSet.contains(record.getReadName())) { @@ -101,6 +102,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the pair of records matches filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { if (includeReads) { if (readNameFilterSet.contains(first.getReadName()) && diff --git a/src/main/java/htsjdk/samtools/filter/SecondaryAlignmentFilter.java b/src/main/java/htsjdk/samtools/filter/SecondaryAlignmentFilter.java index d91212d40..22741ae0d 100644 --- a/src/main/java/htsjdk/samtools/filter/SecondaryAlignmentFilter.java +++ b/src/main/java/htsjdk/samtools/filter/SecondaryAlignmentFilter.java @@ -9,11 +9,13 @@ /** * Returns true if the read is marked as secondary. */ + @Override public boolean filterOut(final SAMRecord record) { return record.getNotPrimaryAlignmentFlag(); } /** * Returns true if either read is marked as secondary. */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { return first.getNotPrimaryAlignmentFlag() || second.getNotPrimaryAlignmentFlag(); } diff --git a/src/main/java/htsjdk/samtools/filter/SecondaryOrSupplementaryFilter.java b/src/main/java/htsjdk/samtools/filter/SecondaryOrSupplementaryFilter.java index ae57fd9d3..b7d21d157 100644 --- a/src/main/java/htsjdk/samtools/filter/SecondaryOrSupplementaryFilter.java +++ b/src/main/java/htsjdk/samtools/filter/SecondaryOrSupplementaryFilter.java @@ -13,6 +13,7 @@ * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { return record.isSecondaryOrSupplementary(); } @@ -25,6 +26,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { // if either fails, exclude them both return first.isSecondaryOrSupplementary() || second.isSecondaryOrSupplementary(); diff --git a/src/main/java/htsjdk/samtools/filter/SolexaNoiseFilter.java b/src/main/java/htsjdk/samtools/filter/SolexaNoiseFilter.java index bfb31d6d4..ce169ef83 100644 --- a/src/main/java/htsjdk/samtools/filter/SolexaNoiseFilter.java +++ b/src/main/java/htsjdk/samtools/filter/SolexaNoiseFilter.java @@ -40,6 +40,7 @@ * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord record) { final byte[] sequence = record.getReadBases(); for (final byte base : sequence) { @@ -59,6 +60,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { // only filter out the pair if both first and second reads have all As return (filterOut(first) && filterOut(second)); diff --git a/src/main/java/htsjdk/samtools/filter/TagFilter.java b/src/main/java/htsjdk/samtools/filter/TagFilter.java index 5182e836c..00ca8a46c 100644 --- a/src/main/java/htsjdk/samtools/filter/TagFilter.java +++ b/src/main/java/htsjdk/samtools/filter/TagFilter.java @@ -66,6 +66,7 @@ public TagFilter(String tag, List values) { * @param record the SAMRecord to evaluate * @return true if the SAMRecord matches the filter, otherwise false */ + @Override public boolean filterOut(SAMRecord record) { return values.contains(record.getAttribute(tag)); } @@ -78,6 +79,7 @@ public boolean filterOut(SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { // both first and second must have the tag in order for it to be filtered out return values.contains(first.getAttribute(tag)) && values.contains(second.getAttribute(tag)); diff --git a/src/main/java/htsjdk/samtools/filter/WholeReadClippedFilter.java b/src/main/java/htsjdk/samtools/filter/WholeReadClippedFilter.java index 2a1566ce0..6df3c4454 100644 --- a/src/main/java/htsjdk/samtools/filter/WholeReadClippedFilter.java +++ b/src/main/java/htsjdk/samtools/filter/WholeReadClippedFilter.java @@ -57,6 +57,7 @@ public boolean filterOut(final SAMRecord record) { * * @return true if the SAMRecords matches the filter, otherwise false */ + @Override public boolean filterOut(final SAMRecord first, final SAMRecord second) { // if either fails, exclude them both return (filterOut(first) || filterOut(second)); diff --git a/src/main/java/htsjdk/samtools/metrics/StringHeader.java b/src/main/java/htsjdk/samtools/metrics/StringHeader.java index ced159598..949dd4153 100644 --- a/src/main/java/htsjdk/samtools/metrics/StringHeader.java +++ b/src/main/java/htsjdk/samtools/metrics/StringHeader.java @@ -43,6 +43,7 @@ public StringHeader(String value) { setValue(value); } + @Override public void parse(String in) { value = in.trim(); } public String toString() { return value; } diff --git a/src/main/java/htsjdk/samtools/metrics/VersionHeader.java b/src/main/java/htsjdk/samtools/metrics/VersionHeader.java index ae0845502..82093aaa5 100644 --- a/src/main/java/htsjdk/samtools/metrics/VersionHeader.java +++ b/src/main/java/htsjdk/samtools/metrics/VersionHeader.java @@ -37,6 +37,7 @@ private String versionedItem; private String versionString; + @Override public void parse(String in) { String[] fields = in.split("\t"); this.versionedItem = fields[0]; diff --git a/src/main/java/htsjdk/samtools/reference/AbstractFastaSequenceFile.java b/src/main/java/htsjdk/samtools/reference/AbstractFastaSequenceFile.java index badcf1987..736107bb1 100644 --- a/src/main/java/htsjdk/samtools/reference/AbstractFastaSequenceFile.java +++ b/src/main/java/htsjdk/samtools/reference/AbstractFastaSequenceFile.java @@ -115,6 +115,7 @@ protected Path getPath() { * Returns the list of sequence records associated with the reference sequence if found * otherwise null. */ + @Override public SAMSequenceDictionary getSequenceDictionary() { return this.sequenceDictionary; } @@ -130,14 +131,17 @@ public String toString() { } /** default implementation -- override if index is supported */ + @Override public boolean isIndexed() {return false;} /** default implementation -- override if index is supported */ + @Override public ReferenceSequence getSequence( String contig ) { throw new UnsupportedOperationException(); } /** default implementation -- override if index is supported */ + @Override public ReferenceSequence getSubsequenceAt( String contig, long start, long stop ) { throw new UnsupportedOperationException("Index does not appear to exist for " + getAbsolutePath() + ". samtools faidx can be used to create an index"); } diff --git a/src/main/java/htsjdk/samtools/reference/FastaSequenceFile.java b/src/main/java/htsjdk/samtools/reference/FastaSequenceFile.java index 72c0583bb..744d79773 100644 --- a/src/main/java/htsjdk/samtools/reference/FastaSequenceFile.java +++ b/src/main/java/htsjdk/samtools/reference/FastaSequenceFile.java @@ -62,10 +62,12 @@ public FastaSequenceFile(final Path path, final boolean truncateNamesAtWhitespac /** * It's good to call this to free up memory. */ + @Override public void close() { in.close(); } + @Override public ReferenceSequence nextSequence() { this.sequenceIndex += 1; @@ -83,6 +85,7 @@ public ReferenceSequence nextSequence() { return new ReferenceSequence(name, this.sequenceIndex, bases); } + @Override public void reset() { this.sequenceIndex = -1; this.in.close(); diff --git a/src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java b/src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java index e314fccbe..9ae9f1db9 100644 --- a/src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java +++ b/src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java @@ -184,6 +184,7 @@ public FastaSequenceIndexEntry getIndexEntry( String contigName ) { * Creates an iterator which can iterate through all entries in a fasta index. * @return iterator over all fasta index entries. */ + @Override public Iterator iterator() { return sequenceEntries.values().iterator(); } diff --git a/src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java b/src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java index 60cc3b1b7..5a8703381 100644 --- a/src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java +++ b/src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java @@ -111,6 +111,7 @@ public IndexedFastaSequenceFile(final Path path) throws FileNotFoundException { this(path, new FastaSequenceIndex((findRequiredFastaIndexFile(path)))); } + @Override public boolean isIndexed() {return true;} private static File findFastaIndex(File fastaFile) { @@ -190,6 +191,7 @@ protected static void sanityCheckDictionaryAgainstIndex(final String fastaFile, * Retrieves the sequence dictionary for the fasta file. * @return sequence dictionary of the fasta. */ + @Override public SAMSequenceDictionary getSequenceDictionary() { return sequenceDictionary; } @@ -199,6 +201,7 @@ public SAMSequenceDictionary getSequenceDictionary() { * @param contig contig whose data should be returned. * @return The full sequence associated with this contig. */ + @Override public ReferenceSequence getSequence( String contig ) { return getSubsequenceAt( contig, 1, (int)index.getIndexEntry(contig).getSize() ); } @@ -210,6 +213,7 @@ public ReferenceSequence getSequence( String contig ) { * @param stop inclusive, 1-based stop of region. * @return The partial reference sequence associated with this range. */ + @Override public ReferenceSequence getSubsequenceAt( String contig, long start, long stop ) { if(start > stop + 1) throw new SAMException(String.format("Malformed query; start point %d lies after end point %d",start,stop)); @@ -300,6 +304,7 @@ private static int readFromPosition(final SeekableByteChannel channel, final Byt * Gets the next sequence if available, or null if not present. * @return next sequence if available, or null if not present. */ + @Override public ReferenceSequence nextSequence() { if( !indexIterator.hasNext() ) return null; @@ -309,6 +314,7 @@ public ReferenceSequence nextSequence() { /** * Reset the iterator over the index. */ + @Override public void reset() { indexIterator = index.iterator(); } diff --git a/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFile.java b/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFile.java index e7d3c288c..49f526cbc 100644 --- a/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFile.java +++ b/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFile.java @@ -86,5 +86,6 @@ */ public String toString(); + @Override public void close() throws IOException; } diff --git a/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileWalker.java b/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileWalker.java index d66f0f870..6a820ebbe 100644 --- a/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileWalker.java +++ b/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileWalker.java @@ -105,6 +105,7 @@ public SAMSequenceDictionary getSequenceDictionary() { return referenceSequenceFile.getSequenceDictionary(); } + @Override public void close() throws IOException { referenceSequenceFile.close(); } diff --git a/src/main/java/htsjdk/samtools/seekablestream/SeekableBufferedStream.java b/src/main/java/htsjdk/samtools/seekablestream/SeekableBufferedStream.java index 56b4d0c9c..0c89b0166 100644 --- a/src/main/java/htsjdk/samtools/seekablestream/SeekableBufferedStream.java +++ b/src/main/java/htsjdk/samtools/seekablestream/SeekableBufferedStream.java @@ -67,6 +67,7 @@ public SeekableBufferedStream(final SeekableStream stream) { this(stream, DEFAULT_BUFFER_SIZE); } + @Override public long length() { return wrappedStream.length(); } @@ -84,18 +85,21 @@ public long skip(final long skipLength) throws IOException { } } + @Override public void seek(final long position) throws IOException { this.position = position; wrappedStream.seek(position); bufferedStream = new ExtBufferedInputStream(wrappedStream, bufferSize); } + @Override public int read() throws IOException { int b = bufferedStream.read(); position++; return b; } + @Override public int read(final byte[] buffer, final int offset, final int length) throws IOException { int nBytesRead = bufferedStream.read(buffer, offset, length); if (nBytesRead > 0) { @@ -112,10 +116,12 @@ public int read(final byte[] buffer, final int offset, final int length) throws return nBytesRead; } + @Override public void close() throws IOException { wrappedStream.close(); } + @Override public boolean eof() throws IOException { return position >= wrappedStream.length(); } diff --git a/src/main/java/htsjdk/samtools/seekablestream/SeekableFTPStream.java b/src/main/java/htsjdk/samtools/seekablestream/SeekableFTPStream.java index 0a64a7c09..1723747d5 100644 --- a/src/main/java/htsjdk/samtools/seekablestream/SeekableFTPStream.java +++ b/src/main/java/htsjdk/samtools/seekablestream/SeekableFTPStream.java @@ -39,10 +39,12 @@ public SeekableFTPStream(URL url, UserPasswordInput userPasswordInput) throws IO helper = new SeekableFTPStreamHelper(url, userPasswordInput); } + @Override public void seek(long position) { helper.seek(position); } + @Override public long position() { return helper.position(); } @@ -75,10 +77,12 @@ public int read(byte[] buffer, int offset, int len) throws IOException { } + @Override public void close() throws IOException { helper.close(); } + @Override public int read() throws IOException { return helper.read(); } diff --git a/src/main/java/htsjdk/samtools/seekablestream/SeekableFileStream.java b/src/main/java/htsjdk/samtools/seekablestream/SeekableFileStream.java index 38191d769..b790732a9 100644 --- a/src/main/java/htsjdk/samtools/seekablestream/SeekableFileStream.java +++ b/src/main/java/htsjdk/samtools/seekablestream/SeekableFileStream.java @@ -48,18 +48,22 @@ public SeekableFileStream(final File file) throws FileNotFoundException { allInstances.add(this); } + @Override public long length() { return file.length(); } + @Override public boolean eof() throws IOException { return fis.length() == fis.getFilePointer(); } + @Override public void seek(final long position) throws IOException { fis.seek(position); } + @Override public long position() throws IOException { return fis.getChannel().position(); } @@ -71,6 +75,7 @@ public long skip(long n) throws IOException { return position() - initPos; } + @Override public int read(final byte[] buffer, final int offset, final int length) throws IOException { if (length < 0) { throw new IndexOutOfBoundsException(); @@ -91,6 +96,7 @@ public int read(final byte[] buffer, final int offset, final int length) throws } + @Override public int read() throws IOException { return fis.read(); } @@ -106,6 +112,7 @@ public String getSource() { } + @Override public void close() throws IOException { allInstances.remove(this); fis.close(); diff --git a/src/main/java/htsjdk/samtools/seekablestream/SeekableHTTPStream.java b/src/main/java/htsjdk/samtools/seekablestream/SeekableHTTPStream.java index 4a864b77e..640a14d98 100644 --- a/src/main/java/htsjdk/samtools/seekablestream/SeekableHTTPStream.java +++ b/src/main/java/htsjdk/samtools/seekablestream/SeekableHTTPStream.java @@ -67,10 +67,12 @@ public SeekableHTTPStream(final URL url, Proxy proxy) { } + @Override public long position() { return position; } + @Override public long length() { return contentLength; } @@ -82,14 +84,17 @@ public long skip(long n) throws IOException { return bytesToSkip; } + @Override public boolean eof() throws IOException { return contentLength > 0 && position >= contentLength; } + @Override public void seek(final long position) { this.position = position; } + @Override public int read(byte[] buffer, int offset, int len) throws IOException { if (offset < 0 || len < 0 || (offset + len) > buffer.length) { @@ -168,11 +173,13 @@ public int read(byte[] buffer, int offset, int len) throws IOException { } + @Override public void close() throws IOException { // Nothing to do } + @Override public int read() throws IOException { byte []tmp=new byte[1]; read(tmp,0,1); diff --git a/src/main/java/htsjdk/samtools/seekablestream/SeekableStream.java b/src/main/java/htsjdk/samtools/seekablestream/SeekableStream.java index 673f08c48..45f699043 100644 --- a/src/main/java/htsjdk/samtools/seekablestream/SeekableStream.java +++ b/src/main/java/htsjdk/samtools/seekablestream/SeekableStream.java @@ -35,8 +35,10 @@ public abstract void seek(long position) throws IOException; + @Override public abstract int read(byte[] buffer, int offset, int length) throws IOException; + @Override public abstract void close() throws IOException; public abstract boolean eof() throws IOException; diff --git a/src/main/java/htsjdk/samtools/sra/SRALazyRecord.java b/src/main/java/htsjdk/samtools/sra/SRALazyRecord.java index 4391857e6..c5067116e 100644 --- a/src/main/java/htsjdk/samtools/sra/SRALazyRecord.java +++ b/src/main/java/htsjdk/samtools/sra/SRALazyRecord.java @@ -675,6 +675,7 @@ protected SAMBinaryTagAndValue getBinaryAttributes() { return super.getBinaryAttributes(); } + @Override public boolean isUnsignedArrayAttribute(final String tag) { Short binaryTag = SAMTagUtil.getSingleton().makeBinaryTag(tag); LazyAttribute attr = lazyAttributeTags.get(binaryTag); diff --git a/src/main/java/htsjdk/samtools/util/AbstractAsyncWriter.java b/src/main/java/htsjdk/samtools/util/AbstractAsyncWriter.java index ef1803bce..f69578c07 100644 --- a/src/main/java/htsjdk/samtools/util/AbstractAsyncWriter.java +++ b/src/main/java/htsjdk/samtools/util/AbstractAsyncWriter.java @@ -66,6 +66,7 @@ public void write(final T item) { * Attempts to finish draining the queue and then calls synchronouslyClose() to allow implementation * to do any one time clean up. */ + @Override public void close() { checkAndRethrow(); @@ -110,6 +111,7 @@ private final void checkAndRethrow() { * synchronous writer. */ private class WriterRunnable implements Runnable { + @Override public void run() { try { //The order of the two conditions is important, see https://github.com/samtools/htsjdk/issues/564 diff --git a/src/main/java/htsjdk/samtools/util/AbstractLocusInfo.java b/src/main/java/htsjdk/samtools/util/AbstractLocusInfo.java index 4e020071d..d699dce8f 100644 --- a/src/main/java/htsjdk/samtools/util/AbstractLocusInfo.java +++ b/src/main/java/htsjdk/samtools/util/AbstractLocusInfo.java @@ -83,6 +83,7 @@ public void add(E recordAndOffset) { /** * @return the index of reference sequence */ + @Override public int getSequenceIndex() { return referenceSequence.getSequenceIndex(); } @@ -90,6 +91,7 @@ public int getSequenceIndex() { /** * @return 1-based reference position */ + @Override public int getPosition() { return position; } diff --git a/src/main/java/htsjdk/samtools/util/AbstractLocusIterator.java b/src/main/java/htsjdk/samtools/util/AbstractLocusIterator.java index 6ff8e835c..e35087405 100644 --- a/src/main/java/htsjdk/samtools/util/AbstractLocusIterator.java +++ b/src/main/java/htsjdk/samtools/util/AbstractLocusIterator.java @@ -182,6 +182,7 @@ public AbstractLocusIterator(final SamReader samReader, final IntervalList inter * @return iterator over all/all covered locus position in reference according to emitUncoveredLoci * value. */ + @Override public Iterator iterator() { if (samIterator != null) { throw new IllegalStateException("Cannot call iterator() more than once on " + this.getClass().getSimpleName()); @@ -202,6 +203,7 @@ public AbstractLocusIterator(final SamReader samReader, final IntervalList inter /** * Closes inner SamIterator. */ + @Override public void close() { this.samIterator.close(); } @@ -216,6 +218,7 @@ private boolean samHasMore() { * 2) there are AbstractLocusInfos in some stage of accumulation * 3) there are loci in the target mask that have yet to be accumulated (even if there are no reads covering them) */ + @Override public boolean hasNext() { if (this.samIterator == null) { iterator(); @@ -253,6 +256,7 @@ private boolean hasRemainingMaskBases() { * * @return information about next locus position in reference sequence */ + @Override public K next() { // if we don't have any completed entries to return, try and make some! while (complete.isEmpty() && samHasMore()) { @@ -475,6 +479,7 @@ protected SAMSequenceRecord getReferenceSequence(final int referenceSequenceInde return samReader.getFileHeader().getSequence(referenceSequenceIndex); } + @Override public void remove() { throw new UnsupportedOperationException("Can not remove records from a SAM file via an iterator!"); } diff --git a/src/main/java/htsjdk/samtools/util/AsciiWriter.java b/src/main/java/htsjdk/samtools/util/AsciiWriter.java index 00c6f7f1f..50b08d844 100644 --- a/src/main/java/htsjdk/samtools/util/AsciiWriter.java +++ b/src/main/java/htsjdk/samtools/util/AsciiWriter.java @@ -50,6 +50,7 @@ public AsciiWriter(final OutputStream os) { /** * flushes and closes underlying OutputStream. */ + @Override public void close() throws IOException { flush(); os.close(); @@ -58,6 +59,7 @@ public void close() throws IOException { /** * flushes underlying OutputStream */ + @Override public void flush() throws IOException { os.write(buffer, 0, numBytes); numBytes = 0; @@ -67,6 +69,7 @@ public void flush() throws IOException { /** * All other Writer methods vector through this, so this is the only one that must be overridden. */ + @Override public void write(final char[] chars, int offset, int length) throws IOException { while (length > 0) { final int charsToConvert = Math.min(length, buffer.length - numBytes); diff --git a/src/main/java/htsjdk/samtools/util/AsyncBlockCompressedInputStream.java b/src/main/java/htsjdk/samtools/util/AsyncBlockCompressedInputStream.java index cbb168d43..4f71ef581 100644 --- a/src/main/java/htsjdk/samtools/util/AsyncBlockCompressedInputStream.java +++ b/src/main/java/htsjdk/samtools/util/AsyncBlockCompressedInputStream.java @@ -47,6 +47,7 @@ public class AsyncBlockCompressedInputStream extends BlockCompressedInputStream { private static final int READ_AHEAD_BUFFERS = (int)Math.ceil(Defaults.NON_ZERO_BUFFER_SIZE / BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE); private static final Executor threadpool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(),new ThreadFactory() { + @Override public Thread newThread(Runnable r) { Thread t = Executors.defaultThreadFactory().newThread(r); t.setDaemon(true); diff --git a/src/main/java/htsjdk/samtools/util/BinaryCodec.java b/src/main/java/htsjdk/samtools/util/BinaryCodec.java index 8933ee35d..fdef93196 100644 --- a/src/main/java/htsjdk/samtools/util/BinaryCodec.java +++ b/src/main/java/htsjdk/samtools/util/BinaryCodec.java @@ -587,6 +587,7 @@ public long readUInt() { /** * Close the appropriate stream */ + @Override public void close() { try { if (this.isWriting) { diff --git a/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java b/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java index 70a316199..066a0c001 100755 --- a/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java +++ b/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java @@ -189,6 +189,7 @@ public void setCheckCrcs(final boolean check) { * Note that although the next caller can read this many bytes without blocking, the available() method call itself * may block in order to fill an internal buffer if it has been exhausted. */ + @Override public int available() throws IOException { if (mCurrentBlock == null || mCurrentOffset == mCurrentBlock.mBlock.length) { readBlock(); @@ -210,6 +211,7 @@ public boolean endOfBlock() { /** * Closes the underlying InputStream or RandomAccessFile */ + @Override public void close() throws IOException { if (mFile != null) { mFile.close(); @@ -230,6 +232,7 @@ public void close() throws IOException { * @return the next byte of data, or -1 if the end of the stream is reached. */ + @Override public int read() throws IOException { return (available() > 0) ? (mCurrentBlock.mBlock[mCurrentOffset++] & 0xFF) : -1; } @@ -245,6 +248,7 @@ public int read() throws IOException { * @return the total number of bytes read into the buffer, or -1 is there is no more data because the end of * the stream has been reached. */ + @Override public int read(final byte[] buffer) throws IOException { return read(buffer, 0, buffer.length); } @@ -316,6 +320,7 @@ public String readLine() throws IOException { * @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of * the stream has been reached. */ + @Override public int read(final byte[] buffer, int offset, int length) throws IOException { final int originalLength = length; while (length > 0) { diff --git a/src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java b/src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java index 408282f1f..4e9a59487 100644 --- a/src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java +++ b/src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java @@ -296,6 +296,7 @@ public void close() throws IOException { * @param bite * @throws IOException */ + @Override public void write(final int bite) throws IOException { singleByteArray[0] = (byte)bite; write(singleByteArray); diff --git a/src/main/java/htsjdk/samtools/util/BufferedLineReader.java b/src/main/java/htsjdk/samtools/util/BufferedLineReader.java index de1115dc4..18a4d05c0 100644 --- a/src/main/java/htsjdk/samtools/util/BufferedLineReader.java +++ b/src/main/java/htsjdk/samtools/util/BufferedLineReader.java @@ -59,6 +59,7 @@ public BufferedLineReader(final InputStream is, final int bufferSize) { * * @return the line read, or null if EOF has been reached. */ + @Override public String readLine() { ++lineNumber; try { @@ -78,6 +79,7 @@ public String readLine() { /** * @return 1-based number of line most recently read */ + @Override public int getLineNumber() { return lineNumber; } @@ -87,6 +89,7 @@ public int getLineNumber() { * * @return If not eof, the next character that would be read. If eof, -1. */ + @Override public int peek() { if (peekedLine == null) { try { @@ -104,6 +107,7 @@ public int peek() { return peekedLine.charAt(0); } + @Override public void close() { peekedLine = null; try { diff --git a/src/main/java/htsjdk/samtools/util/CloseableIterator.java b/src/main/java/htsjdk/samtools/util/CloseableIterator.java index d26443e0c..fa657be22 100755 --- a/src/main/java/htsjdk/samtools/util/CloseableIterator.java +++ b/src/main/java/htsjdk/samtools/util/CloseableIterator.java @@ -45,6 +45,7 @@ */ public interface CloseableIterator extends Iterator, Closeable { /** Should be implemented to close/release any underlying resources. */ + @Override void close(); /** Consumes the contents of the iterator and returns it as a List. */ diff --git a/src/main/java/htsjdk/samtools/util/DelegatingIterator.java b/src/main/java/htsjdk/samtools/util/DelegatingIterator.java index 054352bac..9d5174a93 100644 --- a/src/main/java/htsjdk/samtools/util/DelegatingIterator.java +++ b/src/main/java/htsjdk/samtools/util/DelegatingIterator.java @@ -15,20 +15,24 @@ public DelegatingIterator(final Iterator iterator) { this.iterator = iterator; } + @Override public void close() { if (iterator instanceof CloseableIterator) { ((CloseableIterator) this.iterator).close(); } } + @Override public boolean hasNext() { return this.iterator.hasNext(); } + @Override public T next() { return this.iterator.next(); } + @Override public void remove() { this.iterator.remove(); } diff --git a/src/main/java/htsjdk/samtools/util/DiskBackedQueue.java b/src/main/java/htsjdk/samtools/util/DiskBackedQueue.java index bbf38188b..22fca1138 100644 --- a/src/main/java/htsjdk/samtools/util/DiskBackedQueue.java +++ b/src/main/java/htsjdk/samtools/util/DiskBackedQueue.java @@ -130,6 +130,7 @@ public boolean headRecordIsFromDisk() { * @return true (if add successful) * @throws IllegalStateException if the queue cannot be added to */ + @Override public boolean add(final E record) throws IllegalStateException { if (!canAdd) throw new IllegalStateException("Cannot add to DiskBackedQueue whose canAdd() method returns false"); @@ -192,6 +193,7 @@ public E peek() { /** * Return the total number of elements in the queue, both in memory and on disk */ + @Override public int size() { return (this.headRecord == null) ? 0 : (1 + this.ramRecords.size() + this.numRecordsOnDisk); } @@ -238,6 +240,7 @@ public void clear() { * * @throws Throwable */ + @Override protected void finalize() throws Throwable { this.closeIOResources(); super.finalize(); // NB: intellij wanted me to do this. Need I? I'm not extending anything diff --git a/src/main/java/htsjdk/samtools/util/EdgingRecordAndOffset.java b/src/main/java/htsjdk/samtools/util/EdgingRecordAndOffset.java index b83a169e6..85beb66f0 100644 --- a/src/main/java/htsjdk/samtools/util/EdgingRecordAndOffset.java +++ b/src/main/java/htsjdk/samtools/util/EdgingRecordAndOffset.java @@ -103,6 +103,7 @@ protected StartEdgingRecordAndOffset(SAMRecord record, int offset, int length, i * @param position in the reference * @return base quality of a read base, corresponding to a given reference position */ + @Override public byte getBaseQuality(int position) { int rOffset = getRelativeOffset(position); byte[] baseQualities = record.getBaseQualities(); @@ -174,6 +175,7 @@ private int getRelativeOffset(int position) { * @param position in the reference * @return base quality of a read base, corresponding to a given reference position */ + @Override public byte getBaseQuality(int position) { return start.getBaseQuality(position); } diff --git a/src/main/java/htsjdk/samtools/util/FastLineReader.java b/src/main/java/htsjdk/samtools/util/FastLineReader.java index d802fad22..95d620267 100644 --- a/src/main/java/htsjdk/samtools/util/FastLineReader.java +++ b/src/main/java/htsjdk/samtools/util/FastLineReader.java @@ -79,6 +79,7 @@ public boolean skipNewlines() { return sawEoln; } + @Override public void close() { CloserUtil.close(in); in = null; diff --git a/src/main/java/htsjdk/samtools/util/FileAppendStreamLRUCache.java b/src/main/java/htsjdk/samtools/util/FileAppendStreamLRUCache.java index bc8bc01cd..500b93182 100644 --- a/src/main/java/htsjdk/samtools/util/FileAppendStreamLRUCache.java +++ b/src/main/java/htsjdk/samtools/util/FileAppendStreamLRUCache.java @@ -47,6 +47,7 @@ public FileAppendStreamLRUCache(final int cacheSize) { } private static class Functor implements ResourceLimitedMapFunctor { + @Override public OutputStream makeValue(final File file) { try { return IOUtil.maybeBufferOutputStream(new FileOutputStream(file, true)); @@ -65,6 +66,7 @@ public OutputStream makeValue(final File file) { } } + @Override public void finalizeValue(final File file, final OutputStream out) { try { out.flush(); diff --git a/src/main/java/htsjdk/samtools/util/IOUtil.java b/src/main/java/htsjdk/samtools/util/IOUtil.java index 97d4d9cdc..0903a09f0 100644 --- a/src/main/java/htsjdk/samtools/util/IOUtil.java +++ b/src/main/java/htsjdk/samtools/util/IOUtil.java @@ -696,6 +696,7 @@ public static void copyFile(final File input, final File output) { public static File[] getFilesMatchingRegexp(final File directory, final Pattern regexp) { return directory.listFiles( new FilenameFilter() { + @Override public boolean accept(final File dir, final String name) { return regexp.matcher(name).matches(); } diff --git a/src/main/java/htsjdk/samtools/util/Interval.java b/src/main/java/htsjdk/samtools/util/Interval.java index 779bb25c9..51e91270a 100644 --- a/src/main/java/htsjdk/samtools/util/Interval.java +++ b/src/main/java/htsjdk/samtools/util/Interval.java @@ -141,6 +141,7 @@ public static long countBases(final Collection intervals) { * Sort based on sequence.compareTo, then start pos, then end pos * with null objects coming lexically last */ + @Override public int compareTo(final Interval that) { if (that == null) return -1; // nulls last diff --git a/src/main/java/htsjdk/samtools/util/IntervalList.java b/src/main/java/htsjdk/samtools/util/IntervalList.java index 76cb5084c..9bfc718f9 100644 --- a/src/main/java/htsjdk/samtools/util/IntervalList.java +++ b/src/main/java/htsjdk/samtools/util/IntervalList.java @@ -84,6 +84,7 @@ public IntervalList(final SAMSequenceDictionary dict) { public SAMFileHeader getHeader() { return header; } /** Returns an iterator over the intervals. */ + @Override public Iterator iterator() { return this.intervals.iterator(); } /** Adds an interval to the list of intervals. */ @@ -762,6 +763,7 @@ public int hashCode() { this.header = header; } + @Override public int compare(final Interval lhs, final Interval rhs) { final int lhsIndex = this.header.getSequenceIndex(lhs.getContig()); final int rhsIndex = this.header.getSequenceIndex(rhs.getContig()); diff --git a/src/main/java/htsjdk/samtools/util/IntervalListReferenceSequenceMask.java b/src/main/java/htsjdk/samtools/util/IntervalListReferenceSequenceMask.java index 1ddd164c6..08c2dd5e1 100644 --- a/src/main/java/htsjdk/samtools/util/IntervalListReferenceSequenceMask.java +++ b/src/main/java/htsjdk/samtools/util/IntervalListReferenceSequenceMask.java @@ -66,6 +66,7 @@ public IntervalListReferenceSequenceMask(final IntervalList intervalList) { * * @return true if the mask is set for the given sequence and position */ + @Override public boolean get(final int sequenceIndex, final int position) { ensureSequenceLoaded(sequenceIndex); return currentBitSet.get(position); @@ -76,6 +77,7 @@ public boolean get(final int sequenceIndex, final int position) { * * @return the next pos on the given sequence >= position that is set, or -1 if there are no more set positions */ + @Override public int nextPosition(final int sequenceIndex, final int position) { ensureSequenceLoaded(sequenceIndex); // nextSetBit returns the first set bit on or after the starting index, therefore position+1 @@ -108,6 +110,7 @@ private void ensureSequenceLoaded(final int sequenceIndex) { /** * @return Largest sequence index for which there are set bits. */ + @Override public int getMaxSequenceIndex() { return lastSequenceIndex; } @@ -115,6 +118,7 @@ public int getMaxSequenceIndex() { /** * @return the largest position on the last sequence index */ + @Override public int getMaxPosition() { return lastPosition; } diff --git a/src/main/java/htsjdk/samtools/util/IntervalTree.java b/src/main/java/htsjdk/samtools/util/IntervalTree.java index cd4acdb69..3efc4dfbb 100644 --- a/src/main/java/htsjdk/samtools/util/IntervalTree.java +++ b/src/main/java/htsjdk/samtools/util/IntervalTree.java @@ -340,6 +340,7 @@ public int getIndex( final int start, final int end ) * Return an iterator over the entire tree. * @return An iterator. */ + @Override public Iterator> iterator() { return new FwdIterator(min()); @@ -1069,11 +1070,13 @@ public FwdIterator( final Node node ) mNext = node; } + @Override public boolean hasNext() { return mNext != null; } + @Override public Node next() { if ( mNext == null ) @@ -1092,6 +1095,7 @@ public boolean hasNext() return mLast; } + @Override public void remove() { if ( mLast == null ) @@ -1115,11 +1119,13 @@ public RevIterator( final Node node ) mNext = node; } + @Override public boolean hasNext() { return mNext != null; } + @Override public Node next() { if ( mNext == null ) @@ -1135,6 +1141,7 @@ public boolean hasNext() return mLast; } + @Override public void remove() { if ( mLast == null ) @@ -1160,11 +1167,13 @@ public OverlapIterator( final int start, final int end ) mEnd = end; } + @Override public boolean hasNext() { return mNext != null; } + @Override public Node next() { if ( mNext == null ) @@ -1182,6 +1191,7 @@ public boolean hasNext() return mLast; } + @Override public void remove() { if ( mLast == null ) @@ -1207,16 +1217,19 @@ public ValuesIterator( final Iterator> itr ) mItr = itr; } + @Override public boolean hasNext() { return mItr.hasNext(); } + @Override public V1 next() { return mItr.next().getValue(); } + @Override public void remove() { mItr.remove(); diff --git a/src/main/java/htsjdk/samtools/util/IntervalTreeMap.java b/src/main/java/htsjdk/samtools/util/IntervalTreeMap.java index ffeae9439..259308732 100644 --- a/src/main/java/htsjdk/samtools/util/IntervalTreeMap.java +++ b/src/main/java/htsjdk/samtools/util/IntervalTreeMap.java @@ -60,10 +60,12 @@ public IntervalTreeMap(final Map map) { } } + @Override public void clear() { mSequenceMap.clear(); } + @Override public boolean containsKey(final Object object) { if (!(object instanceof Interval)) { return false; @@ -79,6 +81,7 @@ public boolean containsKey(final Interval key) { return (tree.find(key.getStart(), key.getEnd()) != null); } + @Override public Set> entrySet() { return mEntrySet; } @@ -95,6 +98,7 @@ public int hashCode() { return mSequenceMap.hashCode(); } + @Override public T get(final Object object) { if (!(object instanceof Interval)) { return null; @@ -114,6 +118,7 @@ public T get(final Interval key) { return node.getValue(); } + @Override public boolean isEmpty() { for (final IntervalTree tree : mSequenceMap.values()) { if (tree.size() > 0) { @@ -123,6 +128,7 @@ public boolean isEmpty() { return true; } + @Override public T put(final Interval key, final T value) { IntervalTree tree = mSequenceMap.get(key.getContig()); if (tree == null) { @@ -132,6 +138,7 @@ public T put(final Interval key, final T value) { return tree.put(key.getStart(), key.getEnd(), value); } + @Override public T remove(final Object object) { if (!(object instanceof Interval)) { return null; @@ -147,6 +154,7 @@ public T remove(final Interval key) { return tree.remove(key.getStart(), key.getEnd()); } + @Override public int size() { // Note: We should think about caching the size to avoid having to recompute it. int size = 0; @@ -214,6 +222,7 @@ public boolean containsContained(final Interval key) { private class EntrySet extends AbstractSet> { + @Override public void clear() { IntervalTreeMap.this.clear(); } @@ -225,14 +234,17 @@ public boolean contains(final Map.Entry entry) { return entry.getValue().equals(IntervalTreeMap.this.get(entry.getKey())); } + @Override public boolean isEmpty() { return IntervalTreeMap.this.isEmpty(); } + @Override public Iterator> iterator() { return new EntryIterator(); } + @Override @SuppressWarnings("unchecked") public boolean remove(final Object object) { // Note: Could not figure out how to eliminate the unchecked cast. @@ -251,6 +263,7 @@ public boolean remove(final Map.Entry entry) { } } + @Override public int size() { return IntervalTreeMap.this.size(); } @@ -268,10 +281,12 @@ public int size() { advanceSequence(); } + @Override public boolean hasNext() { return (mTreeIterator != null && mTreeIterator.hasNext()); } + @Override public Map.Entry next() { if (!hasNext()) { throw new NoSuchElementException("Iterator exhausted"); @@ -286,6 +301,7 @@ public boolean hasNext() { return new MapEntry(key, value); } + @Override public void remove() { if (mTreeIterator == null) { throw new IllegalStateException("Iterator.next() has not been called"); @@ -315,14 +331,17 @@ private void advanceSequence() { mValue = value; } + @Override public Interval getKey() { return mKey; } + @Override public T getValue() { return mValue; } + @Override public T setValue(final T value) { mValue = value; return IntervalTreeMap.this.put(mKey, mValue); diff --git a/src/main/java/htsjdk/samtools/util/Iso8601Date.java b/src/main/java/htsjdk/samtools/util/Iso8601Date.java index 912886724..e173bd385 100644 --- a/src/main/java/htsjdk/samtools/util/Iso8601Date.java +++ b/src/main/java/htsjdk/samtools/util/Iso8601Date.java @@ -36,6 +36,7 @@ */ public class Iso8601Date extends Date { private static final ThreadLocal iso8601DateFormatter = new ThreadLocal() { + @Override protected synchronized DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); } diff --git a/src/main/java/htsjdk/samtools/util/LineReader.java b/src/main/java/htsjdk/samtools/util/LineReader.java index 018570083..4a07f15b8 100644 --- a/src/main/java/htsjdk/samtools/util/LineReader.java +++ b/src/main/java/htsjdk/samtools/util/LineReader.java @@ -47,5 +47,6 @@ */ int peek(); + @Override public void close(); } diff --git a/src/main/java/htsjdk/samtools/util/LocusComparator.java b/src/main/java/htsjdk/samtools/util/LocusComparator.java index e0f04d922..efbe09f26 100644 --- a/src/main/java/htsjdk/samtools/util/LocusComparator.java +++ b/src/main/java/htsjdk/samtools/util/LocusComparator.java @@ -34,6 +34,7 @@ public class LocusComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; + @Override public int compare(T thing1, T thing2) { int refCompare = thing1.getSequenceIndex() - thing2.getSequenceIndex(); return refCompare == 0 ? thing1.getPosition() - thing2.getPosition() : refCompare; diff --git a/src/main/java/htsjdk/samtools/util/LocusImpl.java b/src/main/java/htsjdk/samtools/util/LocusImpl.java index 862907854..5986a6c94 100644 --- a/src/main/java/htsjdk/samtools/util/LocusImpl.java +++ b/src/main/java/htsjdk/samtools/util/LocusImpl.java @@ -36,6 +36,7 @@ public LocusImpl(int sequenceIndex, int position) { this.sequenceIndex = sequenceIndex; } + @Override public int getSequenceIndex() { return sequenceIndex; } @@ -43,6 +44,7 @@ public int getSequenceIndex() { /** * @return 1-based position */ + @Override public int getPosition() { return position; } diff --git a/src/main/java/htsjdk/samtools/util/Md5CalculatingInputStream.java b/src/main/java/htsjdk/samtools/util/Md5CalculatingInputStream.java index e0e7cd520..47ea9ff3b 100755 --- a/src/main/java/htsjdk/samtools/util/Md5CalculatingInputStream.java +++ b/src/main/java/htsjdk/samtools/util/Md5CalculatingInputStream.java @@ -65,12 +65,14 @@ public Md5CalculatingInputStream(InputStream is, File digestFile) { } } + @Override public int read() throws IOException { int result = is.read(); if (result != -1) md5.update((byte)result); return result; } + @Override public int read(byte[] b) throws IOException { int result = is.read(b); if (result != -1) md5.update(b, 0, result); @@ -78,6 +80,7 @@ public int read(byte[] b) throws IOException { } + @Override public int read(byte[] b, int off, int len) throws IOException { int result = is.read(b, off, len); if (result != -1) md5.update(b, off, result); @@ -104,6 +107,7 @@ private String makeHash() { } } + @Override public void close() throws IOException { is.close(); makeHash(); @@ -116,18 +120,23 @@ public void close() throws IOException { } // Methods not supported or overridden because they would not result in a valid hash + @Override public boolean markSupported() { return false; } + @Override public void mark(int readlimit) { throw new UnsupportedOperationException("mark() is not supported by the MD5CalculatingInputStream"); } + @Override public void reset() throws IOException { throw new UnsupportedOperationException("reset() is not supported by the MD5CalculatingInputStream"); } + @Override public long skip(long n) throws IOException { throw new UnsupportedOperationException("skip() is not supported by the MD5CalculatingInputStream"); } // Methods delegated to the wrapped InputStream + @Override public int available() throws IOException { return is.available(); } } diff --git a/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java b/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java index 3c5a492c7..361c99032 100755 --- a/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java +++ b/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java @@ -65,17 +65,20 @@ public Md5CalculatingOutputStream(OutputStream os, File digestFile) { } } + @Override public void write(int b) throws IOException { md5.update((byte)b); os.write(b); } + @Override public void write(byte[] b) throws IOException { md5.update(b); os.write(b); } + @Override public void write(byte[] b, int off, int len) throws IOException { md5.update(b, off, len); os.write(b, off, len); @@ -102,6 +105,7 @@ private String makeHash() { } } + @Override public void close() throws IOException { os.close(); makeHash(); @@ -114,6 +118,7 @@ public void close() throws IOException { } // Pass-through method + @Override public void flush() throws IOException { os.flush(); } } diff --git a/src/main/java/htsjdk/samtools/util/PeekIterator.java b/src/main/java/htsjdk/samtools/util/PeekIterator.java index 9f16a5143..3a43ba54b 100644 --- a/src/main/java/htsjdk/samtools/util/PeekIterator.java +++ b/src/main/java/htsjdk/samtools/util/PeekIterator.java @@ -41,6 +41,7 @@ public PeekIterator(final Iterator underlyingIterator) { * @return true if the iteration has more elements. (In other words, returns true if next would return an element * rather than throwing an exception.) */ + @Override public boolean hasNext() { return peekedElement != null || underlyingIterator.hasNext(); } @@ -49,6 +50,7 @@ public boolean hasNext() { * @return the next element in the iteration. Calling this method repeatedly until the hasNext() method returns * false will return each element in the underlying collection exactly once. */ + @Override public T next() { if (peekedElement != null) { final T ret = peekedElement; @@ -72,6 +74,7 @@ public T peek() { /** * Unsupported */ + @Override public void remove() { throw new UnsupportedOperationException(); } diff --git a/src/main/java/htsjdk/samtools/util/PeekableIterator.java b/src/main/java/htsjdk/samtools/util/PeekableIterator.java index 1587dd299..3df4c42ca 100644 --- a/src/main/java/htsjdk/samtools/util/PeekableIterator.java +++ b/src/main/java/htsjdk/samtools/util/PeekableIterator.java @@ -39,16 +39,19 @@ public PeekableIterator(Iterator iterator) { } /** Closes the underlying iterator. */ + @Override public void close() { CloserUtil.close(iterator); } /** True if there are more items, in which case both next() and peek() will return a value. */ + @Override public boolean hasNext() { return this.nextObject != null; } /** Returns the next object and advances the iterator. */ + @Override public Object next() { Object retval = this.nextObject; advance(); @@ -73,6 +76,7 @@ private void advance(){ } /** Unsupported Operation. */ + @Override public void remove() { throw new UnsupportedOperationException("Not supported: remove"); } diff --git a/src/main/java/htsjdk/samtools/util/PositionalOutputStream.java b/src/main/java/htsjdk/samtools/util/PositionalOutputStream.java index ef28be610..a4643db42 100644 --- a/src/main/java/htsjdk/samtools/util/PositionalOutputStream.java +++ b/src/main/java/htsjdk/samtools/util/PositionalOutputStream.java @@ -41,20 +41,24 @@ public PositionalOutputStream(final OutputStream out) { this.out = out; } + @Override public final void write(final byte[] bytes) throws IOException { write(bytes, 0, bytes.length); } + @Override public final void write(final byte[] bytes, final int startIndex, final int numBytes) throws IOException { position += numBytes; out.write(bytes, startIndex, numBytes); } + @Override public final void write(final int c) throws IOException { position++; out.write(c); } + @Override public final long getPosition() { return position; } @Override diff --git a/src/main/java/htsjdk/samtools/util/QualityEncodingDetector.java b/src/main/java/htsjdk/samtools/util/QualityEncodingDetector.java index b0a965ca1..0147daa35 100644 --- a/src/main/java/htsjdk/samtools/util/QualityEncodingDetector.java +++ b/src/main/java/htsjdk/samtools/util/QualityEncodingDetector.java @@ -270,6 +270,7 @@ public boolean isDeterminationAmbiguous() { } } + @Override public boolean hasNext() { // If this returns true, the head of the queue will have a next element while (!queue.isEmpty()) { @@ -281,6 +282,7 @@ public boolean hasNext() { return false; } + @Override public FastqRecord next() { if (!hasNext()) throw new NoSuchElementException(); final Iterator i = queue.poll(); @@ -289,6 +291,7 @@ public FastqRecord next() { return result; } + @Override public void remove() { throw new UnsupportedOperationException(); } diff --git a/src/main/java/htsjdk/samtools/util/SamRecordIntervalIteratorFactory.java b/src/main/java/htsjdk/samtools/util/SamRecordIntervalIteratorFactory.java index 5d173a5a4..5dd7589d7 100644 --- a/src/main/java/htsjdk/samtools/util/SamRecordIntervalIteratorFactory.java +++ b/src/main/java/htsjdk/samtools/util/SamRecordIntervalIteratorFactory.java @@ -107,6 +107,7 @@ private StopAfterFilteringIterator(Iterator iterator, SamRecordFilter * * @return true if the iteration has more elements. Otherwise returns false. */ + @Override public boolean hasNext() { return next != null; } @@ -117,6 +118,7 @@ public boolean hasNext() { * @return the next element in the iteration * @throws java.util.NoSuchElementException */ + @Override public SAMRecord next() { if (next == null) { throw new NoSuchElementException("Iterator has no more elements."); @@ -131,10 +133,12 @@ public SAMRecord next() { * * @throws UnsupportedOperationException */ + @Override public void remove() { throw new UnsupportedOperationException("Remove() not supported by FilteringSamIterator"); } + @Override public void close() { CloserUtil.close(iterator); } diff --git a/src/main/java/htsjdk/samtools/util/SortingCollection.java b/src/main/java/htsjdk/samtools/util/SortingCollection.java index 6babd4e35..69ce2556b 100644 --- a/src/main/java/htsjdk/samtools/util/SortingCollection.java +++ b/src/main/java/htsjdk/samtools/util/SortingCollection.java @@ -259,6 +259,7 @@ private File newTempFile() throws IOException { * Prepare to iterate through the records in order. This method may be called more than once, * but add() may not be called after this method has been called. */ + @Override public CloseableIterator iterator() { if (this.cleanedUp) { throw new IllegalStateException("Cannot call iterator() after cleanup() was called."); @@ -354,14 +355,17 @@ public void cleanup() { SortingCollection.this.comparator); } + @Override public void close() { // nothing to do } + @Override public boolean hasNext() { return this.iterationIndex < SortingCollection.this.numRecordsInRam; } + @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -372,6 +376,7 @@ public T next() { return ret; } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -409,10 +414,12 @@ public void remove() { } } + @Override public boolean hasNext() { return !this.queue.isEmpty(); } + @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -430,10 +437,12 @@ public T next() { return ret; } + @Override public void remove() { throw new UnsupportedOperationException(); } + @Override public void close() { while (!this.queue.isEmpty()) { final PeekFileRecordIterator it = this.queue.pollFirst(); @@ -464,10 +473,12 @@ public void close() { } } + @Override public boolean hasNext() { return this.currentRecord != null; } + @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -477,6 +488,7 @@ public T next() { return ret; } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -485,6 +497,7 @@ private void advance() { this.currentRecord = this.codec.decode(); } + @Override public void close() { CloserUtil.close(this.is); } @@ -505,6 +518,7 @@ public void close() { class PeekFileRecordIteratorComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; + @Override public int compare(final PeekFileRecordIterator lhs, final PeekFileRecordIterator rhs) { final int result = comparator.compare(lhs.peek(), rhs.peek()); if (result == 0) return lhs.n - rhs.n; diff --git a/src/main/java/htsjdk/samtools/util/SortingLongCollection.java b/src/main/java/htsjdk/samtools/util/SortingLongCollection.java index 4cf0c367f..e75c3362e 100644 --- a/src/main/java/htsjdk/samtools/util/SortingLongCollection.java +++ b/src/main/java/htsjdk/samtools/util/SortingLongCollection.java @@ -336,6 +336,7 @@ void close() { private static class PeekFileValueIteratorComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; + @Override public int compare(final PeekFileValueIterator it1, final PeekFileValueIterator it2) { if (it1.peek() < it2.peek()) { return -1; diff --git a/src/main/java/htsjdk/samtools/util/StringLineReader.java b/src/main/java/htsjdk/samtools/util/StringLineReader.java index ed383a2f2..cca3d9531 100644 --- a/src/main/java/htsjdk/samtools/util/StringLineReader.java +++ b/src/main/java/htsjdk/samtools/util/StringLineReader.java @@ -46,6 +46,7 @@ public StringLineReader(final String s) { /** * Read a line and remove the line terminator */ + @Override public String readLine() { return readLine(false); } @@ -77,6 +78,7 @@ private String readLine(final boolean includeTerminators) { /** * @return 1-based number of line most recently read */ + @Override public int getLineNumber() { return lineNumber; } @@ -86,6 +88,7 @@ public int getLineNumber() { * * @return If not eof, the next character that would be read. If eof, -1. */ + @Override public int peek() { if (curPos == theString.length()) { return -1; @@ -93,6 +96,7 @@ public int peek() { return theString.charAt(curPos); } + @Override public void close() { curPos = theString.length(); } diff --git a/src/main/java/htsjdk/samtools/util/WholeGenomeReferenceSequenceMask.java b/src/main/java/htsjdk/samtools/util/WholeGenomeReferenceSequenceMask.java index 1263285a8..b9ef975a8 100644 --- a/src/main/java/htsjdk/samtools/util/WholeGenomeReferenceSequenceMask.java +++ b/src/main/java/htsjdk/samtools/util/WholeGenomeReferenceSequenceMask.java @@ -41,6 +41,7 @@ public WholeGenomeReferenceSequenceMask(final SAMFileHeader header) { /** * @return true if the mask is set for the given sequence and position */ + @Override public boolean get(final int sequenceIndex, final int position) { if (sequenceIndex < 0) { throw new IllegalArgumentException("Negative sequence index " + sequenceIndex); @@ -55,6 +56,7 @@ public boolean get(final int sequenceIndex, final int position) { /** * @return the next pos on the given sequence >= position that is set, or -1 if there are no more set positions */ + @Override public int nextPosition(final int sequenceIndex, final int position) { if (get(sequenceIndex, position + 1)) { return position + 1; @@ -66,6 +68,7 @@ public int nextPosition(final int sequenceIndex, final int position) { /** * @return Largest sequence index for which there are set bits. */ + @Override public int getMaxSequenceIndex() { return header.getSequenceDictionary().size() - 1; } @@ -73,6 +76,7 @@ public int getMaxSequenceIndex() { /** * @return the largest position on the last sequence index */ + @Override public int getMaxPosition() { SAMSequenceRecord lastSequenceRecord = header.getSequence(getMaxSequenceIndex()); return lastSequenceRecord.getSequenceLength(); diff --git a/src/main/java/htsjdk/tribble/FeatureReader.java b/src/main/java/htsjdk/tribble/FeatureReader.java index 3471393b8..c7773a27e 100644 --- a/src/main/java/htsjdk/tribble/FeatureReader.java +++ b/src/main/java/htsjdk/tribble/FeatureReader.java @@ -32,6 +32,7 @@ public CloseableTribbleIterator iterator() throws IOException; + @Override public void close() throws IOException; public List getSequenceNames(); diff --git a/src/main/java/htsjdk/tribble/SimpleFeature.java b/src/main/java/htsjdk/tribble/SimpleFeature.java index ddc62fa10..0365dc594 100644 --- a/src/main/java/htsjdk/tribble/SimpleFeature.java +++ b/src/main/java/htsjdk/tribble/SimpleFeature.java @@ -39,14 +39,17 @@ public SimpleFeature(final String contig, final int start, final int end) { this.end = end; } + @Override public String getContig() { return contig; } + @Override public int getStart() { return start; } + @Override public int getEnd() { return end; } diff --git a/src/main/java/htsjdk/tribble/TribbleException.java b/src/main/java/htsjdk/tribble/TribbleException.java index 86202ebfb..18f1f81f8 100644 --- a/src/main/java/htsjdk/tribble/TribbleException.java +++ b/src/main/java/htsjdk/tribble/TribbleException.java @@ -54,6 +54,7 @@ public void setSource(String source) { * override the default message with ours, which attaches the source file in question * @return a string with our internal error, along with the causitive source file (or other input source) */ + @Override public String getMessage() { String ret = super.getMessage(); if ( source != null ) diff --git a/src/main/java/htsjdk/tribble/bed/FullBEDFeature.java b/src/main/java/htsjdk/tribble/bed/FullBEDFeature.java index eab568837..975777dc2 100644 --- a/src/main/java/htsjdk/tribble/bed/FullBEDFeature.java +++ b/src/main/java/htsjdk/tribble/bed/FullBEDFeature.java @@ -39,6 +39,7 @@ public FullBEDFeature(String chr, int start, int end) { } + @Override public java.util.List getExons() { return exons; } diff --git a/src/main/java/htsjdk/tribble/bed/SimpleBEDFeature.java b/src/main/java/htsjdk/tribble/bed/SimpleBEDFeature.java index 77a030fa9..4a6416867 100644 --- a/src/main/java/htsjdk/tribble/bed/SimpleBEDFeature.java +++ b/src/main/java/htsjdk/tribble/bed/SimpleBEDFeature.java @@ -56,14 +56,17 @@ public String getContig() { return chr; } + @Override public int getStart() { return start; } + @Override public int getEnd() { return end; } + @Override public Strand getStrand() { return strand; } @@ -84,6 +87,7 @@ public void setEnd(int end) { this.end = end; } + @Override public String getType() { return type; } @@ -92,6 +96,7 @@ public void setType(String type) { this.type = type; } + @Override public Color getColor() { return color; } @@ -100,6 +105,7 @@ public void setColor(Color color) { this.color = color; } + @Override public String getDescription() { return description; } @@ -108,6 +114,7 @@ public void setDescription(String description) { this.description = description; } + @Override public String getName() { return name; } @@ -116,6 +123,7 @@ public void setName(String name) { this.name = name; } + @Override public float getScore() { return score; } @@ -124,6 +132,7 @@ public void setScore(float score) { this.score = score; } + @Override public String getLink() { return link; } @@ -134,6 +143,7 @@ public void setLink(String link) { final static List emptyExonList = new ArrayList(); + @Override public java.util.List getExons() { return emptyExonList; } diff --git a/src/main/java/htsjdk/tribble/index/AbstractIndex.java b/src/main/java/htsjdk/tribble/index/AbstractIndex.java index 47e31ccef..5ae5492d6 100644 --- a/src/main/java/htsjdk/tribble/index/AbstractIndex.java +++ b/src/main/java/htsjdk/tribble/index/AbstractIndex.java @@ -101,6 +101,7 @@ public boolean hasMD5() { * @param obj * @return true if this and obj are 'effectively' equivalent data structures. */ + @Override public boolean equalsIgnoreProperties(final Object obj) { if (this == obj) return true; if (!(obj instanceof AbstractIndex)) { @@ -194,6 +195,7 @@ protected void validateIndexHeader(final int indexType, final LittleEndianInputS * * @return true if we're up to date, false otherwise */ + @Override public boolean isCurrentVersion() { return version == VERSION; } @@ -226,6 +228,7 @@ public void setMD5(final String md5) { this.indexedFileMD5 = md5; } + @Override public boolean containsChromosome(final String chr) { return chrIndices.containsKey(chr); } @@ -306,10 +309,12 @@ private void readSequenceDictionary(final LittleEndianInputStream dis) throws IO } } + @Override public List getSequenceNames() { return new ArrayList(chrIndices.keySet()); } + @Override public List getBlocks(final String chr, final int start, final int end) { return getChrIndex(chr).getBlocks(start, end); } @@ -332,6 +337,7 @@ private final ChrIndex getChrIndex(final String chr) { } } + @Override public void write(final LittleEndianOutputStream stream) throws IOException { writeHeader(stream); @@ -418,10 +424,12 @@ protected String statsSummary() { return String.format("%12d blocks (%12d empty (%.2f%%))", stats.total, stats.empty, (100.0 * stats.empty) / stats.total); } + @Override public void addProperty(final String key, final String value) { properties.put(key, value); } + @Override public void addProperties(final Map properties) { this.properties.putAll(properties); } @@ -431,6 +439,7 @@ public void addProperties(final Map properties) { * * @return the mapping of values as an unmodifiable map */ + @Override public Map getProperties() { return Collections.unmodifiableMap(properties); } diff --git a/src/main/java/htsjdk/tribble/index/DynamicIndexCreator.java b/src/main/java/htsjdk/tribble/index/DynamicIndexCreator.java index 52153a51f..3552fbb4f 100644 --- a/src/main/java/htsjdk/tribble/index/DynamicIndexCreator.java +++ b/src/main/java/htsjdk/tribble/index/DynamicIndexCreator.java @@ -65,6 +65,7 @@ public DynamicIndexCreator(final File inputFile, final IndexFactory.IndexBalance creators = getIndexCreators(inputFile,iba); } + @Override public Index finalizeIndex(final long finalFilePosition) { // finalize all of the indexes // return the score of the indexes we've generated @@ -123,6 +124,7 @@ public Index finalizeIndex(final long finalFilePosition) { } + @Override public void addFeature(final Feature f, final long filePosition) { // protected static Map createIndex(FileBasedFeatureIterator iterator, Map creators, IndexBalanceApproach iba) { // feed each feature to the indexes we've created diff --git a/src/main/java/htsjdk/tribble/index/IndexFactory.java b/src/main/java/htsjdk/tribble/index/IndexFactory.java index f53d9a82d..928236620 100644 --- a/src/main/java/htsjdk/tribble/index/IndexFactory.java +++ b/src/main/java/htsjdk/tribble/index/IndexFactory.java @@ -475,10 +475,12 @@ private PositionalBufferedStream initStream(final File inputFile, final long ski } } + @Override public boolean hasNext() { return nextFeature != null; } + @Override public Feature next() { final Feature ret = nextFeature; readNextFeature(); @@ -488,6 +490,7 @@ public Feature next() { /** * @throws UnsupportedOperationException */ + @Override public void remove() { throw new UnsupportedOperationException("We cannot remove"); } diff --git a/src/main/java/htsjdk/tribble/index/interval/Interval.java b/src/main/java/htsjdk/tribble/index/interval/Interval.java index 9d4787774..6c0e648ee 100644 --- a/src/main/java/htsjdk/tribble/index/interval/Interval.java +++ b/src/main/java/htsjdk/tribble/index/interval/Interval.java @@ -76,6 +76,7 @@ public int hashCode() { } + @Override public int compareTo(Object o) { Interval other = (Interval) o; if (this.start < other.start) diff --git a/src/main/java/htsjdk/tribble/index/interval/IntervalIndexCreator.java b/src/main/java/htsjdk/tribble/index/interval/IntervalIndexCreator.java index e826edaa7..01219040c 100644 --- a/src/main/java/htsjdk/tribble/index/interval/IntervalIndexCreator.java +++ b/src/main/java/htsjdk/tribble/index/interval/IntervalIndexCreator.java @@ -62,6 +62,7 @@ public IntervalIndexCreator(final File inputFile) { this(inputFile, DEFAULT_FEATURE_COUNT); } + @Override public void addFeature(final Feature feature, final long filePosition) { // if we don't have a chrIndex yet, or if the last one was for the previous contig, create a new one if (chrList.isEmpty() || !chrList.getLast().getName().equals(feature.getContig())) { @@ -105,6 +106,7 @@ private void addIntervalsToLastChr(final long currentPos) { * @param finalFilePosition the final file position, for indexes that have to close out with the final position * @return a Tree Index */ + @Override public Index finalizeIndex(final long finalFilePosition) { final IntervalTreeIndex featureIndex = new IntervalTreeIndex(inputFile.getAbsolutePath()); // dump the remaining bins to the index diff --git a/src/main/java/htsjdk/tribble/index/interval/IntervalTreeIndex.java b/src/main/java/htsjdk/tribble/index/interval/IntervalTreeIndex.java index 055888ecc..9a4206ebf 100644 --- a/src/main/java/htsjdk/tribble/index/interval/IntervalTreeIndex.java +++ b/src/main/java/htsjdk/tribble/index/interval/IntervalTreeIndex.java @@ -119,6 +119,7 @@ public ChrIndex(final String name) { tree = new IntervalTree(); } + @Override public String getName() { return name; } @@ -127,11 +128,13 @@ public void insert(final Interval iv) { tree.insert(iv); } + @Override public List getBlocks() { return null; } + @Override public List getBlocks(final int start, final int end) { // Get intervals and build blocks list @@ -148,6 +151,7 @@ public void insert(final Interval iv) { // Sort blocks by start position Arrays.sort(blocks, new Comparator() { + @Override public int compare(final Block b1, final Block b2) { // this is a little cryptic because the normal method (b1.getStartPosition() - b2.getStartPosition()) wraps in int space and we incorrectly sort the blocks in extreme cases return b1.getStartPosition() - b2.getStartPosition() < 1 ? -1 : (b1.getStartPosition() - b2.getStartPosition() > 1 ? 1 : 0); @@ -175,6 +179,7 @@ public void printTree() { System.out.println(tree.toString()); } + @Override public void write(final LittleEndianOutputStream dos) throws IOException { dos.writeString(name); @@ -190,6 +195,7 @@ public void write(final LittleEndianOutputStream dos) throws IOException { } + @Override public void read(final LittleEndianInputStream dis) throws IOException { tree = new IntervalTree(); diff --git a/src/main/java/htsjdk/tribble/index/linear/LinearIndex.java b/src/main/java/htsjdk/tribble/index/linear/LinearIndex.java index 4f4d9100e..5047ab61a 100644 --- a/src/main/java/htsjdk/tribble/index/linear/LinearIndex.java +++ b/src/main/java/htsjdk/tribble/index/linear/LinearIndex.java @@ -101,6 +101,7 @@ public LinearIndex(final InputStream inputStream) throws IOException { read(dis); } + @Override public boolean isCurrentVersion() { if (!super.isCurrentVersion()) return false; @@ -117,6 +118,7 @@ protected int getType() { return INDEX_TYPE; } + @Override public List getSequenceNames() { return (chrIndices == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(new ArrayList(chrIndices.keySet()))); @@ -173,6 +175,7 @@ public ChrIndex() { this.nFeatures = 0; } + @Override public String getName() { return name; } @@ -186,10 +189,12 @@ public int getNBlocks() { return blocks.size(); } + @Override public List getBlocks() { return blocks; } + @Override public List getBlocks(final int start, final int end) { if (blocks.isEmpty()) { return Collections.emptyList(); @@ -231,6 +236,7 @@ public void incrementFeatureCount() { this.nFeatures++; } + @Override public void write(final LittleEndianOutputStream dos) throws IOException { // Chr name, binSize, # bins, longest feature @@ -253,6 +259,7 @@ public void write(final LittleEndianOutputStream dos) throws IOException { dos.writeLong(pos + size); } + @Override public void read(final LittleEndianInputStream dis) throws IOException { name = dis.readString(); binWidth = dis.readInt(); diff --git a/src/main/java/htsjdk/tribble/index/linear/LinearIndexCreator.java b/src/main/java/htsjdk/tribble/index/linear/LinearIndexCreator.java index 1158fdfd3..daad6cce6 100644 --- a/src/main/java/htsjdk/tribble/index/linear/LinearIndexCreator.java +++ b/src/main/java/htsjdk/tribble/index/linear/LinearIndexCreator.java @@ -64,6 +64,7 @@ public LinearIndexCreator(final File inputFile) { * @param feature the feature, from which we use the contig, start, and stop * @param filePosition the position of the file at the BEGINNING of the current feature */ + @Override public void addFeature(final Feature feature, final long filePosition) { // fi we don't have a chrIndex yet, or if the last one was for the previous contig, create a new one if (chrList.isEmpty() || !chrList.getLast().getName().equals(feature.getContig())) { @@ -97,6 +98,7 @@ public void addFeature(final Feature feature, final long filePosition) { * @param finalFilePosition the final file position, for indexes that have to close out with the final position * @return an Index object */ + @Override public Index finalizeIndex(final long finalFilePosition) { if (finalFilePosition == 0) throw new IllegalArgumentException("finalFilePosition != 0, -> " + finalFilePosition); diff --git a/src/main/java/htsjdk/tribble/readers/AsciiLineReader.java b/src/main/java/htsjdk/tribble/readers/AsciiLineReader.java index 8f06205a7..ad66a17c7 100644 --- a/src/main/java/htsjdk/tribble/readers/AsciiLineReader.java +++ b/src/main/java/htsjdk/tribble/readers/AsciiLineReader.java @@ -57,6 +57,7 @@ public AsciiLineReader(final PositionalBufferedStream is) { /** * @return The position of the InputStream */ + @Override public long getPosition(){ if(is == null){ throw new TribbleException("getPosition() called but no default stream was provided to the class on creation"); @@ -115,6 +116,7 @@ public final String readLine(final PositionalBufferedStream stream) throws IOExc * * @return */ + @Override public final String readLine() throws IOException{ if ( is == null ){ throw new TribbleException("readLine() called without an explicit stream argument but no default stream was provided to the class on creation"); diff --git a/src/main/java/htsjdk/tribble/readers/LineReader.java b/src/main/java/htsjdk/tribble/readers/LineReader.java index 969b6b511..2782afc96 100644 --- a/src/main/java/htsjdk/tribble/readers/LineReader.java +++ b/src/main/java/htsjdk/tribble/readers/LineReader.java @@ -39,5 +39,6 @@ public String readLine() throws IOException; + @Override public void close(); } diff --git a/src/main/java/htsjdk/tribble/readers/LongLineBufferedReader.java b/src/main/java/htsjdk/tribble/readers/LongLineBufferedReader.java index 5ca8e8d13..dbb659343 100644 --- a/src/main/java/htsjdk/tribble/readers/LongLineBufferedReader.java +++ b/src/main/java/htsjdk/tribble/readers/LongLineBufferedReader.java @@ -153,6 +153,7 @@ private void fill() throws IOException { * end of the stream has been reached * @throws IOException If an I/O error occurs */ + @Override public int read() throws IOException { synchronized (lock) { ensureOpen(); @@ -250,6 +251,7 @@ private int read1(char[] cbuf, int off, int len) throws IOException { * stream has been reached * @throws IOException If an I/O error occurs */ + @Override public int read(char cbuf[], int off, int len) throws IOException { synchronized (lock) { ensureOpen(); @@ -362,6 +364,7 @@ public String readLine() throws IOException { * @throws IllegalArgumentException If n is negative. * @throws IOException If an I/O error occurs */ + @Override public long skip(long n) throws IOException { if (n < 0L) { throw new IllegalArgumentException("skip value is negative"); @@ -401,6 +404,7 @@ public long skip(long n) throws IOException { * * @throws IOException If an I/O error occurs */ + @Override public boolean ready() throws IOException { synchronized (lock) { ensureOpen(); @@ -429,6 +433,7 @@ public boolean ready() throws IOException { /** * Tells whether this stream supports the mark() operation, which it does. */ + @Override public boolean markSupported() { return true; } @@ -448,6 +453,7 @@ public boolean markSupported() { * @throws IllegalArgumentException If readAheadLimit is < 0 * @throws IOException If an I/O error occurs */ + @Override public void mark(int readAheadLimit) throws IOException { if (readAheadLimit < 0) { throw new IllegalArgumentException("Read-ahead limit < 0"); @@ -466,6 +472,7 @@ public void mark(int readAheadLimit) throws IOException { * @throws IOException If the stream has never been marked, * or if the mark has been invalidated */ + @Override public void reset() throws IOException { synchronized (lock) { ensureOpen(); @@ -478,6 +485,7 @@ public void reset() throws IOException { } } + @Override public void close() throws IOException { synchronized (lock) { if (in == null) diff --git a/src/main/java/htsjdk/tribble/readers/PositionalBufferedStream.java b/src/main/java/htsjdk/tribble/readers/PositionalBufferedStream.java index ac642df98..4d7ae05eb 100644 --- a/src/main/java/htsjdk/tribble/readers/PositionalBufferedStream.java +++ b/src/main/java/htsjdk/tribble/readers/PositionalBufferedStream.java @@ -48,6 +48,7 @@ public PositionalBufferedStream(final InputStream is, final int bufferSize) { nextChar = nChars = 0; } + @Override public final long getPosition() { return position; } @@ -129,6 +130,7 @@ private final int fill() throws IOException { return nChars; } + @Override public final long skip(final long nBytes) throws IOException { long remainingToSkip = nBytes; @@ -156,6 +158,7 @@ public final long skip(final long nBytes) throws IOException { return actuallySkipped; } + @Override public final void close() { try { is.close(); diff --git a/src/main/java/htsjdk/tribble/readers/TabixIteratorLineReader.java b/src/main/java/htsjdk/tribble/readers/TabixIteratorLineReader.java index 49b6f0cfd..2a04725e7 100644 --- a/src/main/java/htsjdk/tribble/readers/TabixIteratorLineReader.java +++ b/src/main/java/htsjdk/tribble/readers/TabixIteratorLineReader.java @@ -40,6 +40,7 @@ public TabixIteratorLineReader(TabixReader.Iterator iterator) { this.iterator = iterator; } + @Override public String readLine() { try { return iterator != null ? iterator.next() : null; @@ -48,6 +49,7 @@ public String readLine() { } } + @Override public void close() { // Ignore - } diff --git a/src/main/java/htsjdk/tribble/readers/TabixReader.java b/src/main/java/htsjdk/tribble/readers/TabixReader.java index b4882e543..244fcd5d2 100644 --- a/src/main/java/htsjdk/tribble/readers/TabixReader.java +++ b/src/main/java/htsjdk/tribble/readers/TabixReader.java @@ -78,6 +78,7 @@ public TPair64(final TPair64 p) { v = p.v; } + @Override public int compareTo(final TPair64 p) { return u == p.u ? 0 : ((u < p.u) ^ (u < 0) ^ (p.u < 0)) ? -1 : 1; // unsigned 64-bit comparison } diff --git a/src/main/java/htsjdk/tribble/util/HTTPHelper.java b/src/main/java/htsjdk/tribble/util/HTTPHelper.java index 90e622859..1e89bc26b 100644 --- a/src/main/java/htsjdk/tribble/util/HTTPHelper.java +++ b/src/main/java/htsjdk/tribble/util/HTTPHelper.java @@ -57,6 +57,7 @@ public static synchronized void setProxy(Proxy p) { proxy = p; } + @Override public URL getUrl() { return url; } @@ -65,6 +66,7 @@ public URL getUrl() { * @return content length of the resource * @throws IOException */ + @Override public long getContentLength() throws IOException { HttpURLConnection con = null; @@ -84,6 +86,7 @@ public long getContentLength() throws IOException { } + @Override public InputStream openInputStream() throws IOException { HttpURLConnection connection = openConnection(); @@ -99,6 +102,7 @@ public InputStream openInputStream() throws IOException { * @return * @throws IOException */ + @Override @Deprecated public InputStream openInputStreamForRange(long start, long end) throws IOException { @@ -118,6 +122,7 @@ private HttpURLConnection openConnection() throws IOException { return connection; } + @Override public boolean exists() throws IOException { HttpURLConnection con = null; try { diff --git a/src/main/java/htsjdk/tribble/util/LittleEndianOutputStream.java b/src/main/java/htsjdk/tribble/util/LittleEndianOutputStream.java index 9bec07188..eab2f8785 100644 --- a/src/main/java/htsjdk/tribble/util/LittleEndianOutputStream.java +++ b/src/main/java/htsjdk/tribble/util/LittleEndianOutputStream.java @@ -25,11 +25,13 @@ public LittleEndianOutputStream(OutputStream out) { super(out); } + @Override public void write(int b) throws IOException { out.write(b); written++; } + @Override public void write(byte[] data, int offset, int length) throws IOException { out.write(data, offset, length); diff --git a/src/main/java/htsjdk/tribble/util/TabixUtils.java b/src/main/java/htsjdk/tribble/util/TabixUtils.java index aa365cd58..5ae9f8afd 100644 --- a/src/main/java/htsjdk/tribble/util/TabixUtils.java +++ b/src/main/java/htsjdk/tribble/util/TabixUtils.java @@ -55,6 +55,7 @@ public TPair64(final TPair64 p) { v = p.v; } + @Override public int compareTo(final TPair64 p) { return u == p.u ? 0 : ((u < p.u) ^ (u < 0) ^ (p.u < 0)) ? -1 : 1; // unsigned 64-bit comparison } diff --git a/src/main/java/htsjdk/variant/variantcontext/Allele.java b/src/main/java/htsjdk/variant/variantcontext/Allele.java index 44fc6aaa7..71aa20126 100644 --- a/src/main/java/htsjdk/variant/variantcontext/Allele.java +++ b/src/main/java/htsjdk/variant/variantcontext/Allele.java @@ -523,6 +523,7 @@ public static Allele getMatchingAllele(final Collection allAlleles, fina return null; // couldn't find anything } + @Override public int compareTo(final Allele other) { if ( isReference() && other.isNonReference() ) return -1; diff --git a/src/main/java/htsjdk/variant/variantcontext/FastGenotype.java b/src/main/java/htsjdk/variant/variantcontext/FastGenotype.java index 665e67242..495ba4192 100644 --- a/src/main/java/htsjdk/variant/variantcontext/FastGenotype.java +++ b/src/main/java/htsjdk/variant/variantcontext/FastGenotype.java @@ -154,6 +154,7 @@ protected FastGenotype(final String sampleName, // // --------------------------------------------------------------------------------------------------------- + @Override public Map getExtendedAttributes() { return extendedAttributes; } diff --git a/src/main/java/htsjdk/variant/variantcontext/JEXLMap.java b/src/main/java/htsjdk/variant/variantcontext/JEXLMap.java index 33ec59514..c4664b08a 100644 --- a/src/main/java/htsjdk/variant/variantcontext/JEXLMap.java +++ b/src/main/java/htsjdk/variant/variantcontext/JEXLMap.java @@ -78,6 +78,7 @@ public JEXLMap(final Collection jexlCollection, final VariantCon * @throws IllegalArgumentException when {@code key} is {@code null} or * when any of the JexlVCMatchExp (i.e. keys) contains invalid Jexl expressions. */ + @Override public Boolean get(Object key) { if (key == null) { throw new IllegalArgumentException("Query key is null"); @@ -101,8 +102,10 @@ public Boolean get(Object key) { * @param o the key * @return true if we have a value for that key */ + @Override public boolean containsKey(Object o) { return jexl.containsKey(o); } + @Override public Set keySet() { return jexl.keySet(); } @@ -119,6 +122,7 @@ public Boolean get(Object key) { * * @throws IllegalArgumentException when any of the JexlVCMatchExp (i.e. keys) contains invalid Jexl expressions. */ + @Override public Collection values() { for (final JexlVCMatchExp exp : jexl.keySet()) { jexl.computeIfAbsent(exp, k -> evaluateExpression(exp)); @@ -129,16 +133,20 @@ public Boolean get(Object key) { /** * @return the number of keys, i.e. {@link JexlVCMatchExp}'s held by this mapping. */ + @Override public int size() { return jexl.size(); } + @Override public boolean isEmpty() { return this.jexl.isEmpty(); } + @Override public Boolean put(JexlVCMatchExp jexlVCMatchExp, Boolean aBoolean) { return jexl.put(jexlVCMatchExp, aBoolean); } + @Override public void putAll(Map map) { jexl.putAll(map); } @@ -207,21 +215,25 @@ private JexlContext createContext() { // this doesn't make much sense to implement, boolean doesn't offer too much variety to deal // with evaluating every key in the internal map. + @Override public boolean containsValue(Object o) { throw new UnsupportedOperationException("containsValue() not supported on a JEXLMap"); } // this doesn't make much sense + @Override public Boolean remove(Object o) { throw new UnsupportedOperationException("remove() not supported on a JEXLMap"); } + @Override public Set> entrySet() { throw new UnsupportedOperationException("entrySet() not supported on a JEXLMap"); } // nope + @Override public void clear() { throw new UnsupportedOperationException("clear() not supported on a JEXLMap"); } diff --git a/src/main/java/htsjdk/variant/variantcontext/VariantContext.java b/src/main/java/htsjdk/variant/variantcontext/VariantContext.java index 55825fb4d..6def89ef9 100644 --- a/src/main/java/htsjdk/variant/variantcontext/VariantContext.java +++ b/src/main/java/htsjdk/variant/variantcontext/VariantContext.java @@ -1663,6 +1663,7 @@ public String getContig() { * underlying vcf file, VariantContexts representing the same biological event may have different start positions depending on the * specifics of the vcf file they are derived from */ + @Override public int getStart() { return (int)start; } @@ -1673,6 +1674,7 @@ public int getStart() { * For VariantContexts with a single alternate allele, if that allele is an insertion, the end position will be on the reference base * before the insertion event. If the single alt allele is a deletion, the end will be on the final deleted reference base. */ + @Override public int getEnd() { return (int)stop; } diff --git a/src/main/java/htsjdk/variant/variantcontext/VariantJEXLContext.java b/src/main/java/htsjdk/variant/variantcontext/VariantJEXLContext.java index 34cde3395..012586381 100644 --- a/src/main/java/htsjdk/variant/variantcontext/VariantJEXLContext.java +++ b/src/main/java/htsjdk/variant/variantcontext/VariantJEXLContext.java @@ -76,6 +76,7 @@ public VariantJEXLContext(VariantContext vc) { this.vc = vc; } + @Override public Object get(String name) { Object result = null; if ( attributes.containsKey(name) ) { // dynamic resolution of name -> value via map @@ -89,6 +90,7 @@ public Object get(String name) { return result; } + @Override public boolean has(String name) { return get(name) != null; } @@ -96,6 +98,7 @@ public boolean has(String name) { /** * @throws UnsupportedOperationException */ + @Override public void set(String name, Object value) { throw new UnsupportedOperationException("remove() not supported on a VariantJEXLContext"); } diff --git a/src/main/java/htsjdk/variant/variantcontext/writer/AsyncVariantContextWriter.java b/src/main/java/htsjdk/variant/variantcontext/writer/AsyncVariantContextWriter.java index 4604316b2..481ab871e 100644 --- a/src/main/java/htsjdk/variant/variantcontext/writer/AsyncVariantContextWriter.java +++ b/src/main/java/htsjdk/variant/variantcontext/writer/AsyncVariantContextWriter.java @@ -39,10 +39,12 @@ public AsyncVariantContextWriter(final VariantContextWriter out, final int queue @Override protected final String getThreadNamePrefix() { return "VariantContextWriterThread-"; } + @Override public void add(final VariantContext vc) { write(vc); } + @Override public void writeHeader(final VCFHeader header) { this.underlyingWriter.writeHeader(header); } diff --git a/src/main/java/htsjdk/variant/variantcontext/writer/BCF2FieldWriter.java b/src/main/java/htsjdk/variant/variantcontext/writer/BCF2FieldWriter.java index f9dd458d0..9582e00ab 100644 --- a/src/main/java/htsjdk/variant/variantcontext/writer/BCF2FieldWriter.java +++ b/src/main/java/htsjdk/variant/variantcontext/writer/BCF2FieldWriter.java @@ -231,6 +231,7 @@ public FTGenotypesWriter(final VCFHeader header, final BCF2FieldEncoder fieldEnc super(header, fieldEncoder); } + @Override public void addGenotype(final BCF2Encoder encoder, final VariantContext vc, final Genotype g) throws IOException { final String fieldValue = g.getFilters(); getFieldEncoder().encodeValue(encoder, fieldValue, encodingType, nValuesPerGenotype); diff --git a/src/main/java/htsjdk/variant/variantcontext/writer/IndexingVariantContextWriter.java b/src/main/java/htsjdk/variant/variantcontext/writer/IndexingVariantContextWriter.java index 6a77f6b3b..eece2d1ac 100644 --- a/src/main/java/htsjdk/variant/variantcontext/writer/IndexingVariantContextWriter.java +++ b/src/main/java/htsjdk/variant/variantcontext/writer/IndexingVariantContextWriter.java @@ -126,11 +126,13 @@ public String getStreamName() { return name; } + @Override public abstract void writeHeader(VCFHeader header); /** * attempt to close the VCF file */ + @Override public void close() { try { // close the underlying output stream @@ -161,6 +163,7 @@ public SAMSequenceDictionary getRefDict() { * * @param vc the Variant Context object */ + @Override public void add(final VariantContext vc) { // if we are doing on the fly indexing, add the record ***before*** we write any bytes if ( indexer != null ) diff --git a/src/main/java/htsjdk/variant/variantcontext/writer/SortingVariantContextWriter.java b/src/main/java/htsjdk/variant/variantcontext/writer/SortingVariantContextWriter.java index 21854827b..edc70c4ff 100644 --- a/src/main/java/htsjdk/variant/variantcontext/writer/SortingVariantContextWriter.java +++ b/src/main/java/htsjdk/variant/variantcontext/writer/SortingVariantContextWriter.java @@ -51,6 +51,7 @@ public SortingVariantContextWriter(VariantContextWriter innerWriter, int maxCach this(innerWriter, maxCachingStartDistance, false); // by default, don't own inner } + @Override protected void noteCurrentRecord(VariantContext vc) { super.noteCurrentRecord(vc); // first, check for errors diff --git a/src/main/java/htsjdk/variant/variantcontext/writer/SortingVariantContextWriterBase.java b/src/main/java/htsjdk/variant/variantcontext/writer/SortingVariantContextWriterBase.java index 690a7813c..7d9273f97 100644 --- a/src/main/java/htsjdk/variant/variantcontext/writer/SortingVariantContextWriterBase.java +++ b/src/main/java/htsjdk/variant/variantcontext/writer/SortingVariantContextWriterBase.java @@ -186,6 +186,7 @@ private synchronized void emitRecords(boolean emitUnsafe) { private static class VariantContextComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; + @Override public int compare(VCFRecord r1, VCFRecord r2) { return r1.vc.getStart() - r2.vc.getStart(); } diff --git a/src/main/java/htsjdk/variant/variantcontext/writer/VariantContextWriter.java b/src/main/java/htsjdk/variant/variantcontext/writer/VariantContextWriter.java index 187ff17c3..843901a20 100644 --- a/src/main/java/htsjdk/variant/variantcontext/writer/VariantContextWriter.java +++ b/src/main/java/htsjdk/variant/variantcontext/writer/VariantContextWriter.java @@ -40,6 +40,7 @@ /** * attempt to close the VCF file */ + @Override public void close(); /** diff --git a/src/main/java/htsjdk/variant/vcf/AbstractVCFCodec.java b/src/main/java/htsjdk/variant/vcf/AbstractVCFCodec.java index 04887aeea..8a55a1946 100644 --- a/src/main/java/htsjdk/variant/vcf/AbstractVCFCodec.java +++ b/src/main/java/htsjdk/variant/vcf/AbstractVCFCodec.java @@ -257,6 +257,7 @@ public Feature decodeLoc(String line) { * @param line the line * @return a VariantContext */ + @Override public VariantContext decode(String line) { return decodeLine(line, true); } @@ -367,6 +368,7 @@ else if ( parts[2].equals(VCFConstants.EMPTY_ID_FIELD) ) * get the name of this codec * @return our set name */ + @Override public String getName() { return name; } @@ -375,6 +377,7 @@ public String getName() { * set the name of this codec * @param name new name */ + @Override public void setName(String name) { this.name = name; } diff --git a/src/main/java/htsjdk/variant/vcf/VCF3Codec.java b/src/main/java/htsjdk/variant/vcf/VCF3Codec.java index 5f4f48ec5..e9ca3abdf 100644 --- a/src/main/java/htsjdk/variant/vcf/VCF3Codec.java +++ b/src/main/java/htsjdk/variant/vcf/VCF3Codec.java @@ -56,6 +56,7 @@ * @param reader the line reader to take header lines from * @return the number of header lines */ + @Override public Object readActualHeader(final LineIterator reader) { final List headerStrings = new ArrayList(); @@ -97,6 +98,7 @@ else if (line.startsWith(VCFHeader.HEADER_INDICATOR)) { * @param filterString the string to parse * @return a set of the filters applied */ + @Override protected List parseFilters(String filterString) { // null for unfiltered diff --git a/src/main/java/htsjdk/variant/vcf/VCFCodec.java b/src/main/java/htsjdk/variant/vcf/VCFCodec.java index 89d68813e..6e5d3b7d2 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFCodec.java +++ b/src/main/java/htsjdk/variant/vcf/VCFCodec.java @@ -125,6 +125,7 @@ else if (line.startsWith(VCFHeader.HEADER_INDICATOR)) { * @param filterString the string to parse * @return a set of the filters applied or null if filters were not applied to the record (e.g. as per the missing value in a VCF) */ + @Override protected List parseFilters(final String filterString) { // null for unfiltered if ( filterString.equals(VCFConstants.UNFILTERED) ) diff --git a/src/main/java/htsjdk/variant/vcf/VCFCompoundHeaderLine.java b/src/main/java/htsjdk/variant/vcf/VCFCompoundHeaderLine.java index 48e0cdf0d..4d8c3447f 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFCompoundHeaderLine.java +++ b/src/main/java/htsjdk/variant/vcf/VCFCompoundHeaderLine.java @@ -57,6 +57,7 @@ private VCFHeaderLineType type; // access methods + @Override public String getID() { return name; } public String getDescription() { return description; } public VCFHeaderLineType getType() { return type; } @@ -221,6 +222,7 @@ private void validate() { * make a string representation of this header line * @return a string representation */ + @Override protected String toStringEncoding() { Map map = new LinkedHashMap(); map.put("ID", name); diff --git a/src/main/java/htsjdk/variant/vcf/VCFFileReader.java b/src/main/java/htsjdk/variant/vcf/VCFFileReader.java index 9024f34fc..b43935880 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFFileReader.java +++ b/src/main/java/htsjdk/variant/vcf/VCFFileReader.java @@ -129,7 +129,8 @@ public VCFHeader getFileHeader() { } /** Returns an iterator over all records in this VCF/BCF file. */ - public CloseableIterator iterator() { + @Override + public CloseableIterator iterator() { try { return reader.iterator(); } catch (final IOException ioe) { throw new TribbleException("Could not create an iterator from a feature reader.", ioe); @@ -144,7 +145,8 @@ public VCFHeader getFileHeader() { } } - public void close() { + @Override + public void close() { try { this.reader.close(); } catch (final IOException ioe) { throw new TribbleException("Could not close a variant context feature reader.", ioe); diff --git a/src/main/java/htsjdk/variant/vcf/VCFHeaderLine.java b/src/main/java/htsjdk/variant/vcf/VCFHeaderLine.java index c4c1e3bdf..ce12c4273 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFHeaderLine.java +++ b/src/main/java/htsjdk/variant/vcf/VCFHeaderLine.java @@ -127,6 +127,7 @@ public int hashCode() { return result; } + @Override public int compareTo(Object other) { return toString().compareTo(other.toString()); } diff --git a/src/main/java/htsjdk/variant/vcf/VCFHeaderLineTranslator.java b/src/main/java/htsjdk/variant/vcf/VCFHeaderLineTranslator.java index 071d815ca..3ac72b28c 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFHeaderLineTranslator.java +++ b/src/main/java/htsjdk/variant/vcf/VCFHeaderLineTranslator.java @@ -67,6 +67,7 @@ * @param valueLine the line * @return a mapping of the tags parsed out */ + @Override public Map parseLine(String valueLine, List expectedTagOrder) { // our return map Map ret = new LinkedHashMap(); @@ -145,6 +146,7 @@ class VCF3Parser implements VCFLineParser { + @Override public Map parseLine(String valueLine, List expectedTagOrder) { // our return map Map ret = new LinkedHashMap(); diff --git a/src/main/java/htsjdk/variant/vcf/VCFSimpleHeaderLine.java b/src/main/java/htsjdk/variant/vcf/VCFSimpleHeaderLine.java index a5da687e6..1c36f9e95 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFSimpleHeaderLine.java +++ b/src/main/java/htsjdk/variant/vcf/VCFSimpleHeaderLine.java @@ -92,6 +92,7 @@ protected void initialize(String name, Map genericFields) { this.genericFields.putAll(genericFields); } + @Override protected String toStringEncoding() { Map map = new LinkedHashMap(); map.put("ID", name); @@ -121,6 +122,7 @@ public int hashCode() { return result; } + @Override public String getID() { return name; } diff --git a/src/test/java/htsjdk/samtools/MergingSamRecordIteratorGroupCollisionTest.java b/src/test/java/htsjdk/samtools/MergingSamRecordIteratorGroupCollisionTest.java index 18c10c70b..067f853ab 100644 --- a/src/test/java/htsjdk/samtools/MergingSamRecordIteratorGroupCollisionTest.java +++ b/src/test/java/htsjdk/samtools/MergingSamRecordIteratorGroupCollisionTest.java @@ -424,18 +424,22 @@ SamReader newFileReader() { } class ProgramGroupAdapter extends GroupAdapter { + @Override String getGroupId(AbstractSAMHeaderRecord group) { return ((SAMProgramRecord) group).getProgramGroupId(); } + @Override List getGroups(SAMFileHeader header) { return header.getProgramRecords(); } + @Override String getTagName() { return SAMTag.PG.toString(); } + @Override List createGroups(final String[] groupIds) { final List readers = new ArrayList(); for (final String groupId : groupIds) { @@ -457,36 +461,44 @@ String getTagName() { return fileHeaderMerger.getMergedHeader().getProgramRecords(); } + @Override void setAttribute(AbstractSAMHeaderRecord group, String value) { ((SAMProgramRecord) group).setCommandLine(value); } + @Override AbstractSAMHeaderRecord newGroup(String id) { return new SAMProgramRecord(id); } + @Override void setBuilderGroup(SAMRecordSetBuilder builder, AbstractSAMHeaderRecord group) { builder.setProgramRecord((SAMProgramRecord) group); } + @Override boolean equivalent(AbstractSAMHeaderRecord group1, AbstractSAMHeaderRecord group2) { return ((SAMProgramRecord) group1).equivalent((SAMProgramRecord) group2); } } class ReadGroupAdapter extends GroupAdapter { + @Override String getGroupId(AbstractSAMHeaderRecord group) { return ((SAMReadGroupRecord) group).getReadGroupId(); } + @Override List getGroups(SAMFileHeader header) { return header.getReadGroups(); } + @Override String getTagName() { return SAMTag.RG.toString(); } + @Override List createGroups(final String[] groupIds) { final List readers = new ArrayList(); @@ -507,20 +519,24 @@ String getTagName() { return fileHeaderMerger.getMergedHeader().getReadGroups(); } + @Override void setAttribute(AbstractSAMHeaderRecord group, String value) { ((SAMReadGroupRecord) group).setPlatformUnit(value); } + @Override AbstractSAMHeaderRecord newGroup(String id) { SAMReadGroupRecord group = new SAMReadGroupRecord(id); group.setAttribute(SAMTag.SM.name(), id); return group; } + @Override void setBuilderGroup(SAMRecordSetBuilder builder, AbstractSAMHeaderRecord group) { builder.setReadGroup((SAMReadGroupRecord) group); } + @Override boolean equivalent(AbstractSAMHeaderRecord group1, AbstractSAMHeaderRecord group2) { return ((SAMReadGroupRecord) group1).equivalent((SAMReadGroupRecord) group2); } diff --git a/src/test/java/htsjdk/samtools/SamReaderFactoryTest.java b/src/test/java/htsjdk/samtools/SamReaderFactoryTest.java index 43d37bdd3..5b86c8a59 100644 --- a/src/test/java/htsjdk/samtools/SamReaderFactoryTest.java +++ b/src/test/java/htsjdk/samtools/SamReaderFactoryTest.java @@ -59,6 +59,7 @@ public int inflate(byte[] b, int off, int len) throws java.util.zip.DataFormatEx } } final InflaterFactory myInflaterFactory = new InflaterFactory() { + @Override public Inflater makeInflater(final boolean gzipCompatible) { return new MyInflater(gzipCompatible); } @@ -162,11 +163,13 @@ public void queryIntervalIssue76(final String sequenceName, final int start, fin int samRecordsCreated; int bamRecordsCreated; + @Override public SAMRecord createSAMRecord(final SAMFileHeader header) { ++samRecordsCreated; return super.createSAMRecord(header); } + @Override public BAMRecord createBAMRecord(final SAMFileHeader header, final int referenceSequenceIndex, final int alignmentStart, final short readNameLength, final short mappingQuality, final int indexingBin, final int cigarLen, final int flags, final int readLen, final int mateReferenceSequenceIndex, final int mateAlignmentStart, final int insertSize, final byte[] variableLengthBlock) { ++bamRecordsCreated; return super.createBAMRecord(header, referenceSequenceIndex, alignmentStart, readNameLength, mappingQuality, indexingBin, cigarLen, flags, readLen, mateReferenceSequenceIndex, mateAlignmentStart, insertSize, variableLengthBlock); diff --git a/src/test/java/htsjdk/samtools/SamReaderTest.java b/src/test/java/htsjdk/samtools/SamReaderTest.java index 02364f318..5af88214e 100644 --- a/src/test/java/htsjdk/samtools/SamReaderTest.java +++ b/src/test/java/htsjdk/samtools/SamReaderTest.java @@ -103,11 +103,13 @@ public void CRAMNoIndexTest(final String inputFile, final String referenceFile) int samRecordsCreated; int bamRecordsCreated; + @Override public SAMRecord createSAMRecord(final SAMFileHeader header) { ++samRecordsCreated; return super.createSAMRecord(header); } + @Override public BAMRecord createBAMRecord(final SAMFileHeader header, final int referenceSequenceIndex, final int alignmentStart, final short readNameLength, final short mappingQuality, final int indexingBin, final int cigarLen, final int flags, final int readLen, final int mateReferenceSequenceIndex, final int mateAlignmentStart, final int insertSize, final byte[] variableLengthBlock) { ++bamRecordsCreated; return super.createBAMRecord(header, referenceSequenceIndex, alignmentStart, readNameLength, mappingQuality, indexingBin, cigarLen, flags, readLen, mateReferenceSequenceIndex, mateAlignmentStart, insertSize, variableLengthBlock); diff --git a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java index 4ce0b7a29..e577ed876 100644 --- a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java +++ b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java @@ -237,26 +237,32 @@ public void testNmFlagValidation() throws IOException { final Histogram results = executeValidation(samBuilder.getSamReader(), new ReferenceSequenceFile() { private int index = 0; + @Override public SAMSequenceDictionary getSequenceDictionary() { return null; } + @Override public ReferenceSequence nextSequence() { final byte[] bases = new byte[10000]; Arrays.fill(bases, (byte) 'A'); return new ReferenceSequence("foo", index++, bases); } + @Override public void reset() { this.index = 0; } + @Override public boolean isIndexed() { return false; } + @Override public ReferenceSequence getSequence(final String contig) { throw new UnsupportedOperationException(); } + @Override public ReferenceSequence getSubsequenceAt(final String contig, final long start, final long stop) { throw new UnsupportedOperationException(); } diff --git a/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java b/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java index f90af4b01..69d72565f 100644 --- a/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java +++ b/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java @@ -181,6 +181,7 @@ public int deflate(byte[] b, int off, int len) { } final DeflaterFactory myDeflaterFactory= new DeflaterFactory(){ + @Override public Deflater makeDeflater(final int compressionLevel, final boolean gzipCompatible) { return new MyDeflater(compressionLevel, gzipCompatible); } diff --git a/src/test/java/htsjdk/samtools/util/DiskBackedQueueTest.java b/src/test/java/htsjdk/samtools/util/DiskBackedQueueTest.java index 88b05e2b7..95966520b 100644 --- a/src/test/java/htsjdk/samtools/util/DiskBackedQueueTest.java +++ b/src/test/java/htsjdk/samtools/util/DiskBackedQueueTest.java @@ -50,7 +50,9 @@ }; } + @Override @BeforeMethod void setup() { resetTmpDir(); } + @Override @AfterMethod void tearDown() { resetTmpDir(); } /** @@ -59,6 +61,7 @@ * @param numStringsToGenerate * @param maxRecordsInRam */ + @Override @Test(dataProvider = "diskBackedQueueProvider") public void testPositive(final String testName, final int numStringsToGenerate, final int maxRecordsInRam) { final String[] strings = new String[numStringsToGenerate]; diff --git a/src/test/java/htsjdk/samtools/util/SortingCollectionTest.java b/src/test/java/htsjdk/samtools/util/SortingCollectionTest.java index 1ec928d3b..dc9e063cd 100644 --- a/src/test/java/htsjdk/samtools/util/SortingCollectionTest.java +++ b/src/test/java/htsjdk/samtools/util/SortingCollectionTest.java @@ -130,19 +130,23 @@ private void assertIteratorEqualsList(final String[] strings, final Iterator iterator() { return this; } + @Override public boolean hasNext() { return numElementsGenerated < numElementsToGenerate; } + @Override public String next() { ++numElementsGenerated; return Integer.toString(random.nextInt()); } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -150,6 +154,7 @@ public void remove() { static class StringComparator implements Comparator { + @Override public int compare(final String s, final String s1) { return s.compareTo(s1); } @@ -160,6 +165,7 @@ public int compare(final String s, final String s1) { OutputStream os; InputStream is; + @Override public SortingCollection.Codec clone() { return new StringCodec(); } @@ -169,6 +175,7 @@ public int compare(final String s, final String s1) { * * @param os */ + @Override public void setOutputStream(final OutputStream os) { this.os = os; } @@ -178,6 +185,7 @@ public void setOutputStream(final OutputStream os) { * * @param is */ + @Override public void setInputStream(final InputStream is) { this.is = is; } @@ -187,6 +195,7 @@ public void setInputStream(final InputStream is) { * * @param val what to write */ + @Override public void encode(final String val) { try { byteBuffer.clear(); @@ -204,6 +213,7 @@ public void encode(final String val) { * @return null if no more records. Should throw exception if EOF is encountered in the middle of * a record. */ + @Override public String decode() { try { byteBuffer.clear(); From c1227d84733e1f2df9636f7c605ed9028a87c71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B3mez-S=C3=A1nchez?= Date: Mon, 27 Feb 2017 16:49:24 +0100 Subject: [PATCH 04/59] Refactoring FastqRecord (#572) * updating FastqRecord, deprecating some of the existing getters and adding new ones with names that are more consistent with SamRecord. * adding FastqEncoder which has methods for writing FastqRecords and converters for FastqRecord <-> SamRecord. --- .../htsjdk/samtools/fastq/BasicFastqWriter.java | 10 +- .../java/htsjdk/samtools/fastq/FastqConstants.java | 4 +- .../java/htsjdk/samtools/fastq/FastqEncoder.java | 113 ++++++++++ .../java/htsjdk/samtools/fastq/FastqRecord.java | 235 +++++++++++++++------ .../java/htsjdk/samtools/util/SequenceUtil.java | 3 +- .../htsjdk/samtools/fastq/FastqEncoderTest.java | 75 +++++++ .../htsjdk/samtools/fastq/FastqRecordTest.java | 13 +- 7 files changed, 381 insertions(+), 72 deletions(-) create mode 100644 src/main/java/htsjdk/samtools/fastq/FastqEncoder.java create mode 100644 src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java diff --git a/src/main/java/htsjdk/samtools/fastq/BasicFastqWriter.java b/src/main/java/htsjdk/samtools/fastq/BasicFastqWriter.java index 8a5afd38a..0c9596a0b 100644 --- a/src/main/java/htsjdk/samtools/fastq/BasicFastqWriter.java +++ b/src/main/java/htsjdk/samtools/fastq/BasicFastqWriter.java @@ -58,12 +58,10 @@ public BasicFastqWriter(final PrintStream writer) { @Override public void write(final FastqRecord rec) { - writer.print(FastqConstants.SEQUENCE_HEADER); - writer.println(rec.getReadHeader()); - writer.println(rec.getReadString()); - writer.print(FastqConstants.QUALITY_HEADER); - writer.println(rec.getBaseQualityHeader() == null ? "" : rec.getBaseQualityHeader()); - writer.println(rec.getBaseQualityString()); + // encode without creating a String + FastqEncoder.write(writer, rec); + // and print a new line + writer.println(); if (writer.checkError()) { throw new SAMException("Error in writing fastq file " + path); } diff --git a/src/main/java/htsjdk/samtools/fastq/FastqConstants.java b/src/main/java/htsjdk/samtools/fastq/FastqConstants.java index f5d4150ea..4e9b95e5b 100644 --- a/src/main/java/htsjdk/samtools/fastq/FastqConstants.java +++ b/src/main/java/htsjdk/samtools/fastq/FastqConstants.java @@ -29,7 +29,9 @@ public class FastqConstants { public static final String SEQUENCE_HEADER = "@" ; public static final String QUALITY_HEADER = "+" ; - + public static final String FIRST_OF_PAIR = "/1"; + public static final String SECOND_OF_PAIR = "/2"; + public enum FastqExtensions { FASTQ(".fastq"), FASTQ_GZ(".fastq.gz"), diff --git a/src/main/java/htsjdk/samtools/fastq/FastqEncoder.java b/src/main/java/htsjdk/samtools/fastq/FastqEncoder.java new file mode 100644 index 000000000..fdbd02dcc --- /dev/null +++ b/src/main/java/htsjdk/samtools/fastq/FastqEncoder.java @@ -0,0 +1,113 @@ +/* + * The MIT License + * + * Copyright (c) 2016 Daniel Gomez-Sanchez + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package htsjdk.samtools.fastq; + +import htsjdk.samtools.SAMException; +import htsjdk.samtools.SAMFileHeader; +import htsjdk.samtools.SAMRecord; +import htsjdk.samtools.util.SequenceUtil; + +import java.io.IOException; + +/** + * Codec for encoding records into FASTQ format. + * + * @author Daniel Gomez-Sanchez (magicDGS) + */ +public final class FastqEncoder { + + // cannot be instantiated because it is an utility class + private FastqEncoder() {} + + /** + * Encodes a FastqRecord in the String FASTQ format. + */ + public static String encode(final FastqRecord record) { + // reserve some memory based on the read length + int capacity = record.getReadLength() * 2 + 5; + // reserve some memory based on the read name + if (record.getReadName() != null) { + capacity += record.getReadName().length(); + } + return write(new StringBuilder(capacity), record).toString(); + } + + /** + * Writes a FastqRecord into the Appendable output. + * @throws SAMException if any I/O error occurs. + */ + public static Appendable write(final Appendable out,final FastqRecord record) { + final String readName = record.getReadName(); + final String readString = record.getReadString(); + final String qualHeader = record.getBaseQualityHeader(); + final String qualityString = record.getBaseQualityString(); + try { + return out.append(FastqConstants.SEQUENCE_HEADER) + .append(readName == null ? "" : readName).append('\n') + .append(readString == null ? "" : readString).append('\n') + .append(FastqConstants.QUALITY_HEADER) + .append(qualHeader == null ? "" : qualHeader).append('\n') + .append(qualityString == null ? "" : qualityString); + } catch (IOException e) { + throw new SAMException(e); + } + } + + /** + * Encodes a SAMRecord in the String FASTQ format. + * @see #encode(FastqRecord) + * @see #asSAMRecord(FastqRecord, SAMFileHeader) + */ + public static String encode(final SAMRecord record) { + return encode(asFastqRecord(record)); + } + + /** + * Converts a {@link SAMRecord} into a {@link FastqRecord}. + */ + public static FastqRecord asFastqRecord(final SAMRecord record) { + String readName = record.getReadName(); + if(record.getReadPairedFlag() && (record.getFirstOfPairFlag() || record.getSecondOfPairFlag())) { + readName += (record.getFirstOfPairFlag()) ? FastqConstants.FIRST_OF_PAIR : FastqConstants.SECOND_OF_PAIR; + } + return new FastqRecord(readName, record.getReadString(), null, record.getBaseQualityString()); + } + + /** + * Converts a {@link FastqRecord} into a simple unmapped {@link SAMRecord}. + */ + public static SAMRecord asSAMRecord(final FastqRecord record, final SAMFileHeader header) { + // construct the SAMRecord and set the unmapped flag + final SAMRecord samRecord = new SAMRecord(header); + samRecord.setReadUnmappedFlag(true); + // get the read name from the FastqRecord correctly formatted + final String readName = SequenceUtil.getSamReadNameFromFastqHeader(record.getReadName()); + // set the basic information from the FastqRecord + samRecord.setReadName(readName); + samRecord.setReadBases(record.getReadBases()); + samRecord.setBaseQualities(record.getBaseQualities()); + return samRecord; + } + +} diff --git a/src/main/java/htsjdk/samtools/fastq/FastqRecord.java b/src/main/java/htsjdk/samtools/fastq/FastqRecord.java index b1d3f7507..9fbcd3912 100755 --- a/src/main/java/htsjdk/samtools/fastq/FastqRecord.java +++ b/src/main/java/htsjdk/samtools/fastq/FastqRecord.java @@ -23,62 +23,169 @@ */ package htsjdk.samtools.fastq; +import htsjdk.samtools.SAMRecord; +import htsjdk.samtools.SAMUtils; +import htsjdk.samtools.util.StringUtil; + import java.io.Serializable; /** - * Represents a fastq record, fairly literally, i.e. without any conversion. + * Simple representation of a FASTQ record, without any conversion */ public class FastqRecord implements Serializable { private static final long serialVersionUID = 1L; - private final String seqHeaderPrefix; - private final String seqLine; - private final String qualHeaderPrefix; - private final String qualLine; - - public FastqRecord(final String seqHeaderPrefix, final String seqLine, final String qualHeaderPrefix, final String qualLine) { - if (seqHeaderPrefix != null && !seqHeaderPrefix.isEmpty()) this.seqHeaderPrefix = seqHeaderPrefix; - else this.seqHeaderPrefix = null; - if (qualHeaderPrefix != null && !qualHeaderPrefix.isEmpty()) this.qualHeaderPrefix = qualHeaderPrefix; - else this.qualHeaderPrefix = null; - this.seqLine = seqLine ; - this.qualLine = qualLine ; - } - - /** copy constructor */ + private final String readName; + private final String readString; + private final String qualityHeader; + private final String baseQualityString; + + /** + * Default constructor + * + * @param readName the read name (without {@link FastqConstants#SEQUENCE_HEADER}) + * @param readBases the read sequence bases + * @param qualityHeader the quality header (without {@link FastqConstants#SEQUENCE_HEADER}) + * @param baseQualities the base quality scores + */ + public FastqRecord(final String readName, final String readBases, final String qualityHeader, final String baseQualities) { + if (readName != null && !readName.isEmpty()) { + this.readName = readName; + } else { + this.readName = null; + } + if (qualityHeader != null && !qualityHeader.isEmpty()) { + this.qualityHeader = qualityHeader; + } else { + this.qualityHeader = null; + } + this.readString = readBases; + this.baseQualityString = baseQualities; + } + + /** + * Constructor for byte[] arrays + * + * @param readName the read name (without {@link FastqConstants#SEQUENCE_HEADER}) + * @param readBases the read sequence bases as ASCII bytes ACGTN=. + * @param qualityHeader the quality header (without {@link FastqConstants#SEQUENCE_HEADER}) + * @param baseQualities the base qualities as binary PHRED scores (not ASCII) + */ + public FastqRecord(final String readName, final byte[] readBases, final String qualityHeader, final byte[] baseQualities) { + this(readName, StringUtil.bytesToString(readBases), qualityHeader, SAMUtils.phredToFastq(baseQualities)); + } + + /** + * Copy constructor + * + * @param other record to copy + */ public FastqRecord(final FastqRecord other) { - if( other == null ) throw new IllegalArgumentException("new FastqRecord(null)"); - this.seqHeaderPrefix = other.seqHeaderPrefix; - this.seqLine = other.seqLine; - this.qualHeaderPrefix = other.qualHeaderPrefix; - this.qualLine = other.qualLine; - } - - /** @return the read name */ - public String getReadHeader() { return seqHeaderPrefix; } - /** @return the read DNA sequence */ - public String getReadString() { return seqLine; } - /** @return the quality header */ - public String getBaseQualityHeader() { return qualHeaderPrefix; } - /** @return the quality string */ - public String getBaseQualityString() { return qualLine; } - /** shortcut to getReadString().length() */ - public int length() { return this.seqLine==null?0:this.seqLine.length();} - + if (other == null) { + throw new IllegalArgumentException("new FastqRecord(null)"); + } + this.readName = other.readName; + this.readString = other.readString; + this.qualityHeader = other.qualityHeader; + this.baseQualityString = other.baseQualityString; + } + + /** + * @return the read name + * @deprecated since 02/2017. Use {@link #getReadName()} instead + */ + @Deprecated + public String getReadHeader() { + return getReadName(); + } + + /** + * Get the read name + * + * @return the read name + */ + public String getReadName() { + return readName; + } + + /** + * Get the DNA sequence + * + * @return read sequence as a string of ACGTN=. + */ + public String getReadString() { + return readString; + } + + /** + * Get the DNA sequence. + * + * @return read sequence as ASCII bytes ACGTN=; {@link SAMRecord#NULL_SEQUENCE} if no bases are present. + */ + public byte[] getReadBases() { + return (readString == null) ? SAMRecord.NULL_SEQUENCE : StringUtil.stringToBytes(readString); + } + + /** + * Get the base qualities encoded as a FASTQ string + * + * @return the quality string + */ + public String getBaseQualityString() { + return baseQualityString; + } + + /** + * Get the base qualities as binary PHRED scores (not ASCII) + * + * @return the base quality; {@link SAMRecord#NULL_QUALS} if no bases are present. + */ + public byte[] getBaseQualities() { + return (baseQualityString == null) ? SAMRecord.NULL_QUALS : SAMUtils.fastqToPhred(baseQualityString); + } + + /** + * Get the read length + * + * @return number of bases in the read + */ + public int getReadLength() { + return (readString == null) ? 0 : readString.length(); + } + + /** + * Get the base quality header + * + * @return the base quality header + */ + public String getBaseQualityHeader() { + return qualityHeader; + } + + /** + * shortcut to getReadString().length() + * + * @deprecated since 02/2017. Use {@link #getReadLength()} instead + */ + @Deprecated + public int length() { + return getReadLength(); + } + @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result - + ((qualHeaderPrefix == null) ? 0 : qualHeaderPrefix.hashCode()); + + ((qualityHeader == null) ? 0 : qualityHeader.hashCode()); result = prime * result - + ((qualLine == null) ? 0 : qualLine.hashCode()); + + ((baseQualityString == null) ? 0 : baseQualityString.hashCode()); result = prime * result - + ((seqHeaderPrefix == null) ? 0 : seqHeaderPrefix.hashCode()); - result = prime * result + ((seqLine == null) ? 0 : seqLine.hashCode()); + + ((readName == null) ? 0 : readName.hashCode()); + result = prime * result + ((readString == null) ? 0 : readString.hashCode()); return result; } - + @Override public boolean equals(Object obj) { if (this == obj) @@ -88,37 +195,45 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; FastqRecord other = (FastqRecord) obj; - if (seqLine == null) { - if (other.seqLine != null) + if (readString == null) { + if (other.readString != null) return false; - } else if (!seqLine.equals(other.seqLine)) + } else if (!readString.equals(other.readString)) return false; - if (qualHeaderPrefix == null) { - if (other.qualHeaderPrefix != null) + if (qualityHeader == null) { + if (other.qualityHeader != null) return false; - } else if (!qualHeaderPrefix.equals(other.qualHeaderPrefix)) + } else if (!qualityHeader.equals(other.qualityHeader)) return false; - if (qualLine == null) { - if (other.qualLine != null) + if (baseQualityString == null) { + if (other.baseQualityString != null) return false; - } else if (!qualLine.equals(other.qualLine)) + } else if (!baseQualityString.equals(other.baseQualityString)) return false; - if (seqHeaderPrefix == null) { - if (other.seqHeaderPrefix != null) + if (readName == null) { + if (other.readName != null) return false; - } else if (!seqHeaderPrefix.equals(other.seqHeaderPrefix)) + } else if (!readName.equals(other.readName)) return false; - + return true; } - + + /** + * Returns the record as the String FASTQ format. + * @see FastqEncoder#encode(FastqRecord) + */ + public String toFastQString() { + return FastqEncoder.encode(this); + } + + /** + * Returns {@link #toFastQString()} + */ @Override public String toString() { - return new StringBuilder(). - append(FastqConstants.SEQUENCE_HEADER).append(this.seqHeaderPrefix==null?"":this.seqHeaderPrefix).append('\n'). - append(this.seqLine==null?"":this.seqLine).append('\n'). - append(FastqConstants.QUALITY_HEADER).append(this.qualHeaderPrefix==null?"":this.qualHeaderPrefix).append('\n'). - append(this.qualLine==null?"":this.qualLine). - toString(); - } + // TODO: this should be change in the future for a simpler and more informative form such as + // TODO: return String.format("%s: %s bp", readName, getReadLength()); + return toFastQString(); + } } diff --git a/src/main/java/htsjdk/samtools/util/SequenceUtil.java b/src/main/java/htsjdk/samtools/util/SequenceUtil.java index 92c1a507d..7088217da 100644 --- a/src/main/java/htsjdk/samtools/util/SequenceUtil.java +++ b/src/main/java/htsjdk/samtools/util/SequenceUtil.java @@ -32,6 +32,7 @@ import htsjdk.samtools.SAMSequenceDictionary; import htsjdk.samtools.SAMSequenceRecord; import htsjdk.samtools.SAMTag; +import htsjdk.samtools.fastq.FastqConstants; import java.io.File; import java.math.BigInteger; @@ -1005,7 +1006,7 @@ public static String getSamReadNameFromFastqHeader(final String fastqHeader) { // NOTE: the while loop isn't necessarily the most efficient way to handle this but we don't // expect this to ever happen more than once, just trapping pathological cases - while ((readName.endsWith("/1") || readName.endsWith("/2"))) { + while ((readName.endsWith(FastqConstants.FIRST_OF_PAIR) || readName.endsWith(FastqConstants.SECOND_OF_PAIR))) { // If this is an unpaired run we want to make sure that "/1" isn't tacked on the end of the read name, // as this can cause problems down the road (ex. in Picard's MergeBamAlignment). readName = readName.substring(0, readName.length() - 2); diff --git a/src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java b/src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java new file mode 100644 index 000000000..72e59cff7 --- /dev/null +++ b/src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java @@ -0,0 +1,75 @@ +/* + * The MIT License + * + * Copyright (c) 2016 Daniel Gomez-Sanchez + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package htsjdk.samtools.fastq; + +import htsjdk.samtools.SAMRecord; +import htsjdk.samtools.SAMRecordSetBuilder; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * @author Daniel Gomez-Sanchez (magicDGS) + */ +public class FastqEncoderTest { + + @Test + public void testAsFastqRecord() throws Exception { + final SAMRecord record = new SAMRecordSetBuilder().addFrag("test", 0, 1, false, false, "10M", null, 2); + record.setReadPairedFlag(true); + // test first of pair encoding + record.setFirstOfPairFlag(true); + testRecord(record.getReadName() + FastqConstants.FIRST_OF_PAIR, FastqEncoder.asFastqRecord(record), record); + record.setFirstOfPairFlag(false); + record.setSecondOfPairFlag(true); + testRecord(record.getReadName() + FastqConstants.SECOND_OF_PAIR, FastqEncoder.asFastqRecord(record), record); + record.setSecondOfPairFlag(false); + testRecord(record.getReadName(), FastqEncoder.asFastqRecord(record), record); + } + + private void testRecord(final String expectedReadName, final FastqRecord fastqRecord, final SAMRecord samRecord) { + Assert.assertEquals(fastqRecord.getReadName(), expectedReadName); + Assert.assertEquals(fastqRecord.getBaseQualities(), samRecord.getBaseQualities()); + Assert.assertEquals(fastqRecord.getReadBases(), samRecord.getReadBases()); + Assert.assertNull(fastqRecord.getBaseQualityHeader()); + } + + @Test + public void testAsSAMRecord() throws Exception { + // create a random record + final SAMRecord samRecord = new SAMRecordSetBuilder().addFrag("test", 0, 1, false, false, "10M", null, 2); + FastqRecord fastqRecord = new FastqRecord(samRecord.getReadName(), samRecord.getReadBases(), "", samRecord.getBaseQualities()); + testConvertedSAMRecord(FastqEncoder.asSAMRecord(fastqRecord, samRecord.getHeader()), samRecord); + fastqRecord = new FastqRecord(samRecord.getReadName() + FastqConstants.FIRST_OF_PAIR, samRecord.getReadBases(), "", samRecord.getBaseQualities()); + testConvertedSAMRecord(FastqEncoder.asSAMRecord(fastqRecord, samRecord.getHeader()), samRecord); + fastqRecord = new FastqRecord(samRecord.getReadName() + FastqConstants.SECOND_OF_PAIR, samRecord.getReadBases(), "", samRecord.getBaseQualities()); + testConvertedSAMRecord(FastqEncoder.asSAMRecord(fastqRecord, samRecord.getHeader()), samRecord); + } + + private void testConvertedSAMRecord(final SAMRecord converted, final SAMRecord original) { + Assert.assertEquals(converted.getReadName(), original.getReadName()); + Assert.assertEquals(converted.getBaseQualities(), original.getBaseQualities()); + Assert.assertEquals(converted.getReadBases(), original.getReadBases()); + Assert.assertTrue(converted.getReadUnmappedFlag()); + } +} \ No newline at end of file diff --git a/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java b/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java index f6f238eab..97a3d3c8d 100644 --- a/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java +++ b/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java @@ -15,7 +15,7 @@ public void testBasic() { Assert.assertNull(fastqRecord.getBaseQualityHeader()); - Assert.assertEquals(fastqRecord.getReadHeader(), seqHeaderPrefix); + Assert.assertEquals(fastqRecord.getReadName(), seqHeaderPrefix); Assert.assertEquals(fastqRecord.getBaseQualityString(), qualLine); Assert.assertEquals(fastqRecord.getReadString(), seqLine); Assert.assertNotNull(fastqRecord.toString());//just check not nullness @@ -25,9 +25,9 @@ public void testBasic() { Assert.assertEquals(fastqRecord, fastqRecord); Assert.assertNotEquals(fastqRecord, "fred"); Assert.assertNotEquals("fred", fastqRecord); - Assert.assertEquals(fastqRecord.length(), seqLine.length()); + Assert.assertEquals(fastqRecord.getReadLength(), seqLine.length()); Assert.assertEquals(fastqRecord.getBaseQualityString().length(), fastqRecord.getReadString().length()); - Assert.assertEquals(fastqRecord.getReadString().length(), fastqRecord.length()); + Assert.assertEquals(fastqRecord.getReadString().length(), fastqRecord.getReadLength()); } @Test @@ -37,7 +37,7 @@ public void testBasicEmptyHeaderPrefix() { final String qualHeaderPrefix = ""; final String qualLine = ";<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; final FastqRecord fastqRecord = new FastqRecord(seqHeaderPrefix, seqLine, qualHeaderPrefix, qualLine); - Assert.assertNull(fastqRecord.getReadHeader()); + Assert.assertNull(fastqRecord.getReadName()); Assert.assertNull(fastqRecord.getBaseQualityHeader()); } @@ -57,6 +57,11 @@ public void testCopy() { Assert.assertSame(fastqRecord.getBaseQualityHeader(), fastqRecordCopy.getBaseQualityHeader()); } + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullCopy() { + new FastqRecord(null); + } + @Test public void testNullSeq() { final String seqHeaderPrefix = "header"; From c6773801f5309fd18a378cadb04dd6fb5d00998c Mon Sep 17 00:00:00 2001 From: Tim Fennell Date: Thu, 2 Mar 2017 16:33:35 -0500 Subject: [PATCH 05/59] Fixed bug where calling IntervalList.uniqued(false) would fail if the interval names were null. (#815) --- src/main/java/htsjdk/samtools/util/IntervalList.java | 8 +++----- src/test/java/htsjdk/samtools/util/IntervalListTest.java | 12 ++++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/htsjdk/samtools/util/IntervalList.java b/src/main/java/htsjdk/samtools/util/IntervalList.java index 9bfc718f9..f9bfe05ca 100644 --- a/src/main/java/htsjdk/samtools/util/IntervalList.java +++ b/src/main/java/htsjdk/samtools/util/IntervalList.java @@ -356,11 +356,9 @@ static Interval merge(final SortedSet intervals, final boolean concate end = Math.max(end, i.getEnd()); } - if (concatenateNames) { - if (names.isEmpty()) name = null; - else name = StringUtil.join("|", names); - } - else { name = names.iterator().next(); } + if (names.isEmpty()) name = null; + else if (concatenateNames) name = StringUtil.join("|", names); + else name = names.iterator().next(); return new Interval(chrom, start, end, neg, name); } diff --git a/src/test/java/htsjdk/samtools/util/IntervalListTest.java b/src/test/java/htsjdk/samtools/util/IntervalListTest.java index 6c5fcd43c..3d919e881 100644 --- a/src/test/java/htsjdk/samtools/util/IntervalListTest.java +++ b/src/test/java/htsjdk/samtools/util/IntervalListTest.java @@ -517,4 +517,16 @@ public void changeHeader() { Assert.assertTrue(false); } + + @Test public void uniqueIntervalsWithoutNames() { + final IntervalList test = new IntervalList(this.fileHeader); + test.add(new Interval("1", 100, 200)); + test.add(new Interval("1", 500, 600)); + test.add(new Interval("1", 550, 700)); + + for (final boolean concat : new boolean[]{true, false}) { + final IntervalList unique = test.uniqued(concat); + Assert.assertEquals(unique.size(), 2); + } + } } From 737ceec0d62e5a7c57a1d0cf2d7dce09cc78d38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B3mez-S=C3=A1nchez?= Date: Fri, 3 Mar 2017 17:10:47 +0100 Subject: [PATCH 06/59] support Path in Md5CalculatingOutputStream(#814) * added new Md5CalculatingOutputStream constructor that takes Path instead of File --- .../htsjdk/samtools/util/Md5CalculatingOutputStream.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java b/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java index 361c99032..f3b3f0779 100755 --- a/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java +++ b/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java @@ -31,6 +31,8 @@ import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -43,14 +45,14 @@ private final OutputStream os; private final MessageDigest md5; - private final File digestFile; + private final Path digestFile; private String hash; /** * Constructor that takes in the OutputStream that we are wrapping * and creates the MD5 MessageDigest */ - public Md5CalculatingOutputStream(OutputStream os, File digestFile) { + public Md5CalculatingOutputStream(OutputStream os, Path digestFile) { super(); this.hash = null; this.os = os; @@ -65,6 +67,10 @@ public Md5CalculatingOutputStream(OutputStream os, File digestFile) { } } + public Md5CalculatingOutputStream(OutputStream os, File digestFile) { + this(os, digestFile.toPath()); + } + @Override public void write(int b) throws IOException { md5.update((byte)b); @@ -111,7 +117,7 @@ public void close() throws IOException { makeHash(); if(digestFile != null) { - BufferedWriter writer = new BufferedWriter(new FileWriter(digestFile)); + BufferedWriter writer = Files.newBufferedWriter(digestFile); writer.write(hash); writer.close(); } From 9ab551813450c05b7be8964a813171942236ff4d Mon Sep 17 00:00:00 2001 From: Louis Bergelson Date: Tue, 7 Mar 2017 14:33:34 -0500 Subject: [PATCH 07/59] fixing vararg warnings on test compilation (#809) * fix <> on VariantContextUnitTest * fixing ambiguous varargs call warnings in VariantContextUnitTest explicitly casting null values to resolve warnings * adding a test case that seems to been accidentally missed --- .../variantcontext/VariantContextUnitTest.java | 89 ++++++++++++---------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/src/test/java/htsjdk/variant/variantcontext/VariantContextUnitTest.java b/src/test/java/htsjdk/variant/variantcontext/VariantContextUnitTest.java index 14056f833..3d6851598 100644 --- a/src/test/java/htsjdk/variant/variantcontext/VariantContextUnitTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/VariantContextUnitTest.java @@ -178,8 +178,8 @@ public void testMultipleSNPAlleleOrdering() { final List allelesUnnaturalOrder = Arrays.asList(Aref, T, C); VariantContext naturalVC = snpBuilder.alleles(allelesNaturalOrder).make(); VariantContext unnaturalVC = snpBuilder.alleles(allelesUnnaturalOrder).make(); - Assert.assertEquals(new ArrayList(naturalVC.getAlleles()), allelesNaturalOrder); - Assert.assertEquals(new ArrayList(unnaturalVC.getAlleles()), allelesUnnaturalOrder); + Assert.assertEquals(new ArrayList<>(naturalVC.getAlleles()), allelesNaturalOrder); + Assert.assertEquals(new ArrayList<>(unnaturalVC.getAlleles()), allelesUnnaturalOrder); } @Test @@ -371,7 +371,7 @@ public void testBadConstructorArgs3() { @Test (expectedExceptions = Throwable.class) public void testBadConstructorArgs4() { - new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Collections.emptyList()).make(); + new VariantContextBuilder("test", insLoc, insLocStart, insLocStop, Collections.emptyList()).make(); } @Test (expectedExceptions = Exception.class) @@ -528,7 +528,7 @@ public void testFilters() { Assert.assertTrue(vc.filtersWereApplied()); Assert.assertNotNull(vc.getFiltersMaybeNull()); - Set filters = new HashSet(Arrays.asList("BAD_SNP_BAD!", "REALLY_BAD_SNP", "CHRIST_THIS_IS_TERRIBLE")); + Set filters = new HashSet<>(Arrays.asList("BAD_SNP_BAD!", "REALLY_BAD_SNP", "CHRIST_THIS_IS_TERRIBLE")); vc = new VariantContextBuilder(vc).filters(filters).make(); Assert.assertFalse(vc.isNotFiltered()); @@ -570,12 +570,16 @@ public void testVCFfromGenotypes() { Genotype g5 = GenotypeBuilder.create("AC", Arrays.asList(Aref, C)); VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, alleles).genotypes(g1,g2,g3,g4,g5).make(); - VariantContext vc12 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(), g2.getSampleName())), true); - VariantContext vc1 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName())), true); - VariantContext vc23 = vc.subContextFromSamples(new HashSet(Arrays.asList(g2.getSampleName(), g3.getSampleName())), true); - VariantContext vc4 = vc.subContextFromSamples(new HashSet(Arrays.asList(g4.getSampleName())), true); - VariantContext vc14 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(), g4.getSampleName())), true); - VariantContext vc125 = vc.subContextFromSamples(new HashSet(Arrays.asList(g1.getSampleName(), g2.getSampleName(), g5.getSampleName())), true); + VariantContext vc12 = vc.subContextFromSamples( + new HashSet<>(Arrays.asList(g1.getSampleName(), g2.getSampleName())), true); + VariantContext vc1 = vc.subContextFromSamples(new HashSet<>(Arrays.asList(g1.getSampleName())), true); + VariantContext vc23 = vc.subContextFromSamples( + new HashSet<>(Arrays.asList(g2.getSampleName(), g3.getSampleName())), true); + VariantContext vc4 = vc.subContextFromSamples(new HashSet<>(Arrays.asList(g4.getSampleName())), true); + VariantContext vc14 = vc.subContextFromSamples( + new HashSet<>(Arrays.asList(g1.getSampleName(), g4.getSampleName())), true); + VariantContext vc125 = vc.subContextFromSamples( + new HashSet<>(Arrays.asList(g1.getSampleName(), g2.getSampleName(), g5.getSampleName())), true); Assert.assertTrue(vc12.isPolymorphicInSamples()); Assert.assertTrue(vc23.isPolymorphicInSamples()); @@ -676,7 +680,7 @@ public String toString() { @DataProvider(name = "getAlleles") public Object[][] mergeAllelesData() { - List tests = new ArrayList(); + List tests = new ArrayList<>(); tests.add(new Object[]{new GetAllelesTest("A*", Aref)}); tests.add(new Object[]{new GetAllelesTest("A*/C", Aref, C)}); @@ -747,7 +751,7 @@ public String toString() { VariantContext sites = new VariantContextBuilder("sites", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).make(); VariantContext genotypes = new VariantContextBuilder(sites).source("genotypes").genotypes(g1, g2, g3).make(); - List tests = new ArrayList(); + List tests = new ArrayList<>(); tests.add(new Object[]{new SitesAndGenotypesVC("sites", sites)}); tests.add(new Object[]{new SitesAndGenotypesVC("genotypes", genotypes)}); @@ -822,7 +826,7 @@ public void runModifyVCTests(SitesAndGenotypesVC cfg) { boolean updateAlleles; private SubContextTest(Collection samples, boolean updateAlleles) { - this.samples = new HashSet(samples); + this.samples = new HashSet<>(samples); this.updateAlleles = updateAlleles; } @@ -833,10 +837,10 @@ public String toString() { @DataProvider(name = "SubContextTest") public Object[][] MakeSubContextTest() { - List tests = new ArrayList(); + List tests = new ArrayList<>(); for ( boolean updateAlleles : Arrays.asList(true, false)) { - tests.add(new Object[]{new SubContextTest(Collections.emptySet(), updateAlleles)}); + tests.add(new Object[]{new SubContextTest(Collections.emptySet(), updateAlleles)}); tests.add(new Object[]{new SubContextTest(Collections.singleton("MISSING"), updateAlleles)}); tests.add(new Object[]{new SubContextTest(Collections.singleton("AA"), updateAlleles)}); tests.add(new Object[]{new SubContextTest(Collections.singleton("AT"), updateAlleles)}); @@ -871,7 +875,7 @@ public void runSubContextTest(SubContextTest cfg) { Assert.assertEquals(sub.getID(), vc.getID()); Assert.assertEquals(sub.getAttributes(), vc.getAttributes()); - Set expectedGenotypes = new HashSet(); + Set expectedGenotypes = new HashSet<>(); if ( cfg.samples.contains(g1.getSampleName()) ) expectedGenotypes.add(g1); if ( cfg.samples.contains(g2.getSampleName()) ) expectedGenotypes.add(g2); if ( cfg.samples.contains(g3.getSampleName()) ) expectedGenotypes.add(g3); @@ -881,10 +885,10 @@ public void runSubContextTest(SubContextTest cfg) { // these values depend on the results of sub if ( cfg.updateAlleles ) { // do the work to see what alleles should be here, and which not - List expectedAlleles = new ArrayList(); + List expectedAlleles = new ArrayList<>(); expectedAlleles.add(Aref); - Set genotypeAlleles = new HashSet(); + Set genotypeAlleles = new HashSet<>(); for ( final Genotype g : expectedGC ) genotypeAlleles.addAll(g.getAlleles()); genotypeAlleles.remove(Aref); @@ -925,7 +929,7 @@ public String toString() { @DataProvider(name = "SampleNamesTest") public Object[][] MakeSampleNamesTest() { - List tests = new ArrayList(); + List tests = new ArrayList<>(); tests.add(new Object[]{new SampleNamesTest(Arrays.asList("1"), Arrays.asList("1"))}); tests.add(new Object[]{new SampleNamesTest(Arrays.asList("2", "1"), Arrays.asList("1", "2"))}); @@ -959,7 +963,7 @@ public void runSampleNamesTest(SampleNamesTest cfg) { VariantContext vc = new VariantContextBuilder("genotypes", snpLoc, snpLocStart, snpLocStop, Arrays.asList(Aref, T)).genotypes(gc).make(); // same sample names => success - Assert.assertTrue(vc.getSampleNames().equals(new HashSet(cfg.sampleNames)), "vc.getSampleNames() = " + vc.getSampleNames()); + Assert.assertTrue(vc.getSampleNames().equals(new HashSet<>(cfg.sampleNames)), "vc.getSampleNames() = " + vc.getSampleNames()); Assert.assertEquals(vc.getSampleNamesOrderedByName(), cfg.sampleNamesInOrder, "vc.getSampleNamesOrderedByName() = " + vc.getSampleNamesOrderedByName()); assertGenotypesAreInOrder(vc.getGenotypesOrderedByName(), cfg.sampleNamesInOrder); @@ -1147,7 +1151,7 @@ private VariantContext createTestVariantContextRsIds(final String rsId) { fullyDecoded, toValidate); } private Set makeRsIDsSet(final String... rsIds) { - return new HashSet(Arrays.asList(rsIds)); + return new HashSet<>(Arrays.asList(rsIds)); } @@ -1226,14 +1230,14 @@ private VariantContext createValidateAlternateAllelesContext(final List /** AN : total number of alleles in called genotypes **/ // with AN set and hom-ref, we expect AN to be 2 for Aref/Aref - final Map attributesAN = new HashMap(); + final Map attributesAN = new HashMap<>(); attributesAN.put(VCFConstants.ALLELE_NUMBER_KEY, "2"); final VariantContext vcANSet = createValidateChromosomeCountsContext(Arrays.asList(Aref), attributesAN, homRef); // with AN set, one no-call (no-calls get ignored by getCalledChrCount() in VariantContext) // we expect AN to be 1 for Aref/no-call - final Map attributesANNoCall = new HashMap(); + final Map attributesANNoCall = new HashMap<>(); attributesANNoCall.put(VCFConstants.ALLELE_NUMBER_KEY, "1"); final VariantContext vcANSetNoCall = createValidateChromosomeCountsContext(Arrays.asList(Aref), attributesANNoCall, homRefNoCall); @@ -1241,42 +1245,42 @@ private VariantContext createValidateAlternateAllelesContext(final List /** AC : allele count in genotypes, for each ALT allele, in the same order as listed **/ // with AC set, and T/T, we expect AC to be 2 (for 2 counts of ALT T) - final Map attributesAC = new HashMap(); + final Map attributesAC = new HashMap<>(); attributesAC.put(VCFConstants.ALLELE_COUNT_KEY, "2"); final VariantContext vcACSet = createValidateChromosomeCountsContext(Arrays.asList(Aref, T), attributesAC, homVarT); // with AC set and no ALT (GT is 0/0), we expect AC count to be 0 - final Map attributesACNoAlts = new HashMap(); + final Map attributesACNoAlts = new HashMap<>(); attributesACNoAlts.put(VCFConstants.ALLELE_COUNT_KEY, "0"); final VariantContext vcACSetNoAlts = createValidateChromosomeCountsContext(Arrays.asList(Aref), attributesACNoAlts, homRef); // with AC set, and two different ALTs (T and C), with GT of 1/2, we expect a count of 1 for each. // With two ALTs, a list is expected, so we set the attribute as a list of 1,1 - final Map attributesACTwoAlts = new HashMap(); + final Map attributesACTwoAlts = new HashMap<>(); attributesACTwoAlts.put(VCFConstants.ALLELE_COUNT_KEY, Arrays.asList("1", "1")); final VariantContext vcACSetTwoAlts = createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesACTwoAlts, hetVarTC); // with AC set, and two different ALTs (T and C), with no GT, we expect a 2 count values. - final Map attributesACNoGtTwoAlts = new HashMap(); + final Map attributesACNoGtTwoAlts = new HashMap<>(); attributesACNoGtTwoAlts.put(VCFConstants.ALLELE_COUNT_KEY, Arrays.asList("1", "1")); final VariantContext vcACNoGtSetTwoAlts = - createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesACNoGtTwoAlts, null); + createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesACNoGtTwoAlts, (Genotype[]) null); // with AF set, and two different ALTs (T and C), with GT of 1/2, we expect two frequncy values. // With two ALTs, a list is expected, so we set the attribute as a list of 0.5,0.5 - final Map attributesAFTwoAlts = new HashMap(); + final Map attributesAFTwoAlts = new HashMap<>(); attributesAFTwoAlts.put(VCFConstants.ALLELE_FREQUENCY_KEY, Arrays.asList("0.5", "0.5")); final VariantContext vcAFSetTwoAlts = createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesAFTwoAlts, hetVarTC); // with AF set, and two different ALTs (T and C), with no GT, we expect two frequency values. - final Map attributesAFNoGtTwoAlts = new HashMap(); + final Map attributesAFNoGtTwoAlts = new HashMap<>(); attributesAFNoGtTwoAlts.put(VCFConstants.ALLELE_FREQUENCY_KEY, Arrays.asList("0.5", "0.5")); final VariantContext vcAFNoGtSetTwoAlts = - createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesAFNoGtTwoAlts, null); + createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesAFNoGtTwoAlts, (Genotype[]) null); return new Object[][]{ {vcNoGenotypes}, @@ -1284,6 +1288,7 @@ private VariantContext createValidateAlternateAllelesContext(final List {vcANSetNoCall}, {vcACSet}, {vcACSetNoAlts}, + {vcACSetTwoAlts}, {vcACNoGtSetTwoAlts}, {vcAFSetTwoAlts}, {vcAFNoGtSetTwoAlts} @@ -1303,60 +1308,60 @@ public void testValidateChromosomeCounts(final VariantContext vc) { /** AN : total number of alleles in called genotypes **/ // with AN set and hom-ref, we expect AN to be 2 for Aref/Aref, so 3 will fail - final Map attributesAN = new HashMap(); + final Map attributesAN = new HashMap<>(); attributesAN.put(VCFConstants.ALLELE_NUMBER_KEY, "3"); final VariantContext vcANSet = createValidateChromosomeCountsContext(Arrays.asList(Aref), attributesAN, homRef); // with AN set, one no-call (no-calls get ignored by getCalledChrCount() in VariantContext) // we expect AN to be 1 for Aref/no-call, so 2 will fail - final Map attributesANNoCall = new HashMap(); + final Map attributesANNoCall = new HashMap<>(); attributesANNoCall.put(VCFConstants.ALLELE_NUMBER_KEY, "2"); final VariantContext vcANSetNoCall = createValidateChromosomeCountsContext(Arrays.asList(Aref), attributesANNoCall, homRefNoCall); /** AC : allele count in genotypes, for each ALT allele, in the same order as listed **/ // with AC set but no ALTs, we expect a count of 0, so the wrong count will fail here - final Map attributesACWrongCount = new HashMap(); + final Map attributesACWrongCount = new HashMap<>(); attributesACWrongCount.put(VCFConstants.ALLELE_COUNT_KEY, "2"); final VariantContext vcACWrongCount = createValidateChromosomeCountsContext(Arrays.asList(Aref), attributesACWrongCount, homRef); // with AC set, two ALTs, but AC is not a list with count for each ALT - final Map attributesACTwoAlts = new HashMap(); + final Map attributesACTwoAlts = new HashMap<>(); attributesACTwoAlts.put(VCFConstants.ALLELE_COUNT_KEY, "1"); final VariantContext vcACSetTwoAlts = createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesACTwoAlts, hetVarTC); // with AC set, two ALTs, and a list is correctly used, but wrong counts (we expect counts to be 1,1) - final Map attributesACTwoAltsWrongCount = new HashMap(); + final Map attributesACTwoAltsWrongCount = new HashMap<>(); attributesACTwoAltsWrongCount.put(VCFConstants.ALLELE_COUNT_KEY, Arrays.asList("1", "2")); final VariantContext vcACSetTwoAltsWrongCount = createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesACTwoAltsWrongCount, hetVarTC); // with AC set, two ALTs, but only count for one ALT (we expect two items in the list: 1,1) - final Map attributesACTwoAltsOneAltCount = new HashMap(); + final Map attributesACTwoAltsOneAltCount = new HashMap<>(); attributesACTwoAltsOneAltCount.put(VCFConstants.ALLELE_COUNT_KEY, Arrays.asList("1")); final VariantContext vcACSetTwoAltsOneAltCount = createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesACTwoAltsOneAltCount, hetVarTC); // with AC set, no GT, two ALTs, but only count for one ALT (we expect two items in the list: 1,1) - final Map attributesACNoGtTwoAltsOneAltCount = new HashMap(); + final Map attributesACNoGtTwoAltsOneAltCount = new HashMap<>(); attributesACNoGtTwoAltsOneAltCount.put(VCFConstants.ALLELE_COUNT_KEY, Arrays.asList("1")); final VariantContext vcACNoGtSetTwoAltsOneAltCount = - createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesACNoGtTwoAltsOneAltCount, null); + createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesACNoGtTwoAltsOneAltCount, (Genotype[])null); // with AF set, two ALTs, but only frequency for one ALT (we expect two items in the list - final Map attributesAFTwoAltsWrongFreq = new HashMap(); + final Map attributesAFTwoAltsWrongFreq = new HashMap<>(); attributesAFTwoAltsWrongFreq.put(VCFConstants.ALLELE_FREQUENCY_KEY, Arrays.asList("0.5")); final VariantContext vcAFSetTwoAltsWrongFreq = createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesAFTwoAltsWrongFreq, hetVarTC); // with AF set, no GT, two ALTs, but only frequency for one ALT (we expect two items in the list - final Map attributesAFNoGtTwoAltsWrongCount = new HashMap(); + final Map attributesAFNoGtTwoAltsWrongCount = new HashMap<>(); attributesAFNoGtTwoAltsWrongCount.put(VCFConstants.ALLELE_FREQUENCY_KEY, Arrays.asList("0.5")); final VariantContext vcAFNoGtSetTwoAltsWrongFreq = - createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesAFNoGtTwoAltsWrongCount, null); + createValidateChromosomeCountsContext(Arrays.asList(Aref, T, C), attributesAFNoGtTwoAltsWrongCount, (Genotype[])null); return new Object[][]{ {vcANSet}, From 6d2265879702cf65c8f26031d4a1d7759f141120 Mon Sep 17 00:00:00 2001 From: Ekaterina Kazachkova Date: Tue, 7 Mar 2017 22:37:23 +0300 Subject: [PATCH 08/59] Fix for issue #513: NPE in ValidateSamFile with CRAM (#735) * resolve issue #513 NPE when validating a CRAM file with a .fasta reference with no .dict file --- .../java/htsjdk/samtools/SamFileValidator.java | 7 +- .../reference/ReferenceSequenceFileWalker.java | 4 +- .../java/htsjdk/samtools/ValidateSamFileTest.java | 15 +++++ .../ValidateSamFileTest/nm_tag_validation.cram | Bin 0 -> 39392 bytes .../ValidateSamFileTest/nm_tag_validation.fa | 71 +++++++++++++++++++++ .../ValidateSamFileTest/nm_tag_validation.fa.fai | 1 + 6 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.cram create mode 100644 src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.fa create mode 100644 src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.fa.fai diff --git a/src/main/java/htsjdk/samtools/SamFileValidator.java b/src/main/java/htsjdk/samtools/SamFileValidator.java index c774b6fd9..5f82bd194 100644 --- a/src/main/java/htsjdk/samtools/SamFileValidator.java +++ b/src/main/java/htsjdk/samtools/SamFileValidator.java @@ -301,8 +301,7 @@ private void validateSamRecordsAndQualityFormat(final Iterable samRec if (cigarIsValid) { try { validateNmTag(record, recordNumber); - } - catch (SAMException e) { + } catch (SAMException e) { if (hasValidSortOrder) { // If a CRAM file has an invalid sort order, the ReferenceFileWalker will throw a // SAMException due to an out of order request when retrieving reference bases during NM @@ -554,12 +553,12 @@ private void validateHeader(final SAMFileHeader fileHeader) { "A platform (PL) attribute was not found for read group ", readGroupID)); } - else { + else { // NB: cannot be null, so not catching a NPE try { SAMReadGroupRecord.PlatformValue.valueOf(platformValue.toUpperCase()); } catch (IllegalArgumentException e) { - addError(new SAMValidationError(Type.INVALID_PLATFORM_VALUE, + addError(new SAMValidationError(Type.INVALID_PLATFORM_VALUE, "The platform (PL) attribute (" + platformValue + ") + was not one of the valid values for read group ", readGroupID)); } diff --git a/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileWalker.java b/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileWalker.java index 6a820ebbe..936f14c86 100644 --- a/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileWalker.java +++ b/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileWalker.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2009 The Broad Institute + * Copyright (c) 2009-2016 The Broad Institute * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -83,7 +83,7 @@ public ReferenceSequence get(final int sequenceIndex) { } referenceSequence = null; - if(referenceSequenceFile.isIndexed()) { + if(referenceSequenceFile.isIndexed() && referenceSequenceFile.getSequenceDictionary() != null) { final SAMSequenceRecord samSequenceRecord = referenceSequenceFile.getSequenceDictionary().getSequence(sequenceIndex); if(samSequenceRecord != null) { referenceSequence = referenceSequenceFile.getSequence(samSequenceRecord.getSequenceName()) ; diff --git a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java index e577ed876..77ac59f0e 100644 --- a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java +++ b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java @@ -27,6 +27,7 @@ import htsjdk.samtools.BamIndexValidator.IndexValidationStringency; import htsjdk.samtools.metrics.MetricBase; import htsjdk.samtools.metrics.MetricsFile; +import htsjdk.samtools.reference.FastaSequenceFile; import htsjdk.samtools.reference.ReferenceSequence; import htsjdk.samtools.reference.ReferenceSequenceFile; import htsjdk.samtools.util.CloserUtil; @@ -70,6 +71,20 @@ public void testValidSamFile() throws Exception { } @Test + public void testValidCRAMFileWithoutSeqDict() throws Exception { + final File reference = new File(TEST_DATA_DIR, "nm_tag_validation.fa"); + final SamReader samReader = SamReaderFactory + .makeDefault() + .validationStringency(ValidationStringency.SILENT) + .referenceSequence(reference) + .open(new File(TEST_DATA_DIR, "nm_tag_validation.cram")); + final Histogram results = executeValidation(samReader, + new FastaSequenceFile(reference, true), + IndexValidationStringency.EXHAUSTIVE); + Assert.assertTrue(!results.isEmpty()); + } + + @Test public void testSamFileVersion1pt5() throws Exception { final SamReader samReader = SamReaderFactory.makeDefault().validationStringency(ValidationStringency.SILENT).open(new File(TEST_DATA_DIR, "test_samfile_version_1pt5.bam")); final Histogram results = executeValidation(samReader, null, IndexValidationStringency.EXHAUSTIVE); diff --git a/src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.cram b/src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.cram new file mode 100644 index 0000000000000000000000000000000000000000..57c58dfd0cd719aaae1f51a2a44d676b1718009f GIT binary patch literal 39392 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 z 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 literal 0 HcmV?d00001 diff --git a/src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.fa b/src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.fa new file mode 100644 index 000000000..7c2ec2a88 --- /dev/null +++ b/src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.fa @@ -0,0 +1,71 @@ +>17 17:1-4200 +AAGCTTCTCACCCTGTTCCTGCATAGATAATTGCATGACAATTGCCTTGTCCCTGCTGAA +TGTGCTCTGGGGTCTCTGGGGTCTCACCCACGACCAACTCCCTGGGCCTGGCACCAGGGA +GCTTAACAAACATCTGTCCAGCGAATACCTGCATCCCTAGAAGTGAAGCCACCGCCCAAA +GACACGCCCATGTCCAGCTTAACCTGCATCCCTAGAAGTGAAGGCACCGCCCAAAGACAC +GCCCATGTCCAGCTTATTCTGCCCAGTTCCTCTCCAGAAAGGCTGCATGGTTGACACACA +GTGCCTGCGACAAAGCTGAATGCTATCATTTAAAAACTCCTTGCTGGTTTGAGAGGCAGA +AAATGATATCTCATAGTTGCTTTACTTTGCATATTTTAAAATTGTGACTTTCATGGCATA +AATAATACTGGTTTATTACAGAAGCACTAGAAAATGCATGTGGACAAAAGTTGGGATTAG +GAGAGAGAAATGAAGACATATGTCCACACAAAAACCTGTTCATTGCAGCTTTCTACCATC +ACCAAAAATTGCAAACAACCACACGCCCTTCAACTGGGGAACTCATCAACAACAAACTTG +TGGTTTACCCACACAATGGAAGACCACTTAGCAACAAAAAGGACCAAACTCCTGGTACAT +GCAACTGACAGATGAATCTCAAACGCATTCCTCCGTGTGAAAGAAGCCGGACTCACAGGG +CAACACACTATCTGACTGTTTCATGGGAAAGTCTGGAAACGGCAACACCATTGAGACAGA +AAACAGGTGAGTGGTTGCCTGGGGCCAGGGAACTTTCTGGGGTCATATTCTCTGTGTTGA +TTCTGGTGGTGGAAACAAGACTGTCCCAGCCTGGGTGATACAGCGAGACCCCATCTCTAC +CAAAAAATTAAAAATTAGCTGGGCATGGTGGTGCATGCCTGTAGTCCCAGCTATTCACAG +TGCTGAGGTGGGAAGATGCTTGAGCCCAGGAGTTCAAGGCTGCAATGAGCTATGATTGCG +CCACTGCACTTTGGCCTGGACAACAGAGCAAAACCCTGTCTCTAAAAAAAGAAAAGAAAA +GAAAAACTCACTGGATATGAATGATACAGGTTGAGGATCCATTATCTGAAATGCTTGGAC +CAGATGTTTTGAATTTTGGATTTTTTCATATTTTGTAATCTTTGCAGTATATTTACCAGT +TCAGCATCCCTAACTCAAAAATTCAAAAATCTGAAATCCCAAACGCGCCAATAAGCATTC +CCTTTGAGCGTCATGTCGGTGCTTGGAATGTTTGGGGTTTTGGATTTACAGCTTTGGGAC +GCTCAACCTGTACCTCAATAAACCTGATTTTAAAAAAGTTTGGGGGGATTCCCCTAAGCC +CGCCACCCGGAGACAGCGGATTTCCTTAGTTACTTACTATGCTCCTTGGCCATTTCTCTA +GGTATTGGTATATTGTGTCTGCTGTGAACTGTCCTTGGCCTGTTTGGTGACGGGTGAGGA +GCAGGGACAGAAGGGTCCTGCGTGCCCTGCCTTCACAAGCCCCTGGAAGGAAAGTTGTTT +TGGGATCTCTGCACCCTCAGCCTGGACAACTTGTGCCCATCTGGTGACCCCTCACTCAGC +CACCAGACTTCCACGACAGGCTCCAGCCTCGGCACCTTCAGCCATGGACAGTTCCGCCAG +CGTTGCCCTCTGTTCTGCTGTTTTCTCTACCAGAAGTGCCCTTCCCTCCTCACCTGACCA +CTCTGGGGAAATCCCTCAGCACCCTCCCTGAGCATACCCTACTCTGGCACAAGCCCACCC +TGCAAAGCCCCTGAGGCCCGCCCTGTGGCGTCTCTCCCTCCCTTGCTGTCAGGACAGTGG +TCCTGGCCACCGGGGCTCACGGAGCCGCCCTGTGCCGTGTACCTCTGAGCCCTCTGCACA +GTGCCTTCTGCTTGCCTGTGGCTTTGAGAAGAAACCCCTTCTGGTTATACATAAGACAGC +CAGAGAAGGGAGTTGCCCAGGGTGGCACAGCACGTTGCTGCCAGTTACTGCCATTTTCAC +GGGCATGAAATGGAGATAACAACAGGAGCGACCGCACAGGCTGCTGAGCGCGTCACACGC +AGCCATCGCGCAGCTCAGGGATATTACGTGTAACTCGACATGTCAGCGATTGTCACAGGC +ACTGCTACTCCTGGGGTTTTCCATCAAACCCTCAAGAGCTGGGCCTGGGGTCAACTTCCG +GCCTGGGGAAACTGGGGCAAGTATCACCAGAGATGAGCTTTATAAAAATAATGGTGCTAG +CTGGGCATGGTGGCTTGCACCTGTAATCCCAGCACTTTGGGAGGCCGAGCTAGGAGGATC +GTTTGAGTCCAGCAGTTTGAGACCAGCCTGGCCAATACGGCAAAACCCAGTCTCTACAAA +AAATACAAAAAACAACTAGCCAGGCGTGGTGGTGCACACCTGTAGTCCCAGCTACTCAGG +AGGCTGAGGGGGAAGGACTGCTTGAGCCCAGGAGTTTGAGGCTGCTGTGAGCTGTGATCG +CATCACTGCATTCCAGCCCGGTGACAGAGTGAGTCACTGTCTCAAAAAAGAAAGGAAGAA +ATAAAGAAAACAAATAAAAATAATAGTGCAGACAAAAGGCCTTGACCCATCTAGCTTTGG +CCCTCAGCATCAACCGCTAGATACGTCCCTCCCTTTCTTCTGGGGCACAGGTCACACTCT +CTTCCAGGTCTAGGATGCAGCTGAGGGGTGCCCCTCTTACCATCTAATCTGTGCCCTTAT +TTCCTCTGCTTTAGTGAGGAAGAGGCCCCTGGTCCATGAAGGGGCCTTTCAGAGACGGGG +ACCCCTGAGGAGCCCCGAGCAGCAGCCGTCGTGTCTCACCCAGGGTGTCTGAAACAGATG +TGGAGGTCTCGGGTGAGGCGTGGCTCAGATACAGGGAGTGGCCCACAGCTCGGCCTGTCT +TTGAAAGGCCACGTGACCTGGCCCACGGCTGGCAGGTGGGACCCAGCTGCAGGGGTCCAG +CAGCACCCACAGCAGCCACCTGTGGCAGGGAGGAGCTTGTGGTACAGTGGACAGGCCCTG +CCCAGATGGCCCCCCGCCTGCCTGTGGAAGTTGACCAGACCATCTGTCACAGCAGGTAAG +ACTCTGCTTTCTGGGCAACCCAGCAGGTGACCCTGGAATTCCTGTCCATCTGGCAGGTGG +GCATTGAAACTGGTTTAAAAATGTCACACCATAGGCCGGGCACAGTGGCTCACGCCTGTA +ATCCCAGCCCTTTGGGAGGCCAGGGTGGGTGGATCACTTGAGGTCAGGAGTTCAAGACCA +GCCTGGCCAACATGGTGAAACCCCGTCTACTAAAAATACAAAAATTAGCCTGGCGTGGTG +GCGCATGCCTGTAATCCCAGCTACTTGGGAAGCTGAGGGATGAGAACTGCTTGAACCTGG +GAGGCAGACGTTGCAGTGAGCTGAGATCACGCCACTGCACTCCAGCCTGGGCAACAGAGT +AAGACTCTGTCTCAAAAAAAAAAAAATCACACCATTTTGGCTTCAGATTGCATATCCTCC +TGCAAGGATATATACGCGTGAAATTCAAGTCAATGACAAATCAGAAGAAAAAACATATAT +ATACGCAAACCAGTATCCTACTGTGTGTGTCGTTTGTTGTGTTTTCGACAGCTGTCCGTG +TTATAATAATTCCTCTAGTTCAAATTTATTCATTTTTAACTTCATAGTACCACATTCTAC +ACACTGCCCATGTCCCCTCAAGCTTCCCCTGGCTCCTGCAACCACAAATCTACTCTCTGC +CTCTGTGGGTTGACCTATTCTGGACACGTCATAGAAATAGAGTCCTGCAACACGTGGCCG +TCTGTGTCTGGCTTCTCTCGCTTAGCATCTTGTTTCCAAGGTCCTCCCACAGTGTAGCAT +GCACCTGCTACACTCCTTCTTAGGGCTGATATTCCACGCACCTGCTACACTCCTTCTTAT +GGCTGATATTCCACGCACCTGCTACACTCCTTCTTAGGGCTGATATTCCACACACCCGCT +ACACTCCTTCTTAGGGCTGATATTCCACGCACCCGCTACACTCCTTCTTAGGGCTGATAT +TCCACGCACCTGCTACACTCCTTCTTAGGGCTGATATTCCACGCACCTGCTACACTCCTT +CTTAGGGCTGATATTCCACGCACCTGCTACACTCCTTCTTAGGGCTGATATTCCACGCAC diff --git a/src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.fa.fai b/src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.fa.fai new file mode 100644 index 000000000..c2112667e --- /dev/null +++ b/src/test/resources/htsjdk/samtools/ValidateSamFileTest/nm_tag_validation.fa.fai @@ -0,0 +1 @@ +17 4200 14 60 61 From 0c282b8395d4654da2fe8678c2fae36efb40d33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B3mez-S=C3=A1nchez?= Date: Fri, 10 Mar 2017 16:26:21 +0100 Subject: [PATCH 09/59] Tribble/Tabix index path support (#810) * Path support for tribble/tabix index It is now possible to write / read tribble and tabix indexes from arbitrary `Paths` as well as files This is not a backwards compatible change. Anyone who implemented tribbles `Index` will need to update their implementations by implementing 2 new methods. * `write( Path)` * `writeBasedOnFeaturePath()` The original file based versions of these methods have been reimplemented as default methods delegating to the new `Path` based methods and in most cases can be removed. * several protected fields have changed in the Index related classes have changed to use Paths instead Files --- .../java/htsjdk/tribble/TabixFeatureReader.java | 1 - src/main/java/htsjdk/tribble/Tribble.java | 45 ++++++++++---- .../tribble/TribbleIndexedFeatureReader.java | 2 - .../java/htsjdk/tribble/index/AbstractIndex.java | 72 ++++++++++++++++------ .../htsjdk/tribble/index/DynamicIndexCreator.java | 23 ++++--- src/main/java/htsjdk/tribble/index/Index.java | 30 ++++++++- .../index/interval/IntervalIndexCreator.java | 19 ++++-- .../tribble/index/interval/IntervalTreeIndex.java | 10 +++ .../htsjdk/tribble/index/linear/LinearIndex.java | 22 ++++++- .../tribble/index/linear/LinearIndexCreator.java | 17 +++-- .../htsjdk/tribble/index/tabix/TabixIndex.java | 33 +++++++--- src/test/java/htsjdk/tribble/index/IndexTest.java | 32 ++++++++++ 12 files changed, 240 insertions(+), 66 deletions(-) diff --git a/src/main/java/htsjdk/tribble/TabixFeatureReader.java b/src/main/java/htsjdk/tribble/TabixFeatureReader.java index 889fdc22b..e72243325 100644 --- a/src/main/java/htsjdk/tribble/TabixFeatureReader.java +++ b/src/main/java/htsjdk/tribble/TabixFeatureReader.java @@ -23,7 +23,6 @@ */ package htsjdk.tribble; -import htsjdk.samtools.seekablestream.SeekableStreamFactory; import htsjdk.samtools.util.BlockCompressedInputStream; import htsjdk.samtools.util.RuntimeIOException; import htsjdk.tribble.readers.*; diff --git a/src/main/java/htsjdk/tribble/Tribble.java b/src/main/java/htsjdk/tribble/Tribble.java index 468f55d77..f2c07a248 100644 --- a/src/main/java/htsjdk/tribble/Tribble.java +++ b/src/main/java/htsjdk/tribble/Tribble.java @@ -27,6 +27,7 @@ import htsjdk.tribble.util.TabixUtils; import java.io.File; +import java.nio.file.Path; /** * Common, tribble wide constants and static functions @@ -37,9 +38,9 @@ private Tribble() { } // can't be instantiated public final static String STANDARD_INDEX_EXTENSION = ".idx"; /** - * Return the name of the index file for the provided vcf {@code filename} + * Return the name of the index file for the provided {@code filename} * Does not actually create an index - * @param filename name of the vcf file + * @param filename name of the file * @return non-null String representing the index filename */ public static String indexFile(final String filename) { @@ -47,9 +48,9 @@ public static String indexFile(final String filename) { } /** - * Return the File of the index file for the provided vcf {@code file} + * Return the File of the index file for the provided {@code file} * Does not actually create an index - * @param file the vcf file + * @param file the file * @return a non-null File representing the index */ public static File indexFile(final File file) { @@ -57,9 +58,19 @@ public static File indexFile(final File file) { } /** - * Return the name of the tabix index file for the provided vcf {@code filename} + * Return the name of the index file for the provided {@code path} * Does not actually create an index - * @param filename name of the vcf file + * @param path the path + * @return Path representing the index filename + */ + public static Path indexPath(final Path path) { + return path.getFileSystem().getPath(indexFile(path.toAbsolutePath().toString())); + } + + /** + * Return the name of the tabix index file for the provided {@code filename} + * Does not actually create an index + * @param filename name of the file * @return non-null String representing the index filename */ public static String tabixIndexFile(final String filename) { @@ -67,9 +78,9 @@ public static String tabixIndexFile(final String filename) { } /** - * Return the File of the tabix index file for the provided vcf {@code file} + * Return the File of the tabix index file for the provided {@code file} * Does not actually create an index - * @param file the vcf file + * @param file the file * @return a non-null File representing the index */ public static File tabixIndexFile(final File file) { @@ -77,9 +88,19 @@ public static File tabixIndexFile(final File file) { } /** - * Return the name of the index file for the provided vcf {@code filename} and {@code extension} + * Return the name of the tabix index file for the provided {@code path} + * Does not actually create an index + * @param path the path + * @return Path representing the index filename + */ + public static Path tabixIndexPath(final Path path) { + return path.getFileSystem().getPath(tabixIndexFile(path.toAbsolutePath().toString())); + } + + /** + * Return the name of the index file for the provided {@code filename} and {@code extension} * Does not actually create an index - * @param filename name of the vcf file + * @param filename name of the file * @param extension the extension to use for the index * @return non-null String representing the index filename */ @@ -88,9 +109,9 @@ private static String indexFile(final String filename, final String extension) { } /** - * Return the File of the index file for the provided vcf {@code file} and {@code extension} + * Return the File of the index file for the provided {@code file} and {@code extension} * Does not actually create an index - * @param file the vcf file + * @param file the file * @param extension the extension to use for the index * @return a non-null File representing the index */ diff --git a/src/main/java/htsjdk/tribble/TribbleIndexedFeatureReader.java b/src/main/java/htsjdk/tribble/TribbleIndexedFeatureReader.java index 365cc281a..7c39faa04 100644 --- a/src/main/java/htsjdk/tribble/TribbleIndexedFeatureReader.java +++ b/src/main/java/htsjdk/tribble/TribbleIndexedFeatureReader.java @@ -33,11 +33,9 @@ import htsjdk.tribble.util.ParsingUtils; import java.io.BufferedInputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.channels.SeekableByteChannel; import java.util.ArrayList; diff --git a/src/main/java/htsjdk/tribble/index/AbstractIndex.java b/src/main/java/htsjdk/tribble/index/AbstractIndex.java index 5ae5492d6..b1cc1364c 100644 --- a/src/main/java/htsjdk/tribble/index/AbstractIndex.java +++ b/src/main/java/htsjdk/tribble/index/AbstractIndex.java @@ -18,6 +18,9 @@ package htsjdk.tribble.index; +import htsjdk.samtools.util.IOUtil; +import htsjdk.samtools.util.Log; +import htsjdk.samtools.util.RuntimeIOException; import htsjdk.tribble.Tribble; import htsjdk.tribble.TribbleException; import htsjdk.tribble.util.LittleEndianInputStream; @@ -25,8 +28,9 @@ import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -67,11 +71,12 @@ private final static long NO_TS = -1L; protected int version; // Our version value - protected File indexedFile = null; // The file we've created this index for + protected Path indexedPath = null; // The file we've created this index for protected long indexedFileSize = NO_FILE_SIZE; // The size of the indexed file protected long indexedFileTS = NO_TS; // The timestamp protected String indexedFileMD5 = NO_MD5; // The MD5 value, generally not filled in (expensive to calc) protected int flags; + protected final Log logger = Log.getInstance(this.getClass()); public boolean hasFileSize() { return indexedFileSize != NO_FILE_SIZE; @@ -116,8 +121,8 @@ public boolean equalsIgnoreProperties(final Object obj) { return false; } - if (indexedFile != other.indexedFile && (indexedFile == null || !indexedFile.equals(other.indexedFile))) { - System.err.printf("equals indexedFile: this %s != other %s%n", indexedFile, other.indexedFile); + if (indexedPath != other.indexedPath && (indexedPath == null || !indexedPath.equals(other.indexedPath))) { + System.err.printf("equals indexedPath: this %s != other %s%n", indexedPath, other.indexedPath); return false; } @@ -159,18 +164,27 @@ public AbstractIndex() { * @param featureFile the feature file to create an index from */ public AbstractIndex(final String featureFile) { - this(new File(featureFile)); + this(); + try { + this.indexedPath = IOUtil.getPath(featureFile).toAbsolutePath(); + } catch (IOException e) { + throw new IllegalArgumentException("IO error: " + e.getMessage(), e); + } } public AbstractIndex(final File featureFile) { + this(featureFile.toPath()); + } + + public AbstractIndex(final Path featurePath) { this(); - this.indexedFile = featureFile; + this.indexedPath = featurePath.toAbsolutePath(); } public AbstractIndex(final AbstractIndex parent) { this(); this.version = parent.version; - this.indexedFile = parent.indexedFile; + this.indexedPath = parent.indexedPath; this.indexedFileSize = parent.indexedFileSize; this.indexedFileTS = parent.indexedFileTS; this.indexedFileMD5 = parent.indexedFileMD5; @@ -200,8 +214,18 @@ public boolean isCurrentVersion() { return version == VERSION; } + /** + * Gets the indexed file. + * @throws UnsupportedOperationException if the path cannot be represented as a file. + * @deprecated on 03/2017. Use {@link #getIndexedPath()} instead. + */ + @Deprecated public File getIndexedFile() { - return indexedFile; + return getIndexedPath().toFile(); + } + + public Path getIndexedPath() { + return indexedPath; } public long getIndexedFileSize() { @@ -234,10 +258,14 @@ public boolean containsChromosome(final String chr) { } public void finalizeIndex() { - // these two functions must be called now because the file may be being written during on the fly indexing - if (indexedFile != null) { - this.indexedFileSize = indexedFile.length(); - this.indexedFileTS = indexedFile.lastModified(); + try { + // these two functions must be called now because the file may be being written during on the fly indexing + if (indexedPath != null) { + this.indexedFileSize = Files.size(indexedPath); + this.indexedFileTS = Files.getLastModifiedTime(indexedPath).toMillis(); + } + } catch (IOException e) { + throw new RuntimeIOException(e); } } @@ -251,7 +279,7 @@ private void writeHeader(final LittleEndianOutputStream dos) throws IOException dos.writeInt(MAGIC_NUMBER); dos.writeInt(getType()); dos.writeInt(version); - dos.writeString(indexedFile.getAbsolutePath()); + dos.writeString(indexedPath.toUri().toString()); dos.writeLong(indexedFileSize); dos.writeLong(indexedFileTS); dos.writeString(indexedFileMD5); @@ -274,7 +302,7 @@ private void writeHeader(final LittleEndianOutputStream dos) throws IOException private void readHeader(final LittleEndianInputStream dis) throws IOException { version = dis.readInt(); - indexedFile = new File(dis.readString()); + indexedPath = IOUtil.getPath(dis.readString()); indexedFileSize = dis.readLong(); indexedFileTS = dis.readLong(); indexedFileMD5 = dis.readString(); @@ -349,18 +377,22 @@ public void write(final LittleEndianOutputStream stream) throws IOException { } @Override - public void write(final File idxFile) throws IOException { - try(final LittleEndianOutputStream idxStream = new LittleEndianOutputStream(new BufferedOutputStream(new FileOutputStream(idxFile)))) { + public void write(final Path idxPath) throws IOException { + try(final LittleEndianOutputStream idxStream = new LittleEndianOutputStream(new BufferedOutputStream(Files.newOutputStream(idxPath)))) { write(idxStream); } } @Override - public void writeBasedOnFeatureFile(final File featureFile) throws IOException { - if (!featureFile.isFile()) return; - write(Tribble.indexFile(featureFile)); + public void writeBasedOnFeaturePath(final Path featurePath) throws IOException { + if (!Files.isRegularFile(featurePath)) { + logger.warn("Index not written into ", featurePath); + return; + } + write(Tribble.indexPath(featurePath)); } + public void read(final LittleEndianInputStream dis) throws IOException { try { readHeader(dis); @@ -386,7 +418,7 @@ public void read(final LittleEndianInputStream dis) throws IOException { } protected void printIndexInfo() { - System.out.println(String.format("Index for %s with %d indices", indexedFile, chrIndices.size())); + System.out.println(String.format("Index for %s with %d indices", indexedPath, chrIndices.size())); final BlockStats stats = getBlockStats(true); System.out.println(String.format(" total blocks %d", stats.total)); System.out.println(String.format(" total empty blocks %d", stats.empty)); diff --git a/src/main/java/htsjdk/tribble/index/DynamicIndexCreator.java b/src/main/java/htsjdk/tribble/index/DynamicIndexCreator.java index 3552fbb4f..17274ace6 100644 --- a/src/main/java/htsjdk/tribble/index/DynamicIndexCreator.java +++ b/src/main/java/htsjdk/tribble/index/DynamicIndexCreator.java @@ -31,6 +31,7 @@ import htsjdk.tribble.util.MathUtils; import java.io.File; +import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -56,13 +57,15 @@ MathUtils.RunningStat stats = new MathUtils.RunningStat(); long basesSeen = 0; Feature lastFeature = null; - File inputFile; - public DynamicIndexCreator(final File inputFile, final IndexFactory.IndexBalanceApproach iba) { + public DynamicIndexCreator(final Path inputPath, final IndexFactory.IndexBalanceApproach iba) { this.iba = iba; // get a list of index creators - this.inputFile = inputFile; - creators = getIndexCreators(inputFile,iba); + creators = getIndexCreators(inputPath, iba); + } + + public DynamicIndexCreator(final File inputFile, final IndexFactory.IndexBalanceApproach iba) { + this(inputFile.toPath(), iba); } @Override @@ -90,19 +93,19 @@ public Index finalizeIndex(final long finalFilePosition) { /** * create a list of index creators (initialized) representing the common index types we'd suspect they'd like to use - * @param inputFile the input file to use to create the indexes + * @param inputPath the input path to use to create the indexes * @return a map of index type to the best index for that balancing approach */ - private Map getIndexCreators(final File inputFile, final IndexFactory.IndexBalanceApproach iba) { + private Map getIndexCreators(final Path inputPath, final IndexFactory.IndexBalanceApproach iba) { final Map creators = new HashMap(); if (iba == IndexFactory.IndexBalanceApproach.FOR_SIZE) { // add a linear index with the default bin size - final LinearIndexCreator linearNormal = new LinearIndexCreator(inputFile, LinearIndexCreator.DEFAULT_BIN_WIDTH); + final LinearIndexCreator linearNormal = new LinearIndexCreator(inputPath, LinearIndexCreator.DEFAULT_BIN_WIDTH); creators.put(IndexFactory.IndexType.LINEAR,linearNormal); // create a tree index with the default size - final IntervalIndexCreator treeNormal = new IntervalIndexCreator(inputFile, IntervalIndexCreator.DEFAULT_FEATURE_COUNT); + final IntervalIndexCreator treeNormal = new IntervalIndexCreator(inputPath, IntervalIndexCreator.DEFAULT_FEATURE_COUNT); creators.put(IndexFactory.IndexType.INTERVAL_TREE,treeNormal); } @@ -111,12 +114,12 @@ public Index finalizeIndex(final long finalFilePosition) { if (iba == IndexFactory.IndexBalanceApproach.FOR_SEEK_TIME) { // create a linear index with a small bin size final LinearIndexCreator linearSmallBin = - new LinearIndexCreator(inputFile, Math.max(200, LinearIndexCreator.DEFAULT_BIN_WIDTH / 4)); + new LinearIndexCreator(inputPath, Math.max(200, LinearIndexCreator.DEFAULT_BIN_WIDTH / 4)); creators.put(IndexFactory.IndexType.LINEAR,linearSmallBin); // create a tree index with a small index size final IntervalIndexCreator treeSmallBin = - new IntervalIndexCreator(inputFile, Math.max(20, IntervalIndexCreator.DEFAULT_FEATURE_COUNT / 8)); + new IntervalIndexCreator(inputPath, Math.max(20, IntervalIndexCreator.DEFAULT_FEATURE_COUNT / 8)); creators.put(IndexFactory.IndexType.INTERVAL_TREE,treeSmallBin); } diff --git a/src/main/java/htsjdk/tribble/index/Index.java b/src/main/java/htsjdk/tribble/index/Index.java index ca6cc60d3..c5a63ff6e 100644 --- a/src/main/java/htsjdk/tribble/index/Index.java +++ b/src/main/java/htsjdk/tribble/index/Index.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -72,17 +73,42 @@ /** * Writes the index into a file. * + * Default implementation delegates to {@link #write(Path)} + * * @param idxFile Where to write the index. * @throws IOException if the index is unable to write to the specified file */ - public void write(final File idxFile) throws IOException; + public default void write(final File idxFile) throws IOException { + write(idxFile.toPath()); + } + + /** + * Writes the index into a path. + * + * @param indexPath Where to write the index. + * @throws IOException if the index is unable to write to the specified path. + */ + public void write(final Path indexPath) throws IOException; /** * Write an appropriately named and located Index file based on the name and location of the featureFile. * If featureFile is not a normal file, the index will silently not be written. + * + * Default implementation delegates to {@link #writeBasedOnFeaturePath(Path)} + * * @param featureFile */ - public void writeBasedOnFeatureFile(File featureFile) throws IOException; + public default void writeBasedOnFeatureFile(File featureFile) throws IOException { + writeBasedOnFeaturePath(featureFile.toPath()); + } + + /** + * Write an appropriately named and located Index file based on the name and location of the featureFile. + * If featureFile is not a normal file, the index will silently not be written. + * + * @param featurePath + */ + public void writeBasedOnFeaturePath(Path featurePath) throws IOException; /** * @return get the list of properties for this index. Returns null if no properties. diff --git a/src/main/java/htsjdk/tribble/index/interval/IntervalIndexCreator.java b/src/main/java/htsjdk/tribble/index/interval/IntervalIndexCreator.java index 01219040c..58e2f87ee 100644 --- a/src/main/java/htsjdk/tribble/index/interval/IntervalIndexCreator.java +++ b/src/main/java/htsjdk/tribble/index/interval/IntervalIndexCreator.java @@ -25,6 +25,7 @@ import htsjdk.tribble.index.interval.IntervalTreeIndex.ChrIndex; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedList; @@ -51,15 +52,23 @@ private final ArrayList intervals = new ArrayList(); - File inputFile; + Path inputPath; - public IntervalIndexCreator(final File inputFile, final int featuresPerInterval) { - this.inputFile = inputFile; + public IntervalIndexCreator(final Path inputPath, final int featuresPerInterval) { + this.inputPath = inputPath; this.featuresPerInterval = featuresPerInterval; } + public IntervalIndexCreator(final File inputFile, final int featuresPerInterval) { + this(inputFile.toPath(), featuresPerInterval); + } + public IntervalIndexCreator(final File inputFile) { - this(inputFile, DEFAULT_FEATURE_COUNT); + this(inputFile.toPath()); + } + + public IntervalIndexCreator(final Path inputPath) { + this(inputPath, DEFAULT_FEATURE_COUNT); } @Override @@ -108,7 +117,7 @@ private void addIntervalsToLastChr(final long currentPos) { */ @Override public Index finalizeIndex(final long finalFilePosition) { - final IntervalTreeIndex featureIndex = new IntervalTreeIndex(inputFile.getAbsolutePath()); + final IntervalTreeIndex featureIndex = new IntervalTreeIndex(inputPath); // dump the remaining bins to the index addIntervalsToLastChr(finalFilePosition); featureIndex.setChrIndex(chrList); diff --git a/src/main/java/htsjdk/tribble/index/interval/IntervalTreeIndex.java b/src/main/java/htsjdk/tribble/index/interval/IntervalTreeIndex.java index 9a4206ebf..c4b2865dc 100644 --- a/src/main/java/htsjdk/tribble/index/interval/IntervalTreeIndex.java +++ b/src/main/java/htsjdk/tribble/index/interval/IntervalTreeIndex.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -57,6 +58,15 @@ public IntervalTreeIndex(final InputStream inputStream) throws IOException { * * @param featureFile File which we are indexing */ + public IntervalTreeIndex(final Path featureFile) { + super(featureFile); + } + + /** + * Prepare to build an index. + * + * @param featureFile File which we are indexing + */ public IntervalTreeIndex(final String featureFile) { super(featureFile); } diff --git a/src/main/java/htsjdk/tribble/index/linear/LinearIndex.java b/src/main/java/htsjdk/tribble/index/linear/LinearIndex.java index 5047ab61a..3d7905af1 100644 --- a/src/main/java/htsjdk/tribble/index/linear/LinearIndex.java +++ b/src/main/java/htsjdk/tribble/index/linear/LinearIndex.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -71,12 +72,21 @@ * @param indices * @param featureFile */ - public LinearIndex(final List indices, final File featureFile) { - super(featureFile.getAbsolutePath()); + public LinearIndex(final List indices, final Path featureFile) { + super(featureFile); for (final ChrIndex index : indices) chrIndices.put(index.getName(), index); } + /** + * Initialize using the specified {@code indices} + * @param indices + * @param featureFile + */ + public LinearIndex(final List indices, final File featureFile) { + this(indices, featureFile.toPath()); + } + private LinearIndex(final LinearIndex parent, final List indices) { super(parent); for (final ChrIndex index : indices) @@ -92,6 +102,14 @@ public LinearIndex(final String featureFile) { } /** + * Initialize with default parameters + * @param featurePath Path for which this is an index + */ + public LinearIndex(final Path featurePath) { + super(featurePath); + } + + /** * Load from file. * @param inputStream This method assumes that the input stream is already buffered as appropriate. */ diff --git a/src/main/java/htsjdk/tribble/index/linear/LinearIndexCreator.java b/src/main/java/htsjdk/tribble/index/linear/LinearIndexCreator.java index daad6cce6..9109705d2 100644 --- a/src/main/java/htsjdk/tribble/index/linear/LinearIndexCreator.java +++ b/src/main/java/htsjdk/tribble/index/linear/LinearIndexCreator.java @@ -29,6 +29,7 @@ import htsjdk.tribble.index.TribbleIndexCreator; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedList; @@ -43,20 +44,28 @@ private int binWidth = DEFAULT_BIN_WIDTH; // the input file - private final File inputFile; + private final Path inputFile; private final LinkedList chrList = new LinkedList(); private int longestFeature= 0; private final ArrayList blocks = new ArrayList(); - public LinearIndexCreator(final File inputFile, final int binSize) { - this.inputFile = inputFile; + public LinearIndexCreator(final Path inputPath, final int binSize) { + this.inputFile = inputPath; binWidth = binSize; } + public LinearIndexCreator(final File inputFile, final int binSize) { + this(inputFile.toPath(), binSize); + } + public LinearIndexCreator(final File inputFile) { - this(inputFile, DEFAULT_BIN_WIDTH); + this(inputFile.toPath()); + } + + public LinearIndexCreator(final Path inputPath) { + this(inputPath, DEFAULT_BIN_WIDTH); } /** diff --git a/src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java b/src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java index 044cefe61..43b1a2b40 100644 --- a/src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java +++ b/src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java @@ -30,7 +30,10 @@ import htsjdk.samtools.util.BlockCompressedInputStream; import htsjdk.samtools.util.BlockCompressedOutputStream; import htsjdk.samtools.util.CloserUtil; +import htsjdk.samtools.util.IOUtil; +import htsjdk.samtools.util.Log; import htsjdk.samtools.util.StringUtil; +import htsjdk.tribble.Tribble; import htsjdk.tribble.TribbleException; import htsjdk.tribble.index.Block; import htsjdk.tribble.index.Index; @@ -44,6 +47,8 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; /** @@ -61,6 +66,8 @@ MAGIC_NUMBER = bb.order(ByteOrder.LITTLE_ENDIAN).getInt(); } + private static final Log LOGGER = Log.getInstance(TabixIndex.class); + private final TabixFormat formatSpec; private final List sequenceNames; private final BinningIndexContent[] indices; @@ -95,6 +102,13 @@ public TabixIndex(final File tabixFile) throws IOException { this(new BlockCompressedInputStream(tabixFile), true); } + /** + * Convenient ctor that opens the path, wraps with with BGZF reader, and closes after reading index. + */ + public TabixIndex(final Path tabixPath) throws IOException { + this(new BlockCompressedInputStream(Files.newInputStream(tabixPath)), true); + } + private TabixIndex(final InputStream inputStream, final boolean closeInputStream) throws IOException { final LittleEndianInputStream dis = new LittleEndianInputStream(inputStream); if (dis.readInt() != MAGIC_NUMBER) { @@ -199,24 +213,27 @@ public TabixFormat getFormatSpec() { /** * Writes the index with BGZF. * - * @param tabixFile Where to write the index. + * @param tabixPath Where to write the index. */ @Override - public void write(final File tabixFile) throws IOException { - try(final LittleEndianOutputStream los = new LittleEndianOutputStream(new BlockCompressedOutputStream(tabixFile))) { + public void write(final Path tabixPath) throws IOException { + try(final LittleEndianOutputStream los = new LittleEndianOutputStream(new BlockCompressedOutputStream(Files.newOutputStream(tabixPath), null))) { write(los); } } /** - * Writes to a file with appropriate name and directory based on feature file. + * Writes to a path with appropriate name and directory based on feature path. * - * @param featureFile File being indexed. + * @param featurePath Path being indexed. */ @Override - public void writeBasedOnFeatureFile(final File featureFile) throws IOException { - if (!featureFile.isFile()) return; - write(new File(featureFile.getAbsolutePath() + TabixUtils.STANDARD_INDEX_EXTENSION)); + public void writeBasedOnFeaturePath(final Path featurePath) throws IOException { + if (!Files.isRegularFile(featurePath)) { + LOGGER.warn("Index not written into ", featurePath); + return; + } + write(Tribble.tabixIndexPath(featurePath)); } /** diff --git a/src/test/java/htsjdk/tribble/index/IndexTest.java b/src/test/java/htsjdk/tribble/index/IndexTest.java index aa179a9a2..56200e672 100644 --- a/src/test/java/htsjdk/tribble/index/IndexTest.java +++ b/src/test/java/htsjdk/tribble/index/IndexTest.java @@ -1,10 +1,14 @@ package htsjdk.tribble.index; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; import htsjdk.samtools.util.IOUtil; +import htsjdk.tribble.AbstractFeatureReader; import htsjdk.tribble.FeatureCodec; import htsjdk.tribble.TestUtils; import htsjdk.tribble.Tribble; import htsjdk.tribble.bed.BEDCodec; +import htsjdk.tribble.index.interval.IntervalTreeIndex; import htsjdk.tribble.index.linear.LinearIndex; import htsjdk.tribble.index.tabix.TabixFormat; import htsjdk.tribble.index.tabix.TabixIndex; @@ -18,6 +22,11 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -94,4 +103,27 @@ public void testWriteIndex(final File inputFile, final IndexFactory.IndexType ty index.write(new LittleEndianOutputStream(nullOutputStrem)); } + @Test(dataProvider = "writeIndexData") + public void testWritePathIndex(final File inputFile, final IndexFactory.IndexType type, final FeatureCodec codec) throws Exception { + try (final FileSystem fs = Jimfs.newFileSystem("test", Configuration.unix())) { + // create the index + final Index index = IndexFactory.createIndex(inputFile, codec, type); + final Path path = fs.getPath(inputFile.getName() + ".index"); + // write the index to a file + index.write(path); + + // test if the index does not blow up with the path constructor + switch (type) { + case TABIX: + new TabixIndex(path); + break; + case LINEAR: + new LinearIndex(path); + break; + case INTERVAL_TREE: + new IntervalTreeIndex(path); + break; + } + } + } } From 8c97c994f57bcff1446a77c7a1e9715ceb9d1917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B3mez-S=C3=A1nchez?= Date: Fri, 10 Mar 2017 16:50:38 +0100 Subject: [PATCH 10/59] Update javadoc for Feature (#818) * Update javadoc for Feature --- src/main/java/htsjdk/tribble/Feature.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/htsjdk/tribble/Feature.java b/src/main/java/htsjdk/tribble/Feature.java index 941790f34..9ed852b14 100644 --- a/src/main/java/htsjdk/tribble/Feature.java +++ b/src/main/java/htsjdk/tribble/Feature.java @@ -27,13 +27,14 @@ import htsjdk.samtools.util.Locatable; /** - * Represents a locus on a reference sequence. All Features are expected to return 1-based closed-ended intervals. + * Marker interface for Locatables with Tribble support. A Feature represents a record in a tribble-supported file format. + * As {@link Locatable}, represents a locus on a reference sequence and is expected to return 1-based closed-ended intervals. */ public interface Feature extends Locatable { /** * Return the features reference sequence name, e.g chromosome or contig - * @deprecated use getContig() instead + * @deprecated on 03/2015. Use getContig() instead. */ @Deprecated default public String getChr() { From bbab32dbdf0b9f1564871803822f41f93c58e4c2 Mon Sep 17 00:00:00 2001 From: Len Trigg Date: Sat, 25 Mar 2017 10:49:42 +1300 Subject: [PATCH 11/59] Adding getSAMString to AbstractSAMHeaderRecord (#827) * Added getSAMString as abstract method to AbstractSAMHeaderRecord this returns a String that represents the SAM format encoding of the header record * new methods on SAMTextHeaderCodec to encode SamHeaderRecords as Strings * BREAKING CHANGE: any subclasses of AbstractSAMHeaderRecord in downstream projects will have to implement getSAMString() --- .../htsjdk/samtools/AbstractSAMHeaderRecord.java | 9 +++-- src/main/java/htsjdk/samtools/SAMFileHeader.java | 9 +++-- .../java/htsjdk/samtools/SAMProgramRecord.java | 6 ++++ .../java/htsjdk/samtools/SAMReadGroupRecord.java | 5 +++ .../java/htsjdk/samtools/SAMSequenceRecord.java | 12 ++++--- .../java/htsjdk/samtools/SAMTextHeaderCodec.java | 29 +++++++++------ .../java/htsjdk/samtools/SAMProgramRecordTest.java | 42 ++++++++++++++++++++++ .../htsjdk/samtools/SAMReadGroupRecordTest.java | 42 ++++++++++++++++++++++ .../htsjdk/samtools/SAMSequenceRecordTest.java | 42 ++++++++++++++++++++++ 9 files changed, 178 insertions(+), 18 deletions(-) create mode 100644 src/test/java/htsjdk/samtools/SAMProgramRecordTest.java create mode 100644 src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java create mode 100644 src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java diff --git a/src/main/java/htsjdk/samtools/AbstractSAMHeaderRecord.java b/src/main/java/htsjdk/samtools/AbstractSAMHeaderRecord.java index 769a7a735..7078bf1dc 100644 --- a/src/main/java/htsjdk/samtools/AbstractSAMHeaderRecord.java +++ b/src/main/java/htsjdk/samtools/AbstractSAMHeaderRecord.java @@ -23,13 +23,12 @@ */ package htsjdk.samtools; +import javax.xml.bind.annotation.XmlTransient; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; -import javax.xml.bind.annotation.XmlTransient; - /** * Base class for the various concrete records in a SAM header, providing uniform * access to the attributes. @@ -113,4 +112,10 @@ protected int attributesHashCode() { @Override public String toString() { return getClass().getSimpleName() + this.mAttributes.toString(); } + + /** + * Returns the record in the SAM line-based text format. Fields are + * separated by '\t' characters. The String is NOT terminated by '\n'. + */ + abstract public String getSAMString(); } diff --git a/src/main/java/htsjdk/samtools/SAMFileHeader.java b/src/main/java/htsjdk/samtools/SAMFileHeader.java index 41eed4a4c..b94598185 100644 --- a/src/main/java/htsjdk/samtools/SAMFileHeader.java +++ b/src/main/java/htsjdk/samtools/SAMFileHeader.java @@ -358,9 +358,14 @@ public int hashCode() { public final SAMFileHeader clone() { final SAMTextHeaderCodec codec = new SAMTextHeaderCodec(); codec.setValidationStringency(ValidationStringency.SILENT); + return codec.decode(new StringLineReader(getSAMString()), "SAMFileHeader.clone"); + } + + @Override + public String getSAMString() { final StringWriter stringWriter = new StringWriter(); - codec.encode(stringWriter, this); - return codec.decode(new StringLineReader(stringWriter.toString()), "SAMFileHeader.clone"); + new SAMTextHeaderCodec().encode(stringWriter, this); + return stringWriter.toString(); } /** Little class to generate program group IDs */ diff --git a/src/main/java/htsjdk/samtools/SAMProgramRecord.java b/src/main/java/htsjdk/samtools/SAMProgramRecord.java index 91d0dac44..f5ddd964a 100644 --- a/src/main/java/htsjdk/samtools/SAMProgramRecord.java +++ b/src/main/java/htsjdk/samtools/SAMProgramRecord.java @@ -131,4 +131,10 @@ public int hashCode() { Set getStandardTags() { return STANDARD_TAGS; } + + + @Override + public String getSAMString() { + return new SAMTextHeaderCodec().getPGLine(this); + } } diff --git a/src/main/java/htsjdk/samtools/SAMReadGroupRecord.java b/src/main/java/htsjdk/samtools/SAMReadGroupRecord.java index bae3c4f62..14f1c50e3 100644 --- a/src/main/java/htsjdk/samtools/SAMReadGroupRecord.java +++ b/src/main/java/htsjdk/samtools/SAMReadGroupRecord.java @@ -163,5 +163,10 @@ public int hashCode() { Set getStandardTags() { return STANDARD_TAGS; } + + @Override + public String getSAMString() { + return new SAMTextHeaderCodec().getRGLine(this); + } } diff --git a/src/main/java/htsjdk/samtools/SAMSequenceRecord.java b/src/main/java/htsjdk/samtools/SAMSequenceRecord.java index 8e7ccf295..a4b4df236 100644 --- a/src/main/java/htsjdk/samtools/SAMSequenceRecord.java +++ b/src/main/java/htsjdk/samtools/SAMSequenceRecord.java @@ -23,6 +23,9 @@ */ package htsjdk.samtools; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlValue; import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; @@ -32,10 +35,6 @@ import java.util.Set; import java.util.regex.Pattern; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlValue; - /** * Header information about a reference sequence. Corresponds to @SQ header record in SAM text header. */ @@ -246,5 +245,10 @@ public String toString() { getAssembly() ); } + + @Override + public String getSAMString() { + return new SAMTextHeaderCodec().getSQLine(this); + } } diff --git a/src/main/java/htsjdk/samtools/SAMTextHeaderCodec.java b/src/main/java/htsjdk/samtools/SAMTextHeaderCodec.java index fb4b02ac3..402ea3ce8 100644 --- a/src/main/java/htsjdk/samtools/SAMTextHeaderCodec.java +++ b/src/main/java/htsjdk/samtools/SAMTextHeaderCodec.java @@ -429,22 +429,27 @@ private void println(final String s) { } private void writePGLine(final SAMProgramRecord programRecord) { - if (programRecord == null) { - return; - } + println(getPGLine(programRecord)); + } + + protected String getPGLine(final SAMProgramRecord programRecord) { final String[] fields = new String[2 + programRecord.getAttributes().size()]; fields[0] = HEADER_LINE_START + HeaderRecordType.PG; fields[1] = SAMProgramRecord.PROGRAM_GROUP_ID_TAG + TAG_KEY_VALUE_SEPARATOR + programRecord.getProgramGroupId(); encodeTags(programRecord, fields, 2); - println(StringUtil.join(FIELD_SEPARATOR, fields)); + return StringUtil.join(FIELD_SEPARATOR, fields); } private void writeRGLine(final SAMReadGroupRecord readGroup) { - final String[] fields = new String[2 + readGroup.getAttributes().size()]; - fields[0] = HEADER_LINE_START + HeaderRecordType.RG; - fields[1] = SAMReadGroupRecord.READ_GROUP_ID_TAG + TAG_KEY_VALUE_SEPARATOR + readGroup.getReadGroupId(); - encodeTags(readGroup, fields, 2); - println(StringUtil.join(FIELD_SEPARATOR, fields)); + println(getRGLine(readGroup)); + } + + protected String getRGLine(final SAMReadGroupRecord readGroup) { + final String[] fields = new String[2 + readGroup.getAttributes().size()]; + fields[0] = HEADER_LINE_START + HeaderRecordType.RG; + fields[1] = SAMReadGroupRecord.READ_GROUP_ID_TAG + TAG_KEY_VALUE_SEPARATOR + readGroup.getReadGroupId(); + encodeTags(readGroup, fields, 2); + return StringUtil.join(FIELD_SEPARATOR, fields); } private void writeHDLine(final boolean keepExistingVersionNumber) { @@ -470,13 +475,17 @@ private void writeHDLine(final boolean keepExistingVersionNumber) { } private void writeSQLine(final SAMSequenceRecord sequenceRecord) { + println(getSQLine(sequenceRecord)); + } + + protected String getSQLine(final SAMSequenceRecord sequenceRecord) { final int numAttributes = sequenceRecord.getAttributes() != null ? sequenceRecord.getAttributes().size() : 0; final String[] fields = new String[3 + numAttributes]; fields[0] = HEADER_LINE_START + HeaderRecordType.SQ; fields[1] = SAMSequenceRecord.SEQUENCE_NAME_TAG + TAG_KEY_VALUE_SEPARATOR + sequenceRecord.getSequenceName(); fields[2] = SAMSequenceRecord.SEQUENCE_LENGTH_TAG + TAG_KEY_VALUE_SEPARATOR + Integer.toString(sequenceRecord.getSequenceLength()); encodeTags(sequenceRecord, fields, 3); - println(StringUtil.join(FIELD_SEPARATOR, fields)); + return StringUtil.join(FIELD_SEPARATOR, fields); } /** diff --git a/src/test/java/htsjdk/samtools/SAMProgramRecordTest.java b/src/test/java/htsjdk/samtools/SAMProgramRecordTest.java new file mode 100644 index 000000000..5d9a36893 --- /dev/null +++ b/src/test/java/htsjdk/samtools/SAMProgramRecordTest.java @@ -0,0 +1,42 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Broad Institute + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package htsjdk.samtools; + +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Test for SAMReadGroupRecordTest + */ +public class SAMProgramRecordTest { + + @Test + public void testGetSAMString() { + SAMProgramRecord r = new SAMProgramRecord("SW-eIV"); + r.setProgramName("telnet"); + r.setProgramVersion("0.17-40"); + r.setCommandLine("telnet towel.blinkenlights.nl"); + Assert.assertEquals("@PG\tID:SW-eIV\tPN:telnet\tVN:0.17-40\tCL:telnet towel.blinkenlights.nl", r.getSAMString()); + } +} diff --git a/src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java b/src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java new file mode 100644 index 000000000..c3a7423d3 --- /dev/null +++ b/src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java @@ -0,0 +1,42 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Broad Institute + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package htsjdk.samtools; + +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Test for SAMReadGroupRecordTest + */ +public class SAMReadGroupRecordTest { + + @Test + public void testGetSAMString() { + SAMReadGroupRecord r = new SAMReadGroupRecord("rg1"); + r.setSample("mysample"); + r.setPlatform("ILLUMINA"); + r.setDescription("my description"); + Assert.assertEquals("@RG\tID:rg1\tSM:mysample\tPL:ILLUMINA\tDS:my description", r.getSAMString()); + } +} diff --git a/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java b/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java new file mode 100644 index 000000000..1035d1b10 --- /dev/null +++ b/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java @@ -0,0 +1,42 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Broad Institute + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package htsjdk.samtools; + +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Test for SAMReadGroupRecordTest + */ +public class SAMSequenceRecordTest { + + @Test + public void testGetSAMString() { + SAMSequenceRecord r = new SAMSequenceRecord("chr5_but_without_a_prefix", 271828); + r.setSpecies("Psephophorus terrypratchetti"); + r.setAssembly("GRCt01"); + r.setMd5("7a6dd3d307de916b477e7bf304ac22bc"); + Assert.assertEquals("@SQ\tSN:chr5_but_without_a_prefix\tLN:271828\tSP:Psephophorus terrypratchetti\tAS:GRCt01\tM5:7a6dd3d307de916b477e7bf304ac22bc", r.getSAMString()); + } +} From 9d343e792ef0c40072ba8a14c99caf12c41ca90e Mon Sep 17 00:00:00 2001 From: Ron Levine Date: Thu, 30 Mar 2017 16:17:44 -0400 Subject: [PATCH 12/59] Make exception message informative in SAMUtils.charToCompressedBase (#836) * Make exception message informative * Add read name where accessible to the exception message --- src/main/java/htsjdk/samtools/BAMRecord.java | 7 ++++- src/main/java/htsjdk/samtools/BAMRecordCodec.java | 7 ++++- src/main/java/htsjdk/samtools/SAMUtils.java | 27 +++++++++-------- src/test/java/htsjdk/samtools/SAMUtilsTest.java | 36 +++++++++++++++++++++++ 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/main/java/htsjdk/samtools/BAMRecord.java b/src/main/java/htsjdk/samtools/BAMRecord.java index 672e802c3..14b629595 100644 --- a/src/main/java/htsjdk/samtools/BAMRecord.java +++ b/src/main/java/htsjdk/samtools/BAMRecord.java @@ -342,7 +342,12 @@ private String decodeReadName() { return NULL_SEQUENCE; } final int basesOffset = readNameSize() + cigarSize(); - return SAMUtils.compressedBasesToBytes(mReadLength, mRestOfBinaryData, basesOffset); + try { + return SAMUtils.compressedBasesToBytes(mReadLength, mRestOfBinaryData, basesOffset); + } catch ( final IllegalArgumentException ex ) { + final String msg = ex.getMessage() + " in read: " + getReadName(); + throw new IllegalStateException(msg, ex); + } } /* methods for computing disk size of variably-sized elements, in order to locate diff --git a/src/main/java/htsjdk/samtools/BAMRecordCodec.java b/src/main/java/htsjdk/samtools/BAMRecordCodec.java index 5b0a408f6..e363a5b95 100644 --- a/src/main/java/htsjdk/samtools/BAMRecordCodec.java +++ b/src/main/java/htsjdk/samtools/BAMRecordCodec.java @@ -154,7 +154,12 @@ public void encode(final SAMRecord alignment) { // that it is specced as a uint. this.binaryCodec.writeInt(cigarElement); } - this.binaryCodec.writeBytes(SAMUtils.bytesToCompressedBases(alignment.getReadBases())); + try { + this.binaryCodec.writeBytes(SAMUtils.bytesToCompressedBases(alignment.getReadBases())); + } catch ( final IllegalArgumentException ex ) { + final String msg = ex.getMessage() + " in read: " + alignment.getReadName(); + throw new IllegalStateException(msg, ex); + } byte[] qualities = alignment.getBaseQualities(); if (qualities.length == 0) { qualities = new byte[alignment.getReadLength()]; diff --git a/src/main/java/htsjdk/samtools/SAMUtils.java b/src/main/java/htsjdk/samtools/SAMUtils.java index 25b6799c7..d439a4a83 100644 --- a/src/main/java/htsjdk/samtools/SAMUtils.java +++ b/src/main/java/htsjdk/samtools/SAMUtils.java @@ -111,8 +111,8 @@ public static final int MAX_PHRED_SCORE = 93; /** - * Convert from a byte array containing =AaCcGgTtNn represented as ASCII, to a byte array half as long, - * with =, A, C, G, T converted to 0, 1, 2, 4, 8, 15. + * Convert from a byte array containing =AaCcGgTtNnMmRrSsVvWwYyHhKkDdBb represented as ASCII, to a byte array half as long, + * with for example, =, A, C, G, T converted to 0, 1, 2, 4, 8, 15. * * @param readBases Bases as ASCII bytes. * @return New byte array with bases represented as nybbles, in BAM binary format. @@ -126,13 +126,13 @@ } // Last nybble if (i == readBases.length) { - compressedBases[i / 2] = charToCompressedBaseHigh((char) readBases[i - 1]); + compressedBases[i / 2] = charToCompressedBaseHigh(readBases[i - 1]); } return compressedBases; } /** - * Convert from a byte array with basese stored in nybbles, with =, A, C, G, T represented as 0, 1, 2, 4, 8, 15, + * Convert from a byte array with bases stored in nybbles, with for example,=, A, C, G, T, N represented as 0, 1, 2, 4, 8, 15, * to a a byte array containing =AaCcGgTtNn represented as ASCII. * * @param length Number of bases (not bytes) to convert. @@ -158,10 +158,11 @@ /** * Convert from ASCII byte to BAM nybble representation of a base in low-order nybble. * - * @param base One of =AaCcGgTtNn. + * @param base One of =AaCcGgTtNnMmRrSsVvWwYyHhKkDdBb. * @return Low-order nybble-encoded equivalent. + * @throws IllegalArgumentException if the base is not one of =AaCcGgTtNnMmRrSsVvWwYyHhKkDdBb. */ - private static byte charToCompressedBaseLow(final int base) { + private static byte charToCompressedBaseLow(final byte base) { switch (base) { case '=': return COMPRESSED_EQUAL_LOW; @@ -214,17 +215,18 @@ private static byte charToCompressedBaseLow(final int base) { case 'b': return COMPRESSED_B_LOW; default: - throw new IllegalArgumentException("Bad byte passed to charToCompressedBase: " + base); + throw new IllegalArgumentException("Bad base passed to charToCompressedBaseLow: " + Character.toString((char)base) + "(" + base + ")"); } } /** * Convert from ASCII byte to BAM nybble representation of a base in high-order nybble. * - * @param base One of =AaCcGgTtNn. + * @param base One of =AaCcGgTtNnMmRrSsVvWwYyHhKkDdBb. * @return High-order nybble-encoded equivalent. + * @throws IllegalArgumentException if the base is not one of =AaCcGgTtNnMmRrSsVvWwYyHhKkDdBb. */ - private static byte charToCompressedBaseHigh(final int base) { + private static byte charToCompressedBaseHigh(final byte base) { switch (base) { case '=': return COMPRESSED_EQUAL_HIGH; @@ -277,20 +279,21 @@ private static byte charToCompressedBaseHigh(final int base) { case 'b': return COMPRESSED_B_HIGH; default: - throw new IllegalArgumentException("Bad byte passed to charToCompressedBase: " + base); + throw new IllegalArgumentException("Bad base passed to charToCompressedBaseHigh: " + Character.toString((char)base) + "(" + base + ")"); } } /** * Returns the byte corresponding to a certain nybble * @param base One of COMPRESSED_*_LOW, a low-order nybble encoded base. - * @return ASCII base, one of ACGTN=. + * @return ASCII base, one of =ACGTNMRSVWYHKDB. + * @throws IllegalArgumentException if the base is not one of =ACGTNMRSVWYHKDB. */ private static byte compressedBaseToByte(byte base){ try{ return COMPRESSED_LOOKUP_TABLE[base]; }catch(IndexOutOfBoundsException e){ - throw new IllegalArgumentException("Bad byte passed to charToCompressedBase: " + base); + throw new IllegalArgumentException("Bad base passed to charToCompressedBase: " + Character.toString((char)base) + "(" + base + ")"); } } diff --git a/src/test/java/htsjdk/samtools/SAMUtilsTest.java b/src/test/java/htsjdk/samtools/SAMUtilsTest.java index 3be7e390c..e3fe72656 100644 --- a/src/test/java/htsjdk/samtools/SAMUtilsTest.java +++ b/src/test/java/htsjdk/samtools/SAMUtilsTest.java @@ -24,8 +24,10 @@ package htsjdk.samtools; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.Arrays; import java.util.List; public class SAMUtilsTest { @@ -244,7 +246,41 @@ public void testOtherCanonicalAlignments() { Assert.assertEquals(other.getAttribute(SAMTagUtil.getSingleton().NM),null); Assert.assertEquals(other.getCigarString(),"8M2S"); Assert.assertEquals(other.getInferredInsertSize(),-91);//100(mate) - 191(other) + } + + @Test() + public void testBytesToCompressedBases() { + final byte[] bases = new byte[]{'=', 'a', 'A', 'c', 'C', 'g', 'G', 't', 'T', 'n', 'N', '.', 'M', 'm', + 'R', 'r', 'S', 's', 'V', 'v', 'W', 'w', 'Y', 'y', 'H', 'h', 'K', 'k', 'D', 'd', 'B', 'b'}; + final byte[] compressedBases = SAMUtils.bytesToCompressedBases(bases); + String expectedCompressedBases = "[1, 18, 36, 72, -113, -1, 51, 85, 102, 119, -103, -86, -69, -52, -35, -18]"; + Assert.assertEquals(Arrays.toString(compressedBases), expectedCompressedBases); + } + + @DataProvider + public Object[][] testBadBase() { + return new Object[][]{ + {new byte[]{'>', 'A'}, '>'}, + {new byte[]{'A', '>'} , '>'} + }; + } + @Test(dataProvider = "testBadBase", expectedExceptions = IllegalArgumentException.class) + public void testBytesToCompressedBasesException(final byte[] bases, final char failingBase) { + try { + SAMUtils.bytesToCompressedBases(bases); + } catch ( final IllegalArgumentException ex ) { + Assert.assertTrue(ex.getMessage().contains(Character.toString(failingBase))); + throw ex; + } } + @Test + public void testCompressedBasesToBytes() { + final byte[] compressedBases = new byte[]{1, 18, 36, 72, -113, -1, 51, 85, 102, 119, -103, -86, -69, -52, -35, -18}; + final byte[] bytes = SAMUtils.compressedBasesToBytes(2*compressedBases.length, compressedBases, 0); + final byte[] expectedBases = new byte[]{'=', 'A', 'A', 'C', 'C', 'G', 'G', 'T', 'T', 'N', 'N', 'N', 'M', 'M', + 'R', 'R', 'S', 'S', 'V', 'V', 'W', 'W', 'Y', 'Y', 'H', 'H', 'K', 'K', 'D', 'D', 'B', 'B'}; + Assert.assertEquals(new String(bytes), new String(expectedBases)); + } } From 476adb281a2b051d8fa8b4fed6b8605ccb5984ff Mon Sep 17 00:00:00 2001 From: Len Trigg Date: Wed, 5 Apr 2017 07:00:23 +1200 Subject: [PATCH 13/59] Fix DateParserTest unit tests that were failing in my locale. (#832) The tests were failing due to use of the deprecated Date.parse() method. Replaced Date.parse() calls in this test with equivalent tests. Tests now pass. --- src/test/java/htsjdk/samtools/util/DateParserTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/htsjdk/samtools/util/DateParserTest.java b/src/test/java/htsjdk/samtools/util/DateParserTest.java index 71313c15b..04cfa7819 100644 --- a/src/test/java/htsjdk/samtools/util/DateParserTest.java +++ b/src/test/java/htsjdk/samtools/util/DateParserTest.java @@ -76,7 +76,6 @@ This W3C work (including software, documents, or other related items) is import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.text.DateFormat; import java.util.Date; /** @@ -107,8 +106,7 @@ private static void test(final Date date) { final Date dateRoundTrip = DateParser.parse(isodate); assertDatesAreClose(date, dateRoundTrip); - Assert.assertTrue(Math.abs(Date.parse(date.toString()) - Date.parse(dateRoundTrip.toString())) < 10); - + Assert.assertTrue(Math.abs(date.getTime() - dateRoundTrip.getTime()) < 10); } @DataProvider(name="dateDate") From 31b761ba0fc7790918877d755cfa79e20e2945d0 Mon Sep 17 00:00:00 2001 From: Ron Levine Date: Fri, 7 Apr 2017 13:11:35 -0400 Subject: [PATCH 14/59] Maintain ordering of header --- src/main/java/htsjdk/variant/vcf/VCFUtils.java | 8 ++------ src/test/java/htsjdk/variant/vcf/VCFHeaderUnitTest.java | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/htsjdk/variant/vcf/VCFUtils.java b/src/main/java/htsjdk/variant/vcf/VCFUtils.java index c8eceeab5..72f757105 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFUtils.java +++ b/src/main/java/htsjdk/variant/vcf/VCFUtils.java @@ -32,26 +32,23 @@ import java.io.File; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeMap; public class VCFUtils { public static Set smartMergeHeaders(final Collection headers, final boolean emitWarnings) throws IllegalStateException { // We need to maintain the order of the VCFHeaderLines, otherwise they will be scrambled in the returned Set. // This will cause problems for VCFHeader.getSequenceDictionary and anything else that implicitly relies on the line ordering. - final TreeMap map = new TreeMap(); // from KEY.NAME -> line + final LinkedHashMap map = new LinkedHashMap<>(); // from KEY.NAME -> line final HeaderConflictWarner conflictWarner = new HeaderConflictWarner(emitWarnings); // todo -- needs to remove all version headers from sources and add its own VCF version line for ( final VCFHeader source : headers ) { - //System.out.printf("Merging in header %s%n", source); for ( final VCFHeaderLine line : source.getMetaDataInSortedOrder()) { String key = line.getKey(); @@ -102,12 +99,11 @@ } } else { map.put(key, line); - //System.out.printf("Adding header line %s%n", line); } } } // returning a LinkedHashSet so that ordering will be preserved. Ensures the contig lines do not get scrambled. - return new LinkedHashSet(map.values()); + return new LinkedHashSet<>(map.values()); } /** diff --git a/src/test/java/htsjdk/variant/vcf/VCFHeaderUnitTest.java b/src/test/java/htsjdk/variant/vcf/VCFHeaderUnitTest.java index e9135cc72..89d07f48a 100644 --- a/src/test/java/htsjdk/variant/vcf/VCFHeaderUnitTest.java +++ b/src/test/java/htsjdk/variant/vcf/VCFHeaderUnitTest.java @@ -37,6 +37,7 @@ import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -120,10 +121,18 @@ public void testVCFHeaderSampleRenamingSingleSampleVCF() throws Exception { } } - @Test - public void testVCFHeaderDictionaryMerging() { - VCFHeader headerOne = new VCFFileReader(new File(variantTestDataRoot + "dbsnp_135.b37.1000.vcf"), false).getFileHeader(); - VCFHeader headerTwo = new VCFHeader(headerOne); // deep copy + @DataProvider + public Object[][] testVCFHeaderDictionaryMergingData() { + return new Object[][]{ + {"diagnosis_targets_testfile.vcf"}, // numerically ordered contigs + {"dbsnp_135.b37.1000.vcf"} // lexicographically ordered contigs + }; + } + + @Test(dataProvider = "testVCFHeaderDictionaryMergingData") + public void testVCFHeaderDictionaryMerging(final String vcfFileName) { + final VCFHeader headerOne = new VCFFileReader(new File(variantTestDataRoot + vcfFileName), false).getFileHeader(); + final VCFHeader headerTwo = new VCFHeader(headerOne); // deep copy final List sampleList = new ArrayList(); sampleList.addAll(headerOne.getSampleNamesInOrder()); From 656dc24930e1dc7fd0abd4c5e2abedf6f5074b35 Mon Sep 17 00:00:00 2001 From: Tim Fennell Date: Sat, 15 Apr 2017 14:00:28 -0400 Subject: [PATCH 15/59] Enabling writing and running of tests with scalatest. (#789) --- .travis.yml | 2 +- build.gradle | 88 +++++--------- gradle/wrapper/gradle-wrapper.properties | 4 +- src/test/java/htsjdk/HtsjdkTest.java | 10 ++ .../htsjdk/cram/io/ExternalCompressionTest.java | 9 +- .../htsjdk/samtools/AbstractBAMFileIndexTest.java | 5 +- .../java/htsjdk/samtools/BAMCigarOverflowTest.java | 3 +- .../java/htsjdk/samtools/BAMFileIndexTest.java | 6 +- .../java/htsjdk/samtools/BAMFileWriterTest.java | 3 +- .../java/htsjdk/samtools/BAMIndexWriterTest.java | 3 +- src/test/java/htsjdk/samtools/BAMIteratorTest.java | 3 +- ...AMQueryMultipleIntervalsIteratorFilterTest.java | 3 +- .../java/htsjdk/samtools/BAMRemoteFileTest.java | 3 +- src/test/java/htsjdk/samtools/BinTest.java | 3 +- .../java/htsjdk/samtools/CRAMBAIIndexerTest.java | 3 +- .../java/htsjdk/samtools/CRAMCRAIIndexerTest.java | 5 +- .../java/htsjdk/samtools/CRAMComplianceTest.java | 3 +- .../samtools/CRAMContainerStreamWriterTest.java | 3 +- .../java/htsjdk/samtools/CRAMEdgeCasesTest.java | 3 +- .../java/htsjdk/samtools/CRAMFileBAIIndexTest.java | 3 +- .../htsjdk/samtools/CRAMFileCRAIIndexTest.java | 4 +- .../java/htsjdk/samtools/CRAMFileReaderTest.java | 3 +- .../java/htsjdk/samtools/CRAMFileWriterTest.java | 3 +- .../samtools/CRAMFileWriterWithIndexTest.java | 3 +- .../java/htsjdk/samtools/CRAMIndexQueryTest.java | 3 +- src/test/java/htsjdk/samtools/ChunkTest.java | 3 +- src/test/java/htsjdk/samtools/CigarCodecTest.java | 3 +- src/test/java/htsjdk/samtools/CigarTest.java | 3 +- .../htsjdk/samtools/DownsamplingIteratorTests.java | 3 +- .../htsjdk/samtools/DuplicateSetIteratorTest.java | 3 +- .../java/htsjdk/samtools/GenomicIndexUtilTest.java | 5 +- ...MergingSamRecordIteratorGroupCollisionTest.java | 3 +- .../samtools/MergingSamRecordIteratorTest.java | 3 +- .../htsjdk/samtools/ProgramRecordChainingTest.java | 3 +- .../samtools/SAMBinaryTagAndValueUnitTest.java | 3 +- src/test/java/htsjdk/samtools/SAMCloneTest.java | 3 +- .../htsjdk/samtools/SAMFileWriterFactoryTest.java | 3 +- src/test/java/htsjdk/samtools/SAMFlagTest.java | 3 +- .../java/htsjdk/samtools/SAMIntegerTagTest.java | 3 +- .../samtools/SAMRecordDuplicateComparatorTest.java | 3 +- .../java/htsjdk/samtools/SAMRecordUnitTest.java | 3 +- .../samtools/SAMSequenceDictionaryCodecTest.java | 7 +- .../htsjdk/samtools/SAMSequenceDictionaryTest.java | 14 +-- .../java/htsjdk/samtools/SAMTextReaderTest.java | 3 +- .../java/htsjdk/samtools/SAMTextWriterTest.java | 3 +- src/test/java/htsjdk/samtools/SAMUtilsTest.java | 3 +- .../htsjdk/samtools/SamFileHeaderMergerTest.java | 3 +- src/test/java/htsjdk/samtools/SamFilesTest.java | 4 +- .../java/htsjdk/samtools/SamFlagFieldTest.java | 5 +- .../samtools/SamHeaderRecordComparatorTest.java | 3 +- src/test/java/htsjdk/samtools/SamIndexesTest.java | 3 +- src/test/java/htsjdk/samtools/SamPairUtilTest.java | 3 +- .../java/htsjdk/samtools/SamReaderFactoryTest.java | 3 +- .../java/htsjdk/samtools/SamReaderSortTest.java | 3 +- src/test/java/htsjdk/samtools/SamReaderTest.java | 3 +- src/test/java/htsjdk/samtools/SamSpecIntTest.java | 3 +- src/test/java/htsjdk/samtools/SamStreamsTest.java | 5 +- .../SequenceNameTruncationAndValidationTest.java | 3 +- .../java/htsjdk/samtools/ValidateSamFileTest.java | 3 +- .../java/htsjdk/samtools/cram/CRAIEntryTest.java | 3 +- .../java/htsjdk/samtools/cram/CRAIIndexTest.java | 17 +-- .../samtools/cram/LosslessRoundTripTest.java | 21 +--- .../java/htsjdk/samtools/cram/VersionTest.java | 3 +- .../cram/build/CompressionHeaderFactoryTest.java | 3 +- .../samtools/cram/build/ContainerFactoryTest.java | 3 +- .../samtools/cram/build/ContainerParserTest.java | 3 +- .../htsjdk/samtools/cram/build/CramIOTest.java | 3 +- .../cram/encoding/huffman/codec/HuffmanTest.java | 3 +- .../samtools/cram/encoding/rans/RansTest.java | 3 +- .../java/htsjdk/samtools/cram/io/ITF8Test.java | 3 +- .../java/htsjdk/samtools/cram/io/LTF8Test.java | 3 +- .../cram/lossy/QualityScorePreservationTest.java | 3 +- .../samtools/cram/ref/EnaRefServiceTest.java | 3 +- .../cram/structure/CramCompressionRecordTest.java | 9 +- .../samtools/cram/structure/ReadTagTest.java | 10 +- .../htsjdk/samtools/cram/structure/SliceTests.java | 4 +- .../cram/structure/SubstitutionMatrixTest.java | 6 +- .../htsjdk/samtools/fastq/FastqRecordTest.java | 5 +- .../htsjdk/samtools/fastq/FastqWriterTest.java | 3 +- .../filter/FailsVendorReadQualityFilterTest.java | 3 +- .../samtools/filter/InsertSizeFilterTest.java | 3 +- .../filter/IntervalKeepPairFilterTest.java | 3 +- .../filter/JavascriptSamRecordFilterTest.java | 3 +- .../samtools/filter/MappingQualityFilterTest.java | 3 +- .../samtools/filter/OverclippedReadFilterTest.java | 3 +- .../samtools/filter/SolexaNoiseFilterTest.java | 3 +- .../java/htsjdk/samtools/filter/TagFilterTest.java | 5 +- .../htsjdk/samtools/liftover/LiftOverTest.java | 3 +- .../htsjdk/samtools/metrics/MetricBaseTest.java | 3 +- .../htsjdk/samtools/metrics/MetricsFileTest.java | 3 +- .../htsjdk/samtools/metrics/StringHeaderTest.java | 5 +- .../htsjdk/samtools/metrics/VersionHeaderTest.java | 5 +- .../samtools/reference/FastaSequenceFileTest.java | 3 +- .../samtools/reference/FastaSequenceIndexTest.java | 3 +- .../reference/IndexedFastaSequenceFileTest.java | 3 +- .../ReferenceSequenceFileFactoryTests.java | 3 +- .../reference/ReferenceSequenceFileWalkerTest.java | 3 +- .../samtools/reference/ReferenceSequenceTests.java | 3 +- .../seekablestream/SeekableBufferedStreamTest.java | 3 +- .../seekablestream/SeekableFTPStreamTest.java | 3 +- .../seekablestream/SeekableFileStreamTest.java | 3 +- .../seekablestream/SeekableMemoryStreamTest.java | 3 +- .../seekablestream/SeekablePathStreamTest.java | 4 +- .../seekablestream/SeekableStreamFactoryTest.java | 3 +- .../java/htsjdk/samtools/sra/AbstractSRATest.java | 3 +- .../samtools/util/AbstractLocusInfoTest.java | 3 +- .../util/AbstractLocusIteratorTestTemplate.java | 5 +- .../samtools/util/AbstractRecordAndOffsetTest.java | 3 +- .../util/AsyncBlockCompressedInputStreamTest.java | 3 +- .../samtools/util/AsyncBufferedIteratorTest.java | 3 +- .../java/htsjdk/samtools/util/AsyncWriterTest.java | 3 +- .../java/htsjdk/samtools/util/BinaryCodecTest.java | 3 +- .../util/BlockCompressedFilePointerUtilTest.java | 4 +- .../util/BlockCompressedInputStreamTest.java | 23 ++-- .../util/BlockCompressedOutputStreamTest.java | 3 +- .../util/BlockCompressedTerminatorTest.java | 3 +- .../java/htsjdk/samtools/util/CigarUtilTest.java | 3 +- .../samtools/util/CloseableIteratorTest.java | 3 +- .../java/htsjdk/samtools/util/CodeUtilTest.java | 3 +- .../htsjdk/samtools/util/ComparableTupleTest.java | 3 +- .../samtools/util/CoordSpanInputSteamTest.java | 3 +- .../java/htsjdk/samtools/util/DateParserTest.java | 5 +- .../samtools/util/EdgingRecordAndOffsetTest.java | 3 +- .../java/htsjdk/samtools/util/HistogramTest.java | 3 +- .../htsjdk/samtools/util/IntervalListTest.java | 3 +- .../htsjdk/samtools/util/IntervalTreeMapTest.java | 3 +- .../htsjdk/samtools/util/IntervalTreeTest.java | 3 +- src/test/java/htsjdk/samtools/util/IoUtilTest.java | 3 +- .../java/htsjdk/samtools/util/Iso8601DateTest.java | 3 +- src/test/java/htsjdk/samtools/util/IupacTest.java | 3 +- .../htsjdk/samtools/util/MergingIteratorTest.java | 3 +- .../htsjdk/samtools/util/OverlapDetectorTest.java | 3 +- .../samtools/util/PositionalOutputStreamTest.java | 5 +- .../samtools/util/QualityEncodingDetectorTest.java | 3 +- .../samtools/util/RelativeIso8601DateTest.java | 3 +- .../htsjdk/samtools/util/SequenceUtilTest.java | 3 +- .../samtools/util/SolexaQualityConverterTest.java | 3 +- .../samtools/util/SortingCollectionTest.java | 3 +- .../samtools/util/SortingLongCollectionTest.java | 3 +- .../htsjdk/samtools/util/StringLineReaderTest.java | 3 +- .../java/htsjdk/samtools/util/StringUtilTest.java | 122 ------------------- .../htsjdk/samtools/util/TrimmingUtilTest.java | 3 +- src/test/java/htsjdk/samtools/util/TupleTest.java | 5 +- .../htsjdk/tribble/AbstractFeatureReaderTest.java | 3 +- .../java/htsjdk/tribble/BinaryFeaturesTest.java | 3 +- .../java/htsjdk/tribble/FeatureReaderTest.java | 3 +- .../tribble/TribbleIndexFeatureReaderTest.java | 7 +- src/test/java/htsjdk/tribble/TribbleTest.java | 3 +- src/test/java/htsjdk/tribble/bed/BEDCodecTest.java | 3 +- .../htsjdk/tribble/index/IndexFactoryTest.java | 8 +- src/test/java/htsjdk/tribble/index/IndexTest.java | 9 +- .../tribble/index/interval/IntervalTreeTest.java | 3 +- .../tribble/index/linear/LinearIndexTest.java | 3 +- .../htsjdk/tribble/index/tabix/TabixIndexTest.java | 3 +- .../tribble/readers/AsciiLineReaderTest.java | 13 +- .../readers/LongLineBufferedReaderTest.java | 3 +- .../readers/PositionalBufferedStreamTest.java | 3 +- .../java/htsjdk/tribble/readers/ReaderTest.java | 3 +- .../readers/SynchronousLineReaderUnitTest.java | 3 +- .../htsjdk/tribble/readers/TabixReaderTest.java | 3 +- .../java/htsjdk/tribble/util/ParsingUtilsTest.java | 17 +-- .../htsjdk/tribble/util/ftp/FTPClientTest.java | 3 +- .../java/htsjdk/tribble/util/ftp/FTPUtilsTest.java | 3 +- .../util/popgen/HardyWeinbergCalculationTest.java | 3 +- .../htsjdk/variant/PrintVariantsExampleTest.java | 5 +- src/test/java/htsjdk/variant/VariantBaseTest.java | 3 +- .../utils/SAMSequenceDictionaryExtractorTest.java | 3 +- .../variantcontext/VariantContextTestProvider.java | 5 +- .../variantcontext/filter/CompoundFilterTest.java | 5 +- .../FilteringVariantContextIteratorTest.java | 3 +- .../filter/GenotypeQualityFilterTest.java | 3 +- .../filter/HeterozygosityFilterTest.java | 3 +- .../filter/JavascriptVariantFilterTest.java | 3 +- .../filter/PassingVariantFilterTest.java | 5 +- .../variantcontext/filter/SnpFilterTest.java | 3 +- .../writer/TabixOnTheFlyIndexCreationTest.java | 3 +- .../java/htsjdk/variant/vcf/VCFEncoderTest.java | 3 +- src/test/scala/htsjdk/UnitSpec.scala | 6 + .../htsjdk/samtools/util/StringUtilTest.scala | 134 +++++++++++++++++++++ 179 files changed, 575 insertions(+), 477 deletions(-) create mode 100644 src/test/java/htsjdk/HtsjdkTest.java delete mode 100644 src/test/java/htsjdk/samtools/util/StringUtilTest.java create mode 100644 src/test/scala/htsjdk/UnitSpec.scala create mode 100644 src/test/scala/htsjdk/samtools/util/StringUtilTest.scala diff --git a/.travis.yml b/.travis.yml index 9c046e353..270855ea7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ cache: - $HOME/.m2 jdk: - oraclejdk8 -script: ./gradlew testSRA jacocoTestReport; +script: ./gradlew test jacocoTestReport; after_success: - bash <(curl -s https://codecov.io/bash) - echo "TRAVIS_BRANCH='$TRAVIS_BRANCH'"; diff --git a/build.gradle b/build.gradle index ddd12a34d..9760a79fa 100644 --- a/build.gradle +++ b/build.gradle @@ -5,12 +5,14 @@ buildscript { } plugins { - id "java" + id 'java' + id 'scala' id 'maven' id 'signing' id 'jacoco' id 'com.palantir.git-version' version '0.5.1' id 'com.github.johnrengelman.shadow' version '1.2.3' + id 'com.github.maiflai.scalatest' version '0.15' } repositories { @@ -18,7 +20,6 @@ repositories { } jacocoTestReport { - dependsOn test group = "Reporting" description = "Generate Jacoco coverage reports after running tests." additionalSourceDirs = files(sourceSets.main.allJava.srcDirs) @@ -29,10 +30,6 @@ jacocoTestReport { } } -jacoco { - toolVersion = "0.7.5.201505241946" -} - dependencies { compile "org.apache.commons:commons-jexl:2.1.1" compile "commons-logging:commons-logging:1.1.1" @@ -41,6 +38,9 @@ dependencies { compile "org.tukaani:xz:1.5" compile "gov.nih.nlm.ncbi:ngs-java:1.2.4" + testCompile "org.scala-lang:scala-library:2.12.1" + testCompile "org.scalatest:scalatest_2.12:3.0.1" + testRuntime 'org.pegdown:pegdown:1.4.2' // Necessary for generating HTML reports with ScalaTest testCompile "org.testng:testng:6.9.9" testCompile "com.google.jimfs:jimfs:1.1" } @@ -67,70 +67,46 @@ jar { import org.gradle.internal.os.OperatingSystem; -tasks.withType(Test) { - outputs.upToDateWhen { false } // tests will always rerun - useTestNG() - - // set heap size for the test JVM(s) - minHeapSize = "1G" - maxHeapSize = "2G" +tasks.withType(Test) { task -> + task.outputs.upToDateWhen { false } // tests will always rerun - jvmArgs '-Djava.awt.headless=true' //this prevents awt from displaying a java icon while the tests are running + // Always run serially because there are some very badly behaved tests in HTSJDK that + // will cause errors and even deadlocks if run multi-threaded + task.maxParallelForks = 1 - if (System.env.CI == "true") { //if running under a CI output less into the logs - int count = 0 + // set heap size for the test JVM(s) + task.minHeapSize = "1G" + task.maxHeapSize = "2G" - beforeTest { descriptor -> - count++ - if( count % 100 == 0) { - logger.lifecycle("Finished "+ Integer.toString(count++) + " tests") - } - } - } else { - // show standard out and standard error of the test JVM(s) on the console - testLogging.showStandardStreams = true - beforeTest { descriptor -> - logger.lifecycle("Running Test: " + descriptor) - } + task.jvmArgs '-Djava.awt.headless=true' //this prevents awt from displaying a java icon while the tests are running +} - // listen to standard out and standard error of the test JVM(s) - onOutput { descriptor, event -> - logger.lifecycle("Test: " + descriptor + " produced standard out/err: " + event.message ) - } - } +test { + description = "Runs the unit tests other than the SRA tests" testLogging { - testLogging { - events "skipped", "failed" - exceptionFormat = "full" - } - afterSuite { desc, result -> - if (!desc.parent) { // will match the outermost suite - println "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" - } - } + events "failed", "skipped" } -} -test { - description = "Runs the unit tests other than the SRA tests" + if (System.env.CI == "true") { + jvmArgs += '-Dsamjdk.sra_libraries_download=true' + } - useTestNG { - if( OperatingSystem.current().isUnix() ){ - excludeGroups "slow", "broken", "sra" - } else { - excludeGroups "slow", "broken", "unix", "sra" - } + tags { + exclude "slow" + exclude "broken" + if (System.env.CI == "false") exclude "sra" + if (!OperatingSystem.current().isUnix()) exclude "unix" } } task testSRA(type: Test) { - jvmArgs '-Dsamjdk.sra_libraries_download=true' + description = "Run the SRA tests" + jvmArgs += '-Dsamjdk.sra_libraries_download=true' - description "Run the SRA tests" - useTestNG { - configFailurePolicy 'continue' - includeGroups "sra" + tags { + exclude "slow" + exclude "broken" } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7aff310b7..f08cd01bf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Nov 29 15:10:15 EST 2016 +#Fri Jan 20 17:10:11 EST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-all.zip diff --git a/src/test/java/htsjdk/HtsjdkTest.java b/src/test/java/htsjdk/HtsjdkTest.java new file mode 100644 index 000000000..4da626b7e --- /dev/null +++ b/src/test/java/htsjdk/HtsjdkTest.java @@ -0,0 +1,10 @@ +package htsjdk; + +import org.scalatest.testng.TestNGSuite; + +/** + * Base class for all Java tests in HTSJDK. + */ +public class HtsjdkTest extends TestNGSuite { + +} diff --git a/src/test/java/htsjdk/cram/io/ExternalCompressionTest.java b/src/test/java/htsjdk/cram/io/ExternalCompressionTest.java index 09f6e4905..60a65197d 100644 --- a/src/test/java/htsjdk/cram/io/ExternalCompressionTest.java +++ b/src/test/java/htsjdk/cram/io/ExternalCompressionTest.java @@ -1,16 +1,15 @@ -package htsjdk.samtools.cram.io; +package htsjdk.cram.io; -import org.apache.commons.compress.utils.IOUtils; +import htsjdk.HtsjdkTest; +import htsjdk.samtools.cram.io.ExternalCompression; import org.testng.Assert; import org.testng.annotations.Test; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; -public class ExternalCompressionTest { +public class ExternalCompressionTest extends HtsjdkTest { public static final File BZIP2_FILE = new File("src/test/resources/htsjdk/samtools/cram/io/bzip2-test.bz2"); public static final byte [] TEST_BYTES = "This is a simple string to test BZip2".getBytes(); diff --git a/src/test/java/htsjdk/samtools/AbstractBAMFileIndexTest.java b/src/test/java/htsjdk/samtools/AbstractBAMFileIndexTest.java index 74c2dd7f2..cf451b86a 100644 --- a/src/test/java/htsjdk/samtools/AbstractBAMFileIndexTest.java +++ b/src/test/java/htsjdk/samtools/AbstractBAMFileIndexTest.java @@ -1,11 +1,12 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.seekablestream.SeekableStream; import org.testng.annotations.Test; import java.io.IOException; -public class AbstractBAMFileIndexTest { +public class AbstractBAMFileIndexTest extends HtsjdkTest { /** * @see https://github.com/samtools/htsjdk/issues/73 @@ -59,4 +60,4 @@ public int read() throws IOException { buffer.readInteger(); buffer.readBytes(new byte[10000]); } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/BAMCigarOverflowTest.java b/src/test/java/htsjdk/samtools/BAMCigarOverflowTest.java index dd630f937..8f91c6448 100644 --- a/src/test/java/htsjdk/samtools/BAMCigarOverflowTest.java +++ b/src/test/java/htsjdk/samtools/BAMCigarOverflowTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloserUtil; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @@ -10,7 +11,7 @@ * Test the fix of a bug reported by s-andrews in which the use of an arithmetic rather than a logical right shift in BinaryCigarCodec.binaryCigarToCigarElement() * causes an overflow in the CIGAR when reading a BAM file for a read that spans a very large intron. */ -public class BAMCigarOverflowTest { +public class BAMCigarOverflowTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); @Test diff --git a/src/test/java/htsjdk/samtools/BAMFileIndexTest.java b/src/test/java/htsjdk/samtools/BAMFileIndexTest.java index 33c3709c0..0271ade37 100755 --- a/src/test/java/htsjdk/samtools/BAMFileIndexTest.java +++ b/src/test/java/htsjdk/samtools/BAMFileIndexTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloseableIterator; import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.StopWatch; @@ -46,7 +47,7 @@ /** * Test BAM file indexing. */ -public class BAMFileIndexTest { +public class BAMFileIndexTest extends HtsjdkTest { private final File BAM_FILE = new File("src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam"); private final boolean mVerbose = false; @@ -78,8 +79,7 @@ public void testSpecificQueries() } @Test(groups = {"slow"}) - public void testRandomQueries() - throws Exception { + public void testRandomQueries() throws Exception { runRandomTest(BAM_FILE, 1000, new Random()); } diff --git a/src/test/java/htsjdk/samtools/BAMFileWriterTest.java b/src/test/java/htsjdk/samtools/BAMFileWriterTest.java index a8944d0de..b228d76d8 100644 --- a/src/test/java/htsjdk/samtools/BAMFileWriterTest.java +++ b/src/test/java/htsjdk/samtools/BAMFileWriterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloseableIterator; import htsjdk.samtools.util.CloserUtil; import org.testng.Assert; @@ -35,7 +36,7 @@ * Test that BAM writing doesn't blow up. For presorted writing, the resulting BAM file is read and contents are * compared with the original SAM file. */ -public class BAMFileWriterTest { +public class BAMFileWriterTest extends HtsjdkTest { private SAMRecordSetBuilder getRecordSetBuilder(final boolean sortForMe, final SAMFileHeader.SortOrder sortOrder) { final SAMRecordSetBuilder ret = new SAMRecordSetBuilder(sortForMe, sortOrder); diff --git a/src/test/java/htsjdk/samtools/BAMIndexWriterTest.java b/src/test/java/htsjdk/samtools/BAMIndexWriterTest.java index 09f92360e..db9ccb957 100644 --- a/src/test/java/htsjdk/samtools/BAMIndexWriterTest.java +++ b/src/test/java/htsjdk/samtools/BAMIndexWriterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.IOUtil; import org.testng.annotations.DataProvider; @@ -38,7 +39,7 @@ /** * Test BAM file index creation */ -public class BAMIndexWriterTest { +public class BAMIndexWriterTest extends HtsjdkTest { // Two input files for basic test private final String BAM_FILE_LOCATION = "src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam"; private final String BAI_FILE_LOCATION = "src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam.bai"; diff --git a/src/test/java/htsjdk/samtools/BAMIteratorTest.java b/src/test/java/htsjdk/samtools/BAMIteratorTest.java index 5fa9e7dc4..6fa67cd9f 100644 --- a/src/test/java/htsjdk/samtools/BAMIteratorTest.java +++ b/src/test/java/htsjdk/samtools/BAMIteratorTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloseableIterator; import htsjdk.samtools.util.CloserUtil; import org.testng.Assert; @@ -34,7 +35,7 @@ /** * @author alecw@broadinstitute.org */ -public class BAMIteratorTest { +public class BAMIteratorTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); @Test(dataProvider = "dataProvider") diff --git a/src/test/java/htsjdk/samtools/BAMQueryMultipleIntervalsIteratorFilterTest.java b/src/test/java/htsjdk/samtools/BAMQueryMultipleIntervalsIteratorFilterTest.java index 7c0bb1fc2..d25e7ba65 100644 --- a/src/test/java/htsjdk/samtools/BAMQueryMultipleIntervalsIteratorFilterTest.java +++ b/src/test/java/htsjdk/samtools/BAMQueryMultipleIntervalsIteratorFilterTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -7,7 +8,7 @@ import java.util.Arrays; import java.util.Random; -public class BAMQueryMultipleIntervalsIteratorFilterTest { +public class BAMQueryMultipleIntervalsIteratorFilterTest extends HtsjdkTest { private final byte[] BASES = {'A', 'C', 'G', 'T'}; private final Random random = new Random(); diff --git a/src/test/java/htsjdk/samtools/BAMRemoteFileTest.java b/src/test/java/htsjdk/samtools/BAMRemoteFileTest.java index 4b686cf04..dccfddcac 100644 --- a/src/test/java/htsjdk/samtools/BAMRemoteFileTest.java +++ b/src/test/java/htsjdk/samtools/BAMRemoteFileTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.TestUtil; import org.testng.annotations.Test; @@ -40,7 +41,7 @@ /** * Test BAM file indexing. */ -public class BAMRemoteFileTest { +public class BAMRemoteFileTest extends HtsjdkTest { private final File BAM_INDEX_FILE = new File("src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam.bai"); private final File BAM_FILE = new File("src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam"); private final String BAM_URL_STRING = TestUtil.BASE_URL_FOR_HTTP_TESTS + "index_test.bam"; diff --git a/src/test/java/htsjdk/samtools/BinTest.java b/src/test/java/htsjdk/samtools/BinTest.java index 271a41101..6009ed37c 100644 --- a/src/test/java/htsjdk/samtools/BinTest.java +++ b/src/test/java/htsjdk/samtools/BinTest.java @@ -24,12 +24,13 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; import java.util.Collections; -public class BinTest { +public class BinTest extends HtsjdkTest { @Test public void testEmptyBin() { // Construct a new empty bin and ensure that the bin list is empty, not null. diff --git a/src/test/java/htsjdk/samtools/CRAMBAIIndexerTest.java b/src/test/java/htsjdk/samtools/CRAMBAIIndexerTest.java index 6f3b95459..ce32e7a8b 100644 --- a/src/test/java/htsjdk/samtools/CRAMBAIIndexerTest.java +++ b/src/test/java/htsjdk/samtools/CRAMBAIIndexerTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.build.ContainerFactory; import htsjdk.samtools.cram.structure.Container; import htsjdk.samtools.cram.structure.CramCompressionRecord; @@ -17,7 +18,7 @@ /** * Created by vadim on 12/01/2016. */ -public class CRAMBAIIndexerTest { +public class CRAMBAIIndexerTest extends HtsjdkTest { private static CramCompressionRecord createRecord(int recordIndex, int seqId, int start) { byte[] bases = "AAAAA".getBytes(); diff --git a/src/test/java/htsjdk/samtools/CRAMCRAIIndexerTest.java b/src/test/java/htsjdk/samtools/CRAMCRAIIndexerTest.java index 604e78670..c5a9634d4 100644 --- a/src/test/java/htsjdk/samtools/CRAMCRAIIndexerTest.java +++ b/src/test/java/htsjdk/samtools/CRAMCRAIIndexerTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.CRAIEntry; import htsjdk.samtools.cram.build.CramContainerIterator; import htsjdk.samtools.cram.ref.ReferenceSource; @@ -17,7 +18,7 @@ * Companion to CRAMBAIIndexerTest, for testing CRAI indices created on cram * streams; */ -public class CRAMCRAIIndexerTest { +public class CRAMCRAIIndexerTest extends HtsjdkTest { @Test public void testCRAIIndexerFromContainer() throws IOException { @@ -180,4 +181,4 @@ private long getIteratorCount(Iterator it) { return count; } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/CRAMComplianceTest.java b/src/test/java/htsjdk/samtools/CRAMComplianceTest.java index 635fa6791..57167279d 100644 --- a/src/test/java/htsjdk/samtools/CRAMComplianceTest.java +++ b/src/test/java/htsjdk/samtools/CRAMComplianceTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.build.CramIO; import htsjdk.samtools.cram.common.CramVersions; import htsjdk.samtools.cram.ref.ReferenceSource; @@ -22,7 +23,7 @@ /** * Created by vadim on 28/04/2015. */ -public class CRAMComplianceTest { +public class CRAMComplianceTest extends HtsjdkTest { @FunctionalInterface public interface TriConsumer { diff --git a/src/test/java/htsjdk/samtools/CRAMContainerStreamWriterTest.java b/src/test/java/htsjdk/samtools/CRAMContainerStreamWriterTest.java index b26f4b06b..9ab9ed278 100644 --- a/src/test/java/htsjdk/samtools/CRAMContainerStreamWriterTest.java +++ b/src/test/java/htsjdk/samtools/CRAMContainerStreamWriterTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.reference.InMemoryReferenceSequenceFile; import htsjdk.samtools.seekablestream.SeekableMemoryStream; @@ -23,7 +24,7 @@ import java.util.Collections; import java.util.List; -public class CRAMContainerStreamWriterTest { +public class CRAMContainerStreamWriterTest extends HtsjdkTest { @BeforeClass public void initClass() { diff --git a/src/test/java/htsjdk/samtools/CRAMEdgeCasesTest.java b/src/test/java/htsjdk/samtools/CRAMEdgeCasesTest.java index e77e0e8dc..4fa9b1a59 100644 --- a/src/test/java/htsjdk/samtools/CRAMEdgeCasesTest.java +++ b/src/test/java/htsjdk/samtools/CRAMEdgeCasesTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.CRAMException; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.reference.InMemoryReferenceSequenceFile; @@ -20,7 +21,7 @@ /** * A collection of CRAM test based on round trip comparison of SAMRecord before and after CRAM compression. */ -public class CRAMEdgeCasesTest { +public class CRAMEdgeCasesTest extends HtsjdkTest { @BeforeTest public void beforeTest() { diff --git a/src/test/java/htsjdk/samtools/CRAMFileBAIIndexTest.java b/src/test/java/htsjdk/samtools/CRAMFileBAIIndexTest.java index eba2b4cb7..32160920b 100644 --- a/src/test/java/htsjdk/samtools/CRAMFileBAIIndexTest.java +++ b/src/test/java/htsjdk/samtools/CRAMFileBAIIndexTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.build.ContainerParser; import htsjdk.samtools.cram.build.CramContainerIterator; import htsjdk.samtools.cram.ref.ReferenceSource; @@ -32,7 +33,7 @@ * The scan* tests check that for every records in the BAM file the query returns the same records from the CRAM file. * Created by Vadim on 14/03/2015. */ -public class CRAMFileBAIIndexTest { +public class CRAMFileBAIIndexTest extends HtsjdkTest { private final File BAM_FILE = new File("src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam"); private File cramFile; private File indexFile; diff --git a/src/test/java/htsjdk/samtools/CRAMFileCRAIIndexTest.java b/src/test/java/htsjdk/samtools/CRAMFileCRAIIndexTest.java index 9084a0fc5..b919c4619 100644 --- a/src/test/java/htsjdk/samtools/CRAMFileCRAIIndexTest.java +++ b/src/test/java/htsjdk/samtools/CRAMFileCRAIIndexTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.build.ContainerParser; import htsjdk.samtools.cram.build.CramContainerIterator; import htsjdk.samtools.cram.ref.ReferenceSource; @@ -29,7 +30,8 @@ * file as the source of the test data. The scan* tests check that for every records in the * CRAM file the query returns the same records from the CRAM file. */ -public class CRAMFileCRAIIndexTest { +@Test(singleThreaded = true) +public class CRAMFileCRAIIndexTest extends HtsjdkTest { private final File BAM_FILE = new File("src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam"); private final int nofReads = 10000 ; diff --git a/src/test/java/htsjdk/samtools/CRAMFileReaderTest.java b/src/test/java/htsjdk/samtools/CRAMFileReaderTest.java index 9e36c3ccc..da53f170c 100644 --- a/src/test/java/htsjdk/samtools/CRAMFileReaderTest.java +++ b/src/test/java/htsjdk/samtools/CRAMFileReaderTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.reference.InMemoryReferenceSequenceFile; import htsjdk.samtools.seekablestream.SeekableFileStream; @@ -40,7 +41,7 @@ /** * Additional tests for CRAMFileReader are in CRAMFileIndexTest */ -public class CRAMFileReaderTest { +public class CRAMFileReaderTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); private static final File CRAM_WITH_CRAI = new File(TEST_DATA_DIR, "cram_with_crai_index.cram"); diff --git a/src/test/java/htsjdk/samtools/CRAMFileWriterTest.java b/src/test/java/htsjdk/samtools/CRAMFileWriterTest.java index 312bf5404..bd3a5ab5b 100644 --- a/src/test/java/htsjdk/samtools/CRAMFileWriterTest.java +++ b/src/test/java/htsjdk/samtools/CRAMFileWriterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.reference.InMemoryReferenceSequenceFile; import htsjdk.samtools.util.Log; @@ -40,7 +41,7 @@ import java.util.Collections; import java.util.List; -public class CRAMFileWriterTest { +public class CRAMFileWriterTest extends HtsjdkTest { @BeforeClass public void initClass() { diff --git a/src/test/java/htsjdk/samtools/CRAMFileWriterWithIndexTest.java b/src/test/java/htsjdk/samtools/CRAMFileWriterWithIndexTest.java index b7e3eab0b..b7facb6cb 100644 --- a/src/test/java/htsjdk/samtools/CRAMFileWriterWithIndexTest.java +++ b/src/test/java/htsjdk/samtools/CRAMFileWriterWithIndexTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.CRAIIndex; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.reference.InMemoryReferenceSequenceFile; @@ -22,7 +23,7 @@ /** * Created by vadim on 23/03/2015. */ -public class CRAMFileWriterWithIndexTest { +public class CRAMFileWriterWithIndexTest extends HtsjdkTest { private byte[] cramBytes; private byte[] indexBytes; private InMemoryReferenceSequenceFile rsf; diff --git a/src/test/java/htsjdk/samtools/CRAMIndexQueryTest.java b/src/test/java/htsjdk/samtools/CRAMIndexQueryTest.java index df9431071..845433f9f 100644 --- a/src/test/java/htsjdk/samtools/CRAMIndexQueryTest.java +++ b/src/test/java/htsjdk/samtools/CRAMIndexQueryTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.seekablestream.SeekableFileStream; import htsjdk.samtools.util.CloseableIterator; import org.testng.Assert; @@ -42,7 +43,7 @@ * whatever index format (.bai or .crai converted to .bai) is available for the * target file. */ -public class CRAMIndexQueryTest { +public class CRAMIndexQueryTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools/cram"); diff --git a/src/test/java/htsjdk/samtools/ChunkTest.java b/src/test/java/htsjdk/samtools/ChunkTest.java index d2bc157e2..b3a9e0a53 100644 --- a/src/test/java/htsjdk/samtools/ChunkTest.java +++ b/src/test/java/htsjdk/samtools/ChunkTest.java @@ -23,10 +23,11 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; -public class ChunkTest { +public class ChunkTest extends HtsjdkTest { @Test public void testOverlaps() { // Test completely disjoint offsets. diff --git a/src/test/java/htsjdk/samtools/CigarCodecTest.java b/src/test/java/htsjdk/samtools/CigarCodecTest.java index 8275a9484..7ccde7d1b 100644 --- a/src/test/java/htsjdk/samtools/CigarCodecTest.java +++ b/src/test/java/htsjdk/samtools/CigarCodecTest.java @@ -23,12 +23,13 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; import java.util.Arrays; -public class CigarCodecTest { +public class CigarCodecTest extends HtsjdkTest { @Test diff --git a/src/test/java/htsjdk/samtools/CigarTest.java b/src/test/java/htsjdk/samtools/CigarTest.java index acdc22407..07fe34633 100644 --- a/src/test/java/htsjdk/samtools/CigarTest.java +++ b/src/test/java/htsjdk/samtools/CigarTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -33,7 +34,7 @@ /** * @author alecw@broadinstitute.org */ -public class CigarTest { +public class CigarTest extends HtsjdkTest { @DataProvider(name = "positiveTestsData") public Object[][] testPositive() { diff --git a/src/test/java/htsjdk/samtools/DownsamplingIteratorTests.java b/src/test/java/htsjdk/samtools/DownsamplingIteratorTests.java index e84ee2e48..96dff4656 100644 --- a/src/test/java/htsjdk/samtools/DownsamplingIteratorTests.java +++ b/src/test/java/htsjdk/samtools/DownsamplingIteratorTests.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.DownsamplingIteratorFactory.Strategy; import org.testng.Assert; import org.testng.annotations.Test; @@ -14,7 +15,7 @@ * Tests for the downsampling iterator class. * @author Tim Fennell */ -public class DownsamplingIteratorTests { +public class DownsamplingIteratorTests extends HtsjdkTest { final int NUM_TEMPLATES = 50000; final EnumMap ACCURACY = new EnumMap(Strategy.class){{ put(Strategy.HighAccuracy, 0.001); diff --git a/src/test/java/htsjdk/samtools/DuplicateSetIteratorTest.java b/src/test/java/htsjdk/samtools/DuplicateSetIteratorTest.java index 595295345..27e167881 100644 --- a/src/test/java/htsjdk/samtools/DuplicateSetIteratorTest.java +++ b/src/test/java/htsjdk/samtools/DuplicateSetIteratorTest.java @@ -1,12 +1,13 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; import java.util.HashMap; import java.util.Map; -public class DuplicateSetIteratorTest { +public class DuplicateSetIteratorTest extends HtsjdkTest { protected final static int DEFAULT_BASE_QUALITY = 10; private SAMRecordSetBuilder getSAMRecordSetBuilder() { diff --git a/src/test/java/htsjdk/samtools/GenomicIndexUtilTest.java b/src/test/java/htsjdk/samtools/GenomicIndexUtilTest.java index 8f5569c59..0bf322d58 100644 --- a/src/test/java/htsjdk/samtools/GenomicIndexUtilTest.java +++ b/src/test/java/htsjdk/samtools/GenomicIndexUtilTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -7,7 +8,7 @@ /** * Tests for GenomicIndexUtil. */ -public class GenomicIndexUtilTest { +public class GenomicIndexUtilTest extends HtsjdkTest { @Test(dataProvider = "testRegionToBinDataProvider") public void testRegionToBin(final int beg, final int end, final int bin) { @@ -47,4 +48,4 @@ public void testRegionToBin(final int beg, final int end, final int bin) { {1<<26, 1<<26+1, 2} }; } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/MergingSamRecordIteratorGroupCollisionTest.java b/src/test/java/htsjdk/samtools/MergingSamRecordIteratorGroupCollisionTest.java index 067f853ab..d350b8f38 100644 --- a/src/test/java/htsjdk/samtools/MergingSamRecordIteratorGroupCollisionTest.java +++ b/src/test/java/htsjdk/samtools/MergingSamRecordIteratorGroupCollisionTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloserUtil; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -37,7 +38,7 @@ * * @author Dave Tefft, Andre Mesarovic */ -public class MergingSamRecordIteratorGroupCollisionTest { +public class MergingSamRecordIteratorGroupCollisionTest extends HtsjdkTest { private GroupAdapter padapter = new ProgramGroupAdapter(); private GroupAdapter radapter = new ReadGroupAdapter(); diff --git a/src/test/java/htsjdk/samtools/MergingSamRecordIteratorTest.java b/src/test/java/htsjdk/samtools/MergingSamRecordIteratorTest.java index 885321b26..a50c02645 100644 --- a/src/test/java/htsjdk/samtools/MergingSamRecordIteratorTest.java +++ b/src/test/java/htsjdk/samtools/MergingSamRecordIteratorTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.SequenceUtil; import org.testng.Assert; import org.testng.annotations.Test; @@ -38,7 +39,7 @@ * * @author Dave Tefft */ -public class MergingSamRecordIteratorTest { +public class MergingSamRecordIteratorTest extends HtsjdkTest { @Test public void testVanillaCoordinateMultiIterator() throws Exception { diff --git a/src/test/java/htsjdk/samtools/ProgramRecordChainingTest.java b/src/test/java/htsjdk/samtools/ProgramRecordChainingTest.java index cd470c449..4811148f3 100644 --- a/src/test/java/htsjdk/samtools/ProgramRecordChainingTest.java +++ b/src/test/java/htsjdk/samtools/ProgramRecordChainingTest.java @@ -23,13 +23,14 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; /** * Test for SequenceUtil.chainProgramRecord */ -public class ProgramRecordChainingTest { +public class ProgramRecordChainingTest extends HtsjdkTest { @Test public void testChainProgramRecord() { diff --git a/src/test/java/htsjdk/samtools/SAMBinaryTagAndValueUnitTest.java b/src/test/java/htsjdk/samtools/SAMBinaryTagAndValueUnitTest.java index f5f7a5c01..93a20dc6e 100644 --- a/src/test/java/htsjdk/samtools/SAMBinaryTagAndValueUnitTest.java +++ b/src/test/java/htsjdk/samtools/SAMBinaryTagAndValueUnitTest.java @@ -1,11 +1,12 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.BinaryCodec; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -public class SAMBinaryTagAndValueUnitTest { +public class SAMBinaryTagAndValueUnitTest extends HtsjdkTest { @DataProvider(name="allowedAttributeTypes") public Object[][] allowedTypes() { diff --git a/src/test/java/htsjdk/samtools/SAMCloneTest.java b/src/test/java/htsjdk/samtools/SAMCloneTest.java index 8fdfb3bde..e05d29d7a 100644 --- a/src/test/java/htsjdk/samtools/SAMCloneTest.java +++ b/src/test/java/htsjdk/samtools/SAMCloneTest.java @@ -23,13 +23,14 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; /** * @author alecw@broadinstitute.org */ -public class SAMCloneTest { +public class SAMCloneTest extends HtsjdkTest { private SAMRecordSetBuilder getSAMReader(final boolean sortForMe, final SAMFileHeader.SortOrder sortOrder) { final SAMRecordSetBuilder ret = new SAMRecordSetBuilder(sortForMe, sortOrder); ret.addPair("readB", 20, 200, 300); diff --git a/src/test/java/htsjdk/samtools/SAMFileWriterFactoryTest.java b/src/test/java/htsjdk/samtools/SAMFileWriterFactoryTest.java index dc7a6f381..8c15a1656 100644 --- a/src/test/java/htsjdk/samtools/SAMFileWriterFactoryTest.java +++ b/src/test/java/htsjdk/samtools/SAMFileWriterFactoryTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.build.CramIO; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.util.IOUtil; @@ -32,7 +33,7 @@ import java.io.*; -public class SAMFileWriterFactoryTest { +public class SAMFileWriterFactoryTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); diff --git a/src/test/java/htsjdk/samtools/SAMFlagTest.java b/src/test/java/htsjdk/samtools/SAMFlagTest.java index 7b5a5539f..86dd8f0ee 100644 --- a/src/test/java/htsjdk/samtools/SAMFlagTest.java +++ b/src/test/java/htsjdk/samtools/SAMFlagTest.java @@ -24,10 +24,11 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; -public class SAMFlagTest { +public class SAMFlagTest extends HtsjdkTest { @Test public void testFlags() { Assert.assertTrue(SAMFlag.getFlags(83).contains(SAMFlag.READ_PAIRED)); diff --git a/src/test/java/htsjdk/samtools/SAMIntegerTagTest.java b/src/test/java/htsjdk/samtools/SAMIntegerTagTest.java index 133062a15..3fa38df62 100644 --- a/src/test/java/htsjdk/samtools/SAMIntegerTagTest.java +++ b/src/test/java/htsjdk/samtools/SAMIntegerTagTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.util.BinaryCodec; import htsjdk.samtools.util.CloserUtil; @@ -45,7 +46,7 @@ * * @author alecw@broadinstitute.org */ -public class SAMIntegerTagTest { +public class SAMIntegerTagTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools/SAMIntegerTagTest"); private static final String BYTE_TAG = "BY"; diff --git a/src/test/java/htsjdk/samtools/SAMRecordDuplicateComparatorTest.java b/src/test/java/htsjdk/samtools/SAMRecordDuplicateComparatorTest.java index cb509258e..99d187a58 100644 --- a/src/test/java/htsjdk/samtools/SAMRecordDuplicateComparatorTest.java +++ b/src/test/java/htsjdk/samtools/SAMRecordDuplicateComparatorTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -37,7 +38,7 @@ * * @author nhomer */ -public class SAMRecordDuplicateComparatorTest { +public class SAMRecordDuplicateComparatorTest extends HtsjdkTest { private final static SAMRecordDuplicateComparator comparator = new SAMRecordDuplicateComparator(); diff --git a/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java b/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java index 951ecee78..29118197f 100644 --- a/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java +++ b/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java @@ -24,6 +24,7 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.BinaryCodec; import htsjdk.samtools.util.TestUtil; import org.testng.Assert; @@ -34,7 +35,7 @@ import java.util.Arrays; import java.util.List; -public class SAMRecordUnitTest { +public class SAMRecordUnitTest extends HtsjdkTest { @DataProvider(name = "serializationTestData") public Object[][] getSerializationTestData() { diff --git a/src/test/java/htsjdk/samtools/SAMSequenceDictionaryCodecTest.java b/src/test/java/htsjdk/samtools/SAMSequenceDictionaryCodecTest.java index 32de1cd82..4257508cf 100644 --- a/src/test/java/htsjdk/samtools/SAMSequenceDictionaryCodecTest.java +++ b/src/test/java/htsjdk/samtools/SAMSequenceDictionaryCodecTest.java @@ -24,23 +24,24 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.LineReader; import htsjdk.samtools.util.StringLineReader; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import javax.sound.sampled.Line; import java.io.BufferedWriter; import java.io.StringWriter; import java.util.List; import java.util.Random; -import static org.testng.Assert.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; /** * @author Pavel_Silin@epam.com, EPAM Systems, Inc. */ -public class SAMSequenceDictionaryCodecTest { +public class SAMSequenceDictionaryCodecTest extends HtsjdkTest { private static final Random random = new Random(); private SAMSequenceDictionary dictionary; diff --git a/src/test/java/htsjdk/samtools/SAMSequenceDictionaryTest.java b/src/test/java/htsjdk/samtools/SAMSequenceDictionaryTest.java index 0b1a50780..8b1763067 100644 --- a/src/test/java/htsjdk/samtools/SAMSequenceDictionaryTest.java +++ b/src/test/java/htsjdk/samtools/SAMSequenceDictionaryTest.java @@ -26,23 +26,21 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collections; -public class SAMSequenceDictionaryTest { +public class SAMSequenceDictionaryTest extends HtsjdkTest { @Test public void testAliases() { final SAMSequenceRecord ssr1 = new SAMSequenceRecord("1", 1); diff --git a/src/test/java/htsjdk/samtools/SAMTextReaderTest.java b/src/test/java/htsjdk/samtools/SAMTextReaderTest.java index c80924b65..142eea32c 100644 --- a/src/test/java/htsjdk/samtools/SAMTextReaderTest.java +++ b/src/test/java/htsjdk/samtools/SAMTextReaderTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloseableIterator; import htsjdk.samtools.util.CloserUtil; import org.testng.Assert; @@ -31,7 +32,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -public class SAMTextReaderTest { +public class SAMTextReaderTest extends HtsjdkTest { // Simple input, spot check that parsed correctly, and make sure nothing blows up. @Test public void testBasic() throws Exception { diff --git a/src/test/java/htsjdk/samtools/SAMTextWriterTest.java b/src/test/java/htsjdk/samtools/SAMTextWriterTest.java index 123ab6ba1..5c9ff28cd 100644 --- a/src/test/java/htsjdk/samtools/SAMTextWriterTest.java +++ b/src/test/java/htsjdk/samtools/SAMTextWriterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -31,7 +32,7 @@ import java.util.Iterator; import java.util.Map; -public class SAMTextWriterTest { +public class SAMTextWriterTest extends HtsjdkTest { private SAMRecordSetBuilder getSAMReader(final boolean sortForMe, final SAMFileHeader.SortOrder sortOrder) { final SAMRecordSetBuilder ret = new SAMRecordSetBuilder(sortForMe, sortOrder); diff --git a/src/test/java/htsjdk/samtools/SAMUtilsTest.java b/src/test/java/htsjdk/samtools/SAMUtilsTest.java index e3fe72656..28e89f755 100644 --- a/src/test/java/htsjdk/samtools/SAMUtilsTest.java +++ b/src/test/java/htsjdk/samtools/SAMUtilsTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -30,7 +31,7 @@ import java.util.Arrays; import java.util.List; -public class SAMUtilsTest { +public class SAMUtilsTest extends HtsjdkTest { @Test public void testCompareMapqs() { Assert.assertEquals(SAMUtils.compareMapqs(0, 0), 0); diff --git a/src/test/java/htsjdk/samtools/SamFileHeaderMergerTest.java b/src/test/java/htsjdk/samtools/SamFileHeaderMergerTest.java index 6e4fd750f..5c55c0b82 100644 --- a/src/test/java/htsjdk/samtools/SamFileHeaderMergerTest.java +++ b/src/test/java/htsjdk/samtools/SamFileHeaderMergerTest.java @@ -25,6 +25,7 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.SequenceUtil; @@ -58,7 +59,7 @@ *

* Tests the ability of the SamFileHeaderMerger class to merge sequence dictionaries. */ -public class SamFileHeaderMergerTest { +public class SamFileHeaderMergerTest extends HtsjdkTest { private static File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); diff --git a/src/test/java/htsjdk/samtools/SamFilesTest.java b/src/test/java/htsjdk/samtools/SamFilesTest.java index 443a4d1e9..e7c1919d4 100644 --- a/src/test/java/htsjdk/samtools/SamFilesTest.java +++ b/src/test/java/htsjdk/samtools/SamFilesTest.java @@ -1,6 +1,8 @@ package htsjdk.samtools; import java.nio.file.Path; + +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -12,7 +14,7 @@ * Test valid combinations of bam/cram vs bai/crai files. * Created by vadim on 10/08/2015. */ -public class SamFilesTest { +public class SamFilesTest extends HtsjdkTest { private static final String TEST_DATA = "src/test/resources/htsjdk/samtools/BAMFileIndexTest/"; private static final File BAM_FILE = new File(TEST_DATA + "index_test.bam"); diff --git a/src/test/java/htsjdk/samtools/SamFlagFieldTest.java b/src/test/java/htsjdk/samtools/SamFlagFieldTest.java index f09e63683..36008cf69 100644 --- a/src/test/java/htsjdk/samtools/SamFlagFieldTest.java +++ b/src/test/java/htsjdk/samtools/SamFlagFieldTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -7,7 +8,7 @@ /** * @author nhomer */ -public class SamFlagFieldTest { +public class SamFlagFieldTest extends HtsjdkTest { @Test public void testAllFlags() { @@ -147,4 +148,4 @@ public void testIllegalHexadecimalFlagCharacter(){ public void testIllegalStringFlagCharacterExclamation(){ SamFlagField.STRING.parse("pmMr!F1s"); } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/SamHeaderRecordComparatorTest.java b/src/test/java/htsjdk/samtools/SamHeaderRecordComparatorTest.java index c11be38a1..da93add5b 100644 --- a/src/test/java/htsjdk/samtools/SamHeaderRecordComparatorTest.java +++ b/src/test/java/htsjdk/samtools/SamHeaderRecordComparatorTest.java @@ -24,11 +24,12 @@ * THE SOFTWARE. */ +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -public class SamHeaderRecordComparatorTest { +public class SamHeaderRecordComparatorTest extends HtsjdkTest { @DataProvider(name="UsualSuspects") public Object[][] createData() { diff --git a/src/test/java/htsjdk/samtools/SamIndexesTest.java b/src/test/java/htsjdk/samtools/SamIndexesTest.java index d13001f67..f78b0f371 100644 --- a/src/test/java/htsjdk/samtools/SamIndexesTest.java +++ b/src/test/java/htsjdk/samtools/SamIndexesTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.CRAIEntry; import htsjdk.samtools.cram.CRAIIndex; import htsjdk.samtools.seekablestream.SeekableFileStream; @@ -19,7 +20,7 @@ import java.util.List; import java.util.zip.GZIPOutputStream; -public class SamIndexesTest { +public class SamIndexesTest extends HtsjdkTest { @Test public void testEmptyBai() throws IOException { diff --git a/src/test/java/htsjdk/samtools/SamPairUtilTest.java b/src/test/java/htsjdk/samtools/SamPairUtilTest.java index 80841c906..f5c288a54 100644 --- a/src/test/java/htsjdk/samtools/SamPairUtilTest.java +++ b/src/test/java/htsjdk/samtools/SamPairUtilTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SamPairUtil.SetMateInfoIterator; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -32,7 +33,7 @@ import java.util.List; -public class SamPairUtilTest { +public class SamPairUtilTest extends HtsjdkTest { @Test(dataProvider = "testGetPairOrientation") public void testGetPairOrientation(final String testName, diff --git a/src/test/java/htsjdk/samtools/SamReaderFactoryTest.java b/src/test/java/htsjdk/samtools/SamReaderFactoryTest.java index 5b86c8a59..c244f3c8b 100644 --- a/src/test/java/htsjdk/samtools/SamReaderFactoryTest.java +++ b/src/test/java/htsjdk/samtools/SamReaderFactoryTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.seekablestream.ISeekableStreamFactory; import htsjdk.samtools.seekablestream.SeekableFileStream; @@ -30,7 +31,7 @@ import java.util.function.BiFunction; import java.util.zip.Inflater; -public class SamReaderFactoryTest { +public class SamReaderFactoryTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); private static final Log LOG = Log.getInstance(SamReaderFactoryTest.class); diff --git a/src/test/java/htsjdk/samtools/SamReaderSortTest.java b/src/test/java/htsjdk/samtools/SamReaderSortTest.java index e7484c787..4d712100b 100755 --- a/src/test/java/htsjdk/samtools/SamReaderSortTest.java +++ b/src/test/java/htsjdk/samtools/SamReaderSortTest.java @@ -24,6 +24,7 @@ * THE SOFTWARE. */ +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.ref.ReferenceSource; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -36,7 +37,7 @@ * * @author ktibbett@broadinstitute.org */ -public class SamReaderSortTest { +public class SamReaderSortTest extends HtsjdkTest { private static final String COORDINATE_SORTED_FILE = "src/test/resources/htsjdk/samtools/coordinate_sorted.sam"; private static final String QUERYNAME_SORTED_FILE = "src/test/resources/htsjdk/samtools/queryname_sorted.sam"; diff --git a/src/test/java/htsjdk/samtools/SamReaderTest.java b/src/test/java/htsjdk/samtools/SamReaderTest.java index 5af88214e..4d4d05634 100644 --- a/src/test/java/htsjdk/samtools/SamReaderTest.java +++ b/src/test/java/htsjdk/samtools/SamReaderTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloseableIterator; import htsjdk.samtools.util.CloserUtil; import org.testng.Assert; @@ -31,7 +32,7 @@ import java.io.File; -public class SamReaderTest { +public class SamReaderTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); @Test(dataProvider = "variousFormatReaderTestCases") diff --git a/src/test/java/htsjdk/samtools/SamSpecIntTest.java b/src/test/java/htsjdk/samtools/SamSpecIntTest.java index 8305065da..2ebc24e70 100644 --- a/src/test/java/htsjdk/samtools/SamSpecIntTest.java +++ b/src/test/java/htsjdk/samtools/SamSpecIntTest.java @@ -24,6 +24,7 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloserUtil; import org.testng.Assert; import org.testng.annotations.Test; @@ -33,7 +34,7 @@ import java.util.ArrayList; import java.util.List; -public class SamSpecIntTest { +public class SamSpecIntTest extends HtsjdkTest { private static final File SAM_INPUT = new File("src/test/resources/htsjdk/samtools/inttest.sam"); private static final File BAM_INPUT = new File("src/test/resources/htsjdk/samtools/inttest.bam"); diff --git a/src/test/java/htsjdk/samtools/SamStreamsTest.java b/src/test/java/htsjdk/samtools/SamStreamsTest.java index c92d6dbc0..48a074a8e 100644 --- a/src/test/java/htsjdk/samtools/SamStreamsTest.java +++ b/src/test/java/htsjdk/samtools/SamStreamsTest.java @@ -24,6 +24,7 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.seekablestream.SeekableFileStream; import htsjdk.samtools.seekablestream.SeekableStream; import htsjdk.samtools.seekablestream.SeekableStreamFactory; @@ -34,7 +35,7 @@ import java.io.*; import java.net.URL; -public class SamStreamsTest { +public class SamStreamsTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); @@ -121,4 +122,4 @@ public void sourceLikeBam( SeekableStreamFactory.getInstance().getStreamFor(new URL(resourceName)); Assert.assertEquals(SamStreams.sourceLikeBam(strm), expected); } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/SequenceNameTruncationAndValidationTest.java b/src/test/java/htsjdk/samtools/SequenceNameTruncationAndValidationTest.java index 2c3a95c6a..01999c481 100644 --- a/src/test/java/htsjdk/samtools/SequenceNameTruncationAndValidationTest.java +++ b/src/test/java/htsjdk/samtools/SequenceNameTruncationAndValidationTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.CloserUtil; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -36,7 +37,7 @@ * * @author alecw@broadinstitute.org */ -public class SequenceNameTruncationAndValidationTest { +public class SequenceNameTruncationAndValidationTest extends HtsjdkTest { private static File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools"); @Test(expectedExceptions = {SAMException.class}, dataProvider = "badSequenceNames") diff --git a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java index 77ac59f0e..f227332d2 100644 --- a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java +++ b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java @@ -24,6 +24,7 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import htsjdk.samtools.BamIndexValidator.IndexValidationStringency; import htsjdk.samtools.metrics.MetricBase; import htsjdk.samtools.metrics.MetricsFile; @@ -58,7 +59,7 @@ * * @author Doug Voet */ -public class ValidateSamFileTest { +public class ValidateSamFileTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools/ValidateSamFileTest"); private static final int TERMINATION_GZIP_BLOCK_SIZE = 28; private static final int RANDOM_NUMBER_TRUNC_BYTE = 128; diff --git a/src/test/java/htsjdk/samtools/cram/CRAIEntryTest.java b/src/test/java/htsjdk/samtools/cram/CRAIEntryTest.java index 6cf49344b..d43f2fc14 100644 --- a/src/test/java/htsjdk/samtools/cram/CRAIEntryTest.java +++ b/src/test/java/htsjdk/samtools/cram/CRAIEntryTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.structure.Container; import htsjdk.samtools.cram.structure.Slice; import org.testng.Assert; @@ -12,7 +13,7 @@ /** * Created by vadim on 25/08/2015. */ -public class CRAIEntryTest { +public class CRAIEntryTest extends HtsjdkTest { @Test public void testFromContainer() { diff --git a/src/test/java/htsjdk/samtools/cram/CRAIIndexTest.java b/src/test/java/htsjdk/samtools/cram/CRAIIndexTest.java index 7ebdb75e1..9e48d6b4e 100644 --- a/src/test/java/htsjdk/samtools/cram/CRAIIndexTest.java +++ b/src/test/java/htsjdk/samtools/cram/CRAIIndexTest.java @@ -1,23 +1,14 @@ package htsjdk.samtools.cram; -import htsjdk.samtools.BAMFileSpan; -import htsjdk.samtools.CRAMCRAIIndexer; -import htsjdk.samtools.DiskBasedBAMFileIndex; -import htsjdk.samtools.SAMFileHeader; -import htsjdk.samtools.SAMSequenceDictionary; -import htsjdk.samtools.SAMSequenceRecord; +import htsjdk.HtsjdkTest; +import htsjdk.samtools.*; import htsjdk.samtools.seekablestream.SeekableBufferedStream; import htsjdk.samtools.seekablestream.SeekableFileStream; import htsjdk.samtools.seekablestream.SeekableStream; import org.testng.Assert; import org.testng.annotations.Test; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; @@ -26,7 +17,7 @@ /** * Created by vadim on 25/08/2015. */ -public class CRAIIndexTest { +public class CRAIIndexTest extends HtsjdkTest { @Test public void testFind() throws IOException, CloneNotSupportedException { diff --git a/src/test/java/htsjdk/samtools/cram/LosslessRoundTripTest.java b/src/test/java/htsjdk/samtools/cram/LosslessRoundTripTest.java index 67cd4833c..1ae8e142a 100644 --- a/src/test/java/htsjdk/samtools/cram/LosslessRoundTripTest.java +++ b/src/test/java/htsjdk/samtools/cram/LosslessRoundTripTest.java @@ -1,31 +1,18 @@ package htsjdk.samtools.cram; -import htsjdk.samtools.CRAMFileReader; -import htsjdk.samtools.CRAMFileWriter; -import htsjdk.samtools.Cigar; -import htsjdk.samtools.CigarElement; -import htsjdk.samtools.CigarOperator; -import htsjdk.samtools.SAMFileHeader; -import htsjdk.samtools.SAMReadGroupRecord; -import htsjdk.samtools.SAMRecord; -import htsjdk.samtools.SAMRecordIterator; -import htsjdk.samtools.SAMSequenceRecord; -import htsjdk.samtools.ValidationStringency; +import htsjdk.HtsjdkTest; +import htsjdk.samtools.*; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.reference.InMemoryReferenceSequenceFile; import org.testng.Assert; import org.testng.annotations.Test; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; /** * Created by vadim on 19/02/2016. */ -public class LosslessRoundTripTest { +public class LosslessRoundTripTest extends HtsjdkTest { @Test public void test_MD_NM() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); diff --git a/src/test/java/htsjdk/samtools/cram/VersionTest.java b/src/test/java/htsjdk/samtools/cram/VersionTest.java index 0602eb376..be2851eb6 100644 --- a/src/test/java/htsjdk/samtools/cram/VersionTest.java +++ b/src/test/java/htsjdk/samtools/cram/VersionTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram; +import htsjdk.HtsjdkTest; import htsjdk.samtools.CRAMFileWriter; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecord; @@ -26,7 +27,7 @@ /** * Created by vadim on 18/02/2016. */ -public class VersionTest { +public class VersionTest extends HtsjdkTest { /** * The test purpose is to ensure that a CRAM written by {@link CRAMFileWriter} adheres to CRAM3 specs expectations: * 1. version 3.+, via both actual byte comparison and CramIO API diff --git a/src/test/java/htsjdk/samtools/cram/build/CompressionHeaderFactoryTest.java b/src/test/java/htsjdk/samtools/cram/build/CompressionHeaderFactoryTest.java index a3d91cdc7..8e39d9f76 100644 --- a/src/test/java/htsjdk/samtools/cram/build/CompressionHeaderFactoryTest.java +++ b/src/test/java/htsjdk/samtools/cram/build/CompressionHeaderFactoryTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.build; +import htsjdk.HtsjdkTest; import htsjdk.samtools.ValidationStringency; import htsjdk.samtools.cram.encoding.readfeatures.Substitution; import htsjdk.samtools.cram.structure.CompressionHeader; @@ -17,7 +18,7 @@ /** * Created by vadim on 07/01/2016. */ -public class CompressionHeaderFactoryTest { +public class CompressionHeaderFactoryTest extends HtsjdkTest { @Test public void testAllEncodingsPresent() { final CompressionHeader header = new CompressionHeaderFactory().build(new ArrayList<>(), new SubstitutionMatrix(new long[256][256]), true); diff --git a/src/test/java/htsjdk/samtools/cram/build/ContainerFactoryTest.java b/src/test/java/htsjdk/samtools/cram/build/ContainerFactoryTest.java index cb004a729..cf4f91e51 100644 --- a/src/test/java/htsjdk/samtools/cram/build/ContainerFactoryTest.java +++ b/src/test/java/htsjdk/samtools/cram/build/ContainerFactoryTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.build; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMSequenceRecord; @@ -19,7 +20,7 @@ /** * Created by vadim on 15/12/2015. */ -public class ContainerFactoryTest { +public class ContainerFactoryTest extends HtsjdkTest { @Test public void testUnmapped() throws IOException, IllegalAccessException { diff --git a/src/test/java/htsjdk/samtools/cram/build/ContainerParserTest.java b/src/test/java/htsjdk/samtools/cram/build/ContainerParserTest.java index fe25ce667..b16dc0f15 100644 --- a/src/test/java/htsjdk/samtools/cram/build/ContainerParserTest.java +++ b/src/test/java/htsjdk/samtools/cram/build/ContainerParserTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.build; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.ValidationStringency; @@ -24,7 +25,7 @@ /** * Created by vadim on 11/01/2016. */ -public class ContainerParserTest { +public class ContainerParserTest extends HtsjdkTest { @Test public void testEOF() throws IOException, IllegalAccessException { diff --git a/src/test/java/htsjdk/samtools/cram/build/CramIOTest.java b/src/test/java/htsjdk/samtools/cram/build/CramIOTest.java index 1035f242e..bab50dc44 100644 --- a/src/test/java/htsjdk/samtools/cram/build/CramIOTest.java +++ b/src/test/java/htsjdk/samtools/cram/build/CramIOTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.build; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMSequenceRecord; import htsjdk.samtools.cram.common.CramVersions; @@ -15,7 +16,7 @@ /** * Created by vadim on 25/08/2015. */ -public class CramIOTest { +public class CramIOTest extends HtsjdkTest { @Test public void testCheckHeaderAndEOF_v2() throws IOException { final String id = "testid"; diff --git a/src/test/java/htsjdk/samtools/cram/encoding/huffman/codec/HuffmanTest.java b/src/test/java/htsjdk/samtools/cram/encoding/huffman/codec/HuffmanTest.java index f2ca2f2b1..fd24c6b8e 100644 --- a/src/test/java/htsjdk/samtools/cram/encoding/huffman/codec/HuffmanTest.java +++ b/src/test/java/htsjdk/samtools/cram/encoding/huffman/codec/HuffmanTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.encoding.huffman.codec; +import htsjdk.HtsjdkTest; import htsjdk.samtools.cram.io.DefaultBitInputStream; import htsjdk.samtools.cram.io.DefaultBitOutputStream; import htsjdk.samtools.cram.structure.ReadTag; @@ -13,7 +14,7 @@ /** * Created by vadim on 22/04/2015. */ -public class HuffmanTest { +public class HuffmanTest extends HtsjdkTest { @Test public void testHuffmanIntHelper() throws IOException { int size = 1000000; diff --git a/src/test/java/htsjdk/samtools/cram/encoding/rans/RansTest.java b/src/test/java/htsjdk/samtools/cram/encoding/rans/RansTest.java index ca846863b..8e05a12f1 100644 --- a/src/test/java/htsjdk/samtools/cram/encoding/rans/RansTest.java +++ b/src/test/java/htsjdk/samtools/cram/encoding/rans/RansTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.encoding.rans; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -10,7 +11,7 @@ /** * Created by vadim on 22/04/2015. */ -public class RansTest { +public class RansTest extends HtsjdkTest { @Test public void testEmpty() { roundTrip(new byte[0]); diff --git a/src/test/java/htsjdk/samtools/cram/io/ITF8Test.java b/src/test/java/htsjdk/samtools/cram/io/ITF8Test.java index 5d95d2cc7..a206ad1f0 100644 --- a/src/test/java/htsjdk/samtools/cram/io/ITF8Test.java +++ b/src/test/java/htsjdk/samtools/cram/io/ITF8Test.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.io; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.Tuple; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -16,7 +17,7 @@ /** * Created by vadim on 03/02/2015. */ -public class ITF8Test { +public class ITF8Test extends HtsjdkTest { private ExposedByteArrayOutputStream testBAOS; private ByteArrayInputStream testBAIS; diff --git a/src/test/java/htsjdk/samtools/cram/io/LTF8Test.java b/src/test/java/htsjdk/samtools/cram/io/LTF8Test.java index 510379732..03d310dde 100644 --- a/src/test/java/htsjdk/samtools/cram/io/LTF8Test.java +++ b/src/test/java/htsjdk/samtools/cram/io/LTF8Test.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.io; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; @@ -14,7 +15,7 @@ /** * Created by vadim on 03/02/2015. */ -public class LTF8Test { +public class LTF8Test extends HtsjdkTest { private ExposedByteArrayOutputStream ltf8TestBAOS; private ByteArrayInputStream ltf8TestBAIS; diff --git a/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java b/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java index 34b4676d9..575485e82 100644 --- a/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java +++ b/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.cram.lossy; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SamInputResource; @@ -23,7 +24,7 @@ import static org.testng.Assert.*; -public class QualityScorePreservationTest { +public class QualityScorePreservationTest extends HtsjdkTest { @Test public void test1() { diff --git a/src/test/java/htsjdk/samtools/cram/ref/EnaRefServiceTest.java b/src/test/java/htsjdk/samtools/cram/ref/EnaRefServiceTest.java index 852a513b4..7f537843e 100644 --- a/src/test/java/htsjdk/samtools/cram/ref/EnaRefServiceTest.java +++ b/src/test/java/htsjdk/samtools/cram/ref/EnaRefServiceTest.java @@ -1,11 +1,12 @@ package htsjdk.samtools.cram.ref; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; import java.io.IOException; -public class EnaRefServiceTest { +public class EnaRefServiceTest extends HtsjdkTest { @Test public void test() throws IOException, EnaRefService.GaveUpException { diff --git a/src/test/java/htsjdk/samtools/cram/structure/CramCompressionRecordTest.java b/src/test/java/htsjdk/samtools/cram/structure/CramCompressionRecordTest.java index 03360bd6b..a455476fa 100644 --- a/src/test/java/htsjdk/samtools/cram/structure/CramCompressionRecordTest.java +++ b/src/test/java/htsjdk/samtools/cram/structure/CramCompressionRecordTest.java @@ -1,11 +1,8 @@ package htsjdk.samtools.cram.structure; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMRecord; -import htsjdk.samtools.cram.encoding.readfeatures.Deletion; -import htsjdk.samtools.cram.encoding.readfeatures.InsertBase; -import htsjdk.samtools.cram.encoding.readfeatures.Insertion; -import htsjdk.samtools.cram.encoding.readfeatures.ReadFeature; -import htsjdk.samtools.cram.encoding.readfeatures.SoftClip; +import htsjdk.samtools.cram.encoding.readfeatures.*; import org.testng.Assert; import org.testng.annotations.Test; @@ -14,7 +11,7 @@ /** * Created by vadim on 28/09/2015. */ -public class CramCompressionRecordTest { +public class CramCompressionRecordTest extends HtsjdkTest { @Test public void test_getAlignmentEnd() { CramCompressionRecord r = new CramCompressionRecord(); diff --git a/src/test/java/htsjdk/samtools/cram/structure/ReadTagTest.java b/src/test/java/htsjdk/samtools/cram/structure/ReadTagTest.java index 3ed0b4006..314fd2498 100644 --- a/src/test/java/htsjdk/samtools/cram/structure/ReadTagTest.java +++ b/src/test/java/htsjdk/samtools/cram/structure/ReadTagTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.cram.structure; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.ValidationStringency; @@ -31,14 +32,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; -public class ReadTagTest { +public class ReadTagTest extends HtsjdkTest { @Test public void test () { diff --git a/src/test/java/htsjdk/samtools/cram/structure/SliceTests.java b/src/test/java/htsjdk/samtools/cram/structure/SliceTests.java index c52dccba1..eeb34ee09 100644 --- a/src/test/java/htsjdk/samtools/cram/structure/SliceTests.java +++ b/src/test/java/htsjdk/samtools/cram/structure/SliceTests.java @@ -1,7 +1,7 @@ package htsjdk.samtools.cram.structure; +import htsjdk.HtsjdkTest; import htsjdk.samtools.CRAMFileReader; -import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.ValidationStringency; import htsjdk.samtools.cram.CRAMException; @@ -17,7 +17,7 @@ /** * Created by vadim on 07/12/2015. */ -public class SliceTests { +public class SliceTests extends HtsjdkTest { @Test public void testUnmappedValidateRef() { Slice slice = new Slice(); diff --git a/src/test/java/htsjdk/samtools/cram/structure/SubstitutionMatrixTest.java b/src/test/java/htsjdk/samtools/cram/structure/SubstitutionMatrixTest.java index 31e770832..625118923 100644 --- a/src/test/java/htsjdk/samtools/cram/structure/SubstitutionMatrixTest.java +++ b/src/test/java/htsjdk/samtools/cram/structure/SubstitutionMatrixTest.java @@ -1,17 +1,15 @@ package htsjdk.samtools.cram.structure; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.BeforeGroups; import org.testng.annotations.DataProvider; -import org.testng.annotations.Parameters; import org.testng.annotations.Test; -import java.util.Arrays; - /** * Created by Vadim on 12/03/2015. */ -public class SubstitutionMatrixTest { +public class SubstitutionMatrixTest extends HtsjdkTest { SubstitutionMatrix m; long[][] freqs; diff --git a/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java b/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java index 97a3d3c8d..5ace9ffc3 100644 --- a/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java +++ b/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java @@ -1,9 +1,10 @@ package htsjdk.samtools.fastq; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; -public final class FastqRecordTest { +public final class FastqRecordTest extends HtsjdkTest { @Test public void testBasic() { @@ -206,4 +207,4 @@ public void testNotEqualLengths() { new FastqRecord("header", seqLine1, "qualHeaderPrefix", qualLine1); //Note: this does not blow up now but it will once we enforce that seqLine and qualLine be the same length } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/fastq/FastqWriterTest.java b/src/test/java/htsjdk/samtools/fastq/FastqWriterTest.java index eba5c5b9f..22549e904 100644 --- a/src/test/java/htsjdk/samtools/fastq/FastqWriterTest.java +++ b/src/test/java/htsjdk/samtools/fastq/FastqWriterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.fastq; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -35,7 +36,7 @@ /** * test fastq */ -public class FastqWriterTest { +public class FastqWriterTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools/util/QualityEncodingDetectorTest"); @DataProvider(name = "fastqsource") diff --git a/src/test/java/htsjdk/samtools/filter/FailsVendorReadQualityFilterTest.java b/src/test/java/htsjdk/samtools/filter/FailsVendorReadQualityFilterTest.java index cb2cb0545..ed83f094b 100644 --- a/src/test/java/htsjdk/samtools/filter/FailsVendorReadQualityFilterTest.java +++ b/src/test/java/htsjdk/samtools/filter/FailsVendorReadQualityFilterTest.java @@ -23,13 +23,14 @@ */ package htsjdk.samtools.filter; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMRecordSetBuilder; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -public class FailsVendorReadQualityFilterTest { +public class FailsVendorReadQualityFilterTest extends HtsjdkTest { private final SAMRecordSetBuilder builder = new SAMRecordSetBuilder(); private final FailsVendorReadQualityFilter filter = new FailsVendorReadQualityFilter(); diff --git a/src/test/java/htsjdk/samtools/filter/InsertSizeFilterTest.java b/src/test/java/htsjdk/samtools/filter/InsertSizeFilterTest.java index fc4937da4..48d8edc15 100644 --- a/src/test/java/htsjdk/samtools/filter/InsertSizeFilterTest.java +++ b/src/test/java/htsjdk/samtools/filter/InsertSizeFilterTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.filter; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMRecordSetBuilder; import org.testng.Assert; @@ -7,7 +8,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -public class InsertSizeFilterTest { +public class InsertSizeFilterTest extends HtsjdkTest { private static final int READ_LENGTH = 20; private final SAMRecordSetBuilder builder = new SAMRecordSetBuilder(); diff --git a/src/test/java/htsjdk/samtools/filter/IntervalKeepPairFilterTest.java b/src/test/java/htsjdk/samtools/filter/IntervalKeepPairFilterTest.java index 3d30255f5..7d3c23e79 100644 --- a/src/test/java/htsjdk/samtools/filter/IntervalKeepPairFilterTest.java +++ b/src/test/java/htsjdk/samtools/filter/IntervalKeepPairFilterTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.filter; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMRecordSetBuilder; import htsjdk.samtools.util.CollectionUtil; import org.testng.Assert; @@ -11,7 +12,7 @@ import java.util.ArrayList; import java.util.stream.StreamSupport; -public class IntervalKeepPairFilterTest { +public class IntervalKeepPairFilterTest extends HtsjdkTest { private static final int READ_LENGTH = 151; private final SAMRecordSetBuilder builder = new SAMRecordSetBuilder(); diff --git a/src/test/java/htsjdk/samtools/filter/JavascriptSamRecordFilterTest.java b/src/test/java/htsjdk/samtools/filter/JavascriptSamRecordFilterTest.java index 78355760a..043f24d46 100644 --- a/src/test/java/htsjdk/samtools/filter/JavascriptSamRecordFilterTest.java +++ b/src/test/java/htsjdk/samtools/filter/JavascriptSamRecordFilterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.filter; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMRecordIterator; import htsjdk.samtools.SamReader; import htsjdk.samtools.SamReaderFactory; @@ -39,7 +40,7 @@ * @author Pierre Lindenbaum PhD Institut du Thorax - INSERM - Nantes - France */ -public class JavascriptSamRecordFilterTest { +public class JavascriptSamRecordFilterTest extends HtsjdkTest { final File testDir = new File("./src/test/resources/htsjdk/samtools"); @DataProvider diff --git a/src/test/java/htsjdk/samtools/filter/MappingQualityFilterTest.java b/src/test/java/htsjdk/samtools/filter/MappingQualityFilterTest.java index 2bffcd64a..9d9f7b819 100644 --- a/src/test/java/htsjdk/samtools/filter/MappingQualityFilterTest.java +++ b/src/test/java/htsjdk/samtools/filter/MappingQualityFilterTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.filter; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMRecordSetBuilder; import org.testng.Assert; @@ -7,7 +8,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -public class MappingQualityFilterTest { +public class MappingQualityFilterTest extends HtsjdkTest { private final SAMRecordSetBuilder builder = new SAMRecordSetBuilder(); @BeforeTest diff --git a/src/test/java/htsjdk/samtools/filter/OverclippedReadFilterTest.java b/src/test/java/htsjdk/samtools/filter/OverclippedReadFilterTest.java index bff84918c..e154e40ec 100644 --- a/src/test/java/htsjdk/samtools/filter/OverclippedReadFilterTest.java +++ b/src/test/java/htsjdk/samtools/filter/OverclippedReadFilterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.filter; +import htsjdk.HtsjdkTest; import htsjdk.samtools.Cigar; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMRecordSetBuilder; @@ -31,7 +32,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -public class OverclippedReadFilterTest { +public class OverclippedReadFilterTest extends HtsjdkTest { private final SAMRecordSetBuilder builder = new SAMRecordSetBuilder(); private final int unclippedBasesThreshold = 30; diff --git a/src/test/java/htsjdk/samtools/filter/SolexaNoiseFilterTest.java b/src/test/java/htsjdk/samtools/filter/SolexaNoiseFilterTest.java index 96fa324b9..5ea20d406 100644 --- a/src/test/java/htsjdk/samtools/filter/SolexaNoiseFilterTest.java +++ b/src/test/java/htsjdk/samtools/filter/SolexaNoiseFilterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.filter; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMRecordSetBuilder; import org.testng.Assert; @@ -32,7 +33,7 @@ /** * Basic test for the SolexaNoiseFilter */ -public class SolexaNoiseFilterTest { +public class SolexaNoiseFilterTest extends HtsjdkTest { private final SAMRecordSetBuilder builder = new SAMRecordSetBuilder(); private final SolexaNoiseFilter filter = new SolexaNoiseFilter(); diff --git a/src/test/java/htsjdk/samtools/filter/TagFilterTest.java b/src/test/java/htsjdk/samtools/filter/TagFilterTest.java index 6e0c70293..d885cbe9f 100644 --- a/src/test/java/htsjdk/samtools/filter/TagFilterTest.java +++ b/src/test/java/htsjdk/samtools/filter/TagFilterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.filter; +import htsjdk.HtsjdkTest; import htsjdk.samtools.ReservedTagConstants; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMRecordSetBuilder; @@ -36,7 +37,7 @@ /** * Tests for the TagFilter class */ -public class TagFilterTest { +public class TagFilterTest extends HtsjdkTest { private final SAMRecordSetBuilder builder = new SAMRecordSetBuilder(); @@ -74,4 +75,4 @@ public void testTagFilter(final String testName, final String tag, final List */ -public class AbstractLocusInfoTest { +public class AbstractLocusInfoTest extends HtsjdkTest { private final byte[] qualities = {30, 50, 50, 60, 60, 70, 70, 70, 80, 90, 30, 50, 50, 60, 60, 70, 70, 70, 80, 90}; private byte[] bases = {'A', 'C', 'G', 'T', 'A', 'C', 'G', 'T', 'T', 'C', 'A', 'C', 'G', 'T', 'A', 'C', 'G', 'T', 'T', 'C'}; private EdgingRecordAndOffset typedRecordAndOffset; diff --git a/src/test/java/htsjdk/samtools/util/AbstractLocusIteratorTestTemplate.java b/src/test/java/htsjdk/samtools/util/AbstractLocusIteratorTestTemplate.java index 0c08436e5..d1e2f0f2e 100644 --- a/src/test/java/htsjdk/samtools/util/AbstractLocusIteratorTestTemplate.java +++ b/src/test/java/htsjdk/samtools/util/AbstractLocusIteratorTestTemplate.java @@ -25,6 +25,7 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecordSetBuilder; import htsjdk.samtools.SAMSequenceDictionary; @@ -36,7 +37,7 @@ * @author Mariia_Zueva@epam.com, EPAM Systems, Inc. * */ -public abstract class AbstractLocusIteratorTestTemplate { +public abstract class AbstractLocusIteratorTestTemplate extends HtsjdkTest { /** Coverage for tests with the same reads */ final static int coverage = 2; @@ -65,4 +66,4 @@ static SAMRecordSetBuilder getRecordBuilder() { public abstract void testEmitUncoveredLoci(); public abstract void testSimpleGappedAlignment(); public abstract void testOverlappingGappedAlignmentsWithoutIndels(); -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/util/AbstractRecordAndOffsetTest.java b/src/test/java/htsjdk/samtools/util/AbstractRecordAndOffsetTest.java index 568c84c7c..8993e417a 100644 --- a/src/test/java/htsjdk/samtools/util/AbstractRecordAndOffsetTest.java +++ b/src/test/java/htsjdk/samtools/util/AbstractRecordAndOffsetTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecord; import org.testng.annotations.BeforeTest; @@ -36,7 +37,7 @@ * */ -public class AbstractRecordAndOffsetTest { +public class AbstractRecordAndOffsetTest extends HtsjdkTest { private final byte[] qualities = {30, 40, 50, 60, 70, 80 ,90, 70, 80, 90}; private byte[] bases = {'A', 'C', 'G', 'T', 'A', 'C', 'G', 'T', 'T', 'C'}; diff --git a/src/test/java/htsjdk/samtools/util/AsyncBlockCompressedInputStreamTest.java b/src/test/java/htsjdk/samtools/util/AsyncBlockCompressedInputStreamTest.java index 957a86942..a1f9881a0 100644 --- a/src/test/java/htsjdk/samtools/util/AsyncBlockCompressedInputStreamTest.java +++ b/src/test/java/htsjdk/samtools/util/AsyncBlockCompressedInputStreamTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -32,7 +33,7 @@ import java.util.ArrayList; import java.util.List; -public class AsyncBlockCompressedInputStreamTest { +public class AsyncBlockCompressedInputStreamTest extends HtsjdkTest { private final File BAM_FILE = new File("src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam"); @Test public void testAsync() throws Exception { diff --git a/src/test/java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java b/src/test/java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java index 817c60e54..ce4d44599 100644 --- a/src/test/java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java +++ b/src/test/java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java @@ -23,10 +23,11 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; -public class AsyncBufferedIteratorTest { +public class AsyncBufferedIteratorTest extends HtsjdkTest { private static class TestCloseableIterator implements CloseableIterator { private int[] results; private volatile int offset = 0; diff --git a/src/test/java/htsjdk/samtools/util/AsyncWriterTest.java b/src/test/java/htsjdk/samtools/util/AsyncWriterTest.java index c807ceffb..1d2c3043f 100644 --- a/src/test/java/htsjdk/samtools/util/AsyncWriterTest.java +++ b/src/test/java/htsjdk/samtools/util/AsyncWriterTest.java @@ -23,10 +23,11 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; -public class AsyncWriterTest { +public class AsyncWriterTest extends HtsjdkTest { private static class MyException extends RuntimeException { final Integer item; public MyException(Integer item) { diff --git a/src/test/java/htsjdk/samtools/util/BinaryCodecTest.java b/src/test/java/htsjdk/samtools/util/BinaryCodecTest.java index 91e114729..b59c9527d 100644 --- a/src/test/java/htsjdk/samtools/util/BinaryCodecTest.java +++ b/src/test/java/htsjdk/samtools/util/BinaryCodecTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -44,7 +45,7 @@ * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. */ -public class BinaryCodecTest { +public class BinaryCodecTest extends HtsjdkTest { public final static String TEST_BASENAME = "htsjdk-BinaryCodecTest"; @Test diff --git a/src/test/java/htsjdk/samtools/util/BlockCompressedFilePointerUtilTest.java b/src/test/java/htsjdk/samtools/util/BlockCompressedFilePointerUtilTest.java index 850b4bf62..38c3ec374 100644 --- a/src/test/java/htsjdk/samtools/util/BlockCompressedFilePointerUtilTest.java +++ b/src/test/java/htsjdk/samtools/util/BlockCompressedFilePointerUtilTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -31,8 +32,7 @@ import java.util.List; -public class BlockCompressedFilePointerUtilTest -{ +public class BlockCompressedFilePointerUtilTest extends HtsjdkTest { @Test public void basicTest() { diff --git a/src/test/java/htsjdk/samtools/util/BlockCompressedInputStreamTest.java b/src/test/java/htsjdk/samtools/util/BlockCompressedInputStreamTest.java index f4ce2cf52..4c9d532d0 100644 --- a/src/test/java/htsjdk/samtools/util/BlockCompressedInputStreamTest.java +++ b/src/test/java/htsjdk/samtools/util/BlockCompressedInputStreamTest.java @@ -1,24 +1,21 @@ package htsjdk.samtools.util; -import java.io.*; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.Inflater; - -import java.io.File; -import java.io.FileInputStream; -import java.nio.file.Files; -import java.util.Arrays; - +import htsjdk.HtsjdkTest; +import htsjdk.samtools.seekablestream.SeekableFileStream; import htsjdk.samtools.util.zip.InflaterFactory; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import htsjdk.samtools.seekablestream.SeekableFileStream; +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.zip.Inflater; -public class BlockCompressedInputStreamTest { +public class BlockCompressedInputStreamTest extends HtsjdkTest { // random data pulled from /dev/random then compressed using bgzip from tabix private static final File BLOCK_UNCOMPRESSED = new File("src/test/resources/htsjdk/samtools/util/random.bin"); private static final File BLOCK_COMPRESSED = new File("src/test/resources/htsjdk/samtools/util/random.bin.gz"); diff --git a/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java b/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java index 69d72565f..a678c8dca 100644 --- a/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java +++ b/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.FileTruncatedException; import htsjdk.samtools.util.zip.DeflaterFactory; import org.testng.Assert; @@ -39,7 +40,7 @@ import java.util.Random; import java.util.zip.Deflater; -public class BlockCompressedOutputStreamTest { +public class BlockCompressedOutputStreamTest extends HtsjdkTest { private static final String HTSJDK_TRIBBLE_RESOURCES = "src/test/resources/htsjdk/tribble/"; diff --git a/src/test/java/htsjdk/samtools/util/BlockCompressedTerminatorTest.java b/src/test/java/htsjdk/samtools/util/BlockCompressedTerminatorTest.java index 5b5837229..d9d20ccef 100644 --- a/src/test/java/htsjdk/samtools/util/BlockCompressedTerminatorTest.java +++ b/src/test/java/htsjdk/samtools/util/BlockCompressedTerminatorTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -31,7 +32,7 @@ /** * @author alecw@broadinstitute.org */ -public class BlockCompressedTerminatorTest { +public class BlockCompressedTerminatorTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools/util"); @Test diff --git a/src/test/java/htsjdk/samtools/util/CigarUtilTest.java b/src/test/java/htsjdk/samtools/util/CigarUtilTest.java index 0aca3951a..6fe7b7199 100644 --- a/src/test/java/htsjdk/samtools/util/CigarUtilTest.java +++ b/src/test/java/htsjdk/samtools/util/CigarUtilTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.Cigar; import htsjdk.samtools.CigarElement; import htsjdk.samtools.TextCigarCodec; @@ -40,7 +41,7 @@ * * @author Martha Borkan mborkan@broadinstitute.org */ -public class CigarUtilTest { +public class CigarUtilTest extends HtsjdkTest { @Test(dataProvider="clipData") public void basicTest(final String testName, final int start, final String inputCigar, final boolean negativeStrand, diff --git a/src/test/java/htsjdk/samtools/util/CloseableIteratorTest.java b/src/test/java/htsjdk/samtools/util/CloseableIteratorTest.java index b96d1f67c..102b82436 100644 --- a/src/test/java/htsjdk/samtools/util/CloseableIteratorTest.java +++ b/src/test/java/htsjdk/samtools/util/CloseableIteratorTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -8,7 +9,7 @@ import java.util.List; import java.util.stream.Collectors; -public class CloseableIteratorTest { +public class CloseableIteratorTest extends HtsjdkTest { @Test public void testToList() { final List expected = Arrays.asList(1,2,3,4,5); diff --git a/src/test/java/htsjdk/samtools/util/CodeUtilTest.java b/src/test/java/htsjdk/samtools/util/CodeUtilTest.java index e8b9957d2..c4978c196 100644 --- a/src/test/java/htsjdk/samtools/util/CodeUtilTest.java +++ b/src/test/java/htsjdk/samtools/util/CodeUtilTest.java @@ -1,9 +1,10 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; -public class CodeUtilTest { +public class CodeUtilTest extends HtsjdkTest { @Test public void getOrElseTest() { diff --git a/src/test/java/htsjdk/samtools/util/ComparableTupleTest.java b/src/test/java/htsjdk/samtools/util/ComparableTupleTest.java index 7e8b082a5..708058d70 100644 --- a/src/test/java/htsjdk/samtools/util/ComparableTupleTest.java +++ b/src/test/java/htsjdk/samtools/util/ComparableTupleTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.variant.variantcontext.Allele; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -8,7 +9,7 @@ /** * Created by farjoun on 1/28/16. */ -public class ComparableTupleTest { +public class ComparableTupleTest extends HtsjdkTest { private enum Tenum { Hi, diff --git a/src/test/java/htsjdk/samtools/util/CoordSpanInputSteamTest.java b/src/test/java/htsjdk/samtools/util/CoordSpanInputSteamTest.java index 1b9088220..07de15873 100644 --- a/src/test/java/htsjdk/samtools/util/CoordSpanInputSteamTest.java +++ b/src/test/java/htsjdk/samtools/util/CoordSpanInputSteamTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.seekablestream.ByteArraySeekableStream; import org.testng.Assert; import org.testng.annotations.Test; @@ -15,7 +16,7 @@ /** * Created by vadim on 25/03/2015. */ -public class CoordSpanInputSteamTest { +public class CoordSpanInputSteamTest extends HtsjdkTest { @Test public void test_first_3_bytes() throws IOException { diff --git a/src/test/java/htsjdk/samtools/util/DateParserTest.java b/src/test/java/htsjdk/samtools/util/DateParserTest.java index 04cfa7819..11ab2a6f8 100644 --- a/src/test/java/htsjdk/samtools/util/DateParserTest.java +++ b/src/test/java/htsjdk/samtools/util/DateParserTest.java @@ -72,6 +72,7 @@ This W3C work (including software, documents, or other related items) is package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -88,7 +89,7 @@ This W3C work (including software, documents, or other related items) is * @author bmahe@w3.org */ -public class DateParserTest { +public class DateParserTest extends HtsjdkTest { private static void test(final String isodate) { Date date = DateParser.parse(isodate); @@ -147,4 +148,4 @@ public static void assertDatesAreClose(final Date lhs, final Date rhs) { Assert.assertEquals(lhs.getSeconds(), rhs.getSeconds()); Assert.assertEquals(lhs.getTimezoneOffset(), rhs.getTimezoneOffset()); } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/util/EdgingRecordAndOffsetTest.java b/src/test/java/htsjdk/samtools/util/EdgingRecordAndOffsetTest.java index a4f6478b4..eeca090d7 100644 --- a/src/test/java/htsjdk/samtools/util/EdgingRecordAndOffsetTest.java +++ b/src/test/java/htsjdk/samtools/util/EdgingRecordAndOffsetTest.java @@ -24,6 +24,7 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMRecord; import org.testng.annotations.BeforeTest; @@ -39,7 +40,7 @@ * */ -public class EdgingRecordAndOffsetTest { +public class EdgingRecordAndOffsetTest extends HtsjdkTest { private final byte[] qualities = {30, 50, 50, 60, 60, 70 ,70, 70, 80, 90}; private final byte[] bases = {'A', 'C', 'G', 'T', 'A', 'C', 'G', 'T', 'T', 'C'}; private SAMRecord record; diff --git a/src/test/java/htsjdk/samtools/util/HistogramTest.java b/src/test/java/htsjdk/samtools/util/HistogramTest.java index 62b1441ac..ef4446958 100644 --- a/src/test/java/htsjdk/samtools/util/HistogramTest.java +++ b/src/test/java/htsjdk/samtools/util/HistogramTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -12,7 +13,7 @@ import static java.lang.Math.abs; import static java.lang.StrictMath.pow; -public class HistogramTest { +public class HistogramTest extends HtsjdkTest { @Test(dataProvider = "histogramData") public void testHistogramFunctions(final int[] values, final double mean, final double stdev, final Integer trimByWidth) { diff --git a/src/test/java/htsjdk/samtools/util/IntervalListTest.java b/src/test/java/htsjdk/samtools/util/IntervalListTest.java index 3d919e881..613afde45 100644 --- a/src/test/java/htsjdk/samtools/util/IntervalListTest.java +++ b/src/test/java/htsjdk/samtools/util/IntervalListTest.java @@ -24,6 +24,7 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMSequenceRecord; import htsjdk.variant.vcf.VCFFileReader; @@ -45,7 +46,7 @@ /** * Tests the IntervalList class */ -public class IntervalListTest { +public class IntervalListTest extends HtsjdkTest { final SAMFileHeader fileHeader; final IntervalList list1, list2, list3; diff --git a/src/test/java/htsjdk/samtools/util/IntervalTreeMapTest.java b/src/test/java/htsjdk/samtools/util/IntervalTreeMapTest.java index 533f9652e..5e975f917 100644 --- a/src/test/java/htsjdk/samtools/util/IntervalTreeMapTest.java +++ b/src/test/java/htsjdk/samtools/util/IntervalTreeMapTest.java @@ -23,12 +23,13 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; import java.util.Iterator; -public class IntervalTreeMapTest { +public class IntervalTreeMapTest extends HtsjdkTest { @Test public void testBasic() { IntervalTreeMap m=new IntervalTreeMap(); diff --git a/src/test/java/htsjdk/samtools/util/IntervalTreeTest.java b/src/test/java/htsjdk/samtools/util/IntervalTreeTest.java index 34cb3c5be..dcd225ec0 100644 --- a/src/test/java/htsjdk/samtools/util/IntervalTreeTest.java +++ b/src/test/java/htsjdk/samtools/util/IntervalTreeTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -36,7 +37,7 @@ * @author alecw@broadinstitute.org */ @Test(singleThreaded=true) // to assure that the common resources aren't clobbered -public class IntervalTreeTest { +public class IntervalTreeTest extends HtsjdkTest { @Test public void testNoMatches() { diff --git a/src/test/java/htsjdk/samtools/util/IoUtilTest.java b/src/test/java/htsjdk/samtools/util/IoUtilTest.java index 0e4cd7a1c..645d20d42 100644 --- a/src/test/java/htsjdk/samtools/util/IoUtilTest.java +++ b/src/test/java/htsjdk/samtools/util/IoUtilTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; @@ -41,7 +42,7 @@ import java.util.Arrays; import java.util.List; -public class IoUtilTest { +public class IoUtilTest extends HtsjdkTest { private static final File SLURP_TEST_FILE = new File("src/test/resources/htsjdk/samtools/io/slurptest.txt"); private static final File EMPTY_FILE = new File("src/test/resources/htsjdk/samtools/io/empty.txt"); diff --git a/src/test/java/htsjdk/samtools/util/Iso8601DateTest.java b/src/test/java/htsjdk/samtools/util/Iso8601DateTest.java index ce0ae08c1..93b9d6544 100644 --- a/src/test/java/htsjdk/samtools/util/Iso8601DateTest.java +++ b/src/test/java/htsjdk/samtools/util/Iso8601DateTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -31,7 +32,7 @@ /** * @author alecw@broadinstitute.org */ -public class Iso8601DateTest { +public class Iso8601DateTest extends HtsjdkTest { @Test public void testBasic() { final String dateStr = "2008-12-15"; diff --git a/src/test/java/htsjdk/samtools/util/IupacTest.java b/src/test/java/htsjdk/samtools/util/IupacTest.java index 64b78c003..86b0a410e 100644 --- a/src/test/java/htsjdk/samtools/util/IupacTest.java +++ b/src/test/java/htsjdk/samtools/util/IupacTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.BamFileIoUtils; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SAMFileWriter; @@ -38,7 +39,7 @@ import java.io.File; import java.util.Arrays; -public class IupacTest { +public class IupacTest extends HtsjdkTest { @Test(dataProvider = "basicDataProvider") public void basic(final String tempFileExtension) throws Exception { final File outputFile = File.createTempFile("iupacTest.", tempFileExtension); diff --git a/src/test/java/htsjdk/samtools/util/MergingIteratorTest.java b/src/test/java/htsjdk/samtools/util/MergingIteratorTest.java index d36bb6d3b..e5964acf7 100644 --- a/src/test/java/htsjdk/samtools/util/MergingIteratorTest.java +++ b/src/test/java/htsjdk/samtools/util/MergingIteratorTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -34,7 +35,7 @@ import java.util.LinkedList; import java.util.Queue; -public class MergingIteratorTest { +public class MergingIteratorTest extends HtsjdkTest { private static class QueueBackedIterator implements CloseableIterator { diff --git a/src/test/java/htsjdk/samtools/util/OverlapDetectorTest.java b/src/test/java/htsjdk/samtools/util/OverlapDetectorTest.java index ecde96560..d8adf2e2d 100644 --- a/src/test/java/htsjdk/samtools/util/OverlapDetectorTest.java +++ b/src/test/java/htsjdk/samtools/util/OverlapDetectorTest.java @@ -1,12 +1,13 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.*; -public class OverlapDetectorTest { +public class OverlapDetectorTest extends HtsjdkTest { @DataProvider(name="intervalsMultipleContigs") public Object[][] intervalsMultipleContigs(){ diff --git a/src/test/java/htsjdk/samtools/util/PositionalOutputStreamTest.java b/src/test/java/htsjdk/samtools/util/PositionalOutputStreamTest.java index af2acf59e..939c74858 100644 --- a/src/test/java/htsjdk/samtools/util/PositionalOutputStreamTest.java +++ b/src/test/java/htsjdk/samtools/util/PositionalOutputStreamTest.java @@ -24,6 +24,7 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -33,7 +34,7 @@ /** * @author Daniel Gomez-Sanchez (magicDGS) */ -public class PositionalOutputStreamTest { +public class PositionalOutputStreamTest extends HtsjdkTest { @Test public void basicPositionTest() throws Exception { @@ -59,4 +60,4 @@ public void write(int b) throws IOException {} Assert.assertEquals(wrapped.getPosition(), position); } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/samtools/util/QualityEncodingDetectorTest.java b/src/test/java/htsjdk/samtools/util/QualityEncodingDetectorTest.java index 9e014d7b7..071312d9b 100644 --- a/src/test/java/htsjdk/samtools/util/QualityEncodingDetectorTest.java +++ b/src/test/java/htsjdk/samtools/util/QualityEncodingDetectorTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMException; import htsjdk.samtools.SAMRecordSetBuilder; import htsjdk.samtools.SamReader; @@ -13,7 +14,7 @@ import java.util.Arrays; import java.util.List; -public class QualityEncodingDetectorTest { +public class QualityEncodingDetectorTest extends HtsjdkTest { private static class Testcase { private final File f; diff --git a/src/test/java/htsjdk/samtools/util/RelativeIso8601DateTest.java b/src/test/java/htsjdk/samtools/util/RelativeIso8601DateTest.java index e4e9ef993..0e0c9b265 100644 --- a/src/test/java/htsjdk/samtools/util/RelativeIso8601DateTest.java +++ b/src/test/java/htsjdk/samtools/util/RelativeIso8601DateTest.java @@ -1,5 +1,6 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; @@ -9,7 +10,7 @@ /** @author mccowan */ -public class RelativeIso8601DateTest { +public class RelativeIso8601DateTest extends HtsjdkTest { // 1 second resolution is ISO date private final static double DELTA_FOR_TIME = 1000; diff --git a/src/test/java/htsjdk/samtools/util/SequenceUtilTest.java b/src/test/java/htsjdk/samtools/util/SequenceUtilTest.java index 7c96b742d..e57b8fd08 100644 --- a/src/test/java/htsjdk/samtools/util/SequenceUtilTest.java +++ b/src/test/java/htsjdk/samtools/util/SequenceUtilTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.*; import htsjdk.samtools.reference.ReferenceSequenceFile; import htsjdk.samtools.reference.ReferenceSequenceFileFactory; @@ -36,7 +37,7 @@ /** * @author alecw@broadinstitute.org */ -public class SequenceUtilTest { +public class SequenceUtilTest extends HtsjdkTest { private static final String HEADER = "@HD\tVN:1.0\tSO:unsorted\n"; private static final String SEQUENCE_NAME= "@SQ\tSN:phix174.seq\tLN:5386\tUR:/seq/references/PhiX174/v0/PhiX174.fasta\tAS:PhiX174\tM5:3332ed720ac7eaa9b3655c06f6b9e196"; diff --git a/src/test/java/htsjdk/samtools/util/SolexaQualityConverterTest.java b/src/test/java/htsjdk/samtools/util/SolexaQualityConverterTest.java index 09cc82902..1e4e1464a 100644 --- a/src/test/java/htsjdk/samtools/util/SolexaQualityConverterTest.java +++ b/src/test/java/htsjdk/samtools/util/SolexaQualityConverterTest.java @@ -1,12 +1,13 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.Arrays; -public class SolexaQualityConverterTest { +public class SolexaQualityConverterTest extends HtsjdkTest { //declared as a staic variable because we reuse it in IlluminaUtilTest public static Object[][] SOLEXA_QUALS_TO_PHRED_SCORE = new Object[][] { new Object[]{new byte[]{}, new byte[]{}}, diff --git a/src/test/java/htsjdk/samtools/util/SortingCollectionTest.java b/src/test/java/htsjdk/samtools/util/SortingCollectionTest.java index dc9e063cd..29f012084 100644 --- a/src/test/java/htsjdk/samtools/util/SortingCollectionTest.java +++ b/src/test/java/htsjdk/samtools/util/SortingCollectionTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.AfterTest; @@ -41,7 +42,7 @@ import java.util.Iterator; import java.util.Random; -public class SortingCollectionTest { +public class SortingCollectionTest extends HtsjdkTest { // Create a separate directory for files so it is possible to confirm that the directory is emptied protected File tmpDir() { return new File(System.getProperty("java.io.tmpdir") + "/" + System.getProperty("user.name"), getClass().getSimpleName()); diff --git a/src/test/java/htsjdk/samtools/util/SortingLongCollectionTest.java b/src/test/java/htsjdk/samtools/util/SortingLongCollectionTest.java index 4817ef5b1..bcfa77e9c 100644 --- a/src/test/java/htsjdk/samtools/util/SortingLongCollectionTest.java +++ b/src/test/java/htsjdk/samtools/util/SortingLongCollectionTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; @@ -36,7 +37,7 @@ /** * @author alecw@broadinstitute.org */ -public class SortingLongCollectionTest { +public class SortingLongCollectionTest extends HtsjdkTest { // Create a separate directory for files so it is possible to confirm that the directory is emptied private final File tmpDir = new File(System.getProperty("java.io.tmpdir") + "/" + System.getProperty("user.name"), "SortingCollectionTest"); diff --git a/src/test/java/htsjdk/samtools/util/StringLineReaderTest.java b/src/test/java/htsjdk/samtools/util/StringLineReaderTest.java index 9919f891b..f90565024 100644 --- a/src/test/java/htsjdk/samtools/util/StringLineReaderTest.java +++ b/src/test/java/htsjdk/samtools/util/StringLineReaderTest.java @@ -23,10 +23,11 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; -public class StringLineReaderTest { +public class StringLineReaderTest extends HtsjdkTest { private static final String[] TERMINATORS = {"\r", "\n", "\r\n"}; private static final boolean[] LAST_LINE_TERMINATED = {false, true}; diff --git a/src/test/java/htsjdk/samtools/util/StringUtilTest.java b/src/test/java/htsjdk/samtools/util/StringUtilTest.java deleted file mode 100644 index dbb2a0709..000000000 --- a/src/test/java/htsjdk/samtools/util/StringUtilTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2009 The Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package htsjdk.samtools.util; - -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -/** - * @author alecw@broadinstitute.org - */ -public class StringUtilTest { - @Test(dataProvider = "provider") - public void testSplit(final String input, final String[] expectedResult, final boolean concatenateExcess) { - String[] ret = new String[expectedResult.length]; - int tokensExpected; - for (tokensExpected = 0; tokensExpected < expectedResult.length && expectedResult[tokensExpected] != null; - ++tokensExpected) { - } - final int tokensFound; - if (concatenateExcess) { - tokensFound = StringUtil.splitConcatenateExcessTokens(input, ret, ':'); - } else { - tokensFound = StringUtil.split(input, ret, ':'); - } - Assert.assertEquals(tokensFound, tokensExpected); - Assert.assertEquals(ret, expectedResult); - } - - @DataProvider(name="provider") - public Object[][] splitScenarios() { - return new Object[][] { - {"A:BB:C", new String[]{"A", "BB", "C"}, false}, - {"A:BB:C", new String[]{"A", "BB", "C"}, true}, - {"A:BB", new String[]{"A", "BB", null}, false}, - {"A:BB", new String[]{"A", "BB", null}, true}, - {"A:BB:", new String[]{"A", "BB", null}, false}, - {"A:BB:", new String[]{"A", "BB", null}, true}, - {"A:BB:C:DDD", new String[]{"A", "BB", "C"}, false}, - {"A:BB:C:DDD", new String[]{"A", "BB", "C:DDD"}, true}, - {"A:", new String[]{"A", null, null}, false}, - {"A:", new String[]{"A", null, null}, true}, - {"A", new String[]{"A", null, null}, false}, - {"A", new String[]{"A", null, null}, true}, - {"A:BB:C", new String[]{"A", "BB", "C"}, false}, - {"A:BB:C:", new String[]{"A", "BB", "C:"}, true}, - }; - } - - @DataProvider(name="withinHammingDistanceProvider") - public Object[][] isWithinHammingDistanceProvider() { - return new Object[][] { - {"ATAC", "GCAT", 3, true}, - {"ATAC", "GCAT", 2, false}, - {"ATAC", "GCAT", 1, false}, - {"ATAC", "GCAT", 0, false} - }; - } - - @Test(dataProvider = "withinHammingDistanceProvider") - public void testIsWithinHammingDistance(final String s1, final String s2, final int maxHammingDistance, final boolean expectedResult) { - Assert.assertEquals(StringUtil.isWithinHammingDistance(s1, s2, maxHammingDistance), expectedResult); - } - - @DataProvider(name="withinHammingDistanceExceptionProvider") - public Object[][] isWithinHammingDistanceException() { - return new Object[][] { - {"ATAC", "GCT" , 3}, - {"ATAC", "AT" , 2}, - {"ATAC", "T" , 1}, - {"" , "GCAT", 0} - }; - } - - @Test(dataProvider = "withinHammingDistanceExceptionProvider", expectedExceptions = IllegalArgumentException.class) - public void testIsWithinHammingDistanceExceptions(final String s1, final String s2, final int maxHammingDistance) { - StringUtil.isWithinHammingDistance(s1, s2, maxHammingDistance); - } - - @Test(dataProvider = "withinHammingDistanceExceptionProvider", expectedExceptions = IllegalArgumentException.class) - public void testHammingDistanceExceptions(final String s1, final String s2, final int maxHammingDistance) { - StringUtil.hammingDistance(s1, s2); - } - - @DataProvider(name="hammingDistanceProvider") - public Object[][] hammingDistance() { - return new Object[][] { - {"ATAC" , "GCAT" , 3}, - {"ATAGC", "ATAGC", 0}, - {"ATAC" , "atac" , 4}, // Hamming distance is case sensitive. - {"" , "" , 0}, // Two empty strings should have Hamming distance of 0. - {"nAGTN", "nAGTN", 0} // Ensure that matching Ns are not counted as mismatches. - }; - } - - @Test(dataProvider = "hammingDistanceProvider") - public void testHammingDistance(final String s1, final String s2, final int expectedResult) { - Assert.assertEquals(StringUtil.hammingDistance(s1, s2), expectedResult); - } - -} diff --git a/src/test/java/htsjdk/samtools/util/TrimmingUtilTest.java b/src/test/java/htsjdk/samtools/util/TrimmingUtilTest.java index 12cffc671..811083976 100644 --- a/src/test/java/htsjdk/samtools/util/TrimmingUtilTest.java +++ b/src/test/java/htsjdk/samtools/util/TrimmingUtilTest.java @@ -23,13 +23,14 @@ */ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; /** * Tests for a simple phred-style quality trimming algorithm. */ -public class TrimmingUtilTest { +public class TrimmingUtilTest extends HtsjdkTest { @Test public void testEasyCases() { Assert.assertEquals(TrimmingUtil.findQualityTrimPoint(byteArray(30,30,30,30,30, 2, 2, 2, 2, 2), 15), 5); diff --git a/src/test/java/htsjdk/samtools/util/TupleTest.java b/src/test/java/htsjdk/samtools/util/TupleTest.java index bed4550f1..431466ddf 100644 --- a/src/test/java/htsjdk/samtools/util/TupleTest.java +++ b/src/test/java/htsjdk/samtools/util/TupleTest.java @@ -1,12 +1,13 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; /** * Created by farjoun on 1/29/16. */ -public class TupleTest { +public class TupleTest extends HtsjdkTest { @Test public void testEquals() throws Exception { @@ -59,4 +60,4 @@ public void testToString() throws Exception { Assert.assertEquals(new Tuple<>(null, null).toString(), "[null, null]"); } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/tribble/AbstractFeatureReaderTest.java b/src/test/java/htsjdk/tribble/AbstractFeatureReaderTest.java index da0f84301..947d319fe 100644 --- a/src/test/java/htsjdk/tribble/AbstractFeatureReaderTest.java +++ b/src/test/java/htsjdk/tribble/AbstractFeatureReaderTest.java @@ -2,6 +2,7 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; +import htsjdk.HtsjdkTest; import htsjdk.samtools.FileTruncatedException; import htsjdk.samtools.util.TestUtil; import htsjdk.tribble.bed.BEDCodec; @@ -29,7 +30,7 @@ * @author jacob * @date 2013-Apr-10 */ -public class AbstractFeatureReaderTest { +public class AbstractFeatureReaderTest extends HtsjdkTest { final static String HTTP_INDEXED_VCF_PATH = TestUtil.BASE_URL_FOR_HTTP_TESTS + "ex2.vcf"; final static String LOCAL_MIRROR_HTTP_INDEXED_VCF_PATH = VariantBaseTest.variantTestDataRoot + "ex2.vcf"; diff --git a/src/test/java/htsjdk/tribble/BinaryFeaturesTest.java b/src/test/java/htsjdk/tribble/BinaryFeaturesTest.java index eff8939d8..eac19742a 100644 --- a/src/test/java/htsjdk/tribble/BinaryFeaturesTest.java +++ b/src/test/java/htsjdk/tribble/BinaryFeaturesTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble; +import htsjdk.HtsjdkTest; import htsjdk.tribble.bed.BEDCodec; import htsjdk.tribble.example.ExampleBinaryCodec; import htsjdk.tribble.readers.LineIterator; @@ -13,7 +14,7 @@ import java.util.List; -public class BinaryFeaturesTest { +public class BinaryFeaturesTest extends HtsjdkTest { @DataProvider(name = "BinaryFeatureSources") public Object[][] createData1() { return new Object[][] { diff --git a/src/test/java/htsjdk/tribble/FeatureReaderTest.java b/src/test/java/htsjdk/tribble/FeatureReaderTest.java index d62693c19..f43b5b15d 100644 --- a/src/test/java/htsjdk/tribble/FeatureReaderTest.java +++ b/src/test/java/htsjdk/tribble/FeatureReaderTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble; +import htsjdk.HtsjdkTest; import htsjdk.samtools.seekablestream.SeekableFileStream; import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.LocationAware; @@ -22,7 +23,7 @@ import java.util.List; -public class FeatureReaderTest { +public class FeatureReaderTest extends HtsjdkTest { private final static File asciiBedFile = new File(TestUtils.DATA_DIR + "test.bed"); private File binaryBedFile; private final static File tabixBedFile = new File(TestUtils.DATA_DIR + "test.tabix.bed.gz"); diff --git a/src/test/java/htsjdk/tribble/TribbleIndexFeatureReaderTest.java b/src/test/java/htsjdk/tribble/TribbleIndexFeatureReaderTest.java index 0223d41cd..37a5295dc 100644 --- a/src/test/java/htsjdk/tribble/TribbleIndexFeatureReaderTest.java +++ b/src/test/java/htsjdk/tribble/TribbleIndexFeatureReaderTest.java @@ -1,7 +1,7 @@ package htsjdk.tribble; +import htsjdk.HtsjdkTest; import htsjdk.tribble.readers.LineIterator; -import htsjdk.tribble.TestUtils; import htsjdk.variant.variantcontext.VariantContext; import htsjdk.variant.vcf.VCFCodec; import org.testng.Assert; @@ -9,12 +9,9 @@ import org.testng.annotations.Test; import java.io.IOException; -import java.net.URISyntaxException; -import static org.testng.Assert.assertEquals; - -public class TribbleIndexFeatureReaderTest { +public class TribbleIndexFeatureReaderTest extends HtsjdkTest { @DataProvider(name = "featureFileStrings") public Object[][] createFeatureFileStrings() { diff --git a/src/test/java/htsjdk/tribble/TribbleTest.java b/src/test/java/htsjdk/tribble/TribbleTest.java index e8366c4b0..3874c7f71 100644 --- a/src/test/java/htsjdk/tribble/TribbleTest.java +++ b/src/test/java/htsjdk/tribble/TribbleTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble; +import htsjdk.HtsjdkTest; import htsjdk.tribble.util.TabixUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -7,7 +8,7 @@ import java.io.File; -public class TribbleTest { +public class TribbleTest extends HtsjdkTest { @Test public void testStandardIndex() { diff --git a/src/test/java/htsjdk/tribble/bed/BEDCodecTest.java b/src/test/java/htsjdk/tribble/bed/BEDCodecTest.java index dbf23a0e5..cc0255b62 100644 --- a/src/test/java/htsjdk/tribble/bed/BEDCodecTest.java +++ b/src/test/java/htsjdk/tribble/bed/BEDCodecTest.java @@ -24,6 +24,7 @@ package htsjdk.tribble.bed; +import htsjdk.HtsjdkTest; import htsjdk.tribble.AbstractFeatureReader; import htsjdk.tribble.Feature; import htsjdk.tribble.TestUtils; @@ -43,7 +44,7 @@ import java.io.IOException; import java.util.List; -public class BEDCodecTest { +public class BEDCodecTest extends HtsjdkTest { @Test public void testSimpleDecode() { diff --git a/src/test/java/htsjdk/tribble/index/IndexFactoryTest.java b/src/test/java/htsjdk/tribble/index/IndexFactoryTest.java index 016049f32..964a3c3d6 100644 --- a/src/test/java/htsjdk/tribble/index/IndexFactoryTest.java +++ b/src/test/java/htsjdk/tribble/index/IndexFactoryTest.java @@ -23,16 +23,14 @@ */ package htsjdk.tribble.index; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMSequenceDictionary; import htsjdk.samtools.SAMSequenceRecord; -import htsjdk.samtools.util.IOUtil; import htsjdk.tribble.TestUtils; import htsjdk.tribble.TribbleException; import htsjdk.tribble.bed.BEDCodec; -import htsjdk.tribble.index.linear.LinearIndex; import htsjdk.tribble.index.tabix.TabixFormat; import htsjdk.tribble.index.tabix.TabixIndex; -import htsjdk.tribble.util.LittleEndianOutputStream; import htsjdk.variant.vcf.VCFCodec; import htsjdk.variant.vcf.VCFFileReader; import org.testng.Assert; @@ -40,15 +38,13 @@ import org.testng.annotations.Test; import java.io.File; -import java.io.IOException; -import java.io.OutputStream; import java.util.List; /** * User: jacob * Date: 2012-Aug-23 */ -public class IndexFactoryTest { +public class IndexFactoryTest extends HtsjdkTest { final File sortedBedFile = new File(TestUtils.DATA_DIR + "bed/Unigene.sample.bed"); final File unsortedBedFile = new File(TestUtils.DATA_DIR + "bed/unsorted.bed"); diff --git a/src/test/java/htsjdk/tribble/index/IndexTest.java b/src/test/java/htsjdk/tribble/index/IndexTest.java index 56200e672..06fb311a5 100644 --- a/src/test/java/htsjdk/tribble/index/IndexTest.java +++ b/src/test/java/htsjdk/tribble/index/IndexTest.java @@ -2,15 +2,13 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; -import htsjdk.samtools.util.IOUtil; -import htsjdk.tribble.AbstractFeatureReader; +import htsjdk.HtsjdkTest; import htsjdk.tribble.FeatureCodec; import htsjdk.tribble.TestUtils; import htsjdk.tribble.Tribble; import htsjdk.tribble.bed.BEDCodec; import htsjdk.tribble.index.interval.IntervalTreeIndex; import htsjdk.tribble.index.linear.LinearIndex; -import htsjdk.tribble.index.tabix.TabixFormat; import htsjdk.tribble.index.tabix.TabixIndex; import htsjdk.tribble.util.LittleEndianOutputStream; import htsjdk.tribble.util.TabixUtils; @@ -22,16 +20,13 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.net.URISyntaxException; import java.nio.file.FileSystem; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -public class IndexTest { +public class IndexTest extends HtsjdkTest { private final static String CHR = "1"; private final static File MassiveIndexFile = new File(TestUtils.DATA_DIR + "Tb.vcf.idx"); diff --git a/src/test/java/htsjdk/tribble/index/interval/IntervalTreeTest.java b/src/test/java/htsjdk/tribble/index/interval/IntervalTreeTest.java index ca4708933..9a8a0a68e 100644 --- a/src/test/java/htsjdk/tribble/index/interval/IntervalTreeTest.java +++ b/src/test/java/htsjdk/tribble/index/interval/IntervalTreeTest.java @@ -18,6 +18,7 @@ package htsjdk.tribble.index.interval; +import htsjdk.HtsjdkTest; import htsjdk.tribble.AbstractFeatureReader; import htsjdk.tribble.CloseableTribbleIterator; import htsjdk.tribble.FeatureReader; @@ -42,7 +43,7 @@ * User: jrobinso * Date: Mar 24, 2010 */ -public class IntervalTreeTest { +public class IntervalTreeTest extends HtsjdkTest { static IntervalTree tree; diff --git a/src/test/java/htsjdk/tribble/index/linear/LinearIndexTest.java b/src/test/java/htsjdk/tribble/index/linear/LinearIndexTest.java index 09f920e41..e20dc1589 100644 --- a/src/test/java/htsjdk/tribble/index/linear/LinearIndexTest.java +++ b/src/test/java/htsjdk/tribble/index/linear/LinearIndexTest.java @@ -18,6 +18,7 @@ package htsjdk.tribble.index.linear; +import htsjdk.HtsjdkTest; import htsjdk.tribble.AbstractFeatureReader; import htsjdk.tribble.CloseableTribbleIterator; import htsjdk.tribble.FeatureReader; @@ -38,7 +39,7 @@ import java.util.List; import java.util.Set; -public class LinearIndexTest { +public class LinearIndexTest extends HtsjdkTest { private static final File RANDOM_FILE = new File("notMeaningful"); private final static Block CHR1_B1 = new Block(1, 10); diff --git a/src/test/java/htsjdk/tribble/index/tabix/TabixIndexTest.java b/src/test/java/htsjdk/tribble/index/tabix/TabixIndexTest.java index 6981b8751..0473a3d90 100644 --- a/src/test/java/htsjdk/tribble/index/tabix/TabixIndexTest.java +++ b/src/test/java/htsjdk/tribble/index/tabix/TabixIndexTest.java @@ -23,6 +23,7 @@ */ package htsjdk.tribble.index.tabix; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.BlockCompressedOutputStream; import htsjdk.tribble.index.IndexFactory; import htsjdk.tribble.util.LittleEndianOutputStream; @@ -40,7 +41,7 @@ import java.io.IOException; import java.util.Iterator; -public class TabixIndexTest { +public class TabixIndexTest extends HtsjdkTest { private static final File SMALL_TABIX_FILE = new File("src/test/resources/htsjdk/tribble/tabix/trioDup.vcf.gz.tbi"); private static final File BIGGER_TABIX_FILE = new File("src/test/resources/htsjdk/tribble/tabix/bigger.vcf.gz.tbi"); diff --git a/src/test/java/htsjdk/tribble/readers/AsciiLineReaderTest.java b/src/test/java/htsjdk/tribble/readers/AsciiLineReaderTest.java index 822f6cf6a..b4aa6ba2c 100644 --- a/src/test/java/htsjdk/tribble/readers/AsciiLineReaderTest.java +++ b/src/test/java/htsjdk/tribble/readers/AsciiLineReaderTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble.readers; +import htsjdk.HtsjdkTest; import htsjdk.tribble.TestUtils; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -15,17 +16,7 @@ * User: jacob * Date: 2012/05/09 */ -public class AsciiLineReaderTest { - @BeforeMethod - public void setUp() throws Exception { - - } - - @AfterMethod - public void tearDown() throws Exception { - - } - +public class AsciiLineReaderTest extends HtsjdkTest { /** * Test that we read the correct number of lines * from a file diff --git a/src/test/java/htsjdk/tribble/readers/LongLineBufferedReaderTest.java b/src/test/java/htsjdk/tribble/readers/LongLineBufferedReaderTest.java index 6c4c94673..3e498e17c 100644 --- a/src/test/java/htsjdk/tribble/readers/LongLineBufferedReaderTest.java +++ b/src/test/java/htsjdk/tribble/readers/LongLineBufferedReaderTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble.readers; +import htsjdk.HtsjdkTest; import htsjdk.tribble.TestUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -11,7 +12,7 @@ /** * @author mccowan */ -public class LongLineBufferedReaderTest { +public class LongLineBufferedReaderTest extends HtsjdkTest { /** * Test that we read the correct number of lines diff --git a/src/test/java/htsjdk/tribble/readers/PositionalBufferedStreamTest.java b/src/test/java/htsjdk/tribble/readers/PositionalBufferedStreamTest.java index 3dd7cf38e..8d9db2a7b 100644 --- a/src/test/java/htsjdk/tribble/readers/PositionalBufferedStreamTest.java +++ b/src/test/java/htsjdk/tribble/readers/PositionalBufferedStreamTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble.readers; +import htsjdk.HtsjdkTest; import htsjdk.tribble.TestUtils; import org.testng.Assert; import org.testng.annotations.AfterMethod; @@ -20,7 +21,7 @@ * User: jacob * Date: 2012/05/09 */ -public class PositionalBufferedStreamTest { +public class PositionalBufferedStreamTest extends HtsjdkTest { InputStream FileIs; long expectedBytes; diff --git a/src/test/java/htsjdk/tribble/readers/ReaderTest.java b/src/test/java/htsjdk/tribble/readers/ReaderTest.java index d700e041b..7ac1d5787 100644 --- a/src/test/java/htsjdk/tribble/readers/ReaderTest.java +++ b/src/test/java/htsjdk/tribble/readers/ReaderTest.java @@ -1,6 +1,7 @@ package htsjdk.tribble.readers; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -18,7 +19,7 @@ /** * Tests for streams and readers */ -public class ReaderTest { +public class ReaderTest extends HtsjdkTest { @BeforeClass public void setup() throws IOException { } diff --git a/src/test/java/htsjdk/tribble/readers/SynchronousLineReaderUnitTest.java b/src/test/java/htsjdk/tribble/readers/SynchronousLineReaderUnitTest.java index fbb5d188a..0c0deab41 100644 --- a/src/test/java/htsjdk/tribble/readers/SynchronousLineReaderUnitTest.java +++ b/src/test/java/htsjdk/tribble/readers/SynchronousLineReaderUnitTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble.readers; +import htsjdk.HtsjdkTest; import htsjdk.tribble.TestUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -12,7 +13,7 @@ /** * @author mccowan */ -public class SynchronousLineReaderUnitTest { +public class SynchronousLineReaderUnitTest extends HtsjdkTest { @Test public void testLineReaderIterator_streamConstructor() throws Exception { final File filePath = new File(TestUtils.DATA_DIR + "gwas/smallp.gwas"); diff --git a/src/test/java/htsjdk/tribble/readers/TabixReaderTest.java b/src/test/java/htsjdk/tribble/readers/TabixReaderTest.java index d7b36dfab..3b47f417b 100644 --- a/src/test/java/htsjdk/tribble/readers/TabixReaderTest.java +++ b/src/test/java/htsjdk/tribble/readers/TabixReaderTest.java @@ -1,6 +1,7 @@ package htsjdk.tribble.readers; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.TestUtil; import htsjdk.tribble.TestUtils; import org.testng.Assert; @@ -23,7 +24,7 @@ * Time: 8:57:40 PM * To change this template use File | Settings | File Templates. */ -public class TabixReaderTest { +public class TabixReaderTest extends HtsjdkTest { static String tabixFile = TestUtils.DATA_DIR + "tabix/trioDup.vcf.gz"; static TabixReader tabixReader; diff --git a/src/test/java/htsjdk/tribble/util/ParsingUtilsTest.java b/src/test/java/htsjdk/tribble/util/ParsingUtilsTest.java index 85f414e87..c974790dd 100644 --- a/src/test/java/htsjdk/tribble/util/ParsingUtilsTest.java +++ b/src/test/java/htsjdk/tribble/util/ParsingUtilsTest.java @@ -3,20 +3,15 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.IOUtil; -import java.io.BufferedWriter; -import java.io.File; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; import org.testng.Assert; import org.testng.annotations.Test; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; +import java.io.*; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -24,7 +19,7 @@ /** * Parsing utils tests */ -public class ParsingUtilsTest { +public class ParsingUtilsTest extends HtsjdkTest { static final String AVAILABLE_FTP_URL = "ftp://ftp.broadinstitute.org/pub/igv/TEST/test.txt"; static final String UNAVAILABLE_FTP_URL = "ftp://www.example.com/file.txt"; diff --git a/src/test/java/htsjdk/tribble/util/ftp/FTPClientTest.java b/src/test/java/htsjdk/tribble/util/ftp/FTPClientTest.java index 3979b0858..6b77f913e 100644 --- a/src/test/java/htsjdk/tribble/util/ftp/FTPClientTest.java +++ b/src/test/java/htsjdk/tribble/util/ftp/FTPClientTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble.util.ftp; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.ftp.FTPClient; import htsjdk.samtools.util.ftp.FTPReply; import org.testng.Assert; @@ -15,7 +16,7 @@ * @author Jim Robinson * @since 10/3/11 */ -public class FTPClientTest { +public class FTPClientTest extends HtsjdkTest { static String host = "ftp.broadinstitute.org"; static String file = "/pub/igv/TEST/test.txt"; diff --git a/src/test/java/htsjdk/tribble/util/ftp/FTPUtilsTest.java b/src/test/java/htsjdk/tribble/util/ftp/FTPUtilsTest.java index a5f3b0e58..87000ee14 100644 --- a/src/test/java/htsjdk/tribble/util/ftp/FTPUtilsTest.java +++ b/src/test/java/htsjdk/tribble/util/ftp/FTPUtilsTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble.util.ftp; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.ftp.FTPUtils; import org.testng.annotations.Test; @@ -12,7 +13,7 @@ * @author Jim Robinson * @since 10/4/11 */ -public class FTPUtilsTest { +public class FTPUtilsTest extends HtsjdkTest { @Test public void testResourceAvailable() throws Exception { diff --git a/src/test/java/htsjdk/tribble/util/popgen/HardyWeinbergCalculationTest.java b/src/test/java/htsjdk/tribble/util/popgen/HardyWeinbergCalculationTest.java index fcf1bea0b..d2b54555c 100644 --- a/src/test/java/htsjdk/tribble/util/popgen/HardyWeinbergCalculationTest.java +++ b/src/test/java/htsjdk/tribble/util/popgen/HardyWeinbergCalculationTest.java @@ -1,5 +1,6 @@ package htsjdk.tribble.util.popgen; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -7,7 +8,7 @@ /** * Created by farjoun on 7/18/14. */ -public class HardyWeinbergCalculationTest { +public class HardyWeinbergCalculationTest extends HtsjdkTest { @DataProvider public Object[][] testHwCalculateData() { diff --git a/src/test/java/htsjdk/variant/PrintVariantsExampleTest.java b/src/test/java/htsjdk/variant/PrintVariantsExampleTest.java index c82f2dbf3..9f273a94d 100644 --- a/src/test/java/htsjdk/variant/PrintVariantsExampleTest.java +++ b/src/test/java/htsjdk/variant/PrintVariantsExampleTest.java @@ -25,20 +25,19 @@ package htsjdk.variant; +import htsjdk.HtsjdkTest; import htsjdk.samtools.util.IOUtil; import htsjdk.variant.example.PrintVariantsExample; import org.testng.Assert; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; -import java.util.OptionalInt; import java.util.stream.IntStream; -public class PrintVariantsExampleTest { +public class PrintVariantsExampleTest extends HtsjdkTest { @Test public void testExampleWriteFile() throws IOException { final File tempFile = File.createTempFile("example", ".vcf"); diff --git a/src/test/java/htsjdk/variant/VariantBaseTest.java b/src/test/java/htsjdk/variant/VariantBaseTest.java index 87345a054..7a3417b52 100644 --- a/src/test/java/htsjdk/variant/VariantBaseTest.java +++ b/src/test/java/htsjdk/variant/VariantBaseTest.java @@ -25,6 +25,7 @@ package htsjdk.variant; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMSequenceDictionary; import htsjdk.samtools.SAMSequenceRecord; import htsjdk.variant.variantcontext.Genotype; @@ -44,7 +45,7 @@ /** * Base class for test classes within org.broadinstitute.variant */ -public class VariantBaseTest { +public class VariantBaseTest extends HtsjdkTest { public static final String variantTestDataRoot = new File("src/test/resources/htsjdk/variant/").getAbsolutePath() + "/"; diff --git a/src/test/java/htsjdk/variant/utils/SAMSequenceDictionaryExtractorTest.java b/src/test/java/htsjdk/variant/utils/SAMSequenceDictionaryExtractorTest.java index 9fb13e802..af3241112 100644 --- a/src/test/java/htsjdk/variant/utils/SAMSequenceDictionaryExtractorTest.java +++ b/src/test/java/htsjdk/variant/utils/SAMSequenceDictionaryExtractorTest.java @@ -23,6 +23,7 @@ */ package htsjdk.variant.utils; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMSequenceDictionary; import htsjdk.samtools.util.SequenceUtil; import org.testng.annotations.DataProvider; @@ -34,7 +35,7 @@ /** * @author farjoun on 4/9/14. */ -public class SAMSequenceDictionaryExtractorTest { +public class SAMSequenceDictionaryExtractorTest extends HtsjdkTest { String path = "src/test/resources/htsjdk/variant/utils/SamSequenceDictionaryExtractor/"; @DataProvider(name = "testExtractDictionaries") diff --git a/src/test/java/htsjdk/variant/variantcontext/VariantContextTestProvider.java b/src/test/java/htsjdk/variant/variantcontext/VariantContextTestProvider.java index 613dec57a..b8476592e 100644 --- a/src/test/java/htsjdk/variant/variantcontext/VariantContextTestProvider.java +++ b/src/test/java/htsjdk/variant/variantcontext/VariantContextTestProvider.java @@ -25,6 +25,7 @@ package htsjdk.variant.variantcontext; +import htsjdk.HtsjdkTest; import htsjdk.tribble.FeatureCodec; import htsjdk.tribble.FeatureCodecHeader; import htsjdk.tribble.Tribble; @@ -69,7 +70,7 @@ * @author Your Name * @since Date created */ -public class VariantContextTestProvider { +public class VariantContextTestProvider extends HtsjdkTest { final private static boolean ENABLE_GENOTYPE_TESTS = true; final private static boolean ENABLE_A_AND_G_TESTS = true; final private static boolean ENABLE_VARARRAY_TESTS = true; @@ -1011,4 +1012,4 @@ public static void main( String argv[] ) { throw new RuntimeException(e); } } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/variant/variantcontext/filter/CompoundFilterTest.java b/src/test/java/htsjdk/variant/variantcontext/filter/CompoundFilterTest.java index 0a4985373..efa788efb 100644 --- a/src/test/java/htsjdk/variant/variantcontext/filter/CompoundFilterTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/filter/CompoundFilterTest.java @@ -1,5 +1,6 @@ package htsjdk.variant.variantcontext.filter; +import htsjdk.HtsjdkTest; import htsjdk.variant.variantcontext.Allele; import htsjdk.variant.variantcontext.VariantContext; import htsjdk.variant.variantcontext.VariantContextBuilder; @@ -15,7 +16,7 @@ /** * Created by farjoun on 9/9/15. */ -public class CompoundFilterTest { +public class CompoundFilterTest extends HtsjdkTest { static AllPassFilter pass = new AllPassFilter(); static AllFailFilter fail = new AllFailFilter(); @@ -75,4 +76,4 @@ public void testCompoundFilter(final VariantContextFilter filter, final boolean shouldPass) { Assert.assertEquals(filter.test(vc), shouldPass, filter.toString()); } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/variant/variantcontext/filter/FilteringVariantContextIteratorTest.java b/src/test/java/htsjdk/variant/variantcontext/filter/FilteringVariantContextIteratorTest.java index d8decfdd9..eeb221378 100644 --- a/src/test/java/htsjdk/variant/variantcontext/filter/FilteringVariantContextIteratorTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/filter/FilteringVariantContextIteratorTest.java @@ -24,6 +24,7 @@ package htsjdk.variant.variantcontext.filter; +import htsjdk.HtsjdkTest; import htsjdk.variant.variantcontext.VariantContext; import htsjdk.variant.vcf.VCFFileReader; import org.testng.Assert; @@ -36,7 +37,7 @@ * Tests for testing the (VariantContext)FilteringVariantContextIterator, and the HeterozygosityFilter */ -public class FilteringVariantContextIteratorTest { +public class FilteringVariantContextIteratorTest extends HtsjdkTest { final File testDir = new File("src/test/resources/htsjdk/variant"); @DataProvider diff --git a/src/test/java/htsjdk/variant/variantcontext/filter/GenotypeQualityFilterTest.java b/src/test/java/htsjdk/variant/variantcontext/filter/GenotypeQualityFilterTest.java index 809133ff3..a615f8140 100644 --- a/src/test/java/htsjdk/variant/variantcontext/filter/GenotypeQualityFilterTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/filter/GenotypeQualityFilterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.variant.variantcontext.filter; +import htsjdk.HtsjdkTest; import htsjdk.variant.variantcontext.Allele; import htsjdk.variant.variantcontext.GenotypeBuilder; import htsjdk.variant.variantcontext.VariantContext; @@ -37,7 +38,7 @@ import java.util.Iterator; import java.util.List; -public class GenotypeQualityFilterTest { +public class GenotypeQualityFilterTest extends HtsjdkTest { Allele refA = Allele.create("A", true); Allele G = Allele.create("G", false); diff --git a/src/test/java/htsjdk/variant/variantcontext/filter/HeterozygosityFilterTest.java b/src/test/java/htsjdk/variant/variantcontext/filter/HeterozygosityFilterTest.java index b4cd3a84f..e2e988184 100644 --- a/src/test/java/htsjdk/variant/variantcontext/filter/HeterozygosityFilterTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/filter/HeterozygosityFilterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.variant.variantcontext.filter; +import htsjdk.HtsjdkTest; import htsjdk.variant.variantcontext.Allele; import htsjdk.variant.variantcontext.GenotypeBuilder; import htsjdk.variant.variantcontext.VariantContext; @@ -37,7 +38,7 @@ import java.util.Iterator; import java.util.List; -public class HeterozygosityFilterTest { +public class HeterozygosityFilterTest extends HtsjdkTest { Allele refA = Allele.create("A", true); Allele G = Allele.create("G", false); diff --git a/src/test/java/htsjdk/variant/variantcontext/filter/JavascriptVariantFilterTest.java b/src/test/java/htsjdk/variant/variantcontext/filter/JavascriptVariantFilterTest.java index 3993b792f..7fb98c33b 100644 --- a/src/test/java/htsjdk/variant/variantcontext/filter/JavascriptVariantFilterTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/filter/JavascriptVariantFilterTest.java @@ -23,6 +23,7 @@ */ package htsjdk.variant.variantcontext.filter; +import htsjdk.HtsjdkTest; import htsjdk.variant.vcf.VCFFileReader; import org.testng.Assert; @@ -36,7 +37,7 @@ * @author Pierre Lindenbaum PhD Institut du Thorax - INSERM - Nantes - France */ -public class JavascriptVariantFilterTest { +public class JavascriptVariantFilterTest extends HtsjdkTest { final File testDir = new File("src/test/resources/htsjdk/variant"); @DataProvider diff --git a/src/test/java/htsjdk/variant/variantcontext/filter/PassingVariantFilterTest.java b/src/test/java/htsjdk/variant/variantcontext/filter/PassingVariantFilterTest.java index 3cbb60ca3..da2826495 100644 --- a/src/test/java/htsjdk/variant/variantcontext/filter/PassingVariantFilterTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/filter/PassingVariantFilterTest.java @@ -1,5 +1,6 @@ package htsjdk.variant.variantcontext.filter; +import htsjdk.HtsjdkTest; import htsjdk.variant.variantcontext.Allele; import htsjdk.variant.variantcontext.VariantContext; import htsjdk.variant.variantcontext.VariantContextBuilder; @@ -16,7 +17,7 @@ /** * Created by farjoun on 9/10/15. */ -public class PassingVariantFilterTest { +public class PassingVariantFilterTest extends HtsjdkTest { Allele refA = Allele.create("A", true); Allele G = Allele.create("G", false); @@ -43,4 +44,4 @@ public void testPassingVariantFilter(final VariantContext vc, final boolean shou Assert.assertEquals(passingVariantFilter.test(vc), shouldPass, vc.toString()); } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/variant/variantcontext/filter/SnpFilterTest.java b/src/test/java/htsjdk/variant/variantcontext/filter/SnpFilterTest.java index 74f1bb5de..e091ca0b6 100644 --- a/src/test/java/htsjdk/variant/variantcontext/filter/SnpFilterTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/filter/SnpFilterTest.java @@ -1,5 +1,6 @@ package htsjdk.variant.variantcontext.filter; +import htsjdk.HtsjdkTest; import htsjdk.variant.variantcontext.Allele; import htsjdk.variant.variantcontext.VariantContext; import htsjdk.variant.variantcontext.VariantContextBuilder; @@ -16,7 +17,7 @@ /** * Created by farjoun on 9/9/15. */ -public class SnpFilterTest { +public class SnpFilterTest extends HtsjdkTest { Allele refA = Allele.create("A", true); Allele refAG = Allele.create("AG", true); diff --git a/src/test/java/htsjdk/variant/variantcontext/writer/TabixOnTheFlyIndexCreationTest.java b/src/test/java/htsjdk/variant/variantcontext/writer/TabixOnTheFlyIndexCreationTest.java index 2fd1520ba..f8c8fd193 100644 --- a/src/test/java/htsjdk/variant/variantcontext/writer/TabixOnTheFlyIndexCreationTest.java +++ b/src/test/java/htsjdk/variant/variantcontext/writer/TabixOnTheFlyIndexCreationTest.java @@ -23,6 +23,7 @@ */ package htsjdk.variant.variantcontext.writer; +import htsjdk.HtsjdkTest; import htsjdk.tribble.AbstractFeatureReader; import htsjdk.tribble.CloseableTribbleIterator; import htsjdk.tribble.FeatureReader; @@ -36,7 +37,7 @@ import java.io.File; import java.util.EnumSet; -public class TabixOnTheFlyIndexCreationTest { +public class TabixOnTheFlyIndexCreationTest extends HtsjdkTest { private static final File SMALL_VCF = new File("src/test/resources/htsjdk/tribble/tabix/trioDup.vcf.gz"); @Test public void simpleTest() throws Exception { diff --git a/src/test/java/htsjdk/variant/vcf/VCFEncoderTest.java b/src/test/java/htsjdk/variant/vcf/VCFEncoderTest.java index 2c4ff0f08..6d4c23b9d 100644 --- a/src/test/java/htsjdk/variant/vcf/VCFEncoderTest.java +++ b/src/test/java/htsjdk/variant/vcf/VCFEncoderTest.java @@ -1,5 +1,6 @@ package htsjdk.variant.vcf; +import htsjdk.HtsjdkTest; import htsjdk.tribble.util.ParsingUtils; import htsjdk.variant.variantcontext.Allele; import htsjdk.variant.variantcontext.GenotypeBuilder; @@ -18,7 +19,7 @@ import java.util.Set; import java.util.TreeSet; -public class VCFEncoderTest { +public class VCFEncoderTest extends HtsjdkTest { @DataProvider(name = "VCFWriterDoubleFormatTestData") public Object[][] makeVCFWriterDoubleFormatTestData() { diff --git a/src/test/scala/htsjdk/UnitSpec.scala b/src/test/scala/htsjdk/UnitSpec.scala new file mode 100644 index 000000000..db533a12e --- /dev/null +++ b/src/test/scala/htsjdk/UnitSpec.scala @@ -0,0 +1,6 @@ +package htsjdk + +import org.scalatest.{FlatSpec, Matchers} + +/** Base class for all Scala tests. */ +class UnitSpec extends FlatSpec with Matchers diff --git a/src/test/scala/htsjdk/samtools/util/StringUtilTest.scala b/src/test/scala/htsjdk/samtools/util/StringUtilTest.scala new file mode 100644 index 000000000..6962e3674 --- /dev/null +++ b/src/test/scala/htsjdk/samtools/util/StringUtilTest.scala @@ -0,0 +1,134 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Broad Institute + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package htsjdk.samtools.util + +import htsjdk.UnitSpec + +class StringUtilTest extends UnitSpec { + "StringUtil.split" should "behave like String.split(char)" in { + Seq("A:BB:C", "A:BB", "A:BB:", "A:BB:C:DDD", "A:", "A", "A:BB:C").foreach { s => + val arr = new Array[String](10) + val count = StringUtil.split(s, arr, ':') + arr.take(count) shouldBe s.split(':') + } + } + + "StringUtil.splitConcatenateExcessTokens" should "behave like String.split(regex, limit)" in { + Seq("A:BB:C", "A:BB", "A:BB:", "A:BB:C:DDD", "A:", "A", "A:BB:C:").foreach { s => + val arr = new Array[String](3) + val count = StringUtil.splitConcatenateExcessTokens(s, arr, ':') + arr.take(count) shouldBe s.split(":", 3).filter(_.nonEmpty) + } + } + + "StringUtil.join" should "join tokens with a separator" in { + StringUtil.join(",", 1, "hello", 'T') shouldBe "1,hello,T" + StringUtil.join(",") shouldBe "" + } + + "StringUtil.hammingDistance" should "return zero for two empty sequences" in { + StringUtil.hammingDistance("", "") shouldBe 0 + } + + Seq(("ATAC", "GCAT", 3), ("ATAGC", "ATAGC", 0)).foreach { case (s1, s2, distance) => + it should s"return distance $distance between $s1 and $s2" in { + StringUtil.hammingDistance(s1, s2) shouldBe distance + } + } + + it should "be case sensitive" in { + StringUtil.hammingDistance("ATAC", "atac") shouldBe 4 + } + + it should "count Ns as matching when computing distance" in { + StringUtil.hammingDistance("nAGTN", "nAGTN") shouldBe 0 + } + + it should "throw an exception if two strings of different lengths are provided" in { + an[Exception] shouldBe thrownBy { StringUtil.hammingDistance("", "ABC")} + an[Exception] shouldBe thrownBy { StringUtil.hammingDistance("Abc", "wxyz")} + } + + "StringUtil.isWithinHammingDistance" should "agree with StringUtil.hammingDistance" in { + Seq(("ATAC", "GCAT", 3), ("ATAC", "GCAT", 2), ("ATAC", "GCAT", 1), ("ATAC", "GCAT", 0)).foreach { case (s1, s2, within) => + StringUtil.isWithinHammingDistance(s1, s2, within) shouldBe (StringUtil.hammingDistance(s1, s2) <= within) + } + } + + it should "throw an exception if the two strings are of different lengths" in { + an[Exception] shouldBe thrownBy { StringUtil.isWithinHammingDistance("", "ABC", 2)} + an[Exception] shouldBe thrownBy { StringUtil.isWithinHammingDistance("Abc", "wxyz", 2)} + } + + "StringUtil.toLowerCase(byte)" should "work just like Character.toLowerCase" in { + 0 to 127 foreach {i => StringUtil.toLowerCase(i.toByte) shouldBe i.toChar.toLower.toByte } + } + + "StringUtil.toUpperCase(byte)" should "work just like Character.toUpperCase" in { + 0 to 127 foreach {i => StringUtil.toUpperCase(i.toByte) shouldBe i.toChar.toUpper.toByte } + } + + "StringUtil.toUpperCase(byte[])" should "do upper case characters" in { + val seq = "atACgtaCGTgatcCAtATATgATtatgacNryuAN" + val bytes = seq.getBytes + StringUtil.toUpperCase(bytes) + bytes shouldBe seq.toUpperCase.getBytes + } + + "StringUtil.assertCharactersNotInString" should "catch illegal characters" in { + an[Exception] shouldBe thrownBy { + StringUtil.assertCharactersNotInString("Hello World!", ' ', '!', '_') + } + } + + it should "not fail when there are no illegal characters present" in { + StringUtil.assertCharactersNotInString("HelloWorld", ' ', '!', '_') + } + + val textForWrapping = + """This is a little bit + |of text with nice short + |lines. + """.stripMargin.trim + + "StringUtil.wordWrap" should "not wrap when lines are shorter than the given length" in { + StringUtil.wordWrap(textForWrapping, 50) shouldBe textForWrapping + } + + it should "wrap text when lines are longer than length give" in { + val result = StringUtil.wordWrap(textForWrapping, 15) + result.lines.size shouldBe 5 + result.lines.foreach(line => line.length should be <= 15) + } + + "StringUtil.intValuesToString(int[])" should "generate a CSV string of ints" in { + val ints = Array[Int](1, 2, 3, 11, 22, 33, Int.MinValue, 0, Int.MaxValue) + StringUtil.intValuesToString(ints) shouldBe ints.mkString(", ") + } + + "StringUtil.intValuesToString(short[])" should "generate a CSV string of ints" in { + val ints = Array[Short](1, 2, 3, 11, 22, 33, Short.MinValue, 0, Short.MaxValue) + StringUtil.intValuesToString(ints) shouldBe ints.mkString(", ") + } +} From e1a37cfbed04ce47b5e452853c59f8df3d11332f Mon Sep 17 00:00:00 2001 From: Yulia_Kamyshova Date: Sun, 16 Apr 2017 05:44:11 +0300 Subject: [PATCH 16/59] replace method getRefPos() into abstract class EdgingRecordAndOffset and delete constructor in AbstractRecordAndOffset (#849) --- .../htsjdk/samtools/util/AbstractRecordAndOffset.java | 17 ----------------- .../htsjdk/samtools/util/EdgingRecordAndOffset.java | 2 ++ .../samtools/util/AbstractRecordAndOffsetTest.java | 3 +-- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/main/java/htsjdk/samtools/util/AbstractRecordAndOffset.java b/src/main/java/htsjdk/samtools/util/AbstractRecordAndOffset.java index 28b9d34b3..e76b66683 100644 --- a/src/main/java/htsjdk/samtools/util/AbstractRecordAndOffset.java +++ b/src/main/java/htsjdk/samtools/util/AbstractRecordAndOffset.java @@ -49,16 +49,6 @@ /** * @param record inner SAMRecord * @param offset from the start of the read - * @param length of alignment block - * @param refPos corresponding to read offset reference position - */ - public AbstractRecordAndOffset(final SAMRecord record, final int offset, int length, int refPos) { - this(record, offset); - } - - /** - * @param record inner SAMRecord - * @param offset from the start of the read */ public AbstractRecordAndOffset(final SAMRecord record, final int offset) { this.offset = offset; @@ -94,13 +84,6 @@ public int getLength() { } /** - * @return the position in reference sequence, to which the start of alignment block is aligned. - */ - public int getRefPos() { - return -1; - } - - /** * @return read name of inner SAMRecord. */ public String getReadName() { diff --git a/src/main/java/htsjdk/samtools/util/EdgingRecordAndOffset.java b/src/main/java/htsjdk/samtools/util/EdgingRecordAndOffset.java index 85beb66f0..df282b00f 100644 --- a/src/main/java/htsjdk/samtools/util/EdgingRecordAndOffset.java +++ b/src/main/java/htsjdk/samtools/util/EdgingRecordAndOffset.java @@ -56,6 +56,8 @@ private EdgingRecordAndOffset(SAMRecord record, int offset) { public abstract byte getBaseQuality(int position); + public abstract int getRefPos(); + public static EdgingRecordAndOffset createBeginRecord(SAMRecord record, int offset, int length, int refPos) { return new StartEdgingRecordAndOffset(record, offset, length, refPos); } diff --git a/src/test/java/htsjdk/samtools/util/AbstractRecordAndOffsetTest.java b/src/test/java/htsjdk/samtools/util/AbstractRecordAndOffsetTest.java index 8993e417a..372a590d3 100644 --- a/src/test/java/htsjdk/samtools/util/AbstractRecordAndOffsetTest.java +++ b/src/test/java/htsjdk/samtools/util/AbstractRecordAndOffsetTest.java @@ -53,12 +53,11 @@ public void setUp(){ @Test public void testConstructor(){ - AbstractRecordAndOffset abstractRecordAndOffset = new AbstractRecordAndOffset(record, 0, 10, 3); + AbstractRecordAndOffset abstractRecordAndOffset = new AbstractRecordAndOffset(record, 0); assertArrayEquals(qualities, abstractRecordAndOffset.getBaseQualities()); assertArrayEquals(bases, abstractRecordAndOffset.getRecord().getReadBases()); assertEquals('A', abstractRecordAndOffset.getReadBase()); assertEquals(30, abstractRecordAndOffset.getBaseQuality()); assertEquals(0, abstractRecordAndOffset.getOffset()); - assertEquals(-1, abstractRecordAndOffset.getRefPos()); } } From 8c090cea90c414338fc979741097ff1bdd63e3eb Mon Sep 17 00:00:00 2001 From: Steve Huang Date: Sat, 15 Apr 2017 22:57:26 -0400 Subject: [PATCH 17/59] check for negative length in CigarElement ctor (#839) * added check for negative length in CigarElement ctor * added test --- src/main/java/htsjdk/samtools/CigarElement.java | 1 + .../java/htsjdk/samtools/util/CigarElementUnitTest.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java diff --git a/src/main/java/htsjdk/samtools/CigarElement.java b/src/main/java/htsjdk/samtools/CigarElement.java index c645e6cc2..016956c56 100644 --- a/src/main/java/htsjdk/samtools/CigarElement.java +++ b/src/main/java/htsjdk/samtools/CigarElement.java @@ -36,6 +36,7 @@ private final CigarOperator operator; public CigarElement(final int length, final CigarOperator operator) { + if (length < 0) throw new IllegalArgumentException(String.format("Cigar element being constructed with negative length: %d and operation: %s" , length, operator.name())); this.length = length; this.operator = operator; } diff --git a/src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java b/src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java new file mode 100644 index 000000000..54cfdedfc --- /dev/null +++ b/src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java @@ -0,0 +1,14 @@ +package htsjdk.samtools.util; + + +import htsjdk.samtools.CigarElement; +import htsjdk.samtools.CigarOperator; +import org.testng.annotations.Test; + +public class CigarElementUnitTest { + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNegativeLengthCheck(){ + final CigarElement element = new CigarElement(-1, CigarOperator.M); + } +} From a44a5cd90e6271060d2c0b65af48cdffc6d15bb0 Mon Sep 17 00:00:00 2001 From: Tim Fennell Date: Sun, 16 Apr 2017 14:08:28 -0400 Subject: [PATCH 18/59] Expose a couple of protected methods and replace hard coded strings with an enum. (#854) --- .../java/htsjdk/samtools/fastq/FastqReader.java | 48 ++++--- .../htsjdk/samtools/fastq/FastqRecordTest.java | 13 ++ .../htsjdk/samtools/fastq/FastqWriterTest.java | 74 ---------- src/test/scala/htsjdk/UnitSpec.scala | 21 ++- .../samtools/fastq/FastqReaderWriterTest.scala | 153 +++++++++++++++++++++ 5 files changed, 217 insertions(+), 92 deletions(-) delete mode 100644 src/test/java/htsjdk/samtools/fastq/FastqWriterTest.java create mode 100644 src/test/scala/htsjdk/samtools/fastq/FastqReaderWriterTest.scala diff --git a/src/main/java/htsjdk/samtools/fastq/FastqReader.java b/src/main/java/htsjdk/samtools/fastq/FastqReader.java index 7988712f3..d5d8f1889 100755 --- a/src/main/java/htsjdk/samtools/fastq/FastqReader.java +++ b/src/main/java/htsjdk/samtools/fastq/FastqReader.java @@ -41,6 +41,22 @@ * directly. It is provided so that this class can be used in Java for-each loop. */ public class FastqReader implements Iterator, Iterable, Closeable { + /** Enum of the types of lines we see in Fastq. */ + protected enum LineType { + SequenceHeader("Sequence Header"), + SequenceLine("Sequence Line"), + QualityHeader("Quality Header"), + QualityLine("Quality Line"); + + private String printable; + + LineType(String printable) { + this.printable = printable; + } + + @Override public String toString() { return this.printable; } + } + final private File fastqFile; final private BufferedReader reader; private FastqRecord nextRecord; @@ -58,10 +74,7 @@ public FastqReader(final File file) { * @param skipBlankLines should we skip blank lines ? */ public FastqReader(final File file, final boolean skipBlankLines) { - this.skipBlankLines=skipBlankLines; - fastqFile = file; - reader = IOUtil.openFileForBufferedReading(fastqFile); - nextRecord = readNextRecord(); + this(file, IOUtil.openFileForBufferedReading(file), skipBlankLines); } public FastqReader(final BufferedReader reader) { @@ -87,7 +100,6 @@ public FastqReader(final File file, final BufferedReader reader) { private FastqRecord readNextRecord() { try { - // Read sequence header final String seqHeader = readLineConditionallySkippingBlanks(); if (seqHeader == null) return null ; @@ -95,23 +107,23 @@ private FastqRecord readNextRecord() { throw new SAMException(error("Missing sequence header")); } if (!seqHeader.startsWith(FastqConstants.SEQUENCE_HEADER)) { - throw new SAMException(error("Sequence header must start with "+ FastqConstants.SEQUENCE_HEADER+": "+seqHeader)); + throw new SAMException(error("Sequence header must start with " + FastqConstants.SEQUENCE_HEADER + ": " + seqHeader)); } // Read sequence line final String seqLine = readLineConditionallySkippingBlanks(); - checkLine(seqLine,"sequence line"); + checkLine(seqLine, LineType.SequenceLine); // Read quality header final String qualHeader = readLineConditionallySkippingBlanks(); - checkLine(qualHeader,"quality header"); + checkLine(qualHeader, LineType.QualityHeader); if (!qualHeader.startsWith(FastqConstants.QUALITY_HEADER)) { - throw new SAMException(error("Quality header must start with "+ FastqConstants.QUALITY_HEADER+": "+qualHeader)); + throw new SAMException(error("Quality header must start with " + FastqConstants.QUALITY_HEADER + ": "+ qualHeader)); } // Read quality line final String qualLine = readLineConditionallySkippingBlanks(); - checkLine(qualLine,"quality line"); + checkLine(qualLine, LineType.QualityLine); // Check sequence and quality lines are same length if (seqLine.length() != qualLine.length()) { @@ -165,21 +177,23 @@ public void close() { try { reader.close(); } catch (IOException e) { - throw new SAMException("IO problem in fastq file "+getAbsolutePath(), e); + throw new SAMException("IO problem in fastq file " + getAbsolutePath(), e); } } - private void checkLine(final String line, final String kind) { + /** Checks that the line is neither null (representing EOF) or empty (blank line in file). */ + protected void checkLine(final String line, final LineType kind) { if (line == null) { - throw new SAMException(error("File is too short - missing "+kind+" line")); + throw new SAMException(error("File is too short - missing " + kind)); } if (StringUtil.isBlank(line)) { - throw new SAMException(error("Missing "+kind)); + throw new SAMException(error("Missing " + kind)); } } - private String error(final String msg) { - return msg + " at line "+line+" in fastq "+getAbsolutePath(); + /** Generates an error message with line number information. */ + protected String error(final String msg) { + return msg + " at line " + line + " in fastq " + getAbsolutePath(); } private String getAbsolutePath() { @@ -198,6 +212,6 @@ private String readLineConditionallySkippingBlanks() throws IOException { @Override public String toString() { - return "FastqReader["+(this.fastqFile == null?"":this.fastqFile)+ " Line:"+getLineNumber()+"]"; + return "FastqReader[" + (this.fastqFile == null ? "" : this.fastqFile) + " Line:" + getLineNumber() + "]"; } } diff --git a/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java b/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java index 5ace9ffc3..9a47a8688 100644 --- a/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java +++ b/src/test/java/htsjdk/samtools/fastq/FastqRecordTest.java @@ -1,9 +1,12 @@ package htsjdk.samtools.fastq; import htsjdk.HtsjdkTest; +import htsjdk.samtools.util.TestUtil; import org.testng.Assert; import org.testng.annotations.Test; +import java.util.ArrayList; + public final class FastqRecordTest extends HtsjdkTest { @Test @@ -207,4 +210,14 @@ public void testNotEqualLengths() { new FastqRecord("header", seqLine1, "qualHeaderPrefix", qualLine1); //Note: this does not blow up now but it will once we enforce that seqLine and qualLine be the same length } + + @Test + public void testFastqSerialize() throws Exception { + final ArrayList records = new ArrayList<>(); + records.add(new FastqRecord("q1", "ACGTACGT", "", "########")); + records.add(new FastqRecord("q2", "CCAGCGTAATA", "", "????????###")); + records.add(new FastqRecord("q3", "NNNNNNNNNNNN", "", "############")); + + Assert.assertEquals(TestUtil.serializeAndDeserialize(records),records); + } } diff --git a/src/test/java/htsjdk/samtools/fastq/FastqWriterTest.java b/src/test/java/htsjdk/samtools/fastq/FastqWriterTest.java deleted file mode 100644 index 22549e904..000000000 --- a/src/test/java/htsjdk/samtools/fastq/FastqWriterTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * The MIT License - * - * Pierre Lindenbaum PhD - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package htsjdk.samtools.fastq; - -import htsjdk.HtsjdkTest; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import htsjdk.samtools.util.TestUtil; - -import java.io.File; -import java.util.ArrayList; - -/** - * test fastq - */ -public class FastqWriterTest extends HtsjdkTest { - private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools/util/QualityEncodingDetectorTest"); - - @DataProvider(name = "fastqsource") - public Object[][] createTestData() { - return new Object[][]{ - {"solexa_full_range_as_solexa.fastq"}, - {"5k-30BB2AAXX.3.aligned.sam.fastq"} - }; - } - - @Test(dataProvider = "fastqsource") - public void testReadReadWriteFastq(final String basename) throws Exception { - final File tmpFile = File.createTempFile("test.", ".fastq"); - tmpFile.deleteOnExit(); - final FastqReader fastqReader = new FastqReader(new File(TEST_DATA_DIR,basename)); - final FastqWriterFactory writerFactory = new FastqWriterFactory(); - final FastqWriter fastqWriter = writerFactory.newWriter(tmpFile); - for(final FastqRecord rec: fastqReader) fastqWriter.write(rec); - fastqWriter.close(); - fastqReader.close(); - } - - @Test(dataProvider = "fastqsource") - public void testFastqSerialize(final String basename) throws Exception { - //write - final ArrayList records = new ArrayList<>(); - final FastqReader fastqReader = new FastqReader(new File(TEST_DATA_DIR,basename)); - for(final FastqRecord rec: fastqReader) { - records.add(rec); - if(records.size()>100) break; - } - fastqReader.close(); - Assert.assertEquals(TestUtil.serializeAndDeserialize(records),records); - } -} diff --git a/src/test/scala/htsjdk/UnitSpec.scala b/src/test/scala/htsjdk/UnitSpec.scala index db533a12e..a2995d56c 100644 --- a/src/test/scala/htsjdk/UnitSpec.scala +++ b/src/test/scala/htsjdk/UnitSpec.scala @@ -1,6 +1,25 @@ package htsjdk +import java.nio.file.{Files, Path} + import org.scalatest.{FlatSpec, Matchers} /** Base class for all Scala tests. */ -class UnitSpec extends FlatSpec with Matchers +class UnitSpec extends FlatSpec with Matchers { + /** Make a temporary file that will get cleaned up at the end of testing. */ + protected def makeTempFile(prefix: String, suffix: String): Path = { + val path = Files.createTempFile(prefix, suffix) + path.toFile.deleteOnExit() + path + } + + /** Implicit conversion from Java to Scala iterator. */ + implicit def javaIteratorAsScalaIterator[A](iter: java.util.Iterator[A]): Iterator[A] = { + scala.collection.JavaConverters.asScalaIterator(iter) + } + + /** Implicit conversion from Java to Scala iterable. */ + implicit def javaIterableAsScalaIterable[A](iterable: java.lang.Iterable[A]): Iterable[A] = { + scala.collection.JavaConverters.iterableAsScalaIterable(iterable) + } +} diff --git a/src/test/scala/htsjdk/samtools/fastq/FastqReaderWriterTest.scala b/src/test/scala/htsjdk/samtools/fastq/FastqReaderWriterTest.scala new file mode 100644 index 000000000..60e08efbd --- /dev/null +++ b/src/test/scala/htsjdk/samtools/fastq/FastqReaderWriterTest.scala @@ -0,0 +1,153 @@ +package htsjdk.samtools.fastq + +import java.io.{BufferedReader, File, StringReader} + +import htsjdk.UnitSpec +import htsjdk.samtools.SAMUtils +import htsjdk.samtools.util.IOUtil + +import scala.util.Random + +class FastqReaderWriterTest extends UnitSpec { + private val rng = new Random() + private val Bases = Array('A', 'C', 'G', 'T') + + /** Generates a random string of bases of the desired length. */ + def bases(length: Int): String = { + val chs = new Array[Char](length) + chs.indices.foreach(i => chs(i) = Bases(rng.nextInt(Bases.length))) + new String(chs) + } + + /** Generates a FastqRecord with random bases at a given length. */ + def fq(name: String, length: Int, qual: Int = 30): FastqRecord = { + new FastqRecord(name, bases(length), "", SAMUtils.phredToFastq(qual).toString * length) + } + + "FastqWriter" should "write four lines per record to file" in { + val path = makeTempFile("test.", ".fastq") + val out = new FastqWriterFactory().newWriter(path.toFile) + val recs = Seq(fq("q1", 50), fq("q2", 48), fq("q3", 55)) + val Seq(q1, q2, q3) = recs + + recs.foreach(rec => out.write(rec)) + out.close() + + val lines = IOUtil.slurpLines(path.toFile) + lines should have size 12 + + lines.get(0) shouldBe "@q1" + lines.get(1) shouldBe q1.getReadString + lines.get(4) shouldBe "@q2" + lines.get(5) shouldBe q2.getReadString + lines.get(8) shouldBe "@q3" + lines.get(9) shouldBe q3.getReadString + } + + it should "write a record with only a single base" in { + val path = makeTempFile("test.", ".fastq") + val out = new FastqWriterFactory().newWriter(path.toFile) + out.write(fq("q1", 1)) + out.close() + val lines = IOUtil.slurpLines(path.toFile) + lines.get(1) should have length 1 + lines.get(3) should have length 1 + } + + it should "write a record with zero-length bases and quals" in { + val path = makeTempFile("test.", ".fastq") + val out = new FastqWriterFactory().newWriter(path.toFile) + out.write(fq("q1", 0)) + out.close() + val lines = IOUtil.slurpLines(path.toFile) + lines.get(1) should have length 0 + lines.get(3) should have length 0 + } + + + "FastqReader" should "read back a fastq file written by FastqWriter" in { + val path = makeTempFile("test.", ".fastq") + val out = new FastqWriterFactory().newWriter(path.toFile) + val recs = Seq(fq("q1", 50), fq("q2", 100), fq("q3", 150)) + recs.foreach(rec => out.write(rec)) + out.close() + + val in = new FastqReader(path.toFile) + val recs2 = in.iterator().toList + in.close() + recs2 should contain theSameElementsInOrderAs recs + } + + it should "throw an exception if the input fastq is garbled" in { + val fastq = + """ + |@q1 + |AACCGGTT + |+ + |######## + |@q2 + |ACGT + |#### + """.stripMargin.trim + + val in = new FastqReader(null, new BufferedReader(new StringReader(fastq))) + an[Exception] shouldBe thrownBy { in.next() } + } + + it should "throw an exception if the input file doesn't exist" in { + an[Exception] shouldBe thrownBy { new FastqReader(new File("/some/path/that/shouldnt/exist.fq"))} + } + + it should "read an empty file just fine" in { + val path = makeTempFile("empty.", ".fastq") + val in = new FastqReader(path.toFile) + while (in.hasNext) in.next() + an[Exception] shouldBe thrownBy { in.next() } + in.close() + } + + it should "fail on a truncated file" in { + val fastq = + """ + |@q1 + |AACCGGTT + |+ + |######## + """.stripMargin.trim + + Range.inclusive(1, 3).foreach { n => + val text = fastq.lines.take(n).mkString("\n") + val reader = new BufferedReader(new StringReader(text)) + an[Exception] shouldBe thrownBy { new FastqReader(null, reader).iterator().toSeq } + } + } + + it should "fail if the seq and qual lines are different lengths" in { + val fastq = + """ + |@q1 + |AACC + |+ + |######## + """.stripMargin.trim + + val reader = new BufferedReader(new StringReader(fastq)) + an[Exception] shouldBe thrownBy { new FastqReader(null, reader).iterator().toSeq } + } + + it should "fail if either header line is empty" in { + val fastq = + """ + |@q1 + |AACC + |+q1 + |######## + """.stripMargin.trim + + val noSeqHeader = new BufferedReader(new StringReader(fastq.replace("@q1", ""))) + val noQualHeader = new BufferedReader(new StringReader(fastq.replace("+q1", ""))) + an[Exception] shouldBe thrownBy { new FastqReader(noSeqHeader).iterator().toSeq } + an[Exception] shouldBe thrownBy { new FastqReader(noQualHeader).iterator().toSeq } + } + +} From 4fa89b16b8a836c8e3c52c24cfa07ddd55e78776 Mon Sep 17 00:00:00 2001 From: Yossi Farjoun Date: Tue, 18 Apr 2017 14:38:58 -0400 Subject: [PATCH 19/59] =?UTF-8?q?Adding=20a=20pre-test=20check=20that=20lo?= =?UTF-8?q?oks=20for=20java=20files=20in=20the=20test/scala=20d=E2=80=A6?= =?UTF-8?q?=20(#857)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding a pre-test check that looks for java files in the test/scala directory and for scala files anywhere else (and fails if it finds either of these) --- build.gradle | 7 ++++++- scripts/checkScalaAndJavaFiles.sh | 17 +++++++++++++++++ .../scala/htsjdk/samtools/util/StringUtilTest.scala | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100755 scripts/checkScalaAndJavaFiles.sh diff --git a/build.gradle b/build.gradle index 9760a79fa..a60afe5f5 100644 --- a/build.gradle +++ b/build.gradle @@ -81,6 +81,11 @@ tasks.withType(Test) { task -> task.jvmArgs '-Djava.awt.headless=true' //this prevents awt from displaying a java icon while the tests are running } +task findScalaAndJavaTypes(type: Exec) { + description = "Check that Scala files only exist in the scala test dir and that java files do not reside in the scala test dir." + commandLine './scripts/checkScalaAndJavaFiles.sh' +} + test { description = "Runs the unit tests other than the SRA tests" @@ -98,7 +103,7 @@ test { if (System.env.CI == "false") exclude "sra" if (!OperatingSystem.current().isUnix()) exclude "unix" } -} +} dependsOn findScalaAndJavaTypes task testSRA(type: Test) { description = "Run the SRA tests" diff --git a/scripts/checkScalaAndJavaFiles.sh b/scripts/checkScalaAndJavaFiles.sh new file mode 100755 index 000000000..adadfd31c --- /dev/null +++ b/scripts/checkScalaAndJavaFiles.sh @@ -0,0 +1,17 @@ +#/bin/bash + +# Check that Scala files only exist in the scala test dir and +# that java files do not reside in the scala test dir + +if `find src | grep -v '^src/test/scala' | grep -q '\.scala$' ` ; then + echo 'Found scala file(s) outside of scala test directory'; + find src | grep -v '^src/test/scala' | grep '\.scala$' + exit 1; +fi + +if `find src/test/scala | grep -q '\.java$' ` ; then + echo 'Found java file(s) in scala test directory'; + find src/test/scala | grep '\.java$' + exit 1; +fi + diff --git a/src/test/scala/htsjdk/samtools/util/StringUtilTest.scala b/src/test/scala/htsjdk/samtools/util/StringUtilTest.scala index 6962e3674..35957d681 100644 --- a/src/test/scala/htsjdk/samtools/util/StringUtilTest.scala +++ b/src/test/scala/htsjdk/samtools/util/StringUtilTest.scala @@ -106,7 +106,7 @@ class StringUtilTest extends UnitSpec { StringUtil.assertCharactersNotInString("HelloWorld", ' ', '!', '_') } - val textForWrapping = + val textForWrapping: String = """This is a little bit |of text with nice short |lines. From 78da17516d88e75b6874f69e5542f3625501bd98 Mon Sep 17 00:00:00 2001 From: Louis Bergelson Date: Tue, 18 Apr 2017 14:57:10 -0400 Subject: [PATCH 20/59] removing scripts/release_picard.sh (#858) deleting scripts/release_picard.sh since this script is no longer used --- scripts/release_picard.sh | 152 ---------------------------------------------- 1 file changed, 152 deletions(-) delete mode 100755 scripts/release_picard.sh diff --git a/scripts/release_picard.sh b/scripts/release_picard.sh deleted file mode 100755 index 732234ab1..000000000 --- a/scripts/release_picard.sh +++ /dev/null @@ -1,152 +0,0 @@ -#! /bin/bash - -# The MIT License -# -# Copyright (c) $today.year The Broad Institute -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - -PROGNAME=`basename $0` -USERNAME=alecw - -function usage () { - echo "USAGE: $PROGNAME " >&2 - echo "Tags Github Picard source, checks out and builds sources, uploads build results to Sourceforge.">&2 - echo "-t Build in . Default: $TMPDIR." >&2 - echo "-u Sourceforge username. Default: $USERNAME." >&2 -} - -function tag_exists() { - git tag | grep -q "$1$" - if test $? = 0 - then return 0 - else return 1 - fi -} - -function remote_does_not_exist() { - git ls-remote $1 2>/dev/null 1>/dev/null - if test $? = 0 - then return 1 - else return 0 - fi -} - -function remote_tag_does_not_exist() { - git ls-remote --tags $2 | grep -q "$1$"; - if test $? = 0 - then return 0 - else return 1 - fi -} - -set -e - -while getopts "ht:u:" options; do - case $options in - u ) USERNAME=$OPTARG;; - t ) TMPDIR=$OPTARG;; - h ) usage;; - \? ) usage - exit 1;; - * ) usage - exit 1;; - - esac -done -shift $(($OPTIND - 1)) - -if (( $# != 1 )) - then echo "ERROR: Incorrect number of arguments." >&2 - usage - exit 1 -fi - -if [[ x"$EDITOR" == x ]] -then echo "EDITOR environment variable must be set." >&2 - exit 1 -fi - -# Require actual Java 1.6. This is not necessary for compiling, because can run 1.7 with -target 1.6, -# but this is necessary in order to force unit tests to run with 1.6. -(echo $JAVA_HOME | fgrep -q 1.6 ) || { echo "JAVA_HOME $JAVA_HOME is not 1.6" ; exit 1; } -java_version=`java -version 2>&1 | fgrep -i version` -(echo $java_version | fgrep -q 1.6. ) || { echo "java -version: $java_version is not 1.6"; exit 1; } - -GITROOT=git@github.com:samtools/htsjdk.git -REMOTE=origin - -RELEASE_ID=$1 - -# Since releases are lexically sorted, need to filter in order to have 1.1xx be at the bottom. -PREV_RELEASE_ID=`git ls-remote --tags | grep -v "{}$" | awk '{print $2}' | sed -e "s_.*/__g" | egrep '[.]\d\d\d' | tail -1` - -if [[ -e $TMPDIR/htsjdk ]] -then echo "$TMPDIR/htsjdk already exists. Please remove or specify a different TMPDIR." >&2 - exit 1 -fi -cd $TMPDIR - -# clone -git clone $GITROOT htsjdk -cd htsjdk -ant clean # Shouldn't be necessary, but no harm - -# tag must not exist -if tag_exists $RELEASE_ID -then echo "ERROR: Tag $RELEASE_ID locally already exists" - exit 1 -fi - -# remote must exist -if remote_does_not_exist $REMOTE -then echo "ERROR: Remote $REMOTE does not exist" - exit 1 -fi - -# tag at remote must not exist -if remote_tag_does_not_exist $RELEASE_ID $REMOTE -then echo "ERROR: Tag $RELEASE_ID at remote $REMOTE already exists" - exit 1 -fi - -# tag the branch locally then push to remote -echo Tagging master as $tag and pushing the tag to $remote -# NB: we could use annotated tags in the future to store release notes, etc. -git tag $tag -git push $remote $tag # TODO: should we check this return value in case someone made a tag since we last checked? - -ant -lib lib/ant test - -ant -lib lib/ant clean all javadoc - -mkdir -p deploy/picard-tools/$RELEASE_ID - -mkdir -p deploy/htsjdk/$RELEASE_ID -cp dist/htsjdk-$RELEASE_ID.jar deploy/htsjdk/$RELEASE_ID/ - -# Make all files to be pushed to Sourceforge writable by group so that another Picard admin can overwrite them. - -chmod -R gu+rw javadoc deploy dist - -find javadoc deploy dist -type d -exec chmod g+s '{}' ';' - -scp -p -r javadoc $USERNAME,picard@web.sourceforge.net:htdocs - -cd deploy -scp -p -r htsjdk/$RELEASE_ID $USERNAME,picard@web.sourceforge.net:/home/frs/project/p/pi/picard/htsjdk/ From da2b76b95fa806c4d19a28ff47e879b0432d22dd Mon Sep 17 00:00:00 2001 From: Tim Fennell Date: Tue, 18 Apr 2017 17:26:59 -0400 Subject: [PATCH 21/59] Added the ability to query the length of the line terminator to AsciiLineReader. (#843) --- .../htsjdk/tribble/readers/AsciiLineReader.java | 20 ++++++++++++-- .../tribble/readers/AsciiLineReaderTest.java | 32 ++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/main/java/htsjdk/tribble/readers/AsciiLineReader.java b/src/main/java/htsjdk/tribble/readers/AsciiLineReader.java index ad66a17c7..39c7d409c 100644 --- a/src/main/java/htsjdk/tribble/readers/AsciiLineReader.java +++ b/src/main/java/htsjdk/tribble/readers/AsciiLineReader.java @@ -40,8 +40,9 @@ private static final byte LINEFEED = (byte) ('\n' & 0xff); private static final byte CARRIAGE_RETURN = (byte) ('\r' & 0xff); - PositionalBufferedStream is; - char[] lineBuffer; + private final PositionalBufferedStream is; + private char[] lineBuffer; + private int lineTerminatorLength = -1; public AsciiLineReader(final InputStream is){ this(new PositionalBufferedStream(is)); @@ -65,6 +66,16 @@ public long getPosition(){ return is.getPosition(); } + /** Returns the length of the line terminator read after the last read line. Returns either: + * -1 if no line has been read + * 0 after the last line if the last line in the file had no CR or LF line ending + * 1 if the line ended with CR or LF + * 2 if the line ended with CR and LF + */ + public int getLineTerminatorLength() { + return this.lineTerminatorLength; + } + /** * Read a line of text. A line is considered to be terminated by any one * of a line feed ('\n'), a carriage return ('\r'), or a carriage return @@ -83,6 +94,7 @@ public final String readLine(final PositionalBufferedStream stream) throws IOExc if (b == -1) { // eof reached. Return the last line, or null if this is a new line if (linePosition > 0) { + this.lineTerminatorLength = 0; return new String(lineBuffer, 0, linePosition); } else { return null; @@ -93,6 +105,10 @@ public final String readLine(final PositionalBufferedStream stream) throws IOExc if (c == LINEFEED || c == CARRIAGE_RETURN) { if (c == CARRIAGE_RETURN && stream.peek() == LINEFEED) { stream.read(); // <= skip the trailing \n in case of \r\n termination + this.lineTerminatorLength = 2; + } + else { + this.lineTerminatorLength = 1; } return new String(lineBuffer, 0, linePosition); diff --git a/src/test/java/htsjdk/tribble/readers/AsciiLineReaderTest.java b/src/test/java/htsjdk/tribble/readers/AsciiLineReaderTest.java index b4aa6ba2c..b0a8de371 100644 --- a/src/test/java/htsjdk/tribble/readers/AsciiLineReaderTest.java +++ b/src/test/java/htsjdk/tribble/readers/AsciiLineReaderTest.java @@ -2,10 +2,10 @@ import htsjdk.HtsjdkTest; import htsjdk.tribble.TestUtils; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; +import org.testng.Assert; import org.testng.annotations.Test; +import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.InputStream; @@ -40,4 +40,32 @@ public void testReadLines() throws Exception { assertEquals(expectedNumber, actualLines); } + + @Test public void voidTestLineEndingLength() throws Exception { + final String input = "Hello\nThis\rIs A Silly Test\r\nSo There"; + final InputStream is = new ByteArrayInputStream(input.getBytes()); + final AsciiLineReader in = new AsciiLineReader(is); + + Assert.assertEquals(in.getLineTerminatorLength(), -1); + Assert.assertEquals(in.readLine(), "Hello"); + Assert.assertEquals(in.getLineTerminatorLength(), 1); + Assert.assertEquals(in.readLine(), "This"); + Assert.assertEquals(in.getLineTerminatorLength(), 1); + Assert.assertEquals(in.readLine(), "Is A Silly Test"); + Assert.assertEquals(in.getLineTerminatorLength(), 2); + Assert.assertEquals(in.readLine(), "So There"); + Assert.assertEquals(in.getLineTerminatorLength(), 0); + } + + @Test public void voidTestLineEndingLengthAtEof() throws Exception { + final String input = "Hello\nWorld\r\n"; + final InputStream is = new ByteArrayInputStream(input.getBytes()); + final AsciiLineReader in = new AsciiLineReader(is); + + Assert.assertEquals(in.getLineTerminatorLength(), -1); + Assert.assertEquals(in.readLine(), "Hello"); + Assert.assertEquals(in.getLineTerminatorLength(), 1); + Assert.assertEquals(in.readLine(), "World"); + Assert.assertEquals(in.getLineTerminatorLength(), 2); + } } From 6fb6cca3be85cc0551ab1ce0ba367313c7320fe0 Mon Sep 17 00:00:00 2001 From: Ron Levine Date: Fri, 14 Apr 2017 22:12:06 -0400 Subject: [PATCH 22/59] Count the number of warnings and errors during SAM file validation --- .../java/htsjdk/samtools/SamFileValidator.java | 36 +++++++++++++++++++++- .../java/htsjdk/samtools/ValidateSamFileTest.java | 12 +++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/main/java/htsjdk/samtools/SamFileValidator.java b/src/main/java/htsjdk/samtools/SamFileValidator.java index 5f82bd194..d0b745e7f 100644 --- a/src/main/java/htsjdk/samtools/SamFileValidator.java +++ b/src/main/java/htsjdk/samtools/SamFileValidator.java @@ -96,6 +96,8 @@ private boolean bisulfiteSequenced; private IndexValidationStringency indexValidationStringency; private boolean sequenceDictionaryEmptyAndNoWarningEmitted; + private int numWarnings; + private int numErrors; private final int maxTempFiles; @@ -111,6 +113,8 @@ public SamFileValidator(final PrintWriter out, final int maxTempFiles) { this.ignoreWarnings = false; this.bisulfiteSequenced = false; this.sequenceDictionaryEmptyAndNoWarningEmitted = false; + this.numWarnings = 0; + this.numErrors = 0; } Histogram getErrorsByType() { @@ -566,11 +570,41 @@ private void validateHeader(final SAMFileHeader fileHeader) { } } + /** + * Number of warnings during SAM file validation + * + * @return number of warnings + */ + public int getNumWarnings() { + return this.numWarnings; + } + + /** + * Number of errors during SAM file validation + * + * @return number of errors + */ + public int getNumErrors() { + return this.numErrors; + } + private void addError(final SAMValidationError error) { // Just ignore an error if it's of a type we're not interested in if (this.errorsToIgnore.contains(error.getType())) return; - if (this.ignoreWarnings && error.getType().severity == SAMValidationError.Severity.WARNING) return; + switch (error.getType().severity) { + case WARNING: + if ( this.ignoreWarnings ) { + return; + } + this.numWarnings++; + break; + case ERROR: + this.numErrors++; + break; + default: + throw new SAMException("Unknown SAM validation error severity: " + error.getType().severity); + } this.errorsByType.increment(error.getType()); if (verbose) { diff --git a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java index f227332d2..16bd6e1ce 100644 --- a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java +++ b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java @@ -119,7 +119,9 @@ public void testVerbose() throws IOException { validator.validateSamFileVerbose(samBuilder.getSamReader(), null); final int lineCount = results.toString().split("\n").length; - Assert.assertEquals(lineCount, 11); + Assert.assertEquals(lineCount, 11); // 1 extra message added to indicate maximum number of errors + Assert.assertEquals(validator.getNumErrors(), 6); + Assert.assertEquals(validator.getNumWarnings(), 4); } @Test @@ -522,16 +524,18 @@ public void tagCorrectlyProcessTest(byte[] bytesFromFile, @DataProvider(name = "validateBamFileTerminationData") public Object[][] validateBamFileTerminationData() throws IOException { return new Object[][]{ - {getBrokenFile(TERMINATION_GZIP_BLOCK_SIZE), SAMValidationError.Type.BAM_FILE_MISSING_TERMINATOR_BLOCK}, - {getBrokenFile(RANDOM_NUMBER_TRUNC_BYTE), SAMValidationError.Type.TRUNCATED_FILE} + {getBrokenFile(TERMINATION_GZIP_BLOCK_SIZE), SAMValidationError.Type.BAM_FILE_MISSING_TERMINATOR_BLOCK, 1, 0}, + {getBrokenFile(RANDOM_NUMBER_TRUNC_BYTE), SAMValidationError.Type.TRUNCATED_FILE, 0, 1} }; } @Test(dataProvider = "validateBamFileTerminationData") - public void validateBamFileTerminationTest(File file, SAMValidationError.Type errorType) throws IOException { + public void validateBamFileTerminationTest(final File file, final SAMValidationError.Type errorType, final int numWarnings, final int numErrors) throws IOException { final SamFileValidator samFileValidator = new SamFileValidator(new PrintWriter(System.out), 8000); samFileValidator.validateBamFileTermination(file); Assert.assertEquals(samFileValidator.getErrorsByType().get(errorType).getValue(), 1.0); + Assert.assertEquals(samFileValidator.getNumWarnings(), numWarnings); + Assert.assertEquals(samFileValidator.getNumErrors(), numErrors); } private Histogram executeValidation(final SamReader samReader, final ReferenceSequenceFile reference, From ee21d8144dbc6d1fa08120d2316be4237f8bfa47 Mon Sep 17 00:00:00 2001 From: Nils Homer Date: Thu, 20 Apr 2017 14:14:31 -0400 Subject: [PATCH 23/59] Add fromPath to IntervalList. (#846) * Add fromPath to IntervalList. --- src/main/java/htsjdk/samtools/util/IOUtil.java | 7 ++++++- src/main/java/htsjdk/samtools/util/IntervalList.java | 14 ++++++++++++-- src/test/java/htsjdk/samtools/util/IntervalListTest.java | 12 ++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/htsjdk/samtools/util/IOUtil.java b/src/main/java/htsjdk/samtools/util/IOUtil.java index 0903a09f0..373bd5f24 100644 --- a/src/main/java/htsjdk/samtools/util/IOUtil.java +++ b/src/main/java/htsjdk/samtools/util/IOUtil.java @@ -778,9 +778,14 @@ public static File createTempDir(final String prefix, final String suffix) { /** Checks that a file exists and is readable, and then returns a buffered reader for it. */ public static BufferedReader openFileForBufferedReading(final File file) { - return new BufferedReader(new InputStreamReader(openFileForReading(file)), Defaults.NON_ZERO_BUFFER_SIZE); + return openFileForBufferedReading(file.toPath()); } + /** Checks that a path exists and is readable, and then returns a buffered reader for it. */ + public static BufferedReader openFileForBufferedReading(final Path path) { + return new BufferedReader(new InputStreamReader(openFileForReading(path)), Defaults.NON_ZERO_BUFFER_SIZE); + } + /** Takes a string and replaces any characters that are not safe for filenames with an underscore */ public static String makeFileNameSafe(final String str) { return str.trim().replaceAll("[\\s!\"#$%&'()*/:;<=>?@\\[\\]\\\\^`{|}~]", "_"); diff --git a/src/main/java/htsjdk/samtools/util/IntervalList.java b/src/main/java/htsjdk/samtools/util/IntervalList.java index f9bfe05ca..929671a84 100644 --- a/src/main/java/htsjdk/samtools/util/IntervalList.java +++ b/src/main/java/htsjdk/samtools/util/IntervalList.java @@ -34,6 +34,7 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -395,12 +396,21 @@ public static IntervalList copyOf(final IntervalList list){ * @return an IntervalList object that contains the headers and intervals from the file */ public static IntervalList fromFile(final File file) { - final BufferedReader reader= IOUtil.openFileForBufferedReading(file); + return fromPath(file.toPath()); + } + + /** + * Parses an interval list from a path. + * @param path the path containing the intervals + * @return an IntervalList object that contains the headers and intervals from the path + */ + public static IntervalList fromPath(final Path path) { + final BufferedReader reader = IOUtil.openFileForBufferedReading(path); final IntervalList list = fromReader(reader); try { reader.close(); } catch (final IOException e) { - throw new SAMException(String.format("Failed to close file %s after reading",file)); + throw new SAMException(String.format("Failed to close file %s after reading", path.toUri().toString())); } return list; diff --git a/src/test/java/htsjdk/samtools/util/IntervalListTest.java b/src/test/java/htsjdk/samtools/util/IntervalListTest.java index 613afde45..983820bbe 100644 --- a/src/test/java/htsjdk/samtools/util/IntervalListTest.java +++ b/src/test/java/htsjdk/samtools/util/IntervalListTest.java @@ -26,7 +26,9 @@ import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMFileHeader; +import htsjdk.samtools.SAMSequenceDictionary; import htsjdk.samtools.SAMSequenceRecord; +import htsjdk.samtools.SamFileHeaderMerger; import htsjdk.variant.vcf.VCFFileReader; import org.testng.Assert; import org.testng.annotations.BeforeTest; @@ -34,6 +36,7 @@ import org.testng.annotations.Test; import java.io.File; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -76,6 +79,15 @@ public IntervalListTest() { list3.add(new Interval("3", 50, 470)); } + @Test + public void testIntervalListFrom() { + final String testPath = "src/test/resources/htsjdk/samtools/intervallist/IntervalListFromVCFTestComp.interval_list"; + final IntervalList fromFileList = IntervalList.fromFile(new File(testPath)); + final IntervalList fromPathList = IntervalList.fromPath(Paths.get(testPath)); + fromFileList.getHeader().getSequenceDictionary().assertSameDictionary(fromPathList.getHeader().getSequenceDictionary()); + Assert.assertEquals(CollectionUtil.makeCollection(fromFileList.iterator()), CollectionUtil.makeCollection(fromPathList.iterator())); + } + @DataProvider(name = "intersectData") public Object[][] intersectData() { final IntervalList intersect123 = new IntervalList(fileHeader); From 854d7365539c63e39a4ada03ec7abbb408b12993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B3mez-S=C3=A1nchez?= Date: Tue, 25 Apr 2017 12:48:05 +0200 Subject: [PATCH 24/59] Add .fai index creation support (#842) --- .../samtools/reference/FastaSequenceIndex.java | 26 ++- .../reference/FastaSequenceIndexCreator.java | 180 +++++++++++++++++++++ .../reference/IndexedFastaSequenceFile.java | 8 +- .../reference/ReferenceSequenceFileFactory.java | 9 ++ .../reference/FastaSequenceIndexCreatorTest.java | 90 +++++++++++ .../samtools/reference/FastaSequenceIndexTest.java | 32 ++++ .../Homo_sapiens_assembly18.trimmed.fasta.gz | Bin 0 -> 335008 bytes .../Homo_sapiens_assembly18.trimmed.fasta.gz.fai | 2 + .../Homo_sapiens_assembly18.trimmed.fasta.gz.gzi | Bin 0 -> 264 bytes .../resources/htsjdk/samtools/reference/crlf.fasta | 4 + .../htsjdk/samtools/reference/crlf.fasta.fai | 2 + .../reference/header_with_white_space.fasta | 4 + .../reference/header_with_white_space.fasta.fai | 2 + 13 files changed, 352 insertions(+), 7 deletions(-) create mode 100644 src/main/java/htsjdk/samtools/reference/FastaSequenceIndexCreator.java create mode 100644 src/test/java/htsjdk/samtools/reference/FastaSequenceIndexCreatorTest.java create mode 100644 src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz create mode 100644 src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz.fai create mode 100644 src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz.gzi create mode 100644 src/test/resources/htsjdk/samtools/reference/crlf.fasta create mode 100644 src/test/resources/htsjdk/samtools/reference/crlf.fasta.fai create mode 100644 src/test/resources/htsjdk/samtools/reference/header_with_white_space.fasta create mode 100644 src/test/resources/htsjdk/samtools/reference/header_with_white_space.fasta.fai diff --git a/src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java b/src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java index 9ae9f1db9..3668fe671 100644 --- a/src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java +++ b/src/main/java/htsjdk/samtools/reference/FastaSequenceIndex.java @@ -31,6 +31,9 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; import java.util.LinkedHashMap; @@ -39,7 +42,7 @@ import java.util.regex.MatchResult; /** - * Reads a fasta index file (.fai), as generated by `samtools faidx`. + * Reads/writes a fasta index file (.fai), as generated by `samtools faidx`. */ public class FastaSequenceIndex implements Iterable { /** @@ -159,6 +162,27 @@ private void parseIndexFile(Path indexFile) { } /** + * Writes this index to the specified path. + * + * @param indexFile index file to output the index in the .fai format + * + * @throws IOException if an IO error occurs. + */ + public void write(final Path indexFile) throws IOException { + try (final PrintStream writer = new PrintStream(Files.newOutputStream(indexFile))) { + sequenceEntries.values().forEach(se -> + writer.println(String.join("\t", + se.getContig(), + String.valueOf(se.getSize()), + String.valueOf(se.getLocation()), + String.valueOf(se.getBasesPerLine()), + String.valueOf(se.getBytesPerLine())) + ) + ); + } + } + + /** * Does the given contig name have a corresponding entry? * @param contigName The contig name for which to search. * @return True if contig name is present; false otherwise. diff --git a/src/main/java/htsjdk/samtools/reference/FastaSequenceIndexCreator.java b/src/main/java/htsjdk/samtools/reference/FastaSequenceIndexCreator.java new file mode 100644 index 000000000..ee425ffd5 --- /dev/null +++ b/src/main/java/htsjdk/samtools/reference/FastaSequenceIndexCreator.java @@ -0,0 +1,180 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Daniel Gomez-Sanchez + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package htsjdk.samtools.reference; + +import htsjdk.samtools.SAMException; +import htsjdk.samtools.SAMSequenceRecord; +import htsjdk.samtools.util.IOUtil; +import htsjdk.tribble.readers.AsciiLineReader; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Static methods to create an {@link FastaSequenceIndex}. + * + * @author Daniel Gomez-Sanchez (magicDGS) + */ +public final class FastaSequenceIndexCreator { + + // cannot be instantiated because it is an utility class + private FastaSequenceIndexCreator() {} + + /** + * Creates a FASTA .fai index for the provided FASTA. + * + * @param fastaFile the file to build the index from. + * @param overwrite if the .fai index already exists override it if {@code true}; otherwise, throws a {@link SAMException}. + * + * @throws SAMException if the fai file already exists or the file is malformed. + * @throws IOException if an IO error occurs. + */ + public static void create(final Path fastaFile, final boolean overwrite) throws IOException { + // get the index to write the file in + final Path indexFile = ReferenceSequenceFileFactory.getFastaIndexFileName(fastaFile); + if (!overwrite && Files.exists(indexFile)) { + // throw an exception if the file already exists + throw new SAMException("Index file " + indexFile + " already exists for " + fastaFile); + } + // build the index + final FastaSequenceIndex index = buildFromFasta(fastaFile); + index.write(indexFile); + } + + /** + * Builds a FastaSequenceIndex on the fly from a FASTA file. + * + * Note: this also alows to create an index for a compressed file, but does not generate the + * .gzi index required for use it with samtools. + * + * @param fastaFile the FASTA file. + * + * @return a fai index. + * + * @throws SAMException for formatting errors. + * @throws IOException if an IO error occurs. + */ + public static FastaSequenceIndex buildFromFasta(final Path fastaFile) throws IOException { + try(final AsciiLineReader in = new AsciiLineReader(IOUtil.openFileForReading(fastaFile))) { + + // sanity check reference format: + // 1. Non-empty file + // 2. Header name starts with > + String previous = in.readLine(); + if (previous == null) { + throw new SAMException("Cannot index empty file: " + fastaFile); + } else if (previous.charAt(0) != '>') { + throw new SAMException("Wrong sequence header: " + previous); + } + + // initialize the sequence index + int sequenceIndex = -1; + // the location should be kept before iterating over the rest of the lines + long location = in.getPosition(); + + // initialize an empty index and the entry builder to null + final FastaSequenceIndex index = new FastaSequenceIndex(); + FaiEntryBuilder entry = null; + + // read the lines two by two + for (String line = in.readLine(); previous != null; line = in.readLine()) { + // in this case, the previous line contains a header and the current line the first sequence + if (previous.charAt(0) == '>') { + // first entry should be skipped; otherwise it should be added to the index + if (entry != null) index.add(entry.build()); + // creates a new entry (and update sequence index) + entry = new FaiEntryBuilder(sequenceIndex++, previous, line, in.getLineTerminatorLength(), location); + } else if (line != null && line.charAt(0) == '>') { + // update the location, next iteration the sequence will be handled + location = in.getPosition(); + } else if (line != null && !line.isEmpty()) { + // update in case it is not a blank-line + entry.updateWithSequence(line, in.getLineTerminatorLength()); + } + // set the previous to the current line + previous = line; + } + // add the last entry + index.add(entry.build()); + + // and return the index + return index; + } + } + + // utility class for building the FastaSequenceIndexEntry + private static class FaiEntryBuilder { + private final int index; + private final String contig; + private final long location; + // the bytes per line is the bases per line plus the length of the end of the line + private final int basesPerLine; + private final int endOfLineLength; + + // the size is updated for each line in the input using updateWithSequence + private long size; + // flag to check if the supposedly last line was already reached + private boolean lessBasesFound; + + private FaiEntryBuilder(final int index, final String header, final String firstSequenceLine, final int endOfLineLength, final long location) { + if (header == null || header.charAt(0) != '>') { + throw new SAMException("Wrong sequence header: " + header); + } else if (firstSequenceLine == null) { + throw new SAMException("Empty sequences could not be indexed"); + } + this.index = index; + // parse the contig name (without the starting '>' and truncating white-spaces) + this.contig = SAMSequenceRecord.truncateSequenceName(header.substring(1).trim()); + this.location = location; + this.basesPerLine = firstSequenceLine.length(); + this.endOfLineLength = endOfLineLength; + this.size = firstSequenceLine.length(); + this.lessBasesFound = false; + } + + private void updateWithSequence(final String sequence, final int endOfLineLength) { + if (this.endOfLineLength != endOfLineLength) { + throw new SAMException(String.format("Different end of line for the same sequence was found.")); + } + if (sequence.length() > basesPerLine) { + throw new SAMException(String.format("Sequence line for {} was longer than the expected length ({}): {}", + contig, basesPerLine, sequence)); + } else if (sequence.length() < basesPerLine) { + if (lessBasesFound) { + throw new SAMException(String.format("Only last line could have less than {} bases for '{}' sequence, but at least two are different. Last sequence line: {}", + basesPerLine, contig, sequence)); + } + lessBasesFound = true; + } + // update size + this.size += sequence.length(); + } + + private FastaSequenceIndexEntry build() { + return new FastaSequenceIndexEntry(contig, location, size, basesPerLine, basesPerLine + endOfLineLength, index); + } + } +} diff --git a/src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java b/src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java index 5a8703381..5c318782e 100644 --- a/src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java +++ b/src/main/java/htsjdk/samtools/reference/IndexedFastaSequenceFile.java @@ -136,18 +136,14 @@ public static boolean canCreateIndexedFastaReader(final File fastaFile) { } private static Path findFastaIndex(Path fastaFile) { - Path indexFile = getFastaIndexFileName(fastaFile); + Path indexFile = ReferenceSequenceFileFactory.getFastaIndexFileName(fastaFile); if (!Files.exists(indexFile)) return null; return indexFile; } - private static Path getFastaIndexFileName(Path fastaFile) { - return fastaFile.resolveSibling(fastaFile.getFileName() + ".fai"); - } - private static Path findRequiredFastaIndexFile(Path fastaFile) throws FileNotFoundException { Path ret = findFastaIndex(fastaFile); - if (ret == null) throw new FileNotFoundException(getFastaIndexFileName(fastaFile) + " not found."); + if (ret == null) throw new FileNotFoundException(ReferenceSequenceFileFactory.getFastaIndexFileName(fastaFile) + " not found."); return ret; } diff --git a/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileFactory.java b/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileFactory.java index 2b0b1e7fc..654706819 100644 --- a/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileFactory.java +++ b/src/main/java/htsjdk/samtools/reference/ReferenceSequenceFileFactory.java @@ -163,4 +163,13 @@ public static String getFastaExtension(final Path path) { .orElseGet(() -> {throw new IllegalArgumentException("File is not a supported reference file type: " + path.toAbsolutePath());}); } + /** + * Returns the index name for a FASTA file. + * + * @param fastaFile the reference sequence file path. + */ + public static Path getFastaIndexFileName(Path fastaFile) { + return fastaFile.resolveSibling(fastaFile.getFileName() + ".fai"); + } + } diff --git a/src/test/java/htsjdk/samtools/reference/FastaSequenceIndexCreatorTest.java b/src/test/java/htsjdk/samtools/reference/FastaSequenceIndexCreatorTest.java new file mode 100644 index 000000000..193770abd --- /dev/null +++ b/src/test/java/htsjdk/samtools/reference/FastaSequenceIndexCreatorTest.java @@ -0,0 +1,90 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Daniel Gomez-Sanchez + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package htsjdk.samtools.reference; + +import htsjdk.HtsjdkTest; +import htsjdk.samtools.util.IOUtil; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.File; +import java.nio.file.Files; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Daniel Gomez-Sanchez (magicDGS) + */ +public class FastaSequenceIndexCreatorTest extends HtsjdkTest { + private static File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools/reference"); + + + @DataProvider(name = "indexedSequences") + public Object[][] getIndexedSequences() { + return new Object[][]{ + {new File(TEST_DATA_DIR, "Homo_sapiens_assembly18.trimmed.fasta")}, + {new File(TEST_DATA_DIR, "Homo_sapiens_assembly18.trimmed.fasta.gz")}, + {new File(TEST_DATA_DIR, "header_with_white_space.fasta")}, + {new File(TEST_DATA_DIR, "crlf.fasta")} + }; + } + + @Test(dataProvider = "indexedSequences") + public void testBuildFromFasta(final File indexedFile) throws Exception { + final FastaSequenceIndex original = new FastaSequenceIndex(new File(indexedFile.getAbsolutePath() + ".fai")); + final FastaSequenceIndex build = FastaSequenceIndexCreator.buildFromFasta(indexedFile.toPath()); + Assert.assertEquals(original, build); + } + + @Test(dataProvider = "indexedSequences") + public void testCreate(final File indexedFile) throws Exception { + // copy the file to index + final File tempDir = IOUtil.createTempDir("FastaSequenceIndexCreatorTest", "testCreate"); + final File copied = new File(tempDir, indexedFile.getName()); + copied.deleteOnExit(); + Files.copy(indexedFile.toPath(), copied.toPath()); + + // create the index for the copied file + FastaSequenceIndexCreator.create(copied.toPath(), false); + + // test if the expected .fai and the created one are the same + final File expectedFai = new File(indexedFile.getAbsolutePath() + ".fai"); + final File createdFai = new File(copied.getAbsolutePath() + ".fai"); + + // read all the files and compare line by line + try(final Stream expected = Files.lines(expectedFai.toPath()); + final Stream created = Files.lines(createdFai.toPath())) { + final List expectedLines = expected.filter(String::isEmpty).collect(Collectors.toList()); + final List createdLines = created.filter(String::isEmpty).collect(Collectors.toList()); + Assert.assertEquals(expectedLines, createdLines); + } + + // load the tmp index and check that both are the same + Assert.assertEquals(new FastaSequenceIndex(createdFai), new FastaSequenceIndex(expectedFai)); + } + +} \ No newline at end of file diff --git a/src/test/java/htsjdk/samtools/reference/FastaSequenceIndexTest.java b/src/test/java/htsjdk/samtools/reference/FastaSequenceIndexTest.java index e24e28874..c6fa1384a 100644 --- a/src/test/java/htsjdk/samtools/reference/FastaSequenceIndexTest.java +++ b/src/test/java/htsjdk/samtools/reference/FastaSequenceIndexTest.java @@ -30,9 +30,15 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileReader; +import java.nio.file.Files; import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Test the fasta sequence index reader. @@ -254,4 +260,30 @@ public void testSpecialCharacters(FastaSequenceIndex specialCharactersIndex) { Assert.assertEquals(ent.getBasesPerLine(),70,"Contig file:gi|17981852|ref|NC_001807.4| bases per line is not correct"); Assert.assertEquals(ent.getBytesPerLine(),71,"Contig file:gi|17981852|ref|NC_001807.4| bytes per line is not correct"); } + + @Test + public void testWrite() throws Exception { + // gets the original file and index + final File originalFile = new File(TEST_DATA_DIR, "testing.fai"); + final FastaSequenceIndex originalIndex = new FastaSequenceIndex(originalFile); + + // write the index to a temp file and test if files are the same + final File fileToWrite = File.createTempFile("testing.toWrite", "fai"); + fileToWrite.deleteOnExit(); + originalIndex.write(fileToWrite.toPath()); + + // read all the files and compare line by line + try(final Stream original = Files.lines(originalFile.toPath()); + final Stream written = Files.lines(fileToWrite.toPath())) { + final List originalLines = original.filter(s -> ! s.isEmpty()).collect(Collectors.toList()); + final List actualLines = written.filter(s -> !s.isEmpty()).collect(Collectors.toList()); + Assert.assertEquals(actualLines, originalLines); + } + + // load the tmp index and check that both are the same + final FastaSequenceIndex writtenIndex = new FastaSequenceIndex(fileToWrite); + Assert.assertEquals(writtenIndex, originalIndex); + } + + } diff --git a/src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz b/src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz new file mode 100644 index 0000000000000000000000000000000000000000..aa8ef591b0750b572d7255bfa43ba44504b9f1a7 GIT binary patch literal 335008 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¨>F(s)c;}L1L$R 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 Dnj|g7m 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 I000000M+8LNdN!< literal 0 HcmV?d00001 diff --git a/src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz.fai b/src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz.fai new file mode 100644 index 000000000..04a438b94 --- /dev/null +++ b/src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz.fai @@ -0,0 +1,2 @@ +chrM 16571 6 60 61 +chr20 1000000 16861 60 61 diff --git a/src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz.gzi b/src/test/resources/htsjdk/samtools/reference/Homo_sapiens_assembly18.trimmed.fasta.gz.gzi new file mode 100644 index 0000000000000000000000000000000000000000..a536602330c535046c98712ee884727974910ba2 GIT binary patch literal 264 zcmWe&fB=0jDC0kbzBmWU|HlYs$b5kE|1yCY4$cs9hCj?;{*ozB{%;mAKj1l(|BDsO zx3q-lWBADi=AZ0_@_(>{`Azqs{O=rK{tIJ>xeVVp!Fdbr%(v8m j*vs&l2h6|J0Ofz;1@jxPLir#0!2A{&h`TI~@`L#Ra!DY2 literal 0 HcmV?d00001 diff --git a/src/test/resources/htsjdk/samtools/reference/crlf.fasta b/src/test/resources/htsjdk/samtools/reference/crlf.fasta new file mode 100644 index 000000000..70c878536 --- /dev/null +++ b/src/test/resources/htsjdk/samtools/reference/crlf.fasta @@ -0,0 +1,4 @@ +>a test CR+LF +ACTG +>b test CR+LF +ACTG diff --git a/src/test/resources/htsjdk/samtools/reference/crlf.fasta.fai b/src/test/resources/htsjdk/samtools/reference/crlf.fasta.fai new file mode 100644 index 000000000..923386e04 --- /dev/null +++ b/src/test/resources/htsjdk/samtools/reference/crlf.fasta.fai @@ -0,0 +1,2 @@ +a 4 15 4 6 +b 4 36 4 5 diff --git a/src/test/resources/htsjdk/samtools/reference/header_with_white_space.fasta b/src/test/resources/htsjdk/samtools/reference/header_with_white_space.fasta new file mode 100644 index 000000000..24cff02a9 --- /dev/null +++ b/src/test/resources/htsjdk/samtools/reference/header_with_white_space.fasta @@ -0,0 +1,4 @@ +>a test white space +ACTG +>b test whitespace +ACTG diff --git a/src/test/resources/htsjdk/samtools/reference/header_with_white_space.fasta.fai b/src/test/resources/htsjdk/samtools/reference/header_with_white_space.fasta.fai new file mode 100644 index 000000000..bb15aa584 --- /dev/null +++ b/src/test/resources/htsjdk/samtools/reference/header_with_white_space.fasta.fai @@ -0,0 +1,2 @@ +a 4 20 4 5 +b 4 44 4 5 From 909ac4d300e1d569534befd6b1ccf535e33bc0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B3mez-S=C3=A1nchez?= Date: Tue, 25 Apr 2017 22:28:50 +0200 Subject: [PATCH 25/59] More informative IO error for FASTQ reader (#865) --- src/main/java/htsjdk/samtools/fastq/FastqReader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/htsjdk/samtools/fastq/FastqReader.java b/src/main/java/htsjdk/samtools/fastq/FastqReader.java index d5d8f1889..76ead119d 100755 --- a/src/main/java/htsjdk/samtools/fastq/FastqReader.java +++ b/src/main/java/htsjdk/samtools/fastq/FastqReader.java @@ -136,7 +136,7 @@ private FastqRecord readNextRecord() { return frec ; } catch (IOException e) { - throw new SAMException(String.format("Error reading fastq '%s'", getAbsolutePath()), e); + throw new SAMException(error(e.getMessage()), e); } } @@ -177,7 +177,7 @@ public void close() { try { reader.close(); } catch (IOException e) { - throw new SAMException("IO problem in fastq file " + getAbsolutePath(), e); + throw new SAMException(error(e.getMessage()), e); } } From ad8aa02aa2ed02b9cb2bb8801c3e6897e91ec3f6 Mon Sep 17 00:00:00 2001 From: Yossi Farjoun Date: Tue, 2 May 2017 14:04:54 -0400 Subject: [PATCH 26/59] Yf add unknown so (#862) - added a "unknown" enum value to the SortOrder enum. It behaves exactly like unsorted, but the sam-spec allows for it. - wrapped getSortOrder() with a try-catch to translate any unknown values to 'unknown' - added an error for non-conforming tags. - added to the validation a check for both SO and GO to see that they are legal values. - added tests covering SO and GO tags --- src/main/java/htsjdk/samtools/SAMFileHeader.java | 75 ++++++++++++++-------- .../java/htsjdk/samtools/SAMTextHeaderCodec.java | 19 ++++++ .../java/htsjdk/samtools/SAMValidationError.java | 3 + .../java/htsjdk/samtools/ValidateSamFileTest.java | 16 ++++- .../cram/lossy/QualityScorePreservationTest.java | 4 +- 5 files changed, 87 insertions(+), 30 deletions(-) diff --git a/src/main/java/htsjdk/samtools/SAMFileHeader.java b/src/main/java/htsjdk/samtools/SAMFileHeader.java index b94598185..f2750d4cc 100644 --- a/src/main/java/htsjdk/samtools/SAMFileHeader.java +++ b/src/main/java/htsjdk/samtools/SAMFileHeader.java @@ -24,6 +24,8 @@ package htsjdk.samtools; +import htsjdk.samtools.util.CollectionUtil; +import htsjdk.samtools.util.Log; import htsjdk.samtools.util.StringLineReader; import java.io.StringWriter; @@ -47,14 +49,17 @@ public static final String SORT_ORDER_TAG = "SO"; public static final String GROUP_ORDER_TAG = "GO"; public static final String CURRENT_VERSION = "1.5"; - public static final Set ACCEPTABLE_VERSIONS = - new HashSet(Arrays.asList("1.0", "1.3", "1.4", "1.5")); + public static final Set ACCEPTABLE_VERSIONS = CollectionUtil.makeSet("1.0", "1.3", "1.4", "1.5"); + private SortOrder sortOrder = null; + private GroupOrder groupOrder = null; + + private static final Log log = Log.getInstance(SAMFileHeader.class); /** * These tags are of known type, so don't need a type field in the text representation. */ public static final Set STANDARD_TAGS = - new HashSet(Arrays.asList(VERSION_TAG, SORT_ORDER_TAG, GROUP_ORDER_TAG)); + new HashSet<>(Arrays.asList(VERSION_TAG, SORT_ORDER_TAG, GROUP_ORDER_TAG)); @Override Set getStandardTags() { @@ -65,11 +70,11 @@ * Ways in which a SAM or BAM may be sorted. */ public enum SortOrder { - unsorted(null), queryname(SAMRecordQueryNameComparator.class), coordinate(SAMRecordCoordinateComparator.class), - duplicate(SAMRecordDuplicateComparator.class); // NB: this is not in the SAM spec! + duplicate(SAMRecordDuplicateComparator.class), // NB: this is not in the SAM spec! + unknown(null); private final Class comparator; @@ -106,16 +111,14 @@ public SAMRecordComparator getComparatorInstance() { none, query, reference } - private List mReadGroups = - new ArrayList(); - private List mProgramRecords = new ArrayList(); - private final Map mReadGroupMap = - new HashMap(); - private final Map mProgramRecordMap = new HashMap(); + private List mReadGroups = new ArrayList<>(); + private List mProgramRecords = new ArrayList<>(); + private final Map mReadGroupMap = new HashMap<>(); + private final Map mProgramRecordMap = new HashMap<>(); private SAMSequenceDictionary mSequenceDictionary = new SAMSequenceDictionary(); - final private List mComments = new ArrayList(); + final private List mComments = new ArrayList<>(); private String textHeader; - private final List mValidationErrors = new ArrayList(); + private final List mValidationErrors = new ArrayList<>(); public SAMFileHeader() { setAttribute(VERSION_TAG, CURRENT_VERSION); @@ -128,11 +131,11 @@ public SAMFileHeader(final SAMSequenceDictionary dict) { } public String getVersion() { - return (String) getAttribute("VN"); + return getAttribute(VERSION_TAG); } public String getCreator() { - return (String) getAttribute("CR"); + return getAttribute("CR"); } public SAMSequenceDictionary getSequenceDictionary() { @@ -249,26 +252,47 @@ public SAMProgramRecord createProgramRecord() { } public SortOrder getSortOrder() { - final String so = getAttribute("SO"); - if (so == null || so.equals("unknown")) { - return SortOrder.unsorted; + if (sortOrder == null) { + final String so = getAttribute(SORT_ORDER_TAG); + if (so == null) { + sortOrder = SortOrder.unsorted; + } else { + try { + return SortOrder.valueOf(so); + } catch (IllegalArgumentException e) { + log.warn("Found non conforming header SO tag: " + so + ". Treating as 'unknown'."); + sortOrder = SortOrder.unknown; + } + } } - return SortOrder.valueOf((String) so); + return sortOrder; } public void setSortOrder(final SortOrder so) { - setAttribute("SO", so.name()); + sortOrder = so; + setAttribute(SORT_ORDER_TAG, so.name()); } public GroupOrder getGroupOrder() { - if (getAttribute("GO") == null) { - return GroupOrder.none; + if (groupOrder == null) { + final String go = getAttribute(GROUP_ORDER_TAG); + if (go == null) { + groupOrder = GroupOrder.none; + } else { + try { + return GroupOrder.valueOf(go); + } catch (IllegalArgumentException e) { + log.warn("Found non conforming header GO tag: " + go + ". Treating as 'none'."); + groupOrder = GroupOrder.none; + } + } } - return GroupOrder.valueOf((String)getAttribute("GO")); + return groupOrder; } public void setGroupOrder(final GroupOrder go) { - setAttribute("GO", go.name()); + groupOrder = go; + setAttribute(GROUP_ORDER_TAG, go.name()); } /** @@ -372,7 +396,7 @@ public String getSAMString() { public static class PgIdGenerator { private int recordCounter; - private final Set idsThatAreAlreadyTaken = new HashSet(); + private final Set idsThatAreAlreadyTaken = new HashSet<>(); public PgIdGenerator(final SAMFileHeader header) { for (final SAMProgramRecord pgRecord : header.getProgramRecords()) { @@ -400,7 +424,6 @@ public String getNonCollidingId(final String recordId) { idsThatAreAlreadyTaken.add(newId); return newId; } - } } } diff --git a/src/main/java/htsjdk/samtools/SAMTextHeaderCodec.java b/src/main/java/htsjdk/samtools/SAMTextHeaderCodec.java index 402ea3ce8..908e8360b 100644 --- a/src/main/java/htsjdk/samtools/SAMTextHeaderCodec.java +++ b/src/main/java/htsjdk/samtools/SAMTextHeaderCodec.java @@ -228,6 +228,25 @@ private void parseHDLine(final ParsedHeaderLine parsedHeaderLine) { if (!parsedHeaderLine.requireTag(SAMFileHeader.VERSION_TAG)) { return; } + + final String soString = parsedHeaderLine.getValue(SAMFileHeader.SORT_ORDER_TAG); + try { + if (soString != null) SAMFileHeader.SortOrder.valueOf(soString); + } catch (IllegalArgumentException e) { + reportErrorParsingLine(HEADER_LINE_START + parsedHeaderLine.getHeaderRecordType() + + " line has non-conforming SO tag value: "+ soString + ".", + SAMValidationError.Type.HEADER_TAG_NON_CONFORMING_VALUE, null); + } + + final String goString = parsedHeaderLine.getValue(SAMFileHeader.GROUP_ORDER_TAG); + try { + if (goString != null) SAMFileHeader.GroupOrder.valueOf(goString); + } catch (IllegalArgumentException e) { + reportErrorParsingLine(HEADER_LINE_START + parsedHeaderLine.getHeaderRecordType() + + " line has non-conforming GO tag value: "+ goString + ".", + SAMValidationError.Type.HEADER_TAG_NON_CONFORMING_VALUE, null); + } + transferAttributes(mFileHeader, parsedHeaderLine.mKeyValuePairs); } diff --git a/src/main/java/htsjdk/samtools/SAMValidationError.java b/src/main/java/htsjdk/samtools/SAMValidationError.java index d560b119e..452e92cf5 100644 --- a/src/main/java/htsjdk/samtools/SAMValidationError.java +++ b/src/main/java/htsjdk/samtools/SAMValidationError.java @@ -171,6 +171,9 @@ HEADER_RECORD_MISSING_REQUIRED_TAG, + /** Header tag contains illegal value */ + HEADER_TAG_NON_CONFORMING_VALUE, + /** Date string is not ISO-8601 */ INVALID_DATE_STRING(Severity.WARNING), diff --git a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java index 16bd6e1ce..292758b8c 100644 --- a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java +++ b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java @@ -499,10 +499,24 @@ public void duplicateReadsOutOfOrder() throws Exception { "@RG\tID:0\tSM:Hi,Mom!\n" + "E\t147\tchr1\t15\t255\t10M\t=\t2\t-30\tCAACAGAAGC\t)'.*.+2,))\tU2:Z:CAA"; + final String SOTagCorrectlyProcessTestData = + "@HD\tVN:1.0\tSO:NOTKNOWN\n" + + "@SQ\tSN:chr1\tLN:101\n" + + "@RG\tID:0\tSM:Hi,Mom!\n" + + "E\t147\tchr1\t15\t255\t10M\t=\t2\t-30\tCAACAGAAGC\t)'.*.+2,))\tU2:Z:CAA"; + + final String GOTagCorrectlyProcessTestData = + "@HD\tVN:1.0\tGO:NOTKNOWN\n" + + "@SQ\tSN:chr1\tLN:101\n" + + "@RG\tID:0\tSM:Hi,Mom!\n" + + "E\t147\tchr1\t15\t255\t10M\t=\t2\t-30\tCAACAGAAGC\t)'.*.+2,))\tU2:Z:CAA"; + return new Object[][]{ {E2TagCorrectlyProcessTestData.getBytes(), SAMValidationError.Type.E2_BASE_EQUALS_PRIMARY_BASE}, {E2TagCorrectlyProcessTestData.getBytes(), SAMValidationError.Type.MISMATCH_READ_LENGTH_AND_E2_LENGTH}, - {U2TagCorrectlyProcessTestData.getBytes(), SAMValidationError.Type.MISMATCH_READ_LENGTH_AND_U2_LENGTH} + {U2TagCorrectlyProcessTestData.getBytes(), SAMValidationError.Type.MISMATCH_READ_LENGTH_AND_U2_LENGTH}, + {SOTagCorrectlyProcessTestData.getBytes(), SAMValidationError.Type.HEADER_TAG_NON_CONFORMING_VALUE}, + {GOTagCorrectlyProcessTestData.getBytes(), SAMValidationError.Type.HEADER_TAG_NON_CONFORMING_VALUE} }; } diff --git a/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java b/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java index 575485e82..a33766762 100644 --- a/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java +++ b/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java @@ -97,12 +97,10 @@ public void test2() { } } - private SAMFileHeader samFileHeader = new SAMFileHeader(); - private SAMRecord buildSAMRecord(String seqName, String line) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - baos.write("@HD\tVN:1.0\tGO:none SO:coordinate\n".getBytes()); + baos.write("@HD\tVN:1.0\tGO:none\tSO:coordinate\n".getBytes()); baos.write(("@SQ\tSN:" + seqName + "\tLN:247249719\n").getBytes()); baos.write(line.replaceAll("\\s+", "\t").getBytes()); baos.close(); From dd78f7717a0b1b260727cd990b2ad0e583d0c40e Mon Sep 17 00:00:00 2001 From: Len Trigg Date: Wed, 3 May 2017 06:54:24 +1200 Subject: [PATCH 27/59] Obey the tmpDir setting in several constructors that currently ignore it (#826) * Obey the tmpDir setting in several constructors that currently ignore it. As part of this, made one constructor follow the existing convention of calling initWriter() (This private method had an unused parameter, which has been removed), and ensured that both initWriter and initializeBAMWriter both call setTempDirectory() when needed. * Test that setMaxRecordsInRam and setTempDirectory settings on SAMFileWriterFactory make it to the writer implementation --- .../java/htsjdk/samtools/SAMFileWriterFactory.java | 36 +++++++++++++--------- .../java/htsjdk/samtools/SAMFileWriterImpl.java | 10 +++++- .../htsjdk/samtools/SAMFileWriterFactoryTest.java | 24 ++++++++++++++- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/main/java/htsjdk/samtools/SAMFileWriterFactory.java b/src/main/java/htsjdk/samtools/SAMFileWriterFactory.java index b788fbe89..30b36d7b3 100644 --- a/src/main/java/htsjdk/samtools/SAMFileWriterFactory.java +++ b/src/main/java/htsjdk/samtools/SAMFileWriterFactory.java @@ -174,6 +174,14 @@ public SAMFileWriterFactory setMaxRecordsInRam(final int maxRecordsInRam) { } /** + * Gets the maximum number of records held in RAM before spilling to disk during sorting. + * @see #setMaxRecordsInRam(int) + */ + public int getMaxRecordsInRam() { + return maxRecordsInRam; + } + + /** * Turn on or off the use of asynchronous IO for writing output SAM and BAM files. If true then * each SAMFileWriter creates a dedicated thread which is used for compression and IO activities. */ @@ -211,6 +219,14 @@ public SAMFileWriterFactory setTempDirectory(final File tmpDir) { } /** + * Gets the temporary directory that will be used when sorting data. + * @see #setTempDirectory(File) + */ + public File getTempDirectory() { + return tmpDir; + } + + /** * Set the flag output format only when writing text. * Default value: [[htsjdk.samtools.SAMTextWriter.samFlagFieldOutput.DECIMAL]] */ @@ -253,7 +269,6 @@ public SAMFileWriter makeBAMWriter(final SAMFileHeader header, final boolean pre if (this.createIndex && !createIndex) { log.warn("Cannot create index for BAM because output file is not a regular file: " + outputFile.getAbsolutePath()); } - if (this.tmpDir != null) ret.setTempDirectory(this.tmpDir); initializeBAMWriter(ret, header, presorted, createIndex); if (this.useAsyncIo) return new AsyncSAMFileWriter(ret, this.asyncOutputBufferSize); @@ -268,6 +283,7 @@ private void initializeBAMWriter(final BAMFileWriter writer, final SAMFileHeader if (maxRecordsInRam != null) { writer.setMaxRecordsInRam(maxRecordsInRam); } + if (this.tmpDir != null) writer.setTempDirectory(this.tmpDir); writer.setHeader(header); if (createIndex && writer.getSortOrder().equals(SAMFileHeader.SortOrder.coordinate)) { writer.enableBamIndexConstruction(); @@ -294,14 +310,7 @@ public SAMFileWriter makeSAMWriter(final SAMFileHeader header, final boolean pre ? new SAMTextWriter(new Md5CalculatingOutputStream(new FileOutputStream(outputFile, false), new File(outputFile.getAbsolutePath() + ".md5")), samFlagFieldOutput) : new SAMTextWriter(outputFile, samFlagFieldOutput); - ret.setSortOrder(header.getSortOrder(), presorted); - if (maxRecordsInRam != null) { - ret.setMaxRecordsInRam(maxRecordsInRam); - } - ret.setHeader(header); - - if (this.useAsyncIo) return new AsyncSAMFileWriter(ret, this.asyncOutputBufferSize); - else return ret; + return initWriter(header, presorted, ret); } catch (final IOException ioe) { throw new RuntimeIOException("Error opening file: " + outputFile.getAbsolutePath()); } @@ -324,7 +333,7 @@ public SAMFileWriter makeSAMWriter(final SAMFileHeader header, final boolean pre if (samFlagFieldOutput == SamFlagField.NONE) { samFlagFieldOutput = Defaults.SAM_FLAG_FIELD_FORMAT; } - return initWriter(header, presorted, false, new SAMTextWriter(stream, samFlagFieldOutput)); + return initWriter(header, presorted, new SAMTextWriter(stream, samFlagFieldOutput)); } /** @@ -338,24 +347,23 @@ public SAMFileWriter makeSAMWriter(final SAMFileHeader header, final boolean pre */ public SAMFileWriter makeBAMWriter(final SAMFileHeader header, final boolean presorted, final OutputStream stream) { - return initWriter(header, presorted, true, new BAMFileWriter(stream, null, this.getCompressionLevel(), this.deflaterFactory)); + return initWriter(header, presorted, new BAMFileWriter(stream, null, this.getCompressionLevel(), this.deflaterFactory)); } /** * Initialize SAMTextWriter or a BAMFileWriter and possibly wrap in AsyncSAMFileWriter - * * @param header entire header. Sort order is determined by the sortOrder property of this arg. * @param presorted if true, SAMRecords must be added to the SAMFileWriter in order that agrees with header.sortOrder. - * @param binary do we want to generate a BAM or a SAM * @param writer SAM or BAM writer to initialize and maybe wrap. */ - private SAMFileWriter initWriter(final SAMFileHeader header, final boolean presorted, final boolean binary, + private SAMFileWriter initWriter(final SAMFileHeader header, final boolean presorted, final SAMFileWriterImpl writer) { writer.setSortOrder(header.getSortOrder(), presorted); if (maxRecordsInRam != null) { writer.setMaxRecordsInRam(maxRecordsInRam); } + if (this.tmpDir != null) writer.setTempDirectory(this.tmpDir); writer.setHeader(header); if (this.useAsyncIo) return new AsyncSAMFileWriter(writer, this.asyncOutputBufferSize); diff --git a/src/main/java/htsjdk/samtools/SAMFileWriterImpl.java b/src/main/java/htsjdk/samtools/SAMFileWriterImpl.java index 5e0ecdb45..31a8604dc 100644 --- a/src/main/java/htsjdk/samtools/SAMFileWriterImpl.java +++ b/src/main/java/htsjdk/samtools/SAMFileWriterImpl.java @@ -111,7 +111,11 @@ void setMaxRecordsInRam(final int maxRecordsInRam) { } this.maxRecordsInRam = maxRecordsInRam; } - + + int getMaxRecordsInRam() { + return maxRecordsInRam; + } + /** * When writing records that are not presorted, specify the path of the temporary directory * for spilling to disk. Must be called before setHeader(). @@ -123,6 +127,10 @@ void setTempDirectory(final File tmpDir) { } } + File getTempDirectory() { + return tmpDir; + } + /** * Must be called before addAlignment. Header cannot be null. */ diff --git a/src/test/java/htsjdk/samtools/SAMFileWriterFactoryTest.java b/src/test/java/htsjdk/samtools/SAMFileWriterFactoryTest.java index 8c15a1656..0b8d7b5ac 100644 --- a/src/test/java/htsjdk/samtools/SAMFileWriterFactoryTest.java +++ b/src/test/java/htsjdk/samtools/SAMFileWriterFactoryTest.java @@ -167,7 +167,29 @@ private void createSmallBamToOutputStream(final OutputStream outputStream,boolea fillSmallBam(writer); writer.close(); } - + + @Test(description="check that factory settings are propagated to writer") + public void testFactorySettings() throws Exception { + final SAMFileWriterFactory factory = new SAMFileWriterFactory(); + factory.setCreateIndex(false); + factory.setCreateMd5File(false); + final File wontBeUsed = new File("wontBeUsed.tmp"); + final int maxRecsInRam = 271828; + factory.setMaxRecordsInRam(maxRecsInRam); + factory.setTempDirectory(wontBeUsed); + final SAMFileHeader header = new SAMFileHeader(); + header.setSortOrder(SAMFileHeader.SortOrder.coordinate); + header.addSequence(new SAMSequenceRecord("chr1", 123)); + try (final SAMFileWriter writer = factory.makeBAMWriter(header, false, new ByteArrayOutputStream())) { + Assert.assertEquals(maxRecsInRam, ((SAMFileWriterImpl) writer).getMaxRecordsInRam()); + Assert.assertEquals(wontBeUsed, ((SAMFileWriterImpl) writer).getTempDirectory()); + } + try (final SAMFileWriter writer = factory.makeSAMWriter(header, false, new ByteArrayOutputStream())) { + Assert.assertEquals(maxRecsInRam, ((SAMFileWriterImpl) writer).getMaxRecordsInRam()); + Assert.assertEquals(wontBeUsed, ((SAMFileWriterImpl) writer).getTempDirectory()); + } + } + private int fillSmallBam(SAMFileWriter writer) { final SAMRecordSetBuilder builder = new SAMRecordSetBuilder(); builder.addUnmappedFragment("HiMom!"); From e1cf07eb3f98843c0d0320b8b0e147ae8fc62e3c Mon Sep 17 00:00:00 2001 From: Daniel Cameron Date: Wed, 17 May 2017 04:38:47 +1000 Subject: [PATCH 28/59] Defensive programming around test case race condition to address #776 (#790) --- .../java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java b/src/test/java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java index ce4d44599..e35dadc94 100644 --- a/src/test/java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java +++ b/src/test/java/htsjdk/samtools/util/AsyncBufferedIteratorTest.java @@ -74,9 +74,15 @@ public void testBackgroundBlocks() throws InterruptedException { TestCloseableIterator it = new TestCloseableIterator(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); AsyncBufferedIterator abi = new AsyncBufferedIterator(it, 3, 2, "testBackgroundBlocks"); Assert.assertNotNull(getThreadWithName("testBackgroundBlocks")); - Thread.sleep(10); // how do we write this test and not be subject to race conditions? + // how do we write this test and not be subject to race conditions? // should have read 9 records: 2*3 in the buffers, and another 3 read but - // blocking waiting to be added + // blocking waiting to be added + for (int i = 0; i < 64; i++) { + if (it.consumed() >= 9) { + break; + } + Thread.sleep(1); + } Assert.assertEquals(it.consumed(), 9); abi.close(); } From e2801eeeb31e439b050da77195a09daf0e727529 Mon Sep 17 00:00:00 2001 From: jacarey Date: Tue, 16 May 2017 15:41:57 -0400 Subject: [PATCH 29/59] Adding OpenJDK8 to travis configuration --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 270855ea7..a1c1b2bcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ cache: - $HOME/.m2 jdk: - oraclejdk8 + - openjdk8 script: ./gradlew test jacocoTestReport; after_success: - bash <(curl -s https://codecov.io/bash) From d7bae17d96601ac06d79c0cbe53923eddf3846e1 Mon Sep 17 00:00:00 2001 From: Chris Norman Date: Fri, 26 May 2017 12:55:03 -0400 Subject: [PATCH 30/59] Fix ordering of assignments in FastQReader constructor. (#878) --- .../java/htsjdk/samtools/fastq/FastqReader.java | 2 +- .../samtools/fastq/FastqReaderWriterTest.scala | 29 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/java/htsjdk/samtools/fastq/FastqReader.java b/src/main/java/htsjdk/samtools/fastq/FastqReader.java index 76ead119d..c5d52f8dc 100755 --- a/src/main/java/htsjdk/samtools/fastq/FastqReader.java +++ b/src/main/java/htsjdk/samtools/fastq/FastqReader.java @@ -90,8 +90,8 @@ public FastqReader(final BufferedReader reader) { public FastqReader(final File file, final BufferedReader reader,boolean skipBlankLines) { this.fastqFile = file; this.reader = reader; - this.nextRecord = readNextRecord(); this.skipBlankLines = skipBlankLines; + this.nextRecord = readNextRecord(); } public FastqReader(final File file, final BufferedReader reader) { diff --git a/src/test/scala/htsjdk/samtools/fastq/FastqReaderWriterTest.scala b/src/test/scala/htsjdk/samtools/fastq/FastqReaderWriterTest.scala index 60e08efbd..00f62e91a 100644 --- a/src/test/scala/htsjdk/samtools/fastq/FastqReaderWriterTest.scala +++ b/src/test/scala/htsjdk/samtools/fastq/FastqReaderWriterTest.scala @@ -3,7 +3,7 @@ package htsjdk.samtools.fastq import java.io.{BufferedReader, File, StringReader} import htsjdk.UnitSpec -import htsjdk.samtools.SAMUtils +import htsjdk.samtools.{SAMException, SAMUtils} import htsjdk.samtools.util.IOUtil import scala.util.Random @@ -106,6 +106,33 @@ class FastqReaderWriterTest extends UnitSpec { in.close() } + it should "honor skipBlankLines when requested" in { + val fastq = + """ + | + |@SL-XBG:1:1:4:1663#0/2 + |NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN + |+SL-XBG:1:1:4:1663#0/2 + |BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + """.stripMargin + val reader = new BufferedReader(new StringReader(fastq)) + val in = new FastqReader(null, reader, true) + while (in.hasNext) in.next() + } + + it should "fail on blank lines when skipBlankLines is false" in { + val fastq = + """ + | + |@SL-XBG:1:1:4:1663#0/2 + |NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN + |+SL-XBG:1:1:4:1663#0/2 + |BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + """.stripMargin + val reader = new BufferedReader(new StringReader(fastq)) + an[SAMException] shouldBe thrownBy { val in = new FastqReader(null, reader, false) } + } + it should "fail on a truncated file" in { val fastq = """ From 0be8345e18b41a232a239703e6c48fc4916a0f41 Mon Sep 17 00:00:00 2001 From: Yossi Farjoun Date: Fri, 26 May 2017 22:21:46 -0400 Subject: [PATCH 31/59] - fixed a small NPE in MD5CalculatingOutputStream (#881) --- src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java b/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java index f3b3f0779..8b4c643a3 100755 --- a/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java +++ b/src/main/java/htsjdk/samtools/util/Md5CalculatingOutputStream.java @@ -68,7 +68,7 @@ public Md5CalculatingOutputStream(OutputStream os, Path digestFile) { } public Md5CalculatingOutputStream(OutputStream os, File digestFile) { - this(os, digestFile.toPath()); + this(os, digestFile == null ? (Path) null : digestFile.toPath()); } @Override From cc8a1a13f7b0a4af5273fad7d8708dc1f871e5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B3mez-S=C3=A1nchez?= Date: Sat, 27 May 2017 19:29:08 +0200 Subject: [PATCH 32/59] Index.writeBasedOnFeaturePath throws instead of silently failing (#841) * **Breaking Change** This is an API change. Code relying on the old behavior will have to be updated. We believe this should have minimal impact and most clients using Index will not have to make any changes. Anyone with their own implementation of Index should update to match the new behavior. * Changing behavior of Index.writeBasedOnFeaturePath() to throw instead of silently failing when it was unable to write an index. --- src/main/java/htsjdk/tribble/index/AbstractIndex.java | 3 +-- src/main/java/htsjdk/tribble/index/Index.java | 3 ++- src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java | 6 ++---- src/test/java/htsjdk/tribble/index/IndexTest.java | 10 ++++++++++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/htsjdk/tribble/index/AbstractIndex.java b/src/main/java/htsjdk/tribble/index/AbstractIndex.java index b1cc1364c..ac90e5d2b 100644 --- a/src/main/java/htsjdk/tribble/index/AbstractIndex.java +++ b/src/main/java/htsjdk/tribble/index/AbstractIndex.java @@ -386,8 +386,7 @@ public void write(final Path idxPath) throws IOException { @Override public void writeBasedOnFeaturePath(final Path featurePath) throws IOException { if (!Files.isRegularFile(featurePath)) { - logger.warn("Index not written into ", featurePath); - return; + throw new IOException("Cannot write based on a non-regular file: " + featurePath.toUri()); } write(Tribble.indexPath(featurePath)); } diff --git a/src/main/java/htsjdk/tribble/index/Index.java b/src/main/java/htsjdk/tribble/index/Index.java index c5a63ff6e..51982c6d2 100644 --- a/src/main/java/htsjdk/tribble/index/Index.java +++ b/src/main/java/htsjdk/tribble/index/Index.java @@ -92,11 +92,11 @@ public default void write(final File idxFile) throws IOException { /** * Write an appropriately named and located Index file based on the name and location of the featureFile. - * If featureFile is not a normal file, the index will silently not be written. * * Default implementation delegates to {@link #writeBasedOnFeaturePath(Path)} * * @param featureFile + * @throws IOException if featureFile is not a normal file. */ public default void writeBasedOnFeatureFile(File featureFile) throws IOException { writeBasedOnFeaturePath(featureFile.toPath()); @@ -107,6 +107,7 @@ public default void writeBasedOnFeatureFile(File featureFile) throws IOException * If featureFile is not a normal file, the index will silently not be written. * * @param featurePath + * @throws IOException if featureFile is not a normal file. */ public void writeBasedOnFeaturePath(Path featurePath) throws IOException; diff --git a/src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java b/src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java index 43b1a2b40..d7cc31cef 100644 --- a/src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java +++ b/src/main/java/htsjdk/tribble/index/tabix/TabixIndex.java @@ -66,8 +66,6 @@ MAGIC_NUMBER = bb.order(ByteOrder.LITTLE_ENDIAN).getInt(); } - private static final Log LOGGER = Log.getInstance(TabixIndex.class); - private final TabixFormat formatSpec; private final List sequenceNames; private final BinningIndexContent[] indices; @@ -226,12 +224,12 @@ public void write(final Path tabixPath) throws IOException { * Writes to a path with appropriate name and directory based on feature path. * * @param featurePath Path being indexed. + * @throws IOException if featureFile is not a normal file. */ @Override public void writeBasedOnFeaturePath(final Path featurePath) throws IOException { if (!Files.isRegularFile(featurePath)) { - LOGGER.warn("Index not written into ", featurePath); - return; + throw new IOException("Cannot write based on a non-regular file: " + featurePath.toUri()); } write(Tribble.tabixIndexPath(featurePath)); } diff --git a/src/test/java/htsjdk/tribble/index/IndexTest.java b/src/test/java/htsjdk/tribble/index/IndexTest.java index 06fb311a5..d1ff18eb7 100644 --- a/src/test/java/htsjdk/tribble/index/IndexTest.java +++ b/src/test/java/htsjdk/tribble/index/IndexTest.java @@ -3,6 +3,7 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import htsjdk.HtsjdkTest; +import htsjdk.samtools.util.IOUtil; import htsjdk.tribble.FeatureCodec; import htsjdk.tribble.TestUtils; import htsjdk.tribble.Tribble; @@ -121,4 +122,13 @@ public void testWritePathIndex(final File inputFile, final IndexFactory.IndexTyp } } } + + @Test(dataProvider = "writeIndexData") + public void testWriteBasedOnNonRegularFeatureFile(final File inputFile, final IndexFactory.IndexType type, final FeatureCodec codec) throws Exception { + final File tmpFolder = IOUtil.createTempDir("NonRegultarFeatureFile", null); + // create the index + final Index index = IndexFactory.createIndex(inputFile, codec, type); + // try to write based on the tmpFolder + Assert.assertThrows(IOException.class, () -> index.writeBasedOnFeatureFile(tmpFolder)); + } } From 9bef3fc745337e54dee1656279c98a215af95671 Mon Sep 17 00:00:00 2001 From: skashin Date: Tue, 30 May 2017 13:53:22 -0400 Subject: [PATCH 33/59] =?UTF-8?q?The=20existing=20code=20was=20throwing=20?= =?UTF-8?q?a=20NPE=20when=20using=20a=20CRAMFileReader=20for=20=E2=80=A6?= =?UTF-8?q?=20(#879)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * The existing code was throwing a NPE when using a CRAMFileReader for InputResource.Type.PATH * Converted multiple if statements to a switch statement --- .../java/htsjdk/samtools/SamReaderFactory.java | 191 +++++++++++---------- 1 file changed, 98 insertions(+), 93 deletions(-) diff --git a/src/main/java/htsjdk/samtools/SamReaderFactory.java b/src/main/java/htsjdk/samtools/SamReaderFactory.java index 3d6a80fa2..73544a1cc 100644 --- a/src/main/java/htsjdk/samtools/SamReaderFactory.java +++ b/src/main/java/htsjdk/samtools/SamReaderFactory.java @@ -303,106 +303,111 @@ public SamReader open(final SamInputResource resource) { return reader; } } - if (type == InputResource.Type.SEEKABLE_STREAM || type == InputResource.Type.URL) { - if (SamStreams.sourceLikeBam(data.asUnbufferedSeekableStream())) { - final SeekableStream bufferedIndexStream; - if (indexDefined && indexMaybe.asUnbufferedSeekableStream() != null) { - bufferedIndexStream = IOUtil.maybeBufferedSeekableStream(indexMaybe.asUnbufferedSeekableStream()); - } else { - // TODO: Throw an exception here? An index _may_ have been provided, but we're ignoring it - bufferedIndexStream = null; - } + switch (type) { + case PATH: case SEEKABLE_STREAM: case URL: + if (SamStreams.sourceLikeBam(data.asUnbufferedSeekableStream())) { + final SeekableStream bufferedIndexStream; + if (indexDefined && indexMaybe.asUnbufferedSeekableStream() != null) { + bufferedIndexStream = IOUtil.maybeBufferedSeekableStream(indexMaybe.asUnbufferedSeekableStream()); + } else { + // TODO: Throw an exception here? An index _may_ have been provided, but we're ignoring it + bufferedIndexStream = null; + } - primitiveSamReader = new BAMFileReader( - IOUtil.maybeBufferedSeekableStream(data.asUnbufferedSeekableStream()), - bufferedIndexStream, - false, - asynchronousIO, - validationStringency, - this.samRecordFactory, - this.inflaterFactory - ); - } else if (SamStreams.sourceLikeCram(data.asUnbufferedSeekableStream())) { - if (referenceSource == null) { - referenceSource = ReferenceSource.getDefaultCRAMReferenceSource(); - } - SeekableStream bufferedIndexStream = indexDefined ? - IOUtil.maybeBufferedSeekableStream(indexMaybe.asUnbufferedSeekableStream()) : - null; - primitiveSamReader = new CRAMFileReader( - IOUtil.maybeBufferedSeekableStream(data.asUnbufferedSeekableStream()), - bufferedIndexStream, referenceSource, validationStringency); - } else { - // assume its a SAM file/no index - LOG.warn("Unable to detect file format from input URL or stream, assuming SAM format."); - primitiveSamReader = new SAMTextReader( - IOUtil.toBufferedStream(data.asUnbufferedInputStream()), - validationStringency, this.samRecordFactory); - } - } else if (type == InputResource.Type.SRA_ACCESSION) { - primitiveSamReader = new SRAFileReader(data.asSRAAccession()); - } else { - InputStream bufferedStream = - IOUtil.maybeBufferInputStream( - data.asUnbufferedInputStream(), - Math.max(Defaults.BUFFER_SIZE, BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE) + primitiveSamReader = new BAMFileReader( + IOUtil.maybeBufferedSeekableStream(data.asUnbufferedSeekableStream()), + bufferedIndexStream, + false, + asynchronousIO, + validationStringency, + this.samRecordFactory, + this.inflaterFactory ); - File sourceFile = data.asFile(); - // calling asFile is safe even if indexMaybe is a Google Cloud Storage bucket - // (in that case we just get null) - final File indexFile = indexMaybe == null ? null : indexMaybe.asFile(); - if (SamStreams.isBAMFile(bufferedStream)) { - if (sourceFile == null || !sourceFile.isFile()) { - // check whether we can seek - final SeekableStream indexSeekable = indexMaybe == null ? null : indexMaybe.asUnbufferedSeekableStream(); - // do not close bufferedStream, it's the same stream we're getting here. - SeekableStream sourceSeekable = data.asUnbufferedSeekableStream(); - if (null == sourceSeekable || null == indexSeekable) { - // not seekable. - // it's OK that we consumed a bit of the stream already, this ctor expects it. - primitiveSamReader = new BAMFileReader(bufferedStream, indexFile, false, asynchronousIO, - validationStringency, this.samRecordFactory, this.inflaterFactory); - } else { - // seekable. - // need to return to the beginning because it's the same stream we used earlier - // and read a bit from, and that form of the ctor expects the stream to start at 0. - sourceSeekable.seek(0); - primitiveSamReader = new BAMFileReader( - sourceSeekable, indexSeekable, false, asynchronousIO, validationStringency, - this.samRecordFactory, this.inflaterFactory); + } else if (SamStreams.sourceLikeCram(data.asUnbufferedSeekableStream())) { + if (referenceSource == null) { + referenceSource = ReferenceSource.getDefaultCRAMReferenceSource(); } + SeekableStream bufferedIndexStream = indexDefined ? + IOUtil.maybeBufferedSeekableStream(indexMaybe.asUnbufferedSeekableStream()) : + null; + primitiveSamReader = new CRAMFileReader( + IOUtil.maybeBufferedSeekableStream(data.asUnbufferedSeekableStream()), + bufferedIndexStream, referenceSource, validationStringency); } else { - bufferedStream.close(); - primitiveSamReader = new BAMFileReader( - sourceFile, indexFile, false, asynchronousIO, - validationStringency, this.samRecordFactory, this.inflaterFactory); - } - } else if (BlockCompressedInputStream.isValidFile(bufferedStream)) { - primitiveSamReader = new SAMTextReader(new BlockCompressedInputStream(bufferedStream), validationStringency, this.samRecordFactory); - } else if (SamStreams.isGzippedSAMFile(bufferedStream)) { - primitiveSamReader = new SAMTextReader(new GZIPInputStream(bufferedStream), validationStringency, this.samRecordFactory); - } else if (SamStreams.isCRAMFile(bufferedStream)) { - if (referenceSource == null) { - referenceSource = ReferenceSource.getDefaultCRAMReferenceSource(); + // assume its a SAM file/no index + LOG.warn("Unable to detect file format from input URL or stream, assuming SAM format."); + primitiveSamReader = new SAMTextReader( + IOUtil.toBufferedStream(data.asUnbufferedInputStream()), + validationStringency, this.samRecordFactory); } - if (sourceFile == null || !sourceFile.isFile()) { - primitiveSamReader = new CRAMFileReader(bufferedStream, indexFile, referenceSource, validationStringency); + break; + case SRA_ACCESSION: + primitiveSamReader = new SRAFileReader(data.asSRAAccession()); + break; + case FILE: case INPUT_STREAM: + InputStream bufferedStream = + IOUtil.maybeBufferInputStream( + data.asUnbufferedInputStream(), + Math.max(Defaults.BUFFER_SIZE, BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE) + ); + File sourceFile = data.asFile(); + // calling asFile is safe even if indexMaybe is a Google Cloud Storage bucket + // (in that case we just get null) + final File indexFile = indexMaybe == null ? null : indexMaybe.asFile(); + if (SamStreams.isBAMFile(bufferedStream)) { + if (sourceFile == null || !sourceFile.isFile()) { + // check whether we can seek + final SeekableStream indexSeekable = indexMaybe == null ? null : indexMaybe.asUnbufferedSeekableStream(); + // do not close bufferedStream, it's the same stream we're getting here. + SeekableStream sourceSeekable = data.asUnbufferedSeekableStream(); + if (null == sourceSeekable || null == indexSeekable) { + // not seekable. + // it's OK that we consumed a bit of the stream already, this ctor expects it. + primitiveSamReader = new BAMFileReader(bufferedStream, indexFile, false, asynchronousIO, + validationStringency, this.samRecordFactory, this.inflaterFactory); + } else { + // seekable. + // need to return to the beginning because it's the same stream we used earlier + // and read a bit from, and that form of the ctor expects the stream to start at 0. + sourceSeekable.seek(0); + primitiveSamReader = new BAMFileReader( + sourceSeekable, indexSeekable, false, asynchronousIO, validationStringency, + this.samRecordFactory, this.inflaterFactory); + } + } else { + bufferedStream.close(); + primitiveSamReader = new BAMFileReader( + sourceFile, indexFile, false, asynchronousIO, + validationStringency, this.samRecordFactory, this.inflaterFactory); + } + } else if (BlockCompressedInputStream.isValidFile(bufferedStream)) { + primitiveSamReader = new SAMTextReader(new BlockCompressedInputStream(bufferedStream), validationStringency, this.samRecordFactory); + } else if (SamStreams.isGzippedSAMFile(bufferedStream)) { + primitiveSamReader = new SAMTextReader(new GZIPInputStream(bufferedStream), validationStringency, this.samRecordFactory); + } else if (SamStreams.isCRAMFile(bufferedStream)) { + if (referenceSource == null) { + referenceSource = ReferenceSource.getDefaultCRAMReferenceSource(); + } + if (sourceFile == null || !sourceFile.isFile()) { + primitiveSamReader = new CRAMFileReader(bufferedStream, indexFile, referenceSource, validationStringency); + } else { + bufferedStream.close(); + primitiveSamReader = new CRAMFileReader(sourceFile, indexFile, referenceSource, validationStringency); + } + } else if (sourceFile != null && isSra(sourceFile)) { + if (bufferedStream != null) { + bufferedStream.close(); + } + primitiveSamReader = new SRAFileReader(new SRAAccession(sourceFile.getPath())); } else { - bufferedStream.close(); - primitiveSamReader = new CRAMFileReader(sourceFile, indexFile, referenceSource, validationStringency); - } - } else if (sourceFile != null && isSra(sourceFile)) { - if (bufferedStream != null) { - bufferedStream.close(); - } - primitiveSamReader = new SRAFileReader(new SRAAccession(sourceFile.getPath())); - } else { - if (indexDefined) { - bufferedStream.close(); - throw new RuntimeException("Cannot use index file with textual SAM file"); + if (indexDefined) { + bufferedStream.close(); + throw new RuntimeException("Cannot use index file with textual SAM file"); + } + primitiveSamReader = new SAMTextReader(bufferedStream, sourceFile, validationStringency, this.samRecordFactory); } - primitiveSamReader = new SAMTextReader(bufferedStream, sourceFile, validationStringency, this.samRecordFactory); - } + break; + default: throw new SAMException("Opening SamReader for " + type + " is not supported"); } // Apply the options defined by this factory to this reader From 630aa48a45ac581fbc7182d6a109196b54e99313 Mon Sep 17 00:00:00 2001 From: Yossi Farjoun Date: Wed, 31 May 2017 10:08:02 -0400 Subject: [PATCH 34/59] =?UTF-8?q?Revert=20"The=20existing=20code=20was=20t?= =?UTF-8?q?hrowing=20a=20NPE=20when=20using=20a=20CRAMFileReader=20for=20?= =?UTF-8?q?=E2=80=A6=20(#879)"=20(#885)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 9bef3fc745337e54dee1656279c98a215af95671. --- .../java/htsjdk/samtools/SamReaderFactory.java | 191 ++++++++++----------- 1 file changed, 93 insertions(+), 98 deletions(-) diff --git a/src/main/java/htsjdk/samtools/SamReaderFactory.java b/src/main/java/htsjdk/samtools/SamReaderFactory.java index 73544a1cc..3d6a80fa2 100644 --- a/src/main/java/htsjdk/samtools/SamReaderFactory.java +++ b/src/main/java/htsjdk/samtools/SamReaderFactory.java @@ -303,111 +303,106 @@ public SamReader open(final SamInputResource resource) { return reader; } } - switch (type) { - case PATH: case SEEKABLE_STREAM: case URL: - if (SamStreams.sourceLikeBam(data.asUnbufferedSeekableStream())) { - final SeekableStream bufferedIndexStream; - if (indexDefined && indexMaybe.asUnbufferedSeekableStream() != null) { - bufferedIndexStream = IOUtil.maybeBufferedSeekableStream(indexMaybe.asUnbufferedSeekableStream()); - } else { - // TODO: Throw an exception here? An index _may_ have been provided, but we're ignoring it - bufferedIndexStream = null; - } - - primitiveSamReader = new BAMFileReader( - IOUtil.maybeBufferedSeekableStream(data.asUnbufferedSeekableStream()), - bufferedIndexStream, - false, - asynchronousIO, - validationStringency, - this.samRecordFactory, - this.inflaterFactory - ); - } else if (SamStreams.sourceLikeCram(data.asUnbufferedSeekableStream())) { - if (referenceSource == null) { - referenceSource = ReferenceSource.getDefaultCRAMReferenceSource(); - } - SeekableStream bufferedIndexStream = indexDefined ? - IOUtil.maybeBufferedSeekableStream(indexMaybe.asUnbufferedSeekableStream()) : - null; - primitiveSamReader = new CRAMFileReader( - IOUtil.maybeBufferedSeekableStream(data.asUnbufferedSeekableStream()), - bufferedIndexStream, referenceSource, validationStringency); + if (type == InputResource.Type.SEEKABLE_STREAM || type == InputResource.Type.URL) { + if (SamStreams.sourceLikeBam(data.asUnbufferedSeekableStream())) { + final SeekableStream bufferedIndexStream; + if (indexDefined && indexMaybe.asUnbufferedSeekableStream() != null) { + bufferedIndexStream = IOUtil.maybeBufferedSeekableStream(indexMaybe.asUnbufferedSeekableStream()); } else { - // assume its a SAM file/no index - LOG.warn("Unable to detect file format from input URL or stream, assuming SAM format."); - primitiveSamReader = new SAMTextReader( - IOUtil.toBufferedStream(data.asUnbufferedInputStream()), - validationStringency, this.samRecordFactory); + // TODO: Throw an exception here? An index _may_ have been provided, but we're ignoring it + bufferedIndexStream = null; } - break; - case SRA_ACCESSION: - primitiveSamReader = new SRAFileReader(data.asSRAAccession()); - break; - case FILE: case INPUT_STREAM: - InputStream bufferedStream = - IOUtil.maybeBufferInputStream( - data.asUnbufferedInputStream(), - Math.max(Defaults.BUFFER_SIZE, BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE) - ); - File sourceFile = data.asFile(); - // calling asFile is safe even if indexMaybe is a Google Cloud Storage bucket - // (in that case we just get null) - final File indexFile = indexMaybe == null ? null : indexMaybe.asFile(); - if (SamStreams.isBAMFile(bufferedStream)) { - if (sourceFile == null || !sourceFile.isFile()) { - // check whether we can seek - final SeekableStream indexSeekable = indexMaybe == null ? null : indexMaybe.asUnbufferedSeekableStream(); - // do not close bufferedStream, it's the same stream we're getting here. - SeekableStream sourceSeekable = data.asUnbufferedSeekableStream(); - if (null == sourceSeekable || null == indexSeekable) { - // not seekable. - // it's OK that we consumed a bit of the stream already, this ctor expects it. - primitiveSamReader = new BAMFileReader(bufferedStream, indexFile, false, asynchronousIO, - validationStringency, this.samRecordFactory, this.inflaterFactory); - } else { - // seekable. - // need to return to the beginning because it's the same stream we used earlier - // and read a bit from, and that form of the ctor expects the stream to start at 0. - sourceSeekable.seek(0); - primitiveSamReader = new BAMFileReader( - sourceSeekable, indexSeekable, false, asynchronousIO, validationStringency, - this.samRecordFactory, this.inflaterFactory); - } - } else { - bufferedStream.close(); - primitiveSamReader = new BAMFileReader( - sourceFile, indexFile, false, asynchronousIO, + + primitiveSamReader = new BAMFileReader( + IOUtil.maybeBufferedSeekableStream(data.asUnbufferedSeekableStream()), + bufferedIndexStream, + false, + asynchronousIO, + validationStringency, + this.samRecordFactory, + this.inflaterFactory + ); + } else if (SamStreams.sourceLikeCram(data.asUnbufferedSeekableStream())) { + if (referenceSource == null) { + referenceSource = ReferenceSource.getDefaultCRAMReferenceSource(); + } + SeekableStream bufferedIndexStream = indexDefined ? + IOUtil.maybeBufferedSeekableStream(indexMaybe.asUnbufferedSeekableStream()) : + null; + primitiveSamReader = new CRAMFileReader( + IOUtil.maybeBufferedSeekableStream(data.asUnbufferedSeekableStream()), + bufferedIndexStream, referenceSource, validationStringency); + } else { + // assume its a SAM file/no index + LOG.warn("Unable to detect file format from input URL or stream, assuming SAM format."); + primitiveSamReader = new SAMTextReader( + IOUtil.toBufferedStream(data.asUnbufferedInputStream()), + validationStringency, this.samRecordFactory); + } + } else if (type == InputResource.Type.SRA_ACCESSION) { + primitiveSamReader = new SRAFileReader(data.asSRAAccession()); + } else { + InputStream bufferedStream = + IOUtil.maybeBufferInputStream( + data.asUnbufferedInputStream(), + Math.max(Defaults.BUFFER_SIZE, BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE) + ); + File sourceFile = data.asFile(); + // calling asFile is safe even if indexMaybe is a Google Cloud Storage bucket + // (in that case we just get null) + final File indexFile = indexMaybe == null ? null : indexMaybe.asFile(); + if (SamStreams.isBAMFile(bufferedStream)) { + if (sourceFile == null || !sourceFile.isFile()) { + // check whether we can seek + final SeekableStream indexSeekable = indexMaybe == null ? null : indexMaybe.asUnbufferedSeekableStream(); + // do not close bufferedStream, it's the same stream we're getting here. + SeekableStream sourceSeekable = data.asUnbufferedSeekableStream(); + if (null == sourceSeekable || null == indexSeekable) { + // not seekable. + // it's OK that we consumed a bit of the stream already, this ctor expects it. + primitiveSamReader = new BAMFileReader(bufferedStream, indexFile, false, asynchronousIO, validationStringency, this.samRecordFactory, this.inflaterFactory); - } - } else if (BlockCompressedInputStream.isValidFile(bufferedStream)) { - primitiveSamReader = new SAMTextReader(new BlockCompressedInputStream(bufferedStream), validationStringency, this.samRecordFactory); - } else if (SamStreams.isGzippedSAMFile(bufferedStream)) { - primitiveSamReader = new SAMTextReader(new GZIPInputStream(bufferedStream), validationStringency, this.samRecordFactory); - } else if (SamStreams.isCRAMFile(bufferedStream)) { - if (referenceSource == null) { - referenceSource = ReferenceSource.getDefaultCRAMReferenceSource(); - } - if (sourceFile == null || !sourceFile.isFile()) { - primitiveSamReader = new CRAMFileReader(bufferedStream, indexFile, referenceSource, validationStringency); } else { - bufferedStream.close(); - primitiveSamReader = new CRAMFileReader(sourceFile, indexFile, referenceSource, validationStringency); - } - } else if (sourceFile != null && isSra(sourceFile)) { - if (bufferedStream != null) { - bufferedStream.close(); + // seekable. + // need to return to the beginning because it's the same stream we used earlier + // and read a bit from, and that form of the ctor expects the stream to start at 0. + sourceSeekable.seek(0); + primitiveSamReader = new BAMFileReader( + sourceSeekable, indexSeekable, false, asynchronousIO, validationStringency, + this.samRecordFactory, this.inflaterFactory); } - primitiveSamReader = new SRAFileReader(new SRAAccession(sourceFile.getPath())); } else { - if (indexDefined) { - bufferedStream.close(); - throw new RuntimeException("Cannot use index file with textual SAM file"); - } - primitiveSamReader = new SAMTextReader(bufferedStream, sourceFile, validationStringency, this.samRecordFactory); + bufferedStream.close(); + primitiveSamReader = new BAMFileReader( + sourceFile, indexFile, false, asynchronousIO, + validationStringency, this.samRecordFactory, this.inflaterFactory); + } + } else if (BlockCompressedInputStream.isValidFile(bufferedStream)) { + primitiveSamReader = new SAMTextReader(new BlockCompressedInputStream(bufferedStream), validationStringency, this.samRecordFactory); + } else if (SamStreams.isGzippedSAMFile(bufferedStream)) { + primitiveSamReader = new SAMTextReader(new GZIPInputStream(bufferedStream), validationStringency, this.samRecordFactory); + } else if (SamStreams.isCRAMFile(bufferedStream)) { + if (referenceSource == null) { + referenceSource = ReferenceSource.getDefaultCRAMReferenceSource(); + } + if (sourceFile == null || !sourceFile.isFile()) { + primitiveSamReader = new CRAMFileReader(bufferedStream, indexFile, referenceSource, validationStringency); + } else { + bufferedStream.close(); + primitiveSamReader = new CRAMFileReader(sourceFile, indexFile, referenceSource, validationStringency); + } + } else if (sourceFile != null && isSra(sourceFile)) { + if (bufferedStream != null) { + bufferedStream.close(); + } + primitiveSamReader = new SRAFileReader(new SRAAccession(sourceFile.getPath())); + } else { + if (indexDefined) { + bufferedStream.close(); + throw new RuntimeException("Cannot use index file with textual SAM file"); } - break; - default: throw new SAMException("Opening SamReader for " + type + " is not supported"); + primitiveSamReader = new SAMTextReader(bufferedStream, sourceFile, validationStringency, this.samRecordFactory); + } } // Apply the options defined by this factory to this reader From dcd20ff5946b569a798632150e028feb9011950b Mon Sep 17 00:00:00 2001 From: Nils Homer Date: Wed, 7 Jun 2017 11:37:13 -0700 Subject: [PATCH 35/59] Adding an overlaps function to IntervalList. (#877) * Adding an overlaps function to IntervalList. Finds all intervals in the first list that overlap any interval in the second list. --- .../java/htsjdk/samtools/util/IntervalList.java | 58 ++++++++++++++ .../htsjdk/samtools/util/IntervalListTest.java | 92 ++++++++++++++++++++-- 2 files changed, 145 insertions(+), 5 deletions(-) diff --git a/src/main/java/htsjdk/samtools/util/IntervalList.java b/src/main/java/htsjdk/samtools/util/IntervalList.java index 929671a84..26403c512 100644 --- a/src/main/java/htsjdk/samtools/util/IntervalList.java +++ b/src/main/java/htsjdk/samtools/util/IntervalList.java @@ -739,6 +739,64 @@ public static IntervalList difference(final Collection lists1, fin subtract(lists2, lists1)); } + /** + * A utility function for finding the intervals in the first list that have at least 1bp overlap with any interval + * in the second list. + * + * @param lhs the first collection of IntervalLists + * @param lhs the second collection of IntervalLists + * @return an IntervalList comprising of all intervals in the first IntervalList that have at least 1bp overlap with + * any interval in the second. + */ + public static IntervalList overlaps(final IntervalList lhs, final IntervalList rhs) { + return overlaps(Collections.singletonList(lhs), Collections.singletonList(rhs)); + } + + /** + * A utility function for finding the intervals in the first list that have at least 1bp overlap with any interval + * in the second list. + * + * @param lists1 the first collection of IntervalLists + * @param lists2 the second collection of IntervalLists + * @return an IntervalList comprising of all intervals in the first collection of lists that have at least 1bp + * overlap with any interval in the second lists. + */ + public static IntervalList overlaps(final Collection lists1, final Collection lists2) { + if(lists1.isEmpty()){ + throw new SAMException("Cannot call overlaps with the first collection having empty list of IntervalLists."); + } + + final SAMFileHeader header = lists1.iterator().next().getHeader().clone(); + header.setSortOrder(SAMFileHeader.SortOrder.unsorted); + + // Create an overlap detector on list2 + final IntervalList overlapIntervals = new IntervalList(header); + for (final IntervalList list : lists2) { + SequenceUtil.assertSequenceDictionariesEqual(header.getSequenceDictionary(), + list.getHeader().getSequenceDictionary()); + overlapIntervals.addall(list.getIntervals()); + } + final OverlapDetector detector = new OverlapDetector<>(0, 0); + final int dummy = -1; // NB: since we don't actually use the returned objects, we can use a dummy value + for (final Interval interval : overlapIntervals.sorted().uniqued()) { + detector.addLhs(dummy, interval); + } + + // Go through each input interval in in lists1 and see if overlaps any interval in lists2 + final IntervalList merged = new IntervalList(header); + for (final IntervalList list : lists1) { + SequenceUtil.assertSequenceDictionariesEqual(header.getSequenceDictionary(), + list.getHeader().getSequenceDictionary()); + for (final Interval interval : list.getIntervals()) { + if (detector.overlapsAny(interval)) { + merged.add(interval); + } + } + } + + return merged; + } + @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/src/test/java/htsjdk/samtools/util/IntervalListTest.java b/src/test/java/htsjdk/samtools/util/IntervalListTest.java index 983820bbe..e138ee0e1 100644 --- a/src/test/java/htsjdk/samtools/util/IntervalListTest.java +++ b/src/test/java/htsjdk/samtools/util/IntervalListTest.java @@ -25,10 +25,7 @@ package htsjdk.samtools.util; import htsjdk.HtsjdkTest; -import htsjdk.samtools.SAMFileHeader; -import htsjdk.samtools.SAMSequenceDictionary; -import htsjdk.samtools.SAMSequenceRecord; -import htsjdk.samtools.SamFileHeaderMerger; +import htsjdk.samtools.*; import htsjdk.variant.vcf.VCFFileReader; import org.testng.Assert; import org.testng.annotations.BeforeTest; @@ -377,12 +374,97 @@ public void testSubtractSingletonIntervalLists(final IntervalList fromLists, fin } @Test(dataProvider = "subtractSingletonData") - public void testSubtractSingletonasListIntervalList(final IntervalList fromLists, final IntervalList whatLists, final IntervalList list) { + public void testSubtractSingletonAsListIntervalList(final IntervalList fromLists, final IntervalList whatLists, final IntervalList list) { Assert.assertEquals( CollectionUtil.makeCollection(IntervalList.subtract(Collections.singletonList(fromLists), Collections.singletonList(whatLists)).iterator()), CollectionUtil.makeCollection(list.iterator())); } + @DataProvider(name = "overlapsSingletonData") + public Object[][] overlapSingletonData() { + final IntervalList two_overlaps_one = new IntervalList(fileHeader); + final IntervalList three_overlaps_two = new IntervalList(fileHeader); + final IntervalList three_overlaps_one = new IntervalList(fileHeader); + final IntervalList one_overlaps_three = new IntervalList(fileHeader); + + // NB: commented lines below are there to show the intervals in the first list that will not be in the resulting list + + two_overlaps_one.add(new Interval("1", 50, 150)); + //two_overlaps_one.add(new Interval("1", 301, 500)); + two_overlaps_one.add(new Interval("2", 1, 150)); + two_overlaps_one.add(new Interval("2", 250, 270)); + two_overlaps_one.add(new Interval("2", 290, 400)); + + three_overlaps_two.add(new Interval("1", 25, 400)); + three_overlaps_two.add(new Interval("2", 200, 600)); + //three_overlaps_two.add(new Interval("3", 50, 470)); + + three_overlaps_one.add(new Interval("1", 25, 400)); + three_overlaps_one.add(new Interval("2", 200, 600)); + //three_overlaps_one.add(new Interval("3", 50, 470)); + + one_overlaps_three.add(new Interval("1", 1, 100)); + one_overlaps_three.add(new Interval("1", 101, 200)); + one_overlaps_three.add(new Interval("1", 202, 300)); + one_overlaps_three.add(new Interval("2", 200, 300)); + //one_overlaps_three.add(new Interval("2", 100, 150)); + + return new Object[][]{ + new Object[]{list1, list1, list1}, // should return itself + new Object[]{list1, IntervalList.invert(list1), new IntervalList(list1.getHeader())}, // should be empty + new Object[]{list2, list1, two_overlaps_one}, + new Object[]{list3, list2, three_overlaps_two}, + new Object[]{list3, list1, three_overlaps_one}, + new Object[]{list1, list3, one_overlaps_three} + }; + } + + @DataProvider(name = "overlapsData") + public Object[][] overlapData() { + final IntervalList three_overlaps_one_and_two = new IntervalList(fileHeader); + + three_overlaps_one_and_two.add(new Interval("1", 25, 400)); + three_overlaps_one_and_two.add(new Interval("2", 200, 600)); + //three_overlaps_one_and_two.add(new Interval("3", 50, 470)); + + return new Object[][]{ + new Object[]{CollectionUtil.makeList(list3), CollectionUtil.makeList(list1, list2), three_overlaps_one_and_two}, + }; + } + + @Test(dataProvider = "overlapsData") + public void testOverlapsIntervalLists(final List fromLists, final List whatLists, final IntervalList list) { + Assert.assertEquals( + CollectionUtil.makeCollection(IntervalList.overlaps(fromLists, whatLists).iterator()), + CollectionUtil.makeCollection(list.iterator())); + } + + @Test(dataProvider = "overlapsSingletonData") + public void testOverlapsSingletonIntervalLists(final IntervalList fromLists, final IntervalList whatLists, final IntervalList list) { + Assert.assertEquals( + CollectionUtil.makeCollection(IntervalList.overlaps(fromLists, whatLists).iterator()), + CollectionUtil.makeCollection(list.iterator())); + } + + @Test(dataProvider = "overlapsSingletonData") + public void testOverlapsSingletonAsListIntervalList(final IntervalList fromLists, final IntervalList whatLists, final IntervalList list) { + Assert.assertEquals( + CollectionUtil.makeCollection(IntervalList.overlaps(Collections.singletonList(fromLists), Collections.singletonList(whatLists)).iterator()), + CollectionUtil.makeCollection(list.iterator())); + } + + @Test(expectedExceptions = SAMException.class) + public void testOverlapsEmptyFirstList() { + IntervalList.overlaps(Collections.emptyList(), Collections.singletonList(list1)); + } + + @Test + public void testOverlapsEmptySecondList() { + Assert.assertEquals( + CollectionUtil.makeCollection(IntervalList.overlaps(Collections.singletonList(list1), Collections.emptyList()).iterator()), + Collections.emptyList()); + } + @DataProvider(name = "VCFCompData") public Object[][] VCFCompData() { return new Object[][]{ From 98c2438dbd86869e5ac2638ecd5a61021ced9c7e Mon Sep 17 00:00:00 2001 From: Louis Bergelson Date: Thu, 8 Jun 2017 10:49:10 -0400 Subject: [PATCH 36/59] overloading BlockCompressedInputStream.checkTerminator to support NIO (#890) * adding overloads of BlockCompressedInputStream.checkTerminator to support java.nio.Path and SeekableByteChannel * adding additional tests to BlockCompressedTerminatorTest --- .../samtools/util/BlockCompressedInputStream.java | 102 ++++++++++++++++----- .../util/BlockCompressedTerminatorTest.java | 87 +++++++++++++++--- 2 files changed, 155 insertions(+), 34 deletions(-) diff --git a/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java b/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java index 066a0c001..e108d1bb3 100755 --- a/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java +++ b/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java @@ -32,16 +32,15 @@ import htsjdk.samtools.seekablestream.SeekableStream; import htsjdk.samtools.util.zip.InflaterFactory; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; +import java.io.*; import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Arrays; -import java.util.zip.Inflater; /** * Utility class for reading BGZF block compressed files. The caller can treat this file like any other InputStream. @@ -587,41 +586,98 @@ private int unpackInt32(final byte[] buffer, final int offset) { public enum FileTermination {HAS_TERMINATOR_BLOCK, HAS_HEALTHY_LAST_BLOCK, DEFECTIVE} + /** + * + * @param file the file to check + * @return status of the last compressed block + * @throws IOException + */ public static FileTermination checkTermination(final File file) throws IOException { - final long fileSize = file.length(); + return checkTermination(file == null ? null : file.toPath()); + } + + /** + * + * @param path to the file to check + * @return status of the last compressed block + * @throws IOException + */ + public static FileTermination checkTermination(final Path path) throws IOException { + try( final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ) ){ + return checkTermination(channel); + } + } + + /** + * check the status of the final bzgipped block for the given bgzipped resource + * + * @param channel an open channel to read from, + * the channel will remain open and the initial position will be restored when the operation completes + * this makes no guarantee about the state of the channel if an exception is thrown during reading + * + * @return the status of the last compressed black + * @throws IOException + */ + public static FileTermination checkTermination(SeekableByteChannel channel) throws IOException { + final long fileSize = channel.size(); if (fileSize < BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length) { return FileTermination.DEFECTIVE; } - final RandomAccessFile raFile = new RandomAccessFile(file, "r"); + final long initialPosition = channel.position(); + boolean exceptionThrown = false; try { - raFile.seek(fileSize - BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); - byte[] buf = new byte[BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length]; - raFile.readFully(buf); - if (Arrays.equals(buf, BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK)) { + channel.position(fileSize - BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); + + //Check if the end of the file is an empty gzip block which is used as the terminator for a bgzipped file + final ByteBuffer lastBlockBuffer = ByteBuffer.allocate(BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); + readFully(channel, lastBlockBuffer); + if (Arrays.equals(lastBlockBuffer.array(), BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK)) { return FileTermination.HAS_TERMINATOR_BLOCK; } - final int bufsize = (int)Math.min(fileSize, BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE); - buf = new byte[bufsize]; - raFile.seek(fileSize - bufsize); - raFile.read(buf); - for (int i = buf.length - BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length; - i >= 0; --i) { + + //if the last block isn't an empty gzip block, check to see if it is a healthy compressed block or if it's corrupted + final int bufsize = (int) Math.min(fileSize, BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE); + final byte[] bufferArray = new byte[bufsize]; + channel.position(fileSize - bufsize); + readFully(channel, ByteBuffer.wrap(bufferArray)); + for (int i = bufferArray.length - BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length; + i >= 0; --i) { if (!preambleEqual(BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE, - buf, i, BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE.length)) { + bufferArray, i, BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE.length)) { continue; } - final ByteBuffer byteBuffer = ByteBuffer.wrap(buf, i + BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE.length, 4); + final ByteBuffer byteBuffer = ByteBuffer.wrap(bufferArray, + i + BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE.length, + 4); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); - final int totalBlockSizeMinusOne = byteBuffer.getShort() & 0xFFFF; - if (buf.length - i == totalBlockSizeMinusOne + 1) { + final int totalBlockSizeMinusOne = byteBuffer.getShort() & 0xFFFF; + if (bufferArray.length - i == totalBlockSizeMinusOne + 1) { return FileTermination.HAS_HEALTHY_LAST_BLOCK; } else { return FileTermination.DEFECTIVE; } } return FileTermination.DEFECTIVE; + } catch (final Throwable e) { + exceptionThrown = true; + throw e; } finally { - raFile.close(); + //if an exception was thrown we don't want to reset the position because that would be likely to throw again + //and suppress the initial exception + if(!exceptionThrown) { + channel.position(initialPosition); + } + } + } + + /** + * read as many bytes as dst's capacity into dst or throw if that's not possible + * @throws EOFException if channel has fewer bytes available than dst's capacity + */ + static void readFully(SeekableByteChannel channel, ByteBuffer dst) throws IOException { + final int bytesRead = channel.read(dst); + if (bytesRead < dst.capacity()){ + throw new EOFException(); } } diff --git a/src/test/java/htsjdk/samtools/util/BlockCompressedTerminatorTest.java b/src/test/java/htsjdk/samtools/util/BlockCompressedTerminatorTest.java index d9d20ccef..4a14bd920 100644 --- a/src/test/java/htsjdk/samtools/util/BlockCompressedTerminatorTest.java +++ b/src/test/java/htsjdk/samtools/util/BlockCompressedTerminatorTest.java @@ -23,38 +23,103 @@ */ package htsjdk.samtools.util; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; import htsjdk.HtsjdkTest; +import htsjdk.samtools.SeekableByteChannelFromBuffer; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.io.EOFException; import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; /** * @author alecw@broadinstitute.org */ public class BlockCompressedTerminatorTest extends HtsjdkTest { private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/samtools/util"); + private static final File DEFECTIVE = new File(TEST_DATA_DIR, "defective_bgzf.bam"); + private static final File NO_TERMINATOR = new File(TEST_DATA_DIR, "no_bgzf_terminator.bam"); - @Test - public void testFileWithTerminator() throws Exception { + @DataProvider + public Object[][] getFiles() throws IOException { + return new Object[][]{ + {getValidCompressedFile(), BlockCompressedInputStream.FileTermination.HAS_TERMINATOR_BLOCK}, + {NO_TERMINATOR, BlockCompressedInputStream.FileTermination.HAS_HEALTHY_LAST_BLOCK}, + {DEFECTIVE, BlockCompressedInputStream.FileTermination.DEFECTIVE} + }; + } + + @Test( dataProvider = "getFiles") + public void testCheckTerminationForFiles(File compressedFile, BlockCompressedInputStream.FileTermination expected) throws IOException { + Assert.assertEquals(BlockCompressedInputStream.checkTermination(compressedFile), expected); + } + + @Test( dataProvider = "getFiles") + public void testCheckTerminationForPaths(File compressedFile, BlockCompressedInputStream.FileTermination expected) throws IOException { + try(FileSystem fs = Jimfs.newFileSystem("test", Configuration.unix())){ + final Path compressedFileInJimfs = Files.copy(compressedFile.toPath(), fs.getPath("something")); + Assert.assertEquals(BlockCompressedInputStream.checkTermination(compressedFileInJimfs), expected); + } + } + + @Test( dataProvider = "getFiles") + public void testCheckTerminationForSeekableByteChannels(File compressedFile, BlockCompressedInputStream.FileTermination expected) throws IOException { + try(SeekableByteChannel channel = Files.newByteChannel(compressedFile.toPath())){ + Assert.assertEquals(BlockCompressedInputStream.checkTermination(channel), expected); + } + } + + @Test(dataProvider = "getFiles") + public void testChannelPositionIsRestored(File compressedFile, BlockCompressedInputStream.FileTermination expected) throws IOException { + final long position = 50; + try(SeekableByteChannel channel = Files.newByteChannel(compressedFile.toPath())){ + channel.position(position); + Assert.assertEquals(channel.position(), position); + Assert.assertEquals(BlockCompressedInputStream.checkTermination(channel), expected); + Assert.assertEquals(channel.position(), position); + } + } + + private static File getValidCompressedFile() throws IOException { final File tmpCompressedFile = File.createTempFile("test.", ".bgzf"); tmpCompressedFile.deleteOnExit(); final BlockCompressedOutputStream os = new BlockCompressedOutputStream(tmpCompressedFile); os.write("Hi, Mom!\n".getBytes()); os.close(); - Assert.assertEquals(BlockCompressedInputStream.checkTermination(tmpCompressedFile), - BlockCompressedInputStream.FileTermination.HAS_TERMINATOR_BLOCK); + return tmpCompressedFile; } @Test - public void testValidFileWithoutTerminator() throws Exception { - Assert.assertEquals(BlockCompressedInputStream.checkTermination(new File(TEST_DATA_DIR, "no_bgzf_terminator.bam")), - BlockCompressedInputStream.FileTermination.HAS_HEALTHY_LAST_BLOCK); + public void testReadFullyReadsBytesCorrectly() throws IOException { + try(final SeekableByteChannel channel = Files.newByteChannel(DEFECTIVE.toPath())){ + final ByteBuffer readBuffer = ByteBuffer.allocate(10); + Assert.assertTrue(channel.size() > readBuffer.capacity()); + BlockCompressedInputStream.readFully(channel, readBuffer); + + ByteBuffer expected = ByteBuffer.allocate(10); + channel.position(0).read(expected); + Assert.assertEquals(readBuffer.array(), expected.array()); + } } - @Test - public void testDefectiveFile() throws Exception { - Assert.assertEquals(BlockCompressedInputStream.checkTermination(new File(TEST_DATA_DIR, "defective_bgzf.bam")), - BlockCompressedInputStream.FileTermination.DEFECTIVE); + @Test(expectedExceptions = EOFException.class) + public void testReadFullyThrowWhenItCantReadEnough() throws IOException { + try(final SeekableByteChannel channel = Files.newByteChannel(DEFECTIVE.toPath())){ + final ByteBuffer readBuffer = ByteBuffer.allocate(1000); + Assert.assertTrue(channel.size() < readBuffer.capacity()); + BlockCompressedInputStream.readFully(channel, readBuffer); + } } + + + } From 9e0199a0036f05e0c0f8bdb6e959c757d69a462b Mon Sep 17 00:00:00 2001 From: Ron Levine Date: Thu, 8 Jun 2017 19:12:28 -0400 Subject: [PATCH 37/59] Fix SAMRecord.getReadLength() so it does not throw an exception if null bases --- src/main/java/htsjdk/samtools/SAMRecord.java | 3 ++- .../java/htsjdk/samtools/SAMRecordUnitTest.java | 24 ++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/htsjdk/samtools/SAMRecord.java b/src/main/java/htsjdk/samtools/SAMRecord.java index 8e61d150d..9ba0b9966 100644 --- a/src/main/java/htsjdk/samtools/SAMRecord.java +++ b/src/main/java/htsjdk/samtools/SAMRecord.java @@ -262,7 +262,8 @@ public void setReadBases(final byte[] value) { * @return number of bases in the read. */ public int getReadLength() { - return getReadBases().length; + final byte[] readBases = getReadBases(); + return readBases == null ? 0 : readBases.length; } /** diff --git a/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java b/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java index 29118197f..acb9a636d 100644 --- a/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java +++ b/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java @@ -776,7 +776,7 @@ public void testNullHeaderRecordValidation() { } @Test - private void testNullHeaderDeepCopy() { + public void testNullHeaderDeepCopy() { SAMRecord sam = createTestRecordHelper(); sam.setHeader(null); final SAMRecord deepCopy = sam.deepCopy(); @@ -805,13 +805,13 @@ private void testNullHeaderCigar(SAMRecord rec) { } @Test - private void testNullHeadGetCigarSAM() { - SAMRecord sam = createTestRecordHelper(); + public void testNullHeadGetCigarSAM() { + final SAMRecord sam = createTestRecordHelper(); testNullHeaderCigar(sam); } @Test - private void testNullHeadGetCigarBAM() { + public void testNullHeadGetCigarBAM() { SAMRecord sam = createTestRecordHelper(); SAMRecordFactory factory = new DefaultSAMRecordFactory(); BAMRecord bamRec = factory.createBAMRecord( @@ -1039,4 +1039,20 @@ public SAMRecord createTestSamRec() { return(rec); } + + @DataProvider + public Object [][] readBasesGetReadLengthData() { + return new Object[][]{ + { null, 0 }, + { SAMRecord.NULL_SEQUENCE, 0 }, + { new byte[] {'A', 'C'}, 2 } + }; + } + + @Test(dataProvider = "readBasesGetReadLengthData") + public void testNullReadBasesGetReadLength(final byte[] readBases, final int readLength) { + final SAMRecord sam = createTestRecordHelper(); + sam.setReadBases(readBases); + Assert.assertEquals(sam.getReadLength(), readLength); + } } From 7e79bbac3be60eeec86ea705498098e023ee6999 Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Fri, 9 Jun 2017 05:00:25 +0200 Subject: [PATCH 38/59] use Locatable interface (#887) --- src/main/java/htsjdk/samtools/util/IntervalTreeMap.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/htsjdk/samtools/util/IntervalTreeMap.java b/src/main/java/htsjdk/samtools/util/IntervalTreeMap.java index 259308732..ebec2f484 100644 --- a/src/main/java/htsjdk/samtools/util/IntervalTreeMap.java +++ b/src/main/java/htsjdk/samtools/util/IntervalTreeMap.java @@ -165,16 +165,16 @@ public int size() { } /** * Test overlapping interval - * @param key the interval + * @param key the Locatable * @return true if it contains an object overlapping the interval */ - public boolean containsOverlapping(final Interval key) { + public boolean containsOverlapping(final Locatable key) { final IntervalTree tree = mSequenceMap.get(key.getContig()); return tree!=null && tree.overlappers(key.getStart(), key.getEnd()).hasNext(); } - public Collection getOverlapping(final Interval key) { + public Collection getOverlapping(final Locatable key) { final List result = new ArrayList(); final IntervalTree tree = mSequenceMap.get(key.getContig()); if (tree != null) { @@ -187,10 +187,10 @@ public boolean containsOverlapping(final Interval key) { } /** * Test if this contains an object that is contained by 'key' - * @param key the interval + * @param key the Locatable * @return true if it contains an object is contained by 'key' */ - public boolean containsContained(final Interval key) { + public boolean containsContained(final Locatable key) { final IntervalTree tree = mSequenceMap.get(key.getContig()); if(tree==null) return false; final Iterator> iterator = tree.overlappers(key.getStart(), key.getEnd()); @@ -204,7 +204,7 @@ public boolean containsContained(final Interval key) { } - public Collection getContained(final Interval key) { + public Collection getContained(final Locatable key) { final List result = new ArrayList(); final IntervalTree tree = mSequenceMap.get(key.getContig()); if (tree != null) { From 112758bd4a7546dd85e70d5ec4ad55a58374b53d Mon Sep 17 00:00:00 2001 From: Ron Levine Date: Fri, 9 Jun 2017 09:01:35 -0400 Subject: [PATCH 39/59] Added test using setReadString() --- src/main/java/htsjdk/samtools/SAMRecord.java | 4 +++- src/main/java/htsjdk/samtools/util/StringUtil.java | 6 ++++++ .../java/htsjdk/samtools/SAMRecordUnitTest.java | 22 +++++++++++++++++++--- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/htsjdk/samtools/SAMRecord.java b/src/main/java/htsjdk/samtools/SAMRecord.java index 9ba0b9966..f93b2d72c 100644 --- a/src/main/java/htsjdk/samtools/SAMRecord.java +++ b/src/main/java/htsjdk/samtools/SAMRecord.java @@ -238,7 +238,9 @@ public void setReadString(final String value) { mReadBases = NULL_SEQUENCE; } else { final byte[] bases = StringUtil.stringToBytes(value); - SAMUtils.normalizeBases(bases); + if (bases != null) { + SAMUtils.normalizeBases(bases); + } setReadBases(bases); } } diff --git a/src/main/java/htsjdk/samtools/util/StringUtil.java b/src/main/java/htsjdk/samtools/util/StringUtil.java index 90492533e..a885ba2db 100644 --- a/src/main/java/htsjdk/samtools/util/StringUtil.java +++ b/src/main/java/htsjdk/samtools/util/StringUtil.java @@ -312,6 +312,9 @@ public static String bytesToString(final byte[] buffer, final int offset, final } return byteBuffer; */ + if (s == null) { + return null; + } final byte[] byteBuffer = new byte[s.length()]; s.getBytes(0, byteBuffer.length, byteBuffer, 0); return byteBuffer; @@ -319,6 +322,9 @@ public static String bytesToString(final byte[] buffer, final int offset, final @SuppressWarnings("deprecation") public static byte[] stringToBytes(final String s, final int offset, final int length) { + if (s == null) { + return null; + } final byte[] byteBuffer = new byte[length]; s.getBytes(offset, offset + length, byteBuffer, 0); return byteBuffer; diff --git a/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java b/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java index acb9a636d..1bfe26345 100644 --- a/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java +++ b/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java @@ -1041,7 +1041,7 @@ public SAMRecord createTestSamRec() { } @DataProvider - public Object [][] readBasesGetReadLengthData() { + public Object [][] readBasesArrayGetReadLengthData() { return new Object[][]{ { null, 0 }, { SAMRecord.NULL_SEQUENCE, 0 }, @@ -1049,10 +1049,26 @@ public SAMRecord createTestSamRec() { }; } - @Test(dataProvider = "readBasesGetReadLengthData") - public void testNullReadBasesGetReadLength(final byte[] readBases, final int readLength) { + @Test(dataProvider = "readBasesArrayGetReadLengthData") + public void testReadBasesGetReadLength(final byte[] readBases, final int readLength) { final SAMRecord sam = createTestRecordHelper(); sam.setReadBases(readBases); Assert.assertEquals(sam.getReadLength(), readLength); } + + @DataProvider + public Object [][] readBasesStringGetReadLengthData() { + return new Object[][]{ + { null, 0 }, + { SAMRecord.NULL_SEQUENCE_STRING, 0 }, + { "AC", 2 } + }; + } + + @Test(dataProvider = "readBasesStringGetReadLengthData") + public void testReadStringGetReadLength(final String readBases, final int readLength) { + final SAMRecord sam = createTestRecordHelper(); + sam.setReadString(readBases); + Assert.assertEquals(sam.getReadLength(), readLength); + } } From 9b84fe816039aba122c421191b13824f099cefdb Mon Sep 17 00:00:00 2001 From: Yossi Farjoun Date: Fri, 9 Jun 2017 11:00:11 -0400 Subject: [PATCH 40/59] Cleaned up SAMUtils (mostly cosmetic) (#888) * Fixing-up SamUtils: - adding more information to some exceptions - fixing java-doc - java8 <> - if/else alignment & braces - removed use of deprecated SAMRecordUtils class - @Deprecated all methods and members of SAMRecordUtils - fixed javadoc @deprecated across the board. --- .../java/htsjdk/samtools/DuplicateSetIterator.java | 2 +- src/main/java/htsjdk/samtools/SAMRecordUtil.java | 27 +- src/main/java/htsjdk/samtools/SAMUtils.java | 302 ++++----- .../htsjdk/samtools/filter/FilteringIterator.java | 2 +- src/main/java/htsjdk/tribble/util/HTTPHelper.java | 3 + .../variantcontext/GenotypeLikelihoods.java | 6 + .../variantcontext/filter/FilteringIterator.java | 2 +- src/main/java/htsjdk/variant/vcf/VCFEncoder.java | 715 +++++++++++---------- 8 files changed, 547 insertions(+), 512 deletions(-) diff --git a/src/main/java/htsjdk/samtools/DuplicateSetIterator.java b/src/main/java/htsjdk/samtools/DuplicateSetIterator.java index f3b9b072a..6e833035b 100644 --- a/src/main/java/htsjdk/samtools/DuplicateSetIterator.java +++ b/src/main/java/htsjdk/samtools/DuplicateSetIterator.java @@ -114,7 +114,7 @@ public DuplicateSetIterator(final CloseableIterator iterator, } @Deprecated - /** Do not use this method as the first duplicate set will not be compared with this scoring strategy. + /** @deprecated Do not use this method as the first duplicate set will not be compared with this scoring strategy. * Instead, provide a comparator to the constructor that has the scoring strategy set. */ public void setScoringStrategy(final DuplicateScoringStrategy.ScoringStrategy scoringStrategy) { this.comparator.setScoringStrategy(scoringStrategy); diff --git a/src/main/java/htsjdk/samtools/SAMRecordUtil.java b/src/main/java/htsjdk/samtools/SAMRecordUtil.java index d778789d7..9435934c5 100644 --- a/src/main/java/htsjdk/samtools/SAMRecordUtil.java +++ b/src/main/java/htsjdk/samtools/SAMRecordUtil.java @@ -23,23 +23,28 @@ */ package htsjdk.samtools; -import htsjdk.samtools.util.SequenceUtil; -import htsjdk.samtools.util.StringUtil; - import java.util.Arrays; import java.util.Collection; import java.util.List; /** * - * Use {@link SAMRecord#reverseComplement()} instead, which defaults to making a copy of attributes for reverse - * complement rather than changing them in-place. - * * @author alecw@broadinstitute.org + * + * @deprecated 10/27/2016 Use {@link SAMRecord} constants and functions */ @Deprecated public class SAMRecordUtil { + /** + * @deprecated 6/5/2017 Use {@link SAMRecord#TAGS_TO_REVERSE_COMPLEMENT} + */ + @Deprecated public static List TAGS_TO_REVERSE_COMPLEMENT = Arrays.asList(SAMTag.E2.name(), SAMTag.SQ.name()); + + /** + * @deprecated 6/5/2017 Use {@link SAMRecord#TAGS_TO_REVERSE} + */ + @Deprecated public static List TAGS_TO_REVERSE = Arrays.asList(SAMTag.OQ.name(), SAMTag.U2.name()); /** @@ -48,7 +53,11 @@ * or attributes. If a copy is needed use {@link #reverseComplement(SAMRecord, boolean)}. * See {@link #TAGS_TO_REVERSE_COMPLEMENT} {@link #TAGS_TO_REVERSE} * for the default set of tags that are handled. + * + * @deprecated 6/5/2017 Use {@link SAMRecord#reverseComplement} but note that the default behavior there is different + * It will default to making a copy, not reverse-complementing in-place! */ + @Deprecated public static void reverseComplement(final SAMRecord rec) { rec.reverseComplement(TAGS_TO_REVERSE_COMPLEMENT, TAGS_TO_REVERSE, true); } @@ -61,7 +70,10 @@ public static void reverseComplement(final SAMRecord rec) { * * @param rec Record to reverse complement. * @param inplace Setting this to false will clone all attributes, bases and qualities before changing the values. + * + * @deprecated 6/5/2017 Use {@link SAMRecord#reverseComplement} */ + @Deprecated public static void reverseComplement(final SAMRecord rec, boolean inplace) { rec.reverseComplement(TAGS_TO_REVERSE_COMPLEMENT, TAGS_TO_REVERSE, inplace); } @@ -70,7 +82,10 @@ public static void reverseComplement(final SAMRecord rec, boolean inplace) { * Reverse complement bases and reverse quality scores. In addition reverse complement any * non-null attributes specified by tagsToRevcomp and reverse and non-null attributes * specified by tagsToReverse. + * + * @deprecated 6/5/2017 Use {@link SAMRecord#reverseComplement} */ + @Deprecated public static void reverseComplement(final SAMRecord rec, final Collection tagsToRevcomp, final Collection tagsToReverse, boolean inplace) { rec.reverseComplement(tagsToRevcomp, tagsToReverse, inplace); } diff --git a/src/main/java/htsjdk/samtools/SAMUtils.java b/src/main/java/htsjdk/samtools/SAMUtils.java index d439a4a83..5b81de979 100644 --- a/src/main/java/htsjdk/samtools/SAMUtils.java +++ b/src/main/java/htsjdk/samtools/SAMUtils.java @@ -43,14 +43,17 @@ import java.util.TreeMap; import java.util.regex.Pattern; - /** * Utilty methods. */ public final class SAMUtils { - /** regex for semicolon, used in {@link SAMUtils#getOtherCanonicalAlignments(SAMRecord)} */ + /** + * regex for semicolon, used in {@link SAMUtils#getOtherCanonicalAlignments(SAMRecord)} + */ private static final Pattern SEMICOLON_PAT = Pattern.compile("[;]"); - /** regex for comma, used in {@link SAMUtils#getOtherCanonicalAlignments(SAMRecord)} */ + /** + * regex for comma, used in {@link SAMUtils#getOtherCanonicalAlignments(SAMRecord)} + */ private static final Pattern COMMA_PAT = Pattern.compile("[,]"); // Representation of bases, one for when in low-order nybble, one for when in high-order nybble. @@ -87,27 +90,26 @@ private static final byte COMPRESSED_K_HIGH = (byte) (COMPRESSED_K_LOW << 4); private static final byte COMPRESSED_D_HIGH = (byte) (COMPRESSED_D_LOW << 4); private static final byte COMPRESSED_B_HIGH = (byte) (COMPRESSED_B_LOW << 4); - - private static final byte [] COMPRESSED_LOOKUP_TABLE = - new byte[]{ - '=', - 'A', - 'C', - 'M', - 'G', - 'R', - 'S', - 'V', - 'T', - 'W', - 'Y', - 'H', - 'K', - 'D', - 'B', - 'N' - }; - + + private static final byte[] COMPRESSED_LOOKUP_TABLE = { + '=', + 'A', + 'C', + 'M', + 'G', + 'R', + 'S', + 'V', + 'T', + 'W', + 'Y', + 'H', + 'K', + 'D', + 'B', + 'N' + }; + public static final int MAX_PHRED_SCORE = 93; /** @@ -135,8 +137,8 @@ * Convert from a byte array with bases stored in nybbles, with for example,=, A, C, G, T, N represented as 0, 1, 2, 4, 8, 15, * to a a byte array containing =AaCcGgTtNn represented as ASCII. * - * @param length Number of bases (not bytes) to convert. - * @param compressedBases Bases represented as nybbles, in BAM binary format. + * @param length Number of bases (not bytes) to convert. + * @param compressedBases Bases represented as nybbles, in BAM binary format. * @param compressedOffset Byte offset in compressedBases to start. * @return New byte array with bases as ASCII bytes. */ @@ -215,7 +217,7 @@ private static byte charToCompressedBaseLow(final byte base) { case 'b': return COMPRESSED_B_LOW; default: - throw new IllegalArgumentException("Bad base passed to charToCompressedBaseLow: " + Character.toString((char)base) + "(" + base + ")"); + throw new IllegalArgumentException("Bad base passed to charToCompressedBaseLow: " + Character.toString((char) base) + "(" + base + ")"); } } @@ -279,21 +281,22 @@ private static byte charToCompressedBaseHigh(final byte base) { case 'b': return COMPRESSED_B_HIGH; default: - throw new IllegalArgumentException("Bad base passed to charToCompressedBaseHigh: " + Character.toString((char)base) + "(" + base + ")"); + throw new IllegalArgumentException("Bad base passed to charToCompressedBaseHigh: " + Character.toString((char) base) + "(" + base + ")"); } } - + /** * Returns the byte corresponding to a certain nybble + * * @param base One of COMPRESSED_*_LOW, a low-order nybble encoded base. * @return ASCII base, one of =ACGTNMRSVWYHKDB. * @throws IllegalArgumentException if the base is not one of =ACGTNMRSVWYHKDB. */ - private static byte compressedBaseToByte(byte base){ - try{ + private static byte compressedBaseToByte(byte base) { + try { return COMPRESSED_LOOKUP_TABLE[base]; - }catch(IndexOutOfBoundsException e){ - throw new IllegalArgumentException("Bad base passed to charToCompressedBase: " + Character.toString((char)base) + "(" + base + ")"); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException("Bad base passed to charToCompressedBase: " + Character.toString((char) base) + "(" + base + ")"); } } @@ -304,7 +307,7 @@ private static byte compressedBaseToByte(byte base){ * @return ASCII base, one of ACGTN=. */ private static byte compressedBaseToByteLow(final int base) { - return compressedBaseToByte((byte)(base & 0xf)); + return compressedBaseToByte((byte) (base & 0xf)); } /** @@ -314,13 +317,13 @@ private static byte compressedBaseToByteLow(final int base) { * @return ASCII base, one of ACGTN=. */ private static byte compressedBaseToByteHigh(final int base) { - return compressedBaseToByte((byte)((base >> 4) & 0xf)); + return compressedBaseToByte((byte) ((base >> 4) & 0xf)); } /** * Convert bases in place into canonical form, upper case, and with no-call represented as N. * - * @param bases + * @param bases byte array of bases to "normalize", in place. */ static void normalizeBases(final byte[] bases) { for (int i = 0; i < bases.length; ++i) { @@ -434,11 +437,11 @@ static int reg2bin(final int beg, final int end) { /** * Handle a list of validation errors according to the validation stringency. * - * @param validationErrors List of errors to report, or null if there are no errors. - * @param samRecordIndex Record number of the SAMRecord corresponding to the validation errors, or -1 if - * the record number is not known. + * @param validationErrors List of errors to report, or null if there are no errors. + * @param samRecordIndex Record number of the SAMRecord corresponding to the validation errors, or -1 if + * the record number is not known. * @param validationStringency If STRICT, throw a SAMFormatException. If LENIENT, print the validation - * errors to stderr. If SILENT, do nothing. + * errors to stderr. If SILENT, do nothing. */ public static void processValidationErrors(final List validationErrors, final long samRecordIndex, @@ -464,11 +467,10 @@ public static void processValidationError(final SAMValidationError validationErr } else if (validationStringency == ValidationStringency.LENIENT) { System.err.println("Ignoring SAM validation error: " + validationError); } - } private static final SAMHeaderRecordComparator HEADER_RECORD_COMPARATOR = - new SAMHeaderRecordComparator( + new SAMHeaderRecordComparator<>( SAMReadGroupRecord.PLATFORM_UNIT_TAG, SAMReadGroupRecord.LIBRARY_TAG, SAMReadGroupRecord.DATE_RUN_PRODUCED_TAG, @@ -476,7 +478,8 @@ public static void processValidationError(final SAMValidationError validationErr SAMReadGroupRecord.SEQUENCING_CENTER_TAG, SAMReadGroupRecord.PLATFORM_TAG, SAMReadGroupRecord.DESCRIPTION_TAG, - SAMReadGroupRecord.READ_GROUP_ID_TAG // We don't actually want to compare with ID but it's suitable + SAMReadGroupRecord.READ_GROUP_ID_TAG + // We don't actually want to compare with ID but it's suitable // "just in case" since it's the only one that's actually required ); @@ -497,11 +500,11 @@ public static String calculateReadGroupRecordChecksum(final File input, final Fi // Sort the read group records by their first final SamReader reader = SamReaderFactory.makeDefault().referenceSequence(referenceFasta).open(input); - final List sortedRecords = new ArrayList(reader.getFileHeader().getReadGroups()); + final List sortedRecords = new ArrayList<>(reader.getFileHeader().getReadGroups()); Collections.sort(sortedRecords, HEADER_RECORD_COMPARATOR); for (final SAMReadGroupRecord rgRecord : sortedRecords) { - final TreeMap sortedAttributes = new TreeMap(); + final TreeMap sortedAttributes = new TreeMap<>(); for (final Map.Entry attributeEntry : rgRecord.getAttributes()) { sortedAttributes.put(attributeEntry.getKey(), attributeEntry.getValue()); } @@ -539,7 +542,7 @@ public static void chainSAMProgramRecord(final SAMFileHeader header, final SAMPr final List pgs = header.getProgramRecords(); if (!pgs.isEmpty()) { - final List referencedIds = new ArrayList(); + final List referencedIds = new ArrayList<>(); for (final SAMProgramRecord pg : pgs) { if (pg.getPreviousProgramGroupId() != null) { referencedIds.add(pg.getPreviousProgramGroupId()); @@ -560,7 +563,7 @@ public static void chainSAMProgramRecord(final SAMFileHeader header, final SAMPr /** * Strip mapping information from a SAMRecord. - * + *

* WARNING: by clearing the secondary and supplementary flags, * this may have the affect of producing multiple distinct records with the * same read name and flags, which may lead to invalid SAM/BAM output. @@ -568,7 +571,7 @@ public static void chainSAMProgramRecord(final SAMFileHeader header, final SAMPr */ public static void makeReadUnmapped(final SAMRecord rec) { if (rec.getReadNegativeStrandFlag()) { - SAMRecordUtil.reverseComplement(rec); + rec.reverseComplement(true); rec.setReadNegativeStrandFlag(false); } rec.setDuplicateReadFlag(false); @@ -622,13 +625,13 @@ public static boolean cigarMapsNoBasesToRef(final Cigar cigar) { /** * Tests if the provided record is mapped entirely beyond the end of the reference (i.e., the alignment start is greater than the * length of the sequence to which the record is mapped). + * * @param record must not have a null SamFileHeader */ public static boolean recordMapsEntirelyBeyondEndOfReference(final SAMRecord record) { if (record.getHeader() == null) { throw new SAMException("A non-null SAMHeader is required to resolve the mapping position: " + record.getReadName()); - } - else { + } else { return record.getHeader().getSequence(record.getReferenceIndex()).getSequenceLength() < record.getAlignmentStart(); } } @@ -646,7 +649,6 @@ public static int compareMapqs(final int mapq1, final int mapq2) { else return mapq1 - mapq2; } - /** * Hokey algorithm for combining two MAPQs into values that are comparable, being cognizant of the fact * that in MAPQ world, 1 > 255 > 0. In this algorithm, 255 is treated as if it were 0.01, so that @@ -655,11 +657,17 @@ public static int compareMapqs(final int mapq1, final int mapq2) { * invocations of this method. */ public static int combineMapqs(int m1, int m2) { - if (m1 == 255) m1 = 1; - else m1 *= 100; + if (m1 == 255) { + m1 = 1; + } else { + m1 *= 100; + } - if (m2 == 255) m2 = 1; - else m2 *= 100; + if (m2 == 255) { + m2 = 1; + } else { + m2 *= 100; + } return m1 + m2; @@ -682,15 +690,15 @@ public static long findVirtualOffsetOfFirstRecordInBam(final File bamFile) { * reference sequence. Note that clipped portions, and inserted and deleted bases (vs. the reference) * are not represented in the alignment blocks. * - * @param cigar The cigar containing the alignment information + * @param cigar The cigar containing the alignment information * @param alignmentStart The start (1-based) of the alignment - * @param cigarTypeName The type of cigar passed - for error logging. + * @param cigarTypeName The type of cigar passed - for error logging. * @return List of alignment blocks */ public static List getAlignmentBlocks(final Cigar cigar, final int alignmentStart, final String cigarTypeName) { if (cigar == null) return Collections.emptyList(); - final List alignmentBlocks = new ArrayList(); + final List alignmentBlocks = new ArrayList<>(); int readBase = 1; int refBase = alignmentStart; @@ -721,7 +729,7 @@ public static long findVirtualOffsetOfFirstRecordInBam(final File bamFile) { refBase += length; break; default: - throw new IllegalStateException("Case statement didn't deal with " + cigarTypeName + " op: " + e.getOperator()); + throw new IllegalStateException("Case statement didn't deal with " + cigarTypeName + " op: " + e.getOperator() + "in CIGAR: " + cigar); } } return Collections.unmodifiableList(alignmentBlocks); @@ -729,7 +737,7 @@ public static long findVirtualOffsetOfFirstRecordInBam(final File bamFile) { /** * @param alignmentStart The start (1-based) of the alignment - * @param cigar The cigar containing the alignment information + * @param cigar The cigar containing the alignment information * @return the alignment start (1-based, inclusive) adjusted for clipped bases. For example if the read * has an alignment start of 100 but the first 4 bases were clipped (hard or soft clipped) * then this method will return 96. @@ -753,7 +761,7 @@ public static int getUnclippedStart(final int alignmentStart, final Cigar cigar) /** * @param alignmentEnd The end (1-based) of the alignment - * @param cigar The cigar containing the alignment information + * @param cigar The cigar containing the alignment information * @return the alignment end (1-based, inclusive) adjusted for clipped bases. For example if the read * has an alignment end of 100 but the last 7 bases were clipped (hard or soft clipped) * then this method will return 107. @@ -791,7 +799,7 @@ public static String getMateCigarString(final SAMRecord rec) { /** * Returns the Mate Cigar or null if there is none. * - * @param rec the SAM record + * @param rec the SAM record * @param withValidation true if we are to validate the mate cigar before returning, false otherwise. * @return Cigar object for the read's mate, or null if there is none. */ @@ -835,11 +843,11 @@ public static int getMateCigarLength(final SAMRecord rec) { */ public static int getMateAlignmentEnd(final SAMRecord rec) { if (rec.getMateUnmappedFlag()) { - throw new RuntimeException("getMateAlignmentEnd called on an unmapped mate."); + throw new RuntimeException("getMateAlignmentEnd called on an unmapped mate: " + rec); } final Cigar mateCigar = SAMUtils.getMateCigar(rec); if (mateCigar == null) { - throw new SAMException("Mate CIGAR (Tag MC) not found."); + throw new SAMException("Mate CIGAR (Tag MC) not found:" + rec); } return CoordMath.getEnd(rec.getMateAlignmentStart(), mateCigar.getReferenceLength()); } @@ -854,15 +862,14 @@ public static int getMateAlignmentEnd(final SAMRecord rec) { */ public static int getMateUnclippedStart(final SAMRecord rec) { if (rec.getMateUnmappedFlag()) - throw new RuntimeException("getMateUnclippedStart called on an unmapped mate."); + throw new RuntimeException("getMateUnclippedStart called on an unmapped mate: " + rec); final Cigar mateCigar = getMateCigar(rec); if (mateCigar == null) { - throw new SAMException("Mate CIGAR (Tag MC) not found."); + throw new SAMException("Mate CIGAR (Tag MC) not found: " + rec); } return SAMUtils.getUnclippedStart(rec.getMateAlignmentStart(), mateCigar); } - /** * @param rec the SAM record * @return the mate alignment end (1-based, inclusive) adjusted for clipped bases. For example if the mate @@ -873,20 +880,20 @@ public static int getMateUnclippedStart(final SAMRecord rec) { */ public static int getMateUnclippedEnd(final SAMRecord rec) { if (rec.getMateUnmappedFlag()) { - throw new RuntimeException("getMateUnclippedEnd called on an unmapped mate."); + throw new RuntimeException("getMateUnclippedEnd called on an unmapped mate: " + rec); } final Cigar mateCigar = SAMUtils.getMateCigar(rec); if (mateCigar == null) { - throw new SAMException("Mate CIGAR (Tag MC) not found."); + throw new SAMException("Mate CIGAR (Tag MC) not found: " + rec); } return SAMUtils.getUnclippedEnd(getMateAlignmentEnd(rec), mateCigar); } /** * @param rec the SAM record - * Returns blocks of the mate sequence that have been aligned directly to the - * reference sequence. Note that clipped portions of the mate and inserted and - * deleted bases (vs. the reference) are not represented in the alignment blocks. + * Returns blocks of the mate sequence that have been aligned directly to the + * reference sequence. Note that clipped portions of the mate and inserted and + * deleted bases (vs. the reference) are not represented in the alignment blocks. */ public static List getMateAlignmentBlocks(final SAMRecord rec) { return getAlignmentBlocks(getMateCigar(rec), rec.getMateAlignmentStart(), "mate cigar"); @@ -896,12 +903,12 @@ public static int getMateUnclippedEnd(final SAMRecord rec) { * Run all validations of the mate's CIGAR. These include validation that the CIGAR makes sense independent of * placement, plus validation that CIGAR + placement yields all bases with M operator within the range of the reference. * - * @param rec the SAM record - * @param cigar The cigar containing the alignment information - * @param referenceIndex The reference index + * @param rec the SAM record + * @param cigar The cigar containing the alignment information + * @param referenceIndex The reference index * @param alignmentBlocks The alignment blocks (parsed from the cigar) - * @param recordNumber For error reporting. -1 if not known. - * @param cigarTypeName For error reporting. "Read CIGAR" or "Mate Cigar" + * @param recordNumber For error reporting. -1 if not known. + * @param cigarTypeName For error reporting. "Read CIGAR" or "Mate Cigar" * @return List of errors, or null if no errors. */ @@ -916,16 +923,15 @@ public static int getMateUnclippedEnd(final SAMRecord rec) { if (referenceIndex != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { SAMFileHeader samHeader = rec.getHeader(); if (null == samHeader) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.MISSING_HEADER, cigarTypeName + " A non-null SAMHeader is required to validate cigar elements for: ", rec.getReadName(), recordNumber)); - } - else { + } else { final SAMSequenceRecord sequence = samHeader.getSequence(referenceIndex); final int referenceSequenceLength = sequence.getSequenceLength(); for (final AlignmentBlock alignmentBlock : alignmentBlocks) { if (alignmentBlock.getReferenceStart() + alignmentBlock.getLength() - 1 > referenceSequenceLength) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.CIGAR_MAPS_OFF_REFERENCE, cigarTypeName + " M operator maps off end of reference", rec.getReadName(), recordNumber)); break; @@ -940,7 +946,7 @@ public static int getMateUnclippedEnd(final SAMRecord rec) { * Run all validations of the mate's CIGAR. These include validation that the CIGAR makes sense independent of * placement, plus validation that CIGAR + placement yields all bases with M operator within the range of the reference. * - * @param rec the SAM record + * @param rec the SAM record * @param recordNumber For error reporting. -1 if not known. * @return List of errors, or null if no errors. */ @@ -954,7 +960,7 @@ public static int getMateUnclippedEnd(final SAMRecord rec) { } } else { if (getMateCigarString(rec) != null) { - ret = new ArrayList(); + ret = new ArrayList<>(); if (!rec.getReadPairedFlag()) { // If the read is not paired, and the Mate Cigar String (MC Attribute) exists, that is a validation error ret.add(new SAMValidationError(SAMValidationError.Type.MATE_CIGAR_STRING_INVALID_PRESENCE, @@ -984,11 +990,11 @@ public static boolean hasMateCigar(SAMRecord rec) { } /** - * Returns a string that is the the read group ID and read name separated by a colon. This is meant to cannonically + * Returns a string that is the the read group ID and read name separated by a colon. This is meant to canonically * identify a given record within a set of records. * - * @param record - * @return + * @param record SAMRecord for which "canonical" read name is requested + * @return The record's readgroup-id (if non-null) and the read name, separated by a colon, ':' */ public static String getCanonicalRecordName(final SAMRecord record) { String name = record.getStringAttribute(ReservedTagConstants.READ_GROUP_ID); @@ -1002,7 +1008,7 @@ public static String getCanonicalRecordName(final SAMRecord record) { * or the given record's start position is greater than its mate's start position, zero is automatically returned. * NB: This method assumes that the record's mate is not contained within the given record's alignment. * - * @param rec + * @param rec SAMRecord that needs clipping due to overlapping pairs. * @return the number of bases at the end of the read that need to be clipped such that there would be no overlapping bases with its mate. * Read bases include only those from insertion, match, or mismatch Cigar operators. */ @@ -1013,7 +1019,8 @@ public static int getNumOverlappingAlignedBasesToClip(final SAMRecord rec) { // Only clip records that are left-most in genomic order and overlapping. if (rec.getMateAlignmentStart() < rec.getAlignmentStart()) return 0; // right-most, so ignore. - else if (rec.getMateAlignmentStart() == rec.getAlignmentStart() && rec.getFirstOfPairFlag()) return 0; // same start, so pick the first end + else if (rec.getMateAlignmentStart() == rec.getAlignmentStart() && rec.getFirstOfPairFlag()) + return 0; // same start, so pick the first end // Find the number of read bases after the given mate's alignment start. int numBasesToClip = 0; @@ -1026,12 +1033,11 @@ public static int getNumOverlappingAlignedBasesToClip(final SAMRecord rec) { if (refStartPos <= refPos + refBasesLength - 1) { // add to clipped bases if (operator == CigarOperator.MATCH_OR_MISMATCH) { // M if (refStartPos < refPos) numBasesToClip += refBasesLength; // use all of the bases - else numBasesToClip += (refPos + refBasesLength) - refStartPos; // since the mate's alignment start can be in the middle of a cigar element - } - else if (operator == CigarOperator.SOFT_CLIP || operator == CigarOperator.HARD_CLIP || operator == CigarOperator.PADDING || operator == CigarOperator.SKIPPED_REGION) { + else + numBasesToClip += (refPos + refBasesLength) - refStartPos; // since the mate's alignment start can be in the middle of a cigar element + } else if (operator == CigarOperator.SOFT_CLIP || operator == CigarOperator.HARD_CLIP || operator == CigarOperator.PADDING || operator == CigarOperator.SKIPPED_REGION) { // ignore - } - else { // ID + } else { // ID numBasesToClip += operator.consumesReadBases() ? el.getLength() : 0; // clip all the bases in the read from this operator } } @@ -1044,14 +1050,14 @@ else if (operator == CigarOperator.SOFT_CLIP || operator == CigarOperator.HARD_C } /** - * Returns a (possibly new) record that has been clipped if isa mapped paired and has overlapping bases with its mate. + * Returns a (possibly new) record that has been clipped if input is a mapped paired and has overlapping bases with its mate. * See {@link #getNumOverlappingAlignedBasesToClip(SAMRecord)} for how the number of overlapping bases is computed. * NB: this does not properly consider a cigar like: 100M20S10H. * NB: This method assumes that the record's mate is not contained within the given record's alignment. * - * @param record the record from which to clip bases. + * @param record the record from which to clip bases. * @param noSideEffects if true a modified clone of the original record is returned, otherwise we modify the record directly. - * @return + * @return a (possibly new) record that has been clipped */ public static SAMRecord clipOverlappingAlignedBases(final SAMRecord record, final boolean noSideEffects) { return clipOverlappingAlignedBases(record, getNumOverlappingAlignedBasesToClip(record), noSideEffects); @@ -1063,18 +1069,20 @@ public static SAMRecord clipOverlappingAlignedBases(final SAMRecord record, fina * NB: this does not properly consider a cigar like: 100M20S10H. * NB: This method assumes that the record's mate is not contained within the given record's alignment. * - * @param record the record from which to clip bases. + * @param record the record from which to clip bases. * @param numOverlappingBasesToClip the number of bases to clip at the end of the read. - * @param noSideEffects if true a modified clone of the original record is returned, otherwise we modify the record directly. - * @return + * @param noSideEffects if true a modified clone of the original record is returned, otherwise we modify the record directly. + * @return Returns a (possibly new) SAMRecord with the given number of bases soft-clipped */ public static SAMRecord clipOverlappingAlignedBases(final SAMRecord record, final int numOverlappingBasesToClip, final boolean noSideEffects) { // NB: ignores how to handle supplemental records when present for both ends by just using the mate information in the record. - if (numOverlappingBasesToClip <= 0 || record.getReadUnmappedFlag() || record.getMateUnmappedFlag()) return record; + if (numOverlappingBasesToClip <= 0 || record.getReadUnmappedFlag() || record.getMateUnmappedFlag()) { + return record; + } try { - final SAMRecord rec = noSideEffects ? ((SAMRecord)record.clone()) : record; + final SAMRecord rec = noSideEffects ? ((SAMRecord) record.clone()) : record; // watch out for when the second read overlaps all of the first read if (rec.getMateAlignmentStart() <= rec.getAlignmentStart()) { // make it unmapped @@ -1085,7 +1093,7 @@ public static SAMRecord clipOverlappingAlignedBases(final SAMRecord record, fina // 1-based index of first base in read to clip. int clipFrom = rec.getReadLength() - numOverlappingBasesToClip + 1; // we have to check if the last cigar element is soft-clipping, so we can subtract that from clipFrom - final CigarElement cigarElement = rec.getCigar().getCigarElement(rec.getCigarLength()-1); + final CigarElement cigarElement = rec.getCigar().getCigarElement(rec.getCigarLength() - 1); if (CigarOperator.SOFT_CLIP == cigarElement.getOperator()) clipFrom -= cigarElement.getLength(); // FIXME: does not properly consider a cigar like: 100M20S10H @@ -1111,100 +1119,102 @@ public static boolean isValidUnsignedIntegerAttribute(long value) { * Extract a List of 'other canonical alignments' from a SAM record. Those alignments are stored as a string in the 'SA' tag as defined * in the SAM specification. * The name, sequence and qualities, mate data are copied from the original record. + * * @param record must be non null and must have a non-null associated header. * @return a list of 'other canonical alignments' SAMRecords. The list is empty if the 'SA' attribute is missing. */ public static List getOtherCanonicalAlignments(final SAMRecord record) { - if( record == null ) throw new IllegalArgumentException("record is null"); - if( record.getHeader() == null ) throw new IllegalArgumentException("record.getHeader() is null"); + if (record == null) throw new IllegalArgumentException("record is null"); + if (record.getHeader() == null) throw new IllegalArgumentException("record.getHeader() is null"); /* extract value of SA tag */ - final Object saValue = record.getAttribute( SAMTagUtil.getSingleton().SA ); - if( saValue == null ) return Collections.emptyList(); - if( ! (saValue instanceof String) ) throw new SAMException( - "Expected a String for attribute 'SA' but got " + saValue.getClass() ); + final Object saValue = record.getAttribute(SAMTagUtil.getSingleton().SA); + if (saValue == null) return Collections.emptyList(); + if (!(saValue instanceof String)) throw new SAMException( + "Expected a String for attribute 'SA' but got " + saValue.getClass() + ". Record: " + record); final SAMRecordFactory samReaderFactory = new DefaultSAMRecordFactory(); /* the spec says: "Other canonical alignments in a chimeric alignment, formatted as a * semicolon-delimited list: (rname,pos,strand,CIGAR,mapQ,NM;)+. * Each element in the list represents a part of the chimeric alignment. - * Conventionally, at a supplementary line, the 1rst element points to the primary line. + * Conventionally, at a supplementary line, the 1st element points to the primary line. */ /* break string using semicolon */ - final String semiColonStrs[] = SEMICOLON_PAT.split((String)saValue); + final String semiColonStrs[] = SEMICOLON_PAT.split((String) saValue); /* the result list */ - final List alignments = new ArrayList<>( semiColonStrs.length ); + final List alignments = new ArrayList<>(semiColonStrs.length); /* base SAM flag */ - int record_flag = record.getFlags() ; + int record_flag = record.getFlags(); record_flag &= ~SAMFlag.PROPER_PAIR.flag; record_flag &= ~SAMFlag.SUPPLEMENTARY_ALIGNMENT.flag; record_flag &= ~SAMFlag.READ_REVERSE_STRAND.flag; - - for(int i=0; i< semiColonStrs.length;++i ) { + for (int i = 0; i < semiColonStrs.length; ++i) { final String semiColonStr = semiColonStrs[i]; /* ignore empty string */ - if( semiColonStr.isEmpty() ) continue; + if (semiColonStr.isEmpty()) continue; /* break string using comma */ final String commaStrs[] = COMMA_PAT.split(semiColonStr); - if( commaStrs.length != 6 ) throw new SAMException("Bad 'SA' attribute in " + semiColonStr); + if (commaStrs.length != 6) + throw new SAMException("Bad 'SA' attribute in " + semiColonStr + ". Record: " + record); /* create the new record */ - final SAMRecord otherRec = samReaderFactory.createSAMRecord( record.getHeader() ); + final SAMRecord otherRec = samReaderFactory.createSAMRecord(record.getHeader()); /* copy fields from the original record */ - otherRec.setReadName( record.getReadName() ); - otherRec.setReadBases( record.getReadBases() ); - otherRec.setBaseQualities( record.getBaseQualities() ); - if( record.getReadPairedFlag() && !record.getMateUnmappedFlag()) { - otherRec.setMateReferenceIndex( record.getMateReferenceIndex() ); - otherRec.setMateAlignmentStart( record.getMateAlignmentStart() ); + otherRec.setReadName(record.getReadName()); + otherRec.setReadBases(record.getReadBases()); + otherRec.setBaseQualities(record.getBaseQualities()); + if (record.getReadPairedFlag() && !record.getMateUnmappedFlag()) { + otherRec.setMateReferenceIndex(record.getMateReferenceIndex()); + otherRec.setMateAlignmentStart(record.getMateAlignmentStart()); } /* get reference sequence */ - final int tid = record.getHeader().getSequenceIndex( commaStrs[0] ); - if( tid == -1 ) throw new SAMException("Unknown contig in " + semiColonStr); - otherRec.setReferenceIndex( tid ); + final int tid = record.getHeader().getSequenceIndex(commaStrs[0]); + if (tid == -1) + throw new SAMException("Unknown contig in " + semiColonStr + ". Record: " + record); + otherRec.setReferenceIndex(tid); /* fill POS */ final int alignStart; try { alignStart = Integer.parseInt(commaStrs[1]); - } catch( final NumberFormatException err ) { - throw new SAMException("bad POS in "+semiColonStr, err); + } catch (final NumberFormatException err) { + throw new SAMException("bad POS in " + semiColonStr + ". Record: " + record, err); } - otherRec.setAlignmentStart( alignStart ); + otherRec.setAlignmentStart(alignStart); /* set TLEN */ - if( record.getReadPairedFlag() && - !record.getMateUnmappedFlag() && - record.getMateReferenceIndex() == tid ) { - otherRec.setInferredInsertSize( record.getMateAlignmentStart() - alignStart ); + if (record.getReadPairedFlag() && + !record.getMateUnmappedFlag() && + record.getMateReferenceIndex() == tid) { + otherRec.setInferredInsertSize(record.getMateAlignmentStart() - alignStart); } /* set FLAG */ - int other_flag = record_flag; - other_flag |= (commaStrs[2].equals("+") ? 0 : SAMFlag.READ_REVERSE_STRAND.flag) ; + int other_flag = record_flag; + other_flag |= (commaStrs[2].equals("+") ? 0 : SAMFlag.READ_REVERSE_STRAND.flag); /* spec: Conventionally, at a supplementary line, the 1st element points to the primary line */ - if( !( record.getSupplementaryAlignmentFlag() && i==0 ) ) { - other_flag |= SAMFlag.SUPPLEMENTARY_ALIGNMENT.flag; - } - otherRec.setFlags(other_flag); + if (!(record.getSupplementaryAlignmentFlag() && i == 0)) { + other_flag |= SAMFlag.SUPPLEMENTARY_ALIGNMENT.flag; + } + otherRec.setFlags(other_flag); /* set CIGAR */ - otherRec.setCigar( TextCigarCodec.decode( commaStrs[3] ) ); + otherRec.setCigar(TextCigarCodec.decode(commaStrs[3])); /* set MAPQ */ try { - otherRec.setMappingQuality( Integer.parseInt(commaStrs[4]) ); + otherRec.setMappingQuality(Integer.parseInt(commaStrs[4])); } catch (final NumberFormatException err) { - throw new SAMException("bad MAPQ in "+semiColonStr, err); + throw new SAMException("bad MAPQ in " + semiColonStr + ". Record: " + record, err); } /* fill NM */ @@ -1213,16 +1223,16 @@ public static boolean isValidUnsignedIntegerAttribute(long value) { otherRec.setAttribute(SAMTagUtil.getSingleton().NM, Integer.parseInt(commaStrs[5])); } } catch (final NumberFormatException err) { - throw new SAMException("bad NM in "+semiColonStr, err); + throw new SAMException("bad NM in " + semiColonStr + ". Record: " + record, err); } /* if strand is not the same: reverse-complement */ - if( otherRec.getReadNegativeStrandFlag() != record.getReadNegativeStrandFlag() ) { - SAMRecordUtil.reverseComplement(otherRec); + if (otherRec.getReadNegativeStrandFlag() != record.getReadNegativeStrandFlag()) { + otherRec.reverseComplement(true); } /* add the alignment */ - alignments.add( otherRec ); + alignments.add(otherRec); } return alignments; } diff --git a/src/main/java/htsjdk/samtools/filter/FilteringIterator.java b/src/main/java/htsjdk/samtools/filter/FilteringIterator.java index 3ce9f96ce..4cdaebe89 100644 --- a/src/main/java/htsjdk/samtools/filter/FilteringIterator.java +++ b/src/main/java/htsjdk/samtools/filter/FilteringIterator.java @@ -36,7 +36,7 @@ * * @author Kathleen Tibbetts * - * use {@link FilteringSamIterator} instead + * @deprecated use {@link FilteringSamIterator} instead */ @Deprecated /** use {@link FilteringSamIterator} instead **/ diff --git a/src/main/java/htsjdk/tribble/util/HTTPHelper.java b/src/main/java/htsjdk/tribble/util/HTTPHelper.java index 1e89bc26b..cdd6b277e 100644 --- a/src/main/java/htsjdk/tribble/util/HTTPHelper.java +++ b/src/main/java/htsjdk/tribble/util/HTTPHelper.java @@ -101,6 +101,9 @@ public InputStream openInputStream() throws IOException { * @param end end of range ni bytes * @return * @throws IOException + * + * @deprecated since 12/10/14 Will be removed in a future release, as is somewhat fragile + * and not used. */ @Override @Deprecated diff --git a/src/main/java/htsjdk/variant/variantcontext/GenotypeLikelihoods.java b/src/main/java/htsjdk/variant/variantcontext/GenotypeLikelihoods.java index ee3e08d47..605f2985f 100644 --- a/src/main/java/htsjdk/variant/variantcontext/GenotypeLikelihoods.java +++ b/src/main/java/htsjdk/variant/variantcontext/GenotypeLikelihoods.java @@ -183,6 +183,10 @@ public String getAsString() { * If you know you're biallelic, use getGQLog10FromLikelihoods directly. * @param genotype - actually a genotype type (no call, hom ref, het, hom var) * @return an unsafe quantity that could be negative. In the bi-allelic case, the GQ resulting from best minus next best (if the type is the best). + * + * @deprecated since 2/5/13 use + * {@link GenotypeLikelihoods#getLog10GQ(Genotype, VariantContext)} or + * {@link GenotypeLikelihoods#getLog10GQ(Genotype, List)} */ @Deprecated public double getLog10GQ(GenotypeType genotype){ @@ -554,6 +558,8 @@ public static synchronized void initializeAnyploidPLIndexToAlleleIndices(final i * * @param PLindex the PL index * @return the allele index pair + * + * @deprecated since 2/5/13 */ @Deprecated public static GenotypeLikelihoodsAllelePair getAllelePairUsingDeprecatedOrdering(final int PLindex) { diff --git a/src/main/java/htsjdk/variant/variantcontext/filter/FilteringIterator.java b/src/main/java/htsjdk/variant/variantcontext/filter/FilteringIterator.java index 04609a89b..44362d6c1 100644 --- a/src/main/java/htsjdk/variant/variantcontext/filter/FilteringIterator.java +++ b/src/main/java/htsjdk/variant/variantcontext/filter/FilteringIterator.java @@ -36,7 +36,7 @@ * * @author Yossi Farjoun * - * use {@link FilteringVariantContextIterator} instead + * @deprecated since 2/29/16 use {@link FilteringVariantContextIterator} instead */ @Deprecated diff --git a/src/main/java/htsjdk/variant/vcf/VCFEncoder.java b/src/main/java/htsjdk/variant/vcf/VCFEncoder.java index a90906684..0605b73b9 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFEncoder.java +++ b/src/main/java/htsjdk/variant/vcf/VCFEncoder.java @@ -22,361 +22,362 @@ */ public class VCFEncoder { - /** - * The encoding used for VCF files: ISO-8859-1 - */ - public static final Charset VCF_CHARSET = Charset.forName("ISO-8859-1"); - private static final String QUAL_FORMAT_STRING = "%.2f"; - private static final String QUAL_FORMAT_EXTENSION_TO_TRIM = ".00"; - - private final IntGenotypeFieldAccessors GENOTYPE_FIELD_ACCESSORS = new IntGenotypeFieldAccessors(); - - private VCFHeader header; - - private boolean allowMissingFieldsInHeader = false; - - private boolean outputTrailingFormatFields = false; - - /** - * Prepare a VCFEncoder that will encode records appropriate to the given VCF header, optionally - * allowing missing fields in the header. - */ - public VCFEncoder(final VCFHeader header, final boolean allowMissingFieldsInHeader, final boolean outputTrailingFormatFields) { - if (header == null) throw new NullPointerException("The VCF header must not be null."); - this.header = header; - this.allowMissingFieldsInHeader = allowMissingFieldsInHeader; - this.outputTrailingFormatFields = outputTrailingFormatFields; - } - - /** - * Please see the notes in the default constructor - */ - @Deprecated - public void setVCFHeader(final VCFHeader header) { - this.header = header; - } - - /** - * Please see the notes in the default constructor - */ - @Deprecated - public void setAllowMissingFieldsInHeader(final boolean allow) { - this.allowMissingFieldsInHeader = allow; - } - - public String encode(final VariantContext context) { - if (this.header == null) { - throw new NullPointerException("The header field must be set on the VCFEncoder before encoding records."); - } - - final StringBuilder stringBuilder = new StringBuilder(); - - // CHROM - stringBuilder.append(context.getContig()).append(VCFConstants.FIELD_SEPARATOR) - // POS - .append(String.valueOf(context.getStart())).append(VCFConstants.FIELD_SEPARATOR) - // ID - .append(context.getID()).append(VCFConstants.FIELD_SEPARATOR) - // REF - .append(context.getReference().getDisplayString()).append(VCFConstants.FIELD_SEPARATOR); - - // ALT - if ( context.isVariant() ) { - Allele altAllele = context.getAlternateAllele(0); - String alt = altAllele.getDisplayString(); - stringBuilder.append(alt); - - for (int i = 1; i < context.getAlternateAlleles().size(); i++) { - altAllele = context.getAlternateAllele(i); - alt = altAllele.getDisplayString(); - stringBuilder.append(','); - stringBuilder.append(alt); - } - } else { - stringBuilder.append(VCFConstants.EMPTY_ALTERNATE_ALLELE_FIELD); - } - - stringBuilder.append(VCFConstants.FIELD_SEPARATOR); - - // QUAL - if ( ! context.hasLog10PError()) stringBuilder.append(VCFConstants.MISSING_VALUE_v4); - else stringBuilder.append(formatQualValue(context.getPhredScaledQual())); - stringBuilder.append(VCFConstants.FIELD_SEPARATOR) - // FILTER - .append(getFilterString(context)).append(VCFConstants.FIELD_SEPARATOR); - - // INFO - final Map infoFields = new TreeMap(); - for (final Map.Entry field : context.getAttributes().entrySet() ) { - if ( ! this.header.hasInfoLine(field.getKey())) fieldIsMissingFromHeaderError(context, field.getKey(), "INFO"); - - final String outputValue = formatVCFField(field.getValue()); - if (outputValue != null) infoFields.put(field.getKey(), outputValue); - } - writeInfoString(infoFields, stringBuilder); - - // FORMAT - final GenotypesContext gc = context.getGenotypes(); - if (gc.isLazyWithData() && ((LazyGenotypesContext) gc).getUnparsedGenotypeData() instanceof String) { - stringBuilder.append(VCFConstants.FIELD_SEPARATOR); - stringBuilder.append(((LazyGenotypesContext) gc).getUnparsedGenotypeData().toString()); - } else { - final List genotypeAttributeKeys = context.calcVCFGenotypeKeys(this.header); - if ( ! genotypeAttributeKeys.isEmpty()) { - for (final String format : genotypeAttributeKeys) - if ( ! this.header.hasFormatLine(format)) - fieldIsMissingFromHeaderError(context, format, "FORMAT"); - - final String genotypeFormatString = ParsingUtils.join(VCFConstants.GENOTYPE_FIELD_SEPARATOR, genotypeAttributeKeys); - - stringBuilder.append(VCFConstants.FIELD_SEPARATOR); - stringBuilder.append(genotypeFormatString); - - final Map alleleStrings = buildAlleleStrings(context); - addGenotypeData(context, alleleStrings, genotypeAttributeKeys, stringBuilder); - } - } - - return stringBuilder.toString(); - } - - VCFHeader getVCFHeader() { - return this.header; - } - - boolean getAllowMissingFieldsInHeader() { - return this.allowMissingFieldsInHeader; - } - - private String getFilterString(final VariantContext vc) { - if (vc.isFiltered()) { - for (final String filter : vc.getFilters()) { - if ( ! this.header.hasFilterLine(filter)) fieldIsMissingFromHeaderError(vc, filter, "FILTER"); - } - - return ParsingUtils.join(";", ParsingUtils.sortList(vc.getFilters())); - } - else if (vc.filtersWereApplied()) return VCFConstants.PASSES_FILTERS_v4; - else return VCFConstants.UNFILTERED; - } - - private String formatQualValue(final double qual) { - String s = String.format(QUAL_FORMAT_STRING, qual); - if ( s.endsWith(QUAL_FORMAT_EXTENSION_TO_TRIM) ) - s = s.substring(0, s.length() - QUAL_FORMAT_EXTENSION_TO_TRIM.length()); - return s; - } - - private void fieldIsMissingFromHeaderError(final VariantContext vc, final String id, final String field) { - if ( ! allowMissingFieldsInHeader) - throw new IllegalStateException("Key " + id + " found in VariantContext field " + field - + " at " + vc.getContig() + ":" + vc.getStart() - + " but this key isn't defined in the VCFHeader. We require all VCFs to have" - + " complete VCF headers by default."); - } - - String formatVCFField(final Object val) { - final String result; - if ( val == null ) - result = VCFConstants.MISSING_VALUE_v4; - else if ( val instanceof Double ) - result = formatVCFDouble((Double) val); - else if ( val instanceof Boolean ) - result = (Boolean)val ? "" : null; // empty string for true, null for false - else if ( val instanceof List ) { - result = formatVCFField(((List)val).toArray()); - } else if ( val.getClass().isArray() ) { - final int length = Array.getLength(val); - if ( length == 0 ) - return formatVCFField(null); - final StringBuilder sb = new StringBuilder(formatVCFField(Array.get(val, 0))); - for ( int i = 1; i < length; i++) { - sb.append(','); - sb.append(formatVCFField(Array.get(val, i))); - } - result = sb.toString(); - } else - result = val.toString(); - - return result; - } - - /** - * Takes a double value and pretty prints it to a String for display - * - * Large doubles => gets %.2f style formatting - * Doubles < 1 / 10 but > 1/100 => get %.3f style formatting - * Double < 1/100 => %.3e formatting - * @param d - * @return - */ - public static String formatVCFDouble(final double d) { - final String format; - if ( d < 1 ) { - if ( d < 0.01 ) { - if ( Math.abs(d) >= 1e-20 ) - format = "%.3e"; - else { - // return a zero format - return "0.00"; - } - } else { - format = "%.3f"; - } - } else { - format = "%.2f"; - } - - return String.format(format, d); - } - - static int countOccurrences(final char c, final String s) { - int count = 0; - for (int i = 0; i < s.length(); i++) { - count += s.charAt(i) == c ? 1 : 0; - } - return count; - } - - static boolean isMissingValue(final String s) { - // we need to deal with the case that it's a list of missing values - return (countOccurrences(VCFConstants.MISSING_VALUE_v4.charAt(0), s) + countOccurrences(',', s) == s.length()); - } - - /* - * Add the genotype data - */ - public void addGenotypeData(final VariantContext vc, final Map alleleMap, final List genotypeFormatKeys, final StringBuilder builder) { - final int ploidy = vc.getMaxPloidy(2); - - for (final String sample : this.header.getGenotypeSamples()) { - builder.append(VCFConstants.FIELD_SEPARATOR); - - Genotype g = vc.getGenotype(sample); - if (g == null) g = GenotypeBuilder.createMissing(sample, ploidy); - - final List attrs = new ArrayList(genotypeFormatKeys.size()); - for (final String field : genotypeFormatKeys) { - if (field.equals(VCFConstants.GENOTYPE_KEY)) { - if ( ! g.isAvailable()) { - throw new IllegalStateException("GTs cannot be missing for some samples if they are available for others in the record"); - } - - writeAllele(g.getAllele(0), alleleMap, builder); - for (int i = 1; i < g.getPloidy(); i++) { - builder.append(g.isPhased() ? VCFConstants.PHASED : VCFConstants.UNPHASED); - writeAllele(g.getAllele(i), alleleMap, builder); - } - continue; - - } else { - final String outputValue; - if ( field.equals(VCFConstants.GENOTYPE_FILTER_KEY ) ) { - outputValue = g.isFiltered() ? g.getFilters() : VCFConstants.PASSES_FILTERS_v4; - } else { - final IntGenotypeFieldAccessors.Accessor accessor = GENOTYPE_FIELD_ACCESSORS.getAccessor(field); - if ( accessor != null ) { - final int[] intValues = accessor.getValues(g); - if ( intValues == null ) - outputValue = VCFConstants.MISSING_VALUE_v4; - else if ( intValues.length == 1 ) // fast path - outputValue = Integer.toString(intValues[0]); - else { - final StringBuilder sb = new StringBuilder(); - sb.append(intValues[0]); - for ( int i = 1; i < intValues.length; i++) { - sb.append(','); - sb.append(intValues[i]); - } - outputValue = sb.toString(); - } - } else { - Object val = g.hasExtendedAttribute(field) ? g.getExtendedAttribute(field) : VCFConstants.MISSING_VALUE_v4; - - final VCFFormatHeaderLine metaData = this.header.getFormatHeaderLine(field); - if ( metaData != null ) { - final int numInFormatField = metaData.getCount(vc); - if ( numInFormatField > 1 && val.equals(VCFConstants.MISSING_VALUE_v4) ) { - // If we have a missing field but multiple values are expected, we need to construct a new string with all fields. - // For example, if Number=2, the string has to be ".,." - final StringBuilder sb = new StringBuilder(VCFConstants.MISSING_VALUE_v4); - for ( int i = 1; i < numInFormatField; i++ ) { - sb.append(','); - sb.append(VCFConstants.MISSING_VALUE_v4); - } - val = sb.toString(); - } - } - - // assume that if key is absent, then the given string encoding suffices - outputValue = formatVCFField(val); - } - } - - if ( outputValue != null ) - attrs.add(outputValue); - } - } - - // strip off trailing missing values - if (!outputTrailingFormatFields) { - for (int i = attrs.size() - 1; i >= 0; i--) { - if (isMissingValue(attrs.get(i))) attrs.remove(i); - else break; - } - } - - for (int i = 0; i < attrs.size(); i++) { - if ( i > 0 || genotypeFormatKeys.contains(VCFConstants.GENOTYPE_KEY)) { - builder.append(VCFConstants.GENOTYPE_FIELD_SEPARATOR); - } - builder.append(attrs.get(i)); - } - } - } - - /* - * Create the info string; assumes that no values are null - */ - private void writeInfoString(final Map infoFields, final StringBuilder builder) { - if ( infoFields.isEmpty() ) { - builder.append(VCFConstants.EMPTY_INFO_FIELD); - return; - } - - boolean isFirst = true; - for (final Map.Entry entry : infoFields.entrySet()) { - if (isFirst) isFirst = false; - else builder.append(VCFConstants.INFO_FIELD_SEPARATOR); - - builder.append(entry.getKey()); - - if ( ! entry.getValue().equals("")) { - final VCFInfoHeaderLine metaData = this.header.getInfoHeaderLine(entry.getKey()); - if ( metaData == null || metaData.getCountType() != VCFHeaderLineCount.INTEGER || metaData.getCount() != 0 ) { - builder.append('='); - builder.append(entry.getValue()); - } - } - } - } - - public Map buildAlleleStrings(final VariantContext vc) { - final Map alleleMap = new HashMap(vc.getAlleles().size()+1); - alleleMap.put(Allele.NO_CALL, VCFConstants.EMPTY_ALLELE); // convenience for lookup - - final List alleles = vc.getAlleles(); - for ( int i = 0; i < alleles.size(); i++ ) { - alleleMap.put(alleles.get(i), String.valueOf(i)); - } - - return alleleMap; - } - - private void writeAllele(final Allele allele, final Map alleleMap, final StringBuilder builder) { - final String encoding = alleleMap.get(allele); - if ( encoding == null ) - throw new RuntimeException("Allele " + allele + " is not an allele in the variant context"); - builder.append(encoding); - } + /** + * The encoding used for VCF files: ISO-8859-1 + */ + public static final Charset VCF_CHARSET = Charset.forName("ISO-8859-1"); + private static final String QUAL_FORMAT_STRING = "%.2f"; + private static final String QUAL_FORMAT_EXTENSION_TO_TRIM = ".00"; + + private final IntGenotypeFieldAccessors GENOTYPE_FIELD_ACCESSORS = new IntGenotypeFieldAccessors(); + + private VCFHeader header; + + private boolean allowMissingFieldsInHeader = false; + + private boolean outputTrailingFormatFields = false; + + /** + * Prepare a VCFEncoder that will encode records appropriate to the given VCF header, optionally + * allowing missing fields in the header. + */ + public VCFEncoder(final VCFHeader header, final boolean allowMissingFieldsInHeader, final boolean outputTrailingFormatFields) { + if (header == null) throw new NullPointerException("The VCF header must not be null."); + this.header = header; + this.allowMissingFieldsInHeader = allowMissingFieldsInHeader; + this.outputTrailingFormatFields = outputTrailingFormatFields; + } + + /** + * @deprecated since 10/24/13 use the constructor + */ + @Deprecated + public void setVCFHeader(final VCFHeader header) { + this.header = header; + } + + /** + * @deprecated since 10/24/13 use the constructor + */ + @Deprecated + public void setAllowMissingFieldsInHeader(final boolean allow) { + this.allowMissingFieldsInHeader = allow; + } + + public String encode(final VariantContext context) { + if (this.header == null) { + throw new NullPointerException("The header field must be set on the VCFEncoder before encoding records."); + } + + final StringBuilder stringBuilder = new StringBuilder(); + + // CHROM + stringBuilder.append(context.getContig()).append(VCFConstants.FIELD_SEPARATOR) + // POS + .append(String.valueOf(context.getStart())).append(VCFConstants.FIELD_SEPARATOR) + // ID + .append(context.getID()).append(VCFConstants.FIELD_SEPARATOR) + // REF + .append(context.getReference().getDisplayString()).append(VCFConstants.FIELD_SEPARATOR); + + // ALT + if (context.isVariant()) { + Allele altAllele = context.getAlternateAllele(0); + String alt = altAllele.getDisplayString(); + stringBuilder.append(alt); + + for (int i = 1; i < context.getAlternateAlleles().size(); i++) { + altAllele = context.getAlternateAllele(i); + alt = altAllele.getDisplayString(); + stringBuilder.append(','); + stringBuilder.append(alt); + } + } else { + stringBuilder.append(VCFConstants.EMPTY_ALTERNATE_ALLELE_FIELD); + } + + stringBuilder.append(VCFConstants.FIELD_SEPARATOR); + + // QUAL + if (!context.hasLog10PError()) stringBuilder.append(VCFConstants.MISSING_VALUE_v4); + else stringBuilder.append(formatQualValue(context.getPhredScaledQual())); + stringBuilder.append(VCFConstants.FIELD_SEPARATOR) + // FILTER + .append(getFilterString(context)).append(VCFConstants.FIELD_SEPARATOR); + + // INFO + final Map infoFields = new TreeMap<>(); + for (final Map.Entry field : context.getAttributes().entrySet()) { + if (!this.header.hasInfoLine(field.getKey())) + fieldIsMissingFromHeaderError(context, field.getKey(), "INFO"); + + final String outputValue = formatVCFField(field.getValue()); + if (outputValue != null) infoFields.put(field.getKey(), outputValue); + } + writeInfoString(infoFields, stringBuilder); + + // FORMAT + final GenotypesContext gc = context.getGenotypes(); + if (gc.isLazyWithData() && ((LazyGenotypesContext) gc).getUnparsedGenotypeData() instanceof String) { + stringBuilder.append(VCFConstants.FIELD_SEPARATOR); + stringBuilder.append(((LazyGenotypesContext) gc).getUnparsedGenotypeData().toString()); + } else { + final List genotypeAttributeKeys = context.calcVCFGenotypeKeys(this.header); + if (!genotypeAttributeKeys.isEmpty()) { + for (final String format : genotypeAttributeKeys) + if (!this.header.hasFormatLine(format)) + fieldIsMissingFromHeaderError(context, format, "FORMAT"); + + final String genotypeFormatString = ParsingUtils.join(VCFConstants.GENOTYPE_FIELD_SEPARATOR, genotypeAttributeKeys); + + stringBuilder.append(VCFConstants.FIELD_SEPARATOR); + stringBuilder.append(genotypeFormatString); + + final Map alleleStrings = buildAlleleStrings(context); + addGenotypeData(context, alleleStrings, genotypeAttributeKeys, stringBuilder); + } + } + + return stringBuilder.toString(); + } + + VCFHeader getVCFHeader() { + return this.header; + } + + boolean getAllowMissingFieldsInHeader() { + return this.allowMissingFieldsInHeader; + } + + private String getFilterString(final VariantContext vc) { + if (vc.isFiltered()) { + for (final String filter : vc.getFilters()) { + if (!this.header.hasFilterLine(filter)) fieldIsMissingFromHeaderError(vc, filter, "FILTER"); + } + + return ParsingUtils.join(";", ParsingUtils.sortList(vc.getFilters())); + } else if (vc.filtersWereApplied()) return VCFConstants.PASSES_FILTERS_v4; + else return VCFConstants.UNFILTERED; + } + + private String formatQualValue(final double qual) { + String s = String.format(QUAL_FORMAT_STRING, qual); + if (s.endsWith(QUAL_FORMAT_EXTENSION_TO_TRIM)) + s = s.substring(0, s.length() - QUAL_FORMAT_EXTENSION_TO_TRIM.length()); + return s; + } + + private void fieldIsMissingFromHeaderError(final VariantContext vc, final String id, final String field) { + if (!allowMissingFieldsInHeader) + throw new IllegalStateException("Key " + id + " found in VariantContext field " + field + + " at " + vc.getContig() + ":" + vc.getStart() + + " but this key isn't defined in the VCFHeader. We require all VCFs to have" + + " complete VCF headers by default."); + } + + String formatVCFField(final Object val) { + final String result; + if (val == null) + result = VCFConstants.MISSING_VALUE_v4; + else if (val instanceof Double) + result = formatVCFDouble((Double) val); + else if (val instanceof Boolean) + result = (Boolean) val ? "" : null; // empty string for true, null for false + else if (val instanceof List) { + result = formatVCFField(((List) val).toArray()); + } else if (val.getClass().isArray()) { + final int length = Array.getLength(val); + if (length == 0) + return formatVCFField(null); + final StringBuilder sb = new StringBuilder(formatVCFField(Array.get(val, 0))); + for (int i = 1; i < length; i++) { + sb.append(','); + sb.append(formatVCFField(Array.get(val, i))); + } + result = sb.toString(); + } else + result = val.toString(); + + return result; + } + + /** + * Takes a double value and pretty prints it to a String for display + *

+ * Large doubles => gets %.2f style formatting + * Doubles < 1 / 10 but > 1/100 => get %.3f style formatting + * Double < 1/100 => %.3e formatting + * + * @param d + * @return + */ + public static String formatVCFDouble(final double d) { + final String format; + if (d < 1) { + if (d < 0.01) { + if (Math.abs(d) >= 1e-20) + format = "%.3e"; + else { + // return a zero format + return "0.00"; + } + } else { + format = "%.3f"; + } + } else { + format = "%.2f"; + } + + return String.format(format, d); + } + + static int countOccurrences(final char c, final String s) { + int count = 0; + for (int i = 0; i < s.length(); i++) { + count += s.charAt(i) == c ? 1 : 0; + } + return count; + } + + static boolean isMissingValue(final String s) { + // we need to deal with the case that it's a list of missing values + return (countOccurrences(VCFConstants.MISSING_VALUE_v4.charAt(0), s) + countOccurrences(',', s) == s.length()); + } + + /* + * Add the genotype data + */ + public void addGenotypeData(final VariantContext vc, final Map alleleMap, final List genotypeFormatKeys, final StringBuilder builder) { + final int ploidy = vc.getMaxPloidy(2); + + for (final String sample : this.header.getGenotypeSamples()) { + builder.append(VCFConstants.FIELD_SEPARATOR); + + Genotype g = vc.getGenotype(sample); + if (g == null) g = GenotypeBuilder.createMissing(sample, ploidy); + + final List attrs = new ArrayList(genotypeFormatKeys.size()); + for (final String field : genotypeFormatKeys) { + if (field.equals(VCFConstants.GENOTYPE_KEY)) { + if (!g.isAvailable()) { + throw new IllegalStateException("GTs cannot be missing for some samples if they are available for others in the record"); + } + + writeAllele(g.getAllele(0), alleleMap, builder); + for (int i = 1; i < g.getPloidy(); i++) { + builder.append(g.isPhased() ? VCFConstants.PHASED : VCFConstants.UNPHASED); + writeAllele(g.getAllele(i), alleleMap, builder); + } + continue; + + } else { + final String outputValue; + if (field.equals(VCFConstants.GENOTYPE_FILTER_KEY)) { + outputValue = g.isFiltered() ? g.getFilters() : VCFConstants.PASSES_FILTERS_v4; + } else { + final IntGenotypeFieldAccessors.Accessor accessor = GENOTYPE_FIELD_ACCESSORS.getAccessor(field); + if (accessor != null) { + final int[] intValues = accessor.getValues(g); + if (intValues == null) + outputValue = VCFConstants.MISSING_VALUE_v4; + else if (intValues.length == 1) // fast path + outputValue = Integer.toString(intValues[0]); + else { + final StringBuilder sb = new StringBuilder(); + sb.append(intValues[0]); + for (int i = 1; i < intValues.length; i++) { + sb.append(','); + sb.append(intValues[i]); + } + outputValue = sb.toString(); + } + } else { + Object val = g.hasExtendedAttribute(field) ? g.getExtendedAttribute(field) : VCFConstants.MISSING_VALUE_v4; + + final VCFFormatHeaderLine metaData = this.header.getFormatHeaderLine(field); + if (metaData != null) { + final int numInFormatField = metaData.getCount(vc); + if (numInFormatField > 1 && val.equals(VCFConstants.MISSING_VALUE_v4)) { + // If we have a missing field but multiple values are expected, we need to construct a new string with all fields. + // For example, if Number=2, the string has to be ".,." + final StringBuilder sb = new StringBuilder(VCFConstants.MISSING_VALUE_v4); + for (int i = 1; i < numInFormatField; i++) { + sb.append(','); + sb.append(VCFConstants.MISSING_VALUE_v4); + } + val = sb.toString(); + } + } + + // assume that if key is absent, then the given string encoding suffices + outputValue = formatVCFField(val); + } + } + + if (outputValue != null) + attrs.add(outputValue); + } + } + + // strip off trailing missing values + if (!outputTrailingFormatFields) { + for (int i = attrs.size() - 1; i >= 0; i--) { + if (isMissingValue(attrs.get(i))) attrs.remove(i); + else break; + } + } + + for (int i = 0; i < attrs.size(); i++) { + if (i > 0 || genotypeFormatKeys.contains(VCFConstants.GENOTYPE_KEY)) { + builder.append(VCFConstants.GENOTYPE_FIELD_SEPARATOR); + } + builder.append(attrs.get(i)); + } + } + } + + /* + * Create the info string; assumes that no values are null + */ + private void writeInfoString(final Map infoFields, final StringBuilder builder) { + if (infoFields.isEmpty()) { + builder.append(VCFConstants.EMPTY_INFO_FIELD); + return; + } + + boolean isFirst = true; + for (final Map.Entry entry : infoFields.entrySet()) { + if (isFirst) isFirst = false; + else builder.append(VCFConstants.INFO_FIELD_SEPARATOR); + + builder.append(entry.getKey()); + + if (!entry.getValue().equals("")) { + final VCFInfoHeaderLine metaData = this.header.getInfoHeaderLine(entry.getKey()); + if (metaData == null || metaData.getCountType() != VCFHeaderLineCount.INTEGER || metaData.getCount() != 0) { + builder.append('='); + builder.append(entry.getValue()); + } + } + } + } + + public Map buildAlleleStrings(final VariantContext vc) { + final Map alleleMap = new HashMap(vc.getAlleles().size() + 1); + alleleMap.put(Allele.NO_CALL, VCFConstants.EMPTY_ALLELE); // convenience for lookup + + final List alleles = vc.getAlleles(); + for (int i = 0; i < alleles.size(); i++) { + alleleMap.put(alleles.get(i), String.valueOf(i)); + } + + return alleleMap; + } + + private void writeAllele(final Allele allele, final Map alleleMap, final StringBuilder builder) { + final String encoding = alleleMap.get(allele); + if (encoding == null) + throw new RuntimeException("Allele " + allele + " is not an allele in the variant context"); + builder.append(encoding); + } } From 4626fe66c680c4fc65696b462391d695a8e65e71 Mon Sep 17 00:00:00 2001 From: Louis Bergelson Date: Fri, 9 Jun 2017 13:24:19 -0400 Subject: [PATCH 41/59] updating artifactory link to point to new artifactory (#892) * the Broad artifactory moved from a self hosted repository at artifactory.broadinstitute.org to a web based artifactory at broadinstitute.jfrog.io/broadinstitute/ this was causing our snapshot uploads to fail * updating our snapshot repository to point to the new link * the old artifactory has a redirect in place to the new artifactory so artifacts relying on the old url should still resolve. For unknown reasons this redirect doesn't work with gradle uploads, probably a gradle bug. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a60afe5f5..96811bbeb 100644 --- a/build.gradle +++ b/build.gradle @@ -169,7 +169,7 @@ uploadArchives { authentication(userName: project.findProperty("sonatypeUsername"), password: project.findProperty("sonatypePassword")) } - snapshotRepository(url: "https://artifactory.broadinstitute.org/artifactory/libs-snapshot-local/") { + snapshotRepository(url: "https://broadinstitute.jfrog.io/broadinstitute/libs-snapshot-local/") { authentication(userName: System.env.ARTIFACTORY_USERNAME, password: System.env.ARTIFACTORY_PASSWORD) } From 6383b25e76bc05a29852965472ab0091421365bb Mon Sep 17 00:00:00 2001 From: Ron Levine Date: Fri, 9 Jun 2017 14:14:11 -0400 Subject: [PATCH 42/59] Fixed flipped sign when comparing read paired flags (#897) Fixed flipped sign when comparing read paired flags --- .../htsjdk/samtools/DuplicateScoringStrategy.java | 4 ++-- .../samtools/DuplicateScoringStrategyTest.java | 25 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java diff --git a/src/main/java/htsjdk/samtools/DuplicateScoringStrategy.java b/src/main/java/htsjdk/samtools/DuplicateScoringStrategy.java index 292ab0654..26c83a584 100644 --- a/src/main/java/htsjdk/samtools/DuplicateScoringStrategy.java +++ b/src/main/java/htsjdk/samtools/DuplicateScoringStrategy.java @@ -36,7 +36,7 @@ public enum ScoringStrategy { SUM_OF_BASE_QUALITIES, TOTAL_MAPPED_REFERENCE_LENGTH, - RANDOM, + RANDOM } /** Hash used for the RANDOM scoring strategy. */ @@ -128,7 +128,7 @@ public static int compare(final SAMRecord rec1, final SAMRecord rec2, final Scor int cmp; // always prefer paired over non-paired - if (rec1.getReadPairedFlag() != rec2.getReadPairedFlag()) return rec1.getReadPairedFlag() ? 1 : -1; + if (rec1.getReadPairedFlag() != rec2.getReadPairedFlag()) return rec1.getReadPairedFlag() ? -1 : 1; cmp = computeDuplicateScore(rec2, scoringStrategy, assumeMateCigar) - computeDuplicateScore(rec1, scoringStrategy, assumeMateCigar); diff --git a/src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java b/src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java new file mode 100644 index 000000000..5943535af --- /dev/null +++ b/src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java @@ -0,0 +1,25 @@ +package htsjdk.samtools; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class DuplicateScoringStrategyTest { + + @DataProvider + public Object [][] compareData() { + return new Object[][]{ + {SAMFlag.READ_PAIRED.flag, 0, true, DuplicateScoringStrategy.ScoringStrategy.RANDOM, -1}, + {0, SAMFlag.READ_PAIRED.flag, true, DuplicateScoringStrategy.ScoringStrategy.RANDOM, 1}, + }; + } + + @Test(dataProvider = "compareData") + public static void testCompare(final int samFlag1, final int samFlag2, final boolean assumeMateCigar, final DuplicateScoringStrategy.ScoringStrategy strategy, final int expected) { + final SAMRecord rec1 = new SAMRecordSetBuilder().addFrag("test", 0, 1, false, false, "36M", null, 2); + rec1.setFlags(samFlag1); + final SAMRecord rec2 = new SAMRecordSetBuilder().addFrag("test", 0, 1, true, false, "36M", null, 3); + rec2.setFlags(samFlag2); + Assert.assertEquals(DuplicateScoringStrategy.compare(rec1, rec2, strategy, assumeMateCigar), expected); + } +} \ No newline at end of file From 9f1f19111aaa9502211510b7bc6ddc5a2039f6fa Mon Sep 17 00:00:00 2001 From: Nils Homer Date: Fri, 9 Jun 2017 11:18:40 -0700 Subject: [PATCH 43/59] Standardizes mate info computation in SAMRecordSetBuilder. (#692) --- src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java b/src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java index 60aae473a..b55265f71 100644 --- a/src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java +++ b/src/main/java/htsjdk/samtools/SAMRecordSetBuilder.java @@ -363,13 +363,8 @@ public void addPair(final String name, final int contig, final int start1, final end1.setMappingQuality(255); end1.setReadPairedFlag(true); end1.setProperPairFlag(true); - end1.setMateReferenceIndex(contig); - end1.setAttribute(SAMTag.MC.name(), readLength + "M"); - end1.setMateAlignmentStart(start2); - end1.setMateNegativeStrandFlag(true); end1.setFirstOfPairFlag(end1IsFirstOfPair); end1.setSecondOfPairFlag(!end1IsFirstOfPair); - end1.setInferredInsertSize((int) CoordMath.getLength(start1, CoordMath.getEnd(start2, this.readLength))); end1.setAttribute(SAMTag.RG.name(), READ_GROUP_ID); if (programRecord != null) { end1.setAttribute(SAMTag.PG.name(), programRecord.getProgramGroupId()); @@ -388,13 +383,8 @@ public void addPair(final String name, final int contig, final int start1, final end2.setMappingQuality(255); end2.setReadPairedFlag(true); end2.setProperPairFlag(true); - end2.setMateReferenceIndex(contig); - end2.setAttribute(SAMTag.MC.name(), readLength + "M"); - end2.setMateAlignmentStart(start1); - end2.setMateNegativeStrandFlag(false); end2.setFirstOfPairFlag(!end1IsFirstOfPair); end2.setSecondOfPairFlag(end1IsFirstOfPair); - end2.setInferredInsertSize(end1.getInferredInsertSize()); end2.setAttribute(SAMTag.RG.name(), READ_GROUP_ID); if (programRecord != null) { end2.setAttribute(SAMTag.PG.name(), programRecord.getProgramGroupId()); @@ -404,6 +394,9 @@ public void addPair(final String name, final int contig, final int start1, final } fillInBasesAndQualities(end2); + // set mate info + SamPairUtil.setMateInfo(end1, end2, true); + this.records.add(end1); this.records.add(end2); } @@ -492,7 +485,7 @@ public void addUnmappedPair(final String name) { end1.setAttribute(SAMTag.PG.name(), programRecord.getProgramGroupId()); } if (this.unmappedHasBasesAndQualities) { - fillInBasesAndQualities(end1); + fillInBasesAndQualities(end1); } end2.setReadName(name); @@ -508,7 +501,7 @@ public void addUnmappedPair(final String name) { end2.setAttribute(SAMTag.PG.name(), programRecord.getProgramGroupId()); } if (this.unmappedHasBasesAndQualities) { - fillInBasesAndQualities(end2); + fillInBasesAndQualities(end2); } this.records.add(end1); From cc538e2832310d9a7e1cdada61c3540b92ac275f Mon Sep 17 00:00:00 2001 From: Yossi Farjoun Date: Sat, 10 Jun 2017 13:26:23 -0400 Subject: [PATCH 44/59] Typekpb patch 1 (replacing #725) (#883) * seek() checks for negative position argument value * tests --- .../seekablestream/ByteArraySeekableStream.java | 23 ++-- .../ByteArraySeekableStreamTest.java | 116 +++++++++++++++++++++ .../java/htsjdk/samtools/sra/AbstractSRATest.java | 4 +- 3 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 src/test/java/htsjdk/samtools/seekablestream/ByteArraySeekableStreamTest.java diff --git a/src/main/java/htsjdk/samtools/seekablestream/ByteArraySeekableStream.java b/src/main/java/htsjdk/samtools/seekablestream/ByteArraySeekableStream.java index 4f8c322c5..bb3b95af0 100644 --- a/src/main/java/htsjdk/samtools/seekablestream/ByteArraySeekableStream.java +++ b/src/main/java/htsjdk/samtools/seekablestream/ByteArraySeekableStream.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Broad Institute + * Copyright (c) 2015 The Broad Institute * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,13 +24,11 @@ package htsjdk.samtools.seekablestream; -import htsjdk.samtools.seekablestream.SeekableStream; - import java.io.IOException; /** -* Created by vadim on 23/03/2015. -*/ + * Created by vadim on 23/03/2015. + */ public class ByteArraySeekableStream extends SeekableStream { private byte[] bytes; private long position = 0; @@ -51,21 +49,27 @@ public long position() throws IOException { @Override public void seek(long position) throws IOException { - this.position = position; + if (position < 0) { + throw new IllegalArgumentException("Cannot seek to a negative position, position=" + position + "."); + } else { + this.position = position; + } } @Override public int read() throws IOException { - if (position < bytes.length) + if (position < bytes.length) { return 0xFF & bytes[((int) position++)]; - else return -1; + } else { + return -1; + } } @Override public int read(byte[] b, int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); - } else if (off < 0 || len < 0 || len > b.length - off) { + } else if (off < 0 || len < 0 || len + off > b.length) { throw new IndexOutOfBoundsException(); } if (position >= bytes.length) { @@ -85,6 +89,7 @@ public int read(byte[] b, int off, int len) throws IOException { @Override public void close() throws IOException { bytes = null; + position = -1; } @Override diff --git a/src/test/java/htsjdk/samtools/seekablestream/ByteArraySeekableStreamTest.java b/src/test/java/htsjdk/samtools/seekablestream/ByteArraySeekableStreamTest.java new file mode 100644 index 000000000..04a228f94 --- /dev/null +++ b/src/test/java/htsjdk/samtools/seekablestream/ByteArraySeekableStreamTest.java @@ -0,0 +1,116 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Broad Institute + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + */ + +package htsjdk.samtools.seekablestream; + +import htsjdk.HtsjdkTest; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.IOException; + +/** + * Created by farjoun on 5/27/17. + */ +public class ByteArraySeekableStreamTest extends HtsjdkTest { + private final byte[] bytes = "ABCDE12345".getBytes(); + + @Test + public void testNormalBehavior() throws IOException { + ByteArraySeekableStream byteArraySeekableStream = new ByteArraySeekableStream(bytes); + + Assert.assertEquals(byteArraySeekableStream.length(), 10); + for (int i = 0; i < 10; i++) { + Assert.assertFalse(byteArraySeekableStream.eof()); + Assert.assertEquals(byteArraySeekableStream.position(), i); + Assert.assertEquals(byteArraySeekableStream.read(), bytes[i]); + } + + Assert.assertTrue(byteArraySeekableStream.eof()); + Assert.assertEquals(byteArraySeekableStream.position(), 10); + Assert.assertEquals(byteArraySeekableStream.read(), -1); + + final long i = 0; + byteArraySeekableStream.seek(i); + + Assert.assertEquals(byteArraySeekableStream.position(), i); + Assert.assertEquals(byteArraySeekableStream.read(), bytes[(int) i]); + + byte[] copy = new byte[10]; + + Assert.assertEquals(byteArraySeekableStream.read(copy), 9); + Assert.assertEquals(byteArraySeekableStream.position(), 10); + + byteArraySeekableStream.seek(0L); + + Assert.assertEquals(byteArraySeekableStream.read(copy), 10); + Assert.assertEquals(byteArraySeekableStream.position(), 10); + + Assert.assertEquals(copy, bytes); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testCantSeekNegative() throws IOException { + + ByteArraySeekableStream byteArraySeekableStream = new ByteArraySeekableStream(bytes); + + byteArraySeekableStream.seek(-1L); + + // if allowed to seek, this will throw OutOfBounds + final int f = byteArraySeekableStream.read(); + } + + @Test + public void testCantReadPostEof() throws IOException { + + ByteArraySeekableStream byteArraySeekableStream = new ByteArraySeekableStream(bytes); + byte[] copy = new byte[10]; + + byteArraySeekableStream.seek(10); + Assert.assertEquals(byteArraySeekableStream.read(copy), -1); + Assert.assertEquals(byteArraySeekableStream.read(), -1); + } + + @DataProvider(name = "abnormalReadRequests") + public Object[][] abnormalReadRequestsProvider() { + return new Object[][]{ + {new byte[10], -1, 0}, + {new byte[10], -1, -1}, + {new byte[10], 0, -1}, + {new byte[10], 0, -1}, + {new byte[10], 0, 11}, + {new byte[10], 6, 6}, + {new byte[10], 11, 0}, + }; + } + + @Test(dataProvider = "abnormalReadRequests", expectedExceptions = IndexOutOfBoundsException.class) + public void testAbnormalReadRequest(final byte[] b, final int off, final int length) throws IOException { + + ByteArraySeekableStream byteArraySeekableStream = new ByteArraySeekableStream(bytes); + int i = byteArraySeekableStream.read(b, off, length); + + Assert.assertEquals(i, -2); ///impossible + } +} diff --git a/src/test/java/htsjdk/samtools/sra/AbstractSRATest.java b/src/test/java/htsjdk/samtools/sra/AbstractSRATest.java index 1776a9f91..eeba1d2ea 100644 --- a/src/test/java/htsjdk/samtools/sra/AbstractSRATest.java +++ b/src/test/java/htsjdk/samtools/sra/AbstractSRATest.java @@ -25,14 +25,14 @@ public final void checkIfCanResolve() { canResolveNetworkAccession = SRAAccession.isValid(checkAccession); } - @BeforeMethod + @BeforeMethod(groups = "sra") public final void assertSRAIsSupported() { if(SRAAccession.checkIfInitialized() != null){ throw new SkipException("Skipping SRA Test because SRA native code is unavailable."); } } - @BeforeMethod + @BeforeMethod(groups = "sra") public final void skipIfCantResolve(Method method, Object[] params) { String accession = null; From 04fd13e75a180573c58818f37037b78a49717657 Mon Sep 17 00:00:00 2001 From: Tim Fennell Date: Fri, 16 Jun 2017 17:25:01 -0400 Subject: [PATCH 45/59] A little more caution when checking terminator blocks on close of BlockCompressedOutputStream. (#901) --- src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java b/src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java index 4e9a59487..a1fc6c80a 100644 --- a/src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java +++ b/src/main/java/htsjdk/samtools/util/BlockCompressedOutputStream.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Files; import java.util.zip.CRC32; import java.util.zip.Deflater; @@ -282,7 +283,7 @@ public void close() throws IOException { codec.writeBytes(BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK); codec.close(); // Can't re-open something that is not a regular file, e.g. a named pipe or an output stream - if (this.file == null || !this.file.isFile()) return; + if (this.file == null || !this.file.isFile() || !Files.isRegularFile(this.file.toPath())) return; if (BlockCompressedInputStream.checkTermination(this.file) != BlockCompressedInputStream.FileTermination.HAS_TERMINATOR_BLOCK) { throw new IOException("Terminator block not found after closing BGZF file " + this.file); From 8f89e27588416b8057470d875b87ccbde7313337 Mon Sep 17 00:00:00 2001 From: Nils Homer Date: Wed, 21 Jun 2017 13:44:59 -0700 Subject: [PATCH 46/59] Sort and group order not updated when using setAttribute. (#905) --- .../htsjdk/samtools/AbstractSAMHeaderRecord.java | 3 +- src/main/java/htsjdk/samtools/SAMFileHeader.java | 39 ++++++++++++- .../java/htsjdk/samtools/SAMFileHeaderTest.java | 64 ++++++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/test/java/htsjdk/samtools/SAMFileHeaderTest.java diff --git a/src/main/java/htsjdk/samtools/AbstractSAMHeaderRecord.java b/src/main/java/htsjdk/samtools/AbstractSAMHeaderRecord.java index 7078bf1dc..0c3d48420 100644 --- a/src/main/java/htsjdk/samtools/AbstractSAMHeaderRecord.java +++ b/src/main/java/htsjdk/samtools/AbstractSAMHeaderRecord.java @@ -59,8 +59,6 @@ public void setAttribute(final String key, final Object value) { /** * Set the given value for the attribute named 'key'. Replaces an existing value, if any. * If value is null, the attribute is removed. - * Supported types are Character, Integer, Float and String. Byte and Short may also be - * passed in but they will be converted to Integer. * @param key attribute name * @param value attribute value */ @@ -71,6 +69,7 @@ public void setAttribute(final String key, final String value) { mAttributes.put(key, value); } } + /** * Returns the Set of attributes. */ diff --git a/src/main/java/htsjdk/samtools/SAMFileHeader.java b/src/main/java/htsjdk/samtools/SAMFileHeader.java index f2750d4cc..eff595341 100644 --- a/src/main/java/htsjdk/samtools/SAMFileHeader.java +++ b/src/main/java/htsjdk/samtools/SAMFileHeader.java @@ -270,7 +270,7 @@ public SortOrder getSortOrder() { public void setSortOrder(final SortOrder so) { sortOrder = so; - setAttribute(SORT_ORDER_TAG, so.name()); + super.setAttribute(SORT_ORDER_TAG, so.name()); } public GroupOrder getGroupOrder() { @@ -292,7 +292,42 @@ public GroupOrder getGroupOrder() { public void setGroupOrder(final GroupOrder go) { groupOrder = go; - setAttribute(GROUP_ORDER_TAG, go.name()); + super.setAttribute(GROUP_ORDER_TAG, go.name()); + } + + + /** + * Set the given value for the attribute named 'key'. Replaces an existing value, if any. + * If value is null, the attribute is removed. + * Otherwise, the value will be converted to a String with toString. + * @param key attribute name + * @param value attribute value + * @deprecated Use {@link #setAttribute(String, String) instead + */ + @Deprecated + @Override + public void setAttribute(final String key, final Object value) { + if (key.equals(SORT_ORDER_TAG) || key.equals(GROUP_ORDER_TAG)) { + this.setAttribute(key, value.toString()); + } else { + super.setAttribute(key, value); + } + } + + /** + * Set the given value for the attribute named 'key'. Replaces an existing value, if any. + * If value is null, the attribute is removed. + * @param key attribute name + * @param value attribute value + */ + @Override + public void setAttribute(final String key, final String value) { + if (key.equals(SORT_ORDER_TAG)) { + this.sortOrder = null; + } else if (key.equals(GROUP_ORDER_TAG)) { + this.groupOrder = null; + } + super.setAttribute(key, value); } /** diff --git a/src/test/java/htsjdk/samtools/SAMFileHeaderTest.java b/src/test/java/htsjdk/samtools/SAMFileHeaderTest.java new file mode 100644 index 000000000..0723ed9e4 --- /dev/null +++ b/src/test/java/htsjdk/samtools/SAMFileHeaderTest.java @@ -0,0 +1,64 @@ +/* + * The MIT License + * + * Copyright (c) 2017 Nils Homer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + */ +package htsjdk.samtools; + +import htsjdk.HtsjdkTest; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class SAMFileHeaderTest extends HtsjdkTest { + + @Test + public void testSortOrder() { + final SAMFileHeader header = new SAMFileHeader(); + + header.setSortOrder(SAMFileHeader.SortOrder.coordinate); + Assert.assertEquals(header.getSortOrder(), SAMFileHeader.SortOrder.coordinate); + Assert.assertEquals(header.getAttribute(SAMFileHeader.SORT_ORDER_TAG), SAMFileHeader.SortOrder.coordinate.name()); + + header.setAttribute(SAMFileHeader.SORT_ORDER_TAG, SAMFileHeader.SortOrder.queryname.name()); + Assert.assertEquals(header.getSortOrder(), SAMFileHeader.SortOrder.queryname); + Assert.assertEquals(header.getAttribute(SAMFileHeader.SORT_ORDER_TAG), SAMFileHeader.SortOrder.queryname.name()); + + header.setAttribute(SAMFileHeader.SORT_ORDER_TAG, SAMFileHeader.SortOrder.coordinate); + Assert.assertEquals(header.getSortOrder(), SAMFileHeader.SortOrder.coordinate); + Assert.assertEquals(header.getAttribute(SAMFileHeader.SORT_ORDER_TAG), SAMFileHeader.SortOrder.coordinate.name()); + } + + @Test + public void testGroupOrder() { + final SAMFileHeader header = new SAMFileHeader(); + + header.setGroupOrder(SAMFileHeader.GroupOrder.query); + Assert.assertEquals(header.getGroupOrder(), SAMFileHeader.GroupOrder.query); + Assert.assertEquals(header.getAttribute(SAMFileHeader.GROUP_ORDER_TAG), SAMFileHeader.GroupOrder.query.name()); + + header.setAttribute(SAMFileHeader.GROUP_ORDER_TAG, SAMFileHeader.GroupOrder.reference.name()); + Assert.assertEquals(header.getGroupOrder(), SAMFileHeader.GroupOrder.reference); + Assert.assertEquals(header.getAttribute(SAMFileHeader.GROUP_ORDER_TAG), SAMFileHeader.GroupOrder.reference.name()); + + header.setAttribute(SAMFileHeader.GROUP_ORDER_TAG, SAMFileHeader.GroupOrder.query); + Assert.assertEquals(header.getGroupOrder(), SAMFileHeader.GroupOrder.query); + Assert.assertEquals(header.getAttribute(SAMFileHeader.GROUP_ORDER_TAG), SAMFileHeader.GroupOrder.query.name()); + } +} From c20e0edd361189c3f2bc3718b018dac2ec90530b Mon Sep 17 00:00:00 2001 From: Ron Levine Date: Sun, 25 Jun 2017 22:35:24 -0400 Subject: [PATCH 47/59] Add more checks to SamFileValidator (#907) --- src/main/java/htsjdk/samtools/SAMRecord.java | 85 +++++++++++++++------- .../htsjdk/samtools/SAMSequenceDictionary.java | 50 ++++++++++--- src/main/java/htsjdk/samtools/SAMTestUtil.java | 50 ++++--------- .../java/htsjdk/samtools/SAMValidationError.java | 17 ++++- .../java/htsjdk/samtools/SamFileValidator.java | 42 ++++++----- .../java/htsjdk/samtools/SAMRecordUnitTest.java | 2 +- .../htsjdk/samtools/SAMSequenceDictionaryTest.java | 24 ++++++ .../htsjdk/samtools/SAMSequenceRecordTest.java | 45 +++++++++++- .../java/htsjdk/samtools/ValidateSamFileTest.java | 25 +++++-- .../cram/lossy/QualityScorePreservationTest.java | 2 +- .../htsjdk/samtools/util/SequenceUtilTest.java | 4 +- .../SequenceUtil/upper_and_lowercase_read.sam | 2 +- .../ValidateSamFileTest/seq_qual_len_mismatch.sam | 21 ++++++ 13 files changed, 261 insertions(+), 108 deletions(-) create mode 100644 src/test/resources/htsjdk/samtools/ValidateSamFileTest/seq_qual_len_mismatch.sam diff --git a/src/main/java/htsjdk/samtools/SAMRecord.java b/src/main/java/htsjdk/samtools/SAMRecord.java index f93b2d72c..ec394ca17 100644 --- a/src/main/java/htsjdk/samtools/SAMRecord.java +++ b/src/main/java/htsjdk/samtools/SAMRecord.java @@ -1519,7 +1519,7 @@ public SAMTagAndValue(final String tag, final Object value) { */ public List getAttributes() { SAMBinaryTagAndValue binaryAttributes = getBinaryAttributes(); - final List ret = new ArrayList(); + final List ret = new ArrayList<>(); while (binaryAttributes != null) { ret.add(new SAMTagAndValue(SAMTagUtil.getSingleton().makeStringTag(binaryAttributes.tag), binaryAttributes.value)); @@ -1769,7 +1769,7 @@ protected void eagerDecode() { /** * Run all validations of CIGAR. These include validation that the CIGAR makes sense independent of * placement, plus validation that CIGAR + placement yields all bases with M operator within the range of the reference. - * @param recordNumber For error reporting. -1 if not known. + * @param recordNumber For error reporting, the record number in the SAM/BAM file. -1 if not known. * @return List of errors, or null if no errors. */ public List validateCigar(final long recordNumber) { @@ -1878,35 +1878,40 @@ public int hashCode() { ArrayList ret = null; if (!getReadPairedFlag()) { if (getProperPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_PROPER_PAIR, "Proper pair flag should not be set for unpaired read.", getReadName())); if (firstOnly) return ret; } if (getMateUnmappedFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_MATE_UNMAPPED, "Mate unmapped flag should not be set for unpaired read.", getReadName())); if (firstOnly) return ret; } if (getMateNegativeStrandFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_MATE_NEG_STRAND, "Mate negative strand flag should not be set for unpaired read.", getReadName())); if (firstOnly) return ret; } if (getFirstOfPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_FIRST_OF_PAIR, "First of pair flag should not be set for unpaired read.", getReadName())); if (firstOnly) return ret; } if (getSecondOfPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_SECOND_OF_PAIR, "Second of pair flag should not be set for unpaired read.", getReadName())); if (firstOnly) return ret; } if (null != getHeader() && getMateReferenceIndex() != NO_ALIGNMENT_REFERENCE_INDEX) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_MATE_REF_INDEX, "MRNM should not be set for unpaired read.", getReadName())); if (firstOnly) return ret; } + if (!getMateReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME)) { + if (ret == null) ret = new ArrayList<>(); + ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_UNPAIRED_MATE_REFERENCE, "Unpaired read mate reference is " + getMateReferenceName() + " not " + SAMRecord.NO_ALIGNMENT_REFERENCE_NAME + " for unpaired read", getReadName())); + if (firstOnly) return ret; + } } else { final List errors = isValidReferenceIndexAndPosition(mMateReferenceIndex, mMateReferenceName, getMateAlignmentStart(), true, firstOnly); @@ -1937,23 +1942,23 @@ public int hashCode() { */ } if (getInferredInsertSize() > MAX_INSERT_SIZE || getInferredInsertSize() < -MAX_INSERT_SIZE) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_INSERT_SIZE, "Insert size out of range", getReadName())); if (firstOnly) return ret; } if (getReadUnmappedFlag()) { if (getNotPrimaryAlignmentFlag()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_NOT_PRIM_ALIGNMENT, "Not primary alignment flag should not be set for unmapped read.", getReadName())); if (firstOnly) return ret; } if (getSupplementaryAlignmentFlag()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_SUPPLEMENTARY_ALIGNMENT, "Supplementary alignment flag should not be set for unmapped read.", getReadName())); if (firstOnly) return ret; } if (getMappingQuality() != 0) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_MAPPING_QUALITY, "MAPQ should be 0 for unmapped read.", getReadName())); if (firstOnly) return ret; } @@ -1962,22 +1967,22 @@ public int hashCode() { TODO: PIC-97 This validation should be enabled, but probably at this point there are too many BAM files that have the proper pair flag set when read or mate is unmapped. if (getProperPairFlagUnchecked()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_PROPER_PAIR, "Proper pair flag should not be set for unmapped read.", getReadName())); } */ } else { if (getMappingQuality() >= 256) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_MAPPING_QUALITY, "MAPQ should be < 256.", getReadName())); if (firstOnly) return ret; } if (getCigarLength() == 0) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_CIGAR, "CIGAR should have > zero elements for mapped read.", getReadName())); /* todo - will uncomment once unit tests are added } else if (getCigar().getReadLength() != getReadLength()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_CIGAR, "CIGAR read length " + getCigar().getReadLength() + " doesn't match read length " + getReadLength(), getReadName())); */ if (firstOnly) return ret; @@ -1988,7 +1993,7 @@ public int hashCode() { if (firstOnly) return ret; } if (!hasReferenceName()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_FLAG_READ_UNMAPPED, "Mapped read should have valid reference name", getReadName())); if (firstOnly) return ret; } @@ -2006,14 +2011,14 @@ public int hashCode() { // Validate the RG ID is found in header final String rgId = (String)getAttribute(SAMTagUtil.getSingleton().RG); if (rgId != null && getHeader() != null && getHeader().getReadGroup(rgId) == null) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.READ_GROUP_NOT_FOUND, "RG ID on SAMRecord not found in header: " + rgId, getReadName())); if (firstOnly) return ret; } final List errors = isValidReferenceIndexAndPosition(mReferenceIndex, mReferenceName, getAlignmentStart(), false); if (errors != null) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.addAll(errors); if (firstOnly) return ret; } @@ -2024,7 +2029,7 @@ public int hashCode() { final String cq = (String)getAttribute(SAMTagUtil.getSingleton().CQ); final String cs = (String)getAttribute(SAMTagUtil.getSingleton().CS); if (cq == null || cq.isEmpty() || cs == null || cs.isEmpty()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.EMPTY_READ, "Zero-length read without FZ, CS or CQ tag", getReadName())); if (firstOnly) return ret; @@ -2038,7 +2043,7 @@ public int hashCode() { } } if (!hasIndel) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.EMPTY_READ, "Colorspace read with zero-length bases but no indel", getReadName())); if (firstOnly) return ret; @@ -2047,7 +2052,7 @@ public int hashCode() { } } if (this.getReadLength() != getBaseQualities().length && !Arrays.equals(getBaseQualities(), NULL_QUALS)) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.MISMATCH_READ_LENGTH_AND_QUALS_LENGTH, "Read length does not match quals length", getReadName())); if (firstOnly) return ret; @@ -2055,13 +2060,39 @@ public int hashCode() { if (this.getAlignmentStart() != NO_ALIGNMENT_START && this.getIndexingBin() != null && this.computeIndexingBin() != this.getIndexingBin()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_INDEXING_BIN, "bin field of BAM record does not equal value computed based on alignment start and end, and length of sequence to which read is aligned", getReadName())); if (firstOnly) return ret; } + if (getMateReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME) && + getMateAlignmentStart() != SAMRecord.NO_ALIGNMENT_START) { + if (ret == null) ret = new ArrayList<>(); + ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_UNALIGNED_MATE_START, + "The unaligned mate start position is " + getAlignmentStart() + ", should be " + SAMRecord.NO_ALIGNMENT_START, + getReadName())); + if (firstOnly) return ret; + } + + if (getCigar().getReadLength() != 0 && getCigar().getReadLength() != getReadLength()) { + if (ret == null) ret = new ArrayList<>(); + ret.add(new SAMValidationError(SAMValidationError.Type.MISMATCH_CIGAR_SEQ_LENGTH, + "CIGAR covers " + getCigar().getReadLength() + " bases but the sequence is " + getReadLength() + " read bases ", + getReadName())); + if (firstOnly) return ret; + } + + if (getBaseQualities().length != 0 && getReadLength() != getBaseQualities().length) { + if (ret == null) ret = new ArrayList<>(); + ret.add(new SAMValidationError( + SAMValidationError.Type.MISMATCH_SEQ_QUAL_LENGTH, + "Read length is " + getReadLength() + " bases but have " + mBaseQualities.length + " qualities ", + getReadName())); + if (firstOnly) return ret; + } + if (ret == null || ret.isEmpty()) { return null; } @@ -2099,13 +2130,13 @@ protected void setFileSource(final SAMFileSource fileSource) { ArrayList ret = null; if (!hasReference) { if (alignmentStart != 0) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_ALIGNMENT_START, buildMessage("Alignment start should be 0 because reference name = *.", isMate), getReadName())); if (firstOnly) return ret; } } else { if (alignmentStart == 0) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_ALIGNMENT_START, buildMessage("Alignment start should != 0 because reference name != *.", isMate), getReadName())); if (firstOnly) return ret; } @@ -2113,12 +2144,12 @@ protected void setFileSource(final SAMFileSource fileSource) { final SAMSequenceRecord sequence = (referenceIndex != null? getHeader().getSequence(referenceIndex): getHeader().getSequence(referenceName)); if (sequence == null) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_REFERENCE_INDEX, buildMessage("Reference sequence not found in sequence dictionary.", isMate), getReadName())); if (firstOnly) return ret; } else { if (alignmentStart > sequence.getSequenceLength()) { - if (ret == null) ret = new ArrayList(); + if (ret == null) ret = new ArrayList<>(); ret.add(new SAMValidationError(SAMValidationError.Type.INVALID_ALIGNMENT_START, buildMessage("Alignment start (" + alignmentStart + ") must be <= reference sequence length (" + sequence.getSequenceLength() + ") on reference " + sequence.getSequenceName(), isMate), getReadName())); if (firstOnly) return ret; diff --git a/src/main/java/htsjdk/samtools/SAMSequenceDictionary.java b/src/main/java/htsjdk/samtools/SAMSequenceDictionary.java index b7744d796..86ffa6c9f 100644 --- a/src/main/java/htsjdk/samtools/SAMSequenceDictionary.java +++ b/src/main/java/htsjdk/samtools/SAMSequenceDictionary.java @@ -29,7 +29,6 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.util.*; -import java.util.stream.Collector; import java.util.stream.Collectors; import javax.xml.bind.annotation.XmlElement; @@ -50,8 +49,8 @@ getter because the later wraps the list into an unmodifiable List see http://tech.joshuacummings.com/2010/10/problems-with-defensive-collection.html */ @XmlElement(name="Reference") - private List mSequences = new ArrayList(); - private final Map mSequenceMap = new HashMap(); + private List mSequences = new ArrayList<>(); + private final Map mSequenceMap = new HashMap<>(); public SAMSequenceDictionary() { } @@ -150,7 +149,7 @@ public boolean isEmpty() { private static String DICT_MISMATCH_TEMPLATE = "SAM dictionaries are not the same: %s."; /** * Non-comprehensive {@link #equals(Object)}-assertion: instead of calling {@link SAMSequenceRecord#equals(Object)} on constituent - * {@link SAMSequenceRecord}s in this dictionary against its pair in the target dictionary, in order, call + * {@link SAMSequenceRecord}s in this dictionary against its pair in the target dictionary, in order, call * {@link SAMSequenceRecord#isSameSequence(SAMSequenceRecord)}. * Aliases are ignored. * @@ -161,20 +160,49 @@ public void assertSameDictionary(final SAMSequenceDictionary that) { final Iterator thatSequences = that.mSequences.iterator(); for (final SAMSequenceRecord thisSequence : mSequences) { - if (!thatSequences.hasNext()) + if (!thatSequences.hasNext()) { throw new AssertionError(String.format(DICT_MISMATCH_TEMPLATE, thisSequence + " is present in only one dictionary")); - else { + } else { final SAMSequenceRecord thatSequence = thatSequences.next(); - if(!thatSequence.isSameSequence(thisSequence)) + if(!thatSequence.isSameSequence(thisSequence)) { throw new AssertionError( String.format(DICT_MISMATCH_TEMPLATE, thatSequence + " was found when " + thisSequence + " was expected") ); + } } } if (thatSequences.hasNext()) throw new AssertionError(String.format(DICT_MISMATCH_TEMPLATE, thatSequences.next() + " is present in only one dictionary")); } + /** + * Non-comprehensive {@link #equals(Object)}-validation: instead of calling {@link SAMSequenceRecord#equals(Object)} on constituent + * {@link SAMSequenceRecord}s in this dictionary against its pair in the target dictionary, in order, call + * {@link SAMSequenceRecord#isSameSequence(SAMSequenceRecord)}. + * + * @param that {@link SAMSequenceDictionary} to compare against + * @return true if the dictionaries are the same, false otherwise + * + */ + public boolean isSameDictionary(final SAMSequenceDictionary that) { + if (that == null || that.mSequences == null) return false; + if (this == that) return true; + + final Iterator thatSequences = that.mSequences.iterator(); + for (final SAMSequenceRecord thisSequence : mSequences) { + if (!thatSequences.hasNext()) { + return false; + } else { + final SAMSequenceRecord thatSequence = thatSequences.next(); + if (!thatSequence.isSameSequence(thisSequence)) { + return false; + } + } + } + + return !thatSequences.hasNext(); + } + /** returns true if the two dictionaries are the same, aliases are NOT considered */ @Override public boolean equals(Object o) { @@ -183,9 +211,7 @@ public boolean equals(Object o) { SAMSequenceDictionary that = (SAMSequenceDictionary) o; - if (!mSequences.equals(that.mSequences)) return false; - - return true; + return mSequences.equals(that.mSequences); } /** @@ -318,8 +344,8 @@ static public SAMSequenceDictionary mergeDictionaries(final SAMSequenceDictionar finalDict.addSequence(sMerged); final Set allTags = new HashSet<>(); - s1.getAttributes().stream().forEach(a -> allTags.add(a.getKey())); - s2.getAttributes().stream().forEach(a -> allTags.add(a.getKey())); + s1.getAttributes().forEach(a -> allTags.add(a.getKey())); + s2.getAttributes().forEach(a -> allTags.add(a.getKey())); for (final String tag : allTags) { final String value1 = s1.getAttribute(tag); diff --git a/src/main/java/htsjdk/samtools/SAMTestUtil.java b/src/main/java/htsjdk/samtools/SAMTestUtil.java index 83766f367..ec85ce2da 100644 --- a/src/main/java/htsjdk/samtools/SAMTestUtil.java +++ b/src/main/java/htsjdk/samtools/SAMTestUtil.java @@ -23,6 +23,8 @@ */ package htsjdk.samtools; +import java.util.List; + /** * Misc methods for SAM-related unit tests. These are in the src tree rather than the tests tree * so that they will be included in sam.jar, and therefore can be used by tests outside of htsjdk.samtools. @@ -55,47 +57,21 @@ public void assertPairValid(final SAMRecord firstEnd, final SAMRecord secondEnd) } /** - * Basic sanity check for a SAMRecord. - * @throws SanityCheckFailedException if the sanity check failed + * Basic sanity check for a SAMRecord. Print errors to screen. + * @param read SAM record + * @throws IllegalArgumentException if read is null + * @throws SanityCheckFailedException if errors */ - public void assertReadValid(final SAMRecord read) throws SanityCheckFailedException { - assertEquals(read.getReadBases().length, read.getBaseQualities().length); - // Note that it is possible to have an unmapped read that has a coordinate - if (read.getReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME)) { - assertEquals(read.getAlignmentStart(), SAMRecord.NO_ALIGNMENT_START); - assertTrue(read.getReadUnmappedFlag()); - } else { - assertNotSame(read.getAlignmentStart(), SAMRecord.NO_ALIGNMENT_START); - } - if (read.getReadUnmappedFlag()) { - assertEquals(read.getMappingQuality(), SAMRecord.NO_MAPPING_QUALITY); - assertEquals(read.getCigar().getCigarElements().size(), 0); - } else { - assertNotSame(read.getCigar().getCigarElements(), 0); + public static void assertReadValid(final SAMRecord read) throws SanityCheckFailedException { + if (read == null) { + throw new IllegalArgumentException("SAMRecord is null"); } - if (read.getReadPairedFlag()) { - if (read.getMateReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME)) { - assertEquals(read.getMateAlignmentStart(), SAMRecord.NO_ALIGNMENT_START); - assertTrue(read.getMateUnmappedFlag()); - } else { - // Even if the mate is unmapped, if it has a reference name, it should have a position. - assertNotSame(read.getMateAlignmentStart(), SAMRecord.NO_ALIGNMENT_START); - } - if (read.getReadUnmappedFlag() || read.getMateUnmappedFlag() || - !read.getReferenceName().equals(read.getMateReferenceName())) { - assertEquals(read.getInferredInsertSize(), 0); - } else { - assertNotSame(read.getInferredInsertSize(), 0); - } - if (!read.getReadUnmappedFlag() && !read.getMateUnmappedFlag()) { - assertNotSame(read.getReadNegativeStrandFlag(), read.getMateNegativeStrandFlag()); - assertNotSame(read.getMateNegativeStrandFlag(), - read.getReadName()); - } - } else { - assertEquals(read.getInferredInsertSize(), 0); + final List errors = read.isValid(false); + if ( errors != null) { + errors.forEach(v -> System.out.println(v.toString())); } + assertTrue(errors.isEmpty()); } private static void assertEquals(T a, T b) { diff --git a/src/main/java/htsjdk/samtools/SAMValidationError.java b/src/main/java/htsjdk/samtools/SAMValidationError.java index 452e92cf5..edd49c13c 100644 --- a/src/main/java/htsjdk/samtools/SAMValidationError.java +++ b/src/main/java/htsjdk/samtools/SAMValidationError.java @@ -208,7 +208,22 @@ MISMATCH_MATE_CIGAR_STRING, /** There is a Cigar String (stored in the MC Tag) for a read whose mate is NOT mapped. */ - MATE_CIGAR_STRING_INVALID_PRESENCE; + MATE_CIGAR_STRING_INVALID_PRESENCE, + + /** The mate reference of the unpaired read should be "*" */ + INVALID_UNPAIRED_MATE_REFERENCE, + + /** The unaligned mate read start position should be 0 */ + INVALID_UNALIGNED_MATE_START, + + /** Mismatch between the number of bases covered by the CIGAR and sequence */ + MISMATCH_CIGAR_SEQ_LENGTH, + + /** Mismatch between the sequence and quality length */ + MISMATCH_SEQ_QUAL_LENGTH, + + /** Mismatch between file and sequence dictionaries */ + MISMATCH_FILE_SEQ_DICT; public final Severity severity; diff --git a/src/main/java/htsjdk/samtools/SamFileValidator.java b/src/main/java/htsjdk/samtools/SamFileValidator.java index d0b745e7f..3e316a235 100644 --- a/src/main/java/htsjdk/samtools/SamFileValidator.java +++ b/src/main/java/htsjdk/samtools/SamFileValidator.java @@ -88,6 +88,7 @@ private Histogram errorsByType; private PairEndInfoMap pairEndInfoByName; private ReferenceSequenceFileWalker refFileWalker; + private SAMSequenceDictionary samSequenceDictionary; private boolean verbose; private int maxVerboseOutput; private SAMSortOrderChecker orderChecker; @@ -154,7 +155,7 @@ public boolean validateSamFileSummary(final SamReader samReader, final Reference for (final Histogram.Bin bin : errorsByType.values()) { errorsAndWarningsByType.increment(bin.getId().getHistogramString(), bin.getValue()); } - final MetricsFile metricsFile = new MetricsFile(); + final MetricsFile metricsFile = new MetricsFile<>(); errorsByType.setBinLabel("Error Type"); errorsByType.setValueLabel("Count"); metricsFile.setHistogram(errorsAndWarningsByType); @@ -180,7 +181,7 @@ public boolean validateSamFileVerbose(final SamReader samReader, final Reference } catch (MaxOutputExceededException e) { out.println("Maximum output of [" + maxVerboseOutput + "] errors reached."); } - boolean result = errorsByType.isEmpty(); + final boolean result = errorsByType.isEmpty(); cleanup(); return result; } @@ -249,13 +250,13 @@ private void validateUnmatchedPairs() { // For the coordinate-sorted map, need to detect mate pairs in which the mateReferenceIndex on one end // does not match the readReference index on the other end, so the pairs weren't united and validated. inMemoryPairMap = new InMemoryPairEndInfoMap(); - CloseableIterator> it = ((CoordinateSortedPairEndInfoMap) pairEndInfoByName).iterator(); + final CloseableIterator> it = pairEndInfoByName.iterator(); while (it.hasNext()) { - Map.Entry entry = it.next(); - PairEndInfo pei = inMemoryPairMap.remove(entry.getValue().readReferenceIndex, entry.getKey()); + final Map.Entry entry = it.next(); + final PairEndInfo pei = inMemoryPairMap.remove(entry.getValue().readReferenceIndex, entry.getKey()); if (pei != null) { // Found a mismatch btw read.mateReferenceIndex and mate.readReferenceIndex - List errors = pei.validateMates(entry.getValue(), entry.getKey()); + final List errors = pei.validateMates(entry.getValue(), entry.getKey()); for (final SAMValidationError error : errors) { addError(error); } @@ -405,10 +406,7 @@ private void validateSecondaryBaseCalls(final SAMRecord record, final long recor } private boolean validateCigar(final SAMRecord record, final long recordNumber) { - if (record.getReadUnmappedFlag()) { - return true; - } - return validateCigar(record, recordNumber, true); + return record.getReadUnmappedFlag() || validateCigar(record, recordNumber, true); } private boolean validateMateCigar(final SAMRecord record, final long recordNumber) { @@ -458,6 +456,7 @@ private void init(final ReferenceSequenceFile reference, final SAMFileHeader hea } if (reference != null) { this.refFileWalker = new ReferenceSequenceFileWalker(reference); + this.samSequenceDictionary = reference.getSequenceDictionary(); } } @@ -525,6 +524,12 @@ private void validateHeader(final SAMFileHeader fileHeader) { } if (fileHeader.getSequenceDictionary().isEmpty()) { sequenceDictionaryEmptyAndNoWarningEmitted = true; + } else { + if (samSequenceDictionary != null) { + if (!fileHeader.getSequenceDictionary().isSameDictionary(samSequenceDictionary)) { + addError(new SAMValidationError(Type.MISMATCH_FILE_SEQ_DICT, "Mismatch between file and sequence dictionary", null)); + } + } } if (fileHeader.getReadGroups().isEmpty()) { addError(new SAMValidationError(Type.MISSING_READ_GROUP, "Read groups is empty", null)); @@ -540,7 +545,7 @@ private void validateHeader(final SAMFileHeader fileHeader) { } final List rgs = fileHeader.getReadGroups(); - final Set readGroupIDs = new HashSet(); + final Set readGroupIDs = new HashSet<>(); for (final SAMReadGroupRecord record : rgs) { final String readGroupID = record.getReadGroupId(); @@ -692,11 +697,10 @@ public PairEndInfo(final SAMRecord record, final long recordNumber) { this.firstOfPairFlag = record.getFirstOfPairFlag(); } - private PairEndInfo(int readAlignmentStart, int readReferenceIndex, boolean readNegStrandFlag, boolean readUnmappedFlag, - String readCigarString, - int mateAlignmentStart, int mateReferenceIndex, boolean mateNegStrandFlag, boolean mateUnmappedFlag, - String mateCigarString, - boolean firstOfPairFlag, long recordNumber) { + private PairEndInfo(final int readAlignmentStart, final int readReferenceIndex, final boolean readNegStrandFlag, final boolean readUnmappedFlag, + final String readCigarString, + final int mateAlignmentStart, final int mateReferenceIndex, final boolean mateNegStrandFlag, final boolean mateUnmappedFlag, + final String mateCigarString, final boolean firstOfPairFlag, final long recordNumber) { this.readAlignmentStart = readAlignmentStart; this.readReferenceIndex = readReferenceIndex; this.readNegStrandFlag = readNegStrandFlag; @@ -712,7 +716,7 @@ private PairEndInfo(int readAlignmentStart, int readReferenceIndex, boolean read } public List validateMates(final PairEndInfo mate, final String readName) { - final List errors = new ArrayList(); + final List errors = new ArrayList<>(); validateMateFields(this, mate, readName, errors); validateMateFields(mate, this, readName, errors); // Validations that should not be repeated on both ends @@ -789,7 +793,7 @@ private void validateMateFields(final PairEndInfo end1, final PairEndInfo end2, private class CoordinateSortedPairEndInfoMap implements PairEndInfoMap { private final CoordinateSortedPairInfoMap onDiskMap = - new CoordinateSortedPairInfoMap(maxTempFiles, new Codec()); + new CoordinateSortedPairInfoMap<>(maxTempFiles, new Codec()); @Override public void put(int mateReferenceIndex, String key, PairEndInfo value) { @@ -877,7 +881,7 @@ public void encode(final String key, final PairEndInfo record) { } private static class InMemoryPairEndInfoMap implements PairEndInfoMap { - private final Map map = new HashMap(); + private final Map map = new HashMap<>(); @Override public void put(int mateReferenceIndex, String key, PairEndInfo value) { diff --git a/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java b/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java index 1bfe26345..5fa35f3e9 100644 --- a/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java +++ b/src/test/java/htsjdk/samtools/SAMRecordUnitTest.java @@ -463,7 +463,7 @@ public void test_setAttribute_null_removes_tag() { } private SAMRecord createTestRecordHelper() { - return new SAMRecordSetBuilder().addFrag("test", 0, 1, false, false, "3S9M", null, 2); + return new SAMRecordSetBuilder().addFrag("test", 0, 1, false, false, "3S33M", null, 2); } @Test diff --git a/src/test/java/htsjdk/samtools/SAMSequenceDictionaryTest.java b/src/test/java/htsjdk/samtools/SAMSequenceDictionaryTest.java index 8b1763067..a8e60ed50 100644 --- a/src/test/java/htsjdk/samtools/SAMSequenceDictionaryTest.java +++ b/src/test/java/htsjdk/samtools/SAMSequenceDictionaryTest.java @@ -39,6 +39,7 @@ import java.io.StringWriter; import java.util.Arrays; import java.util.Collections; +import java.util.List; public class SAMSequenceDictionaryTest extends HtsjdkTest { @Test @@ -142,4 +143,27 @@ public void testMergeDictionaries(final SAMSequenceRecord rec1, final SAMSequenc throw new Exception("Expected to not be able to merge dictionaries, but was able"); } } + + @DataProvider + public Object[][] testIsSameDictionaryData() { + + final SAMSequenceRecord rec1, rec2; + rec1 = new SAMSequenceRecord("chr1", 100); + rec2 = new SAMSequenceRecord("chr2", 101); + + return new Object[][]{ + new Object[]{Arrays.asList(rec1), Arrays.asList(rec1), true}, + new Object[]{Arrays.asList(rec1), Arrays.asList(rec2), false}, + new Object[]{Arrays.asList(rec1, rec2), Arrays.asList(rec1), false} + }; + } + + @Test(dataProvider = "testIsSameDictionaryData") + public void testIsSameDictionary(final List recs1, final List recs2, final boolean isSameDictionary) { + + final SAMSequenceDictionary dict1 = new SAMSequenceDictionary(recs1); + final SAMSequenceDictionary dict2 = new SAMSequenceDictionary(recs2); + + Assert.assertEquals(dict1.isSameDictionary(dict2), isSameDictionary); + } } diff --git a/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java b/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java index 1035d1b10..e0c73d502 100644 --- a/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java +++ b/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java @@ -24,8 +24,11 @@ package htsjdk.samtools; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.Arrays; + /** * Test for SAMReadGroupRecordTest */ @@ -33,10 +36,50 @@ @Test public void testGetSAMString() { - SAMSequenceRecord r = new SAMSequenceRecord("chr5_but_without_a_prefix", 271828); + final SAMSequenceRecord r = new SAMSequenceRecord("chr5_but_without_a_prefix", 271828); r.setSpecies("Psephophorus terrypratchetti"); r.setAssembly("GRCt01"); r.setMd5("7a6dd3d307de916b477e7bf304ac22bc"); Assert.assertEquals("@SQ\tSN:chr5_but_without_a_prefix\tLN:271828\tSP:Psephophorus terrypratchetti\tAS:GRCt01\tM5:7a6dd3d307de916b477e7bf304ac22bc", r.getSAMString()); } + + @DataProvider + public Object[][] testIsSameSequenceData() { + final SAMSequenceRecord rec1 = new SAMSequenceRecord("chr1", 100); + final SAMSequenceRecord rec2 = new SAMSequenceRecord("chr2", 101); + final SAMSequenceRecord rec3 = new SAMSequenceRecord("chr3", 0); + final SAMSequenceRecord rec4 = new SAMSequenceRecord("chr1", 100); + + final String md5One = "1"; + final String md5Two = "2"; + final int index1 = 1; + final int index2 = 2; + + return new Object[][]{ + new Object[]{rec1, rec1, md5One, md5One, index1, index1, true}, + new Object[]{rec1, null, md5One, md5One, index1, index1, false}, + new Object[]{rec1, rec4, md5One, md5One, index1, index1, true}, + new Object[]{rec1, rec4, md5One, md5One, index1, index2, false}, + new Object[]{rec1, rec3, md5One, md5Two, index1, index1, false}, + new Object[]{rec1, rec2, md5One, md5Two, index1, index1, false}, + new Object[]{rec1, rec4, md5One, null, index1, index1, true}, + new Object[]{rec1, rec4, null, md5One, index1, index1, true}, + new Object[]{rec1, rec4, md5One, md5One, index1, index2, false} + }; + } + + @Test(dataProvider = "testIsSameSequenceData") + public void testIsSameSequence(final SAMSequenceRecord rec1 , final SAMSequenceRecord rec2, final String md5One, final String md5Two, + final int index1, final int index2, final boolean isSame) { + if (rec2 != null) { + rec2.setMd5(md5Two); + rec2.setSequenceIndex(index2); + } + + if (rec1 != null) { + rec1.setMd5(md5One); + rec1.setSequenceIndex(index1); + Assert.assertEquals(rec1.isSameSequence(rec2), isSame); + } + } } diff --git a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java index 292758b8c..8aac6e2e3 100644 --- a/src/test/java/htsjdk/samtools/ValidateSamFileTest.java +++ b/src/test/java/htsjdk/samtools/ValidateSamFileTest.java @@ -147,6 +147,7 @@ public void testUnpairedRecords() throws IOException { Assert.assertEquals(results.get(SAMValidationError.Type.INVALID_FLAG_FIRST_OF_PAIR.getHistogramString()).getValue(), 1.0); Assert.assertEquals(results.get(SAMValidationError.Type.INVALID_FLAG_SECOND_OF_PAIR.getHistogramString()).getValue(), 1.0); Assert.assertEquals(results.get(SAMValidationError.Type.INVALID_MATE_REF_INDEX.getHistogramString()).getValue(), 1.0); + Assert.assertEquals(results.get(SAMValidationError.Type.INVALID_UNPAIRED_MATE_REFERENCE.getHistogramString()).getValue(), 1.0); } @Test @@ -173,6 +174,7 @@ public void testPairedRecords() throws IOException { Assert.assertEquals(results.get(SAMValidationError.Type.MISMATCH_FLAG_MATE_UNMAPPED.getHistogramString()).getValue(), 1.0); Assert.assertEquals(results.get(SAMValidationError.Type.MISMATCH_MATE_ALIGNMENT_START.getHistogramString()).getValue(), 2.0); Assert.assertEquals(results.get(SAMValidationError.Type.MISMATCH_MATE_REF_INDEX.getHistogramString()).getValue(), 2.0); + Assert.assertEquals(results.get(SAMValidationError.Type.INVALID_UNALIGNED_MATE_START.getHistogramString()).getValue(), 1.0); } @Test(dataProvider = "missingMateTestCases") @@ -232,6 +234,7 @@ public void testMappedRecords() throws IOException { Assert.assertEquals(results.get(SAMValidationError.Type.INVALID_CIGAR.getHistogramString()).getValue(), 1.0); Assert.assertEquals(results.get(SAMValidationError.Type.INVALID_FLAG_READ_UNMAPPED.getHistogramString()).getValue(), 1.0); Assert.assertEquals(results.get(SAMValidationError.Type.MISSING_TAG_NM.getHistogramString()).getValue(), 1.0); + Assert.assertEquals(results.get(SAMValidationError.Type.MISMATCH_CIGAR_SEQ_LENGTH.getHistogramString()).getValue(), 1.0); } @Test @@ -300,11 +303,10 @@ public void testMateCigarScenarios(final String scenario, final String inputFile throws Exception { final SamReader reader = SamReaderFactory.makeDefault().open(new File(TEST_DATA_DIR, inputFile)); final Histogram results = executeValidation(reader, null, IndexValidationStringency.EXHAUSTIVE); - Assert.assertNotNull(results.get(expectedError.getHistogramString())); - Assert.assertEquals(results.get(expectedError.getHistogramString()).getValue(), 1.0); + Assert.assertNotNull(results.get(expectedError.getHistogramString()), scenario); + Assert.assertEquals(results.get(expectedError.getHistogramString()).getValue(), 1.0, scenario); } - @DataProvider(name = "testMateCigarScenarios") public Object[][] testMateCigarScenarios() { return new Object[][]{ @@ -318,8 +320,8 @@ public void testTruncated(final String scenario, final String inputFile, final S throws Exception { final SamReader reader = SamReaderFactory.makeDefault().validationStringency(ValidationStringency.SILENT).open(new File(TEST_DATA_DIR, inputFile)); final Histogram results = executeValidation(reader, null, IndexValidationStringency.EXHAUSTIVE); - Assert.assertNotNull(results.get(expectedError.getHistogramString())); - Assert.assertEquals(results.get(expectedError.getHistogramString()).getValue(), 1.0); + Assert.assertNotNull(results.get(expectedError.getHistogramString()), scenario); + Assert.assertEquals(results.get(expectedError.getHistogramString()).getValue(), 1.0, scenario); } @DataProvider(name = "testTruncatedScenarios") @@ -400,9 +402,20 @@ public void testRedundantTags() throws Exception { public void testHeaderValidation() throws Exception { final SamReader samReader = SamReaderFactory.makeDefault().validationStringency(ValidationStringency.SILENT) .open(new File(TEST_DATA_DIR, "buggyHeader.sam")); - final Histogram results = executeValidation(samReader, null, IndexValidationStringency.EXHAUSTIVE); + final File referenceFile = new File(TEST_DATA_DIR, "../hg19mini.fasta"); + final ReferenceSequenceFile reference = new FastaSequenceFile(referenceFile, false); + final Histogram results = executeValidation(samReader, reference, IndexValidationStringency.EXHAUSTIVE); Assert.assertEquals(results.get(SAMValidationError.Type.UNRECOGNIZED_HEADER_TYPE.getHistogramString()).getValue(), 3.0); Assert.assertEquals(results.get(SAMValidationError.Type.HEADER_TAG_MULTIPLY_DEFINED.getHistogramString()).getValue(), 1.0); + Assert.assertEquals(results.get(SAMValidationError.Type.MISMATCH_FILE_SEQ_DICT.getHistogramString()).getValue(), 1.0); + } + + @Test + public void testSeqQualMismatch() throws Exception { + final SamReader samReader = SamReaderFactory.makeDefault().validationStringency(ValidationStringency.SILENT) + .open(new File(TEST_DATA_DIR, "seq_qual_len_mismatch.sam")); + final Histogram results = executeValidation(samReader, null, IndexValidationStringency.EXHAUSTIVE); + Assert.assertEquals(results.get(SAMValidationError.Type.MISMATCH_SEQ_QUAL_LENGTH.getHistogramString()).getValue(), 8.0); } @Test diff --git a/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java b/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java index a33766762..73859a46a 100644 --- a/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java +++ b/src/test/java/htsjdk/samtools/cram/lossy/QualityScorePreservationTest.java @@ -119,7 +119,7 @@ private SAMRecord buildSAMRecord(String seqName, String line) { @Test public void test3() { - String line1 = "98573 0 20 1 10 40M * 0 0 AAAAAAAAAA !!!!!!!!!!"; + String line1 = "98573 0 20 1 10 10M * 0 0 AAAAAAAAAA !!!!!!!!!!"; String seqName = "20"; byte[] ref = new byte[40]; diff --git a/src/test/java/htsjdk/samtools/util/SequenceUtilTest.java b/src/test/java/htsjdk/samtools/util/SequenceUtilTest.java index e57b8fd08..6a115db9b 100644 --- a/src/test/java/htsjdk/samtools/util/SequenceUtilTest.java +++ b/src/test/java/htsjdk/samtools/util/SequenceUtilTest.java @@ -262,11 +262,11 @@ public void testCountInsertedAndDeletedBases(final String cigarString, final int @Test(dataProvider = "testKmerGenerationTestCases") public void testKmerGeneration(final int length, final String[] expectedKmers) { - final Set actualSet = new HashSet(); + final Set actualSet = new HashSet<>(); for (final byte[] kmer : SequenceUtil.generateAllKmers(length)) { actualSet.add(StringUtil.bytesToString(kmer)); } - final Set expectedSet = new HashSet(Arrays.asList(expectedKmers)); + final Set expectedSet = new HashSet<>(Arrays.asList(expectedKmers)); Assert.assertTrue(actualSet.equals(expectedSet)); } diff --git a/src/test/resources/htsjdk/samtools/SequenceUtil/upper_and_lowercase_read.sam b/src/test/resources/htsjdk/samtools/SequenceUtil/upper_and_lowercase_read.sam index 82efe858e..335d8159c 100644 --- a/src/test/resources/htsjdk/samtools/SequenceUtil/upper_and_lowercase_read.sam +++ b/src/test/resources/htsjdk/samtools/SequenceUtil/upper_and_lowercase_read.sam @@ -7,4 +7,4 @@ read1 0 chr1 1 0 16M * 0 0 AcGtAcGTaCGtAcGt AAAAAAAAAAAAAAAA NM:i:0 read2 0 chr1 1 0 16M * 0 0 AcGtAcGTaCGtAcGt AAAAAAAAAAAAAAAA NM:i:0 read3 0 chr2 1 0 16M * 0 0 AcGtAcGTaCGtAcGt AAAAAAAAAAAAAAAA NM:i:8 MD:Z:0T2A0T2A0t2a0t2a0 read4 0 chr2 1 0 8M * 0 0 TCGATCGA AAAAAAAA NM:i:0 -read5 0 chr2 1 0 4M1D2M1S * 0 0 TCGACGAA AAAAAAAA NM:i:1 MD:Z:4^T2 +read5 0 chr2 1 0 4M1D2M2S * 0 0 TCGACGAA AAAAAAAA NM:i:1 MD:Z:4^T2 diff --git a/src/test/resources/htsjdk/samtools/ValidateSamFileTest/seq_qual_len_mismatch.sam b/src/test/resources/htsjdk/samtools/ValidateSamFileTest/seq_qual_len_mismatch.sam new file mode 100644 index 000000000..3c689b135 --- /dev/null +++ b/src/test/resources/htsjdk/samtools/ValidateSamFileTest/seq_qual_len_mismatch.sam @@ -0,0 +1,21 @@ +@HD VN:1.0 SO:coordinate +@SQ SN:chr1 LN:101 +@SQ SN:chr2 LN:101 +@SQ SN:chr3 LN:101 +@SQ SN:chr4 LN:101 +@SQ SN:chr5 LN:101 +@SQ SN:chr6 LN:101 +@SQ SN:chr7 LN:404 +@SQ SN:chr8 LN:202 +@RG ID:0 SM:Hi,Mom! LB:my-library PL:ILLUMINA +@RG ID:1 SM:Hi,Mom! LB:my-library PL:ILLUMINA +@RG ID:2 SM:Hi,Mom! LB:my-library PL:Illumina +@PG ID:1 PN:Hey! VN:2.0 +both_reads_align_clip_marked 1107 chr7 1 255 101M = 302 201 CAACAGAAGCNGGNATCTGTGTTTGTGTTTCGGATTTCCTGCTGAANNGNTTNTCGNNTCNNNNNNNNATCCCGATTTCNTTCCGCAGCTNACCTCCCAAN )'.*.+2,))&&'&*/)-&*-)&.-)&)&),/-&&..)./.,.).*&&,&.&&-)&&&0*&&&&&&&&/32/,01460&&/6/*0*/2/283//36868/ RG:Z:0 PG:Z:1 NM:i:0 MQ:i:255 XT:Z:foo OQ:Z:11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +both_reads_present_only_first_aligns 89 chr7 1 255 101M * 0 0 CAACAGAAGCNGGNATCTGTGTTTGTGTTTCGGATTTCCTGCTGAANNGNTTNTCGNNTCNNNNNNNNATCCCGATTTCNTTCCGCAGCTNACCTCCCAAN )'.*.+2,))&&'&*/)-&*-)&.-)&)&),/-&&..)./.,.).*&&,&.&&-)&&&0*&&&&&&&&/32/,01460&&/6/*0*/2/283//36868/ RG:Z:1 PG:Z:1 NM:i:3 MQ:i:255 XT:Z:foo OQ:Z:11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +read_2_too_many_gaps 83 chr7 1 255 101M = 302 201 CAACAGAAGCNGGNATCTGTGTTTGTGTTTCGGATTTCCTGCTGAANNGNTTNTCGNNTCNNNNNNNNATCCCGATTTCNTTCCGCAGCTNACCTCCCAAN )'.*.+2,))&&'&*/)-&*-)&.-)&)&),/-&&..)./.,.).*&&,&.&&-)&&&0*&&&&&&&&/32/,01460&&/6/*0*/2/283//36868/ RG:Z:2 PG:Z:1 NM:i:8 MQ:i:255 XT:Z:foo2 OQ:Z:11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +both_reads_align_clip_adapter 147 chr7 16 255 101M = 21 -96 CAACAGAAGCNGGNATCTGTGTTTGTGTTTCGGATTTCCTGCTGAANNGNTTNTCGNNTCNNNNNNNNATCCCGATTTCNTTCCGCAGCTNACCTCCCAAN )'.*.+2,))&&'&*/)-&*-)&.-)&)&),/-&&..)./.,.).*&&,&.&&-)&&&0*&&&&&&&&/32/,01460&&/6/*0*/2/283//36868/ RG:Z:1 PG:Z:1 NM:i:1 MQ:i:255 XT:Z:foo2 OQ:Z:11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +both_reads_align_clip_adapter 99 chr7 21 255 101M = 16 96 CAACAGAAGCNGGNATCTGTGTTTGTGTTTCGGATTTCCTGCTGAANNGNTTNTCGNNTCNNNNNNNNATCCCGATTTCNTTCCGCAGCTNACCTCCCAAN )'.*.+2,))&&'&*/)-&*-)&.-)&)&),/-&&..)./.,.).*&&,&.&&-)&&&0*&&&&&&&&/32/,01460&&/6/*0*/2/283//36868/ RG:Z:1 PG:Z:1 NM:i:1 MQ:i:255 XT:Z:foo2 OQ:Z:11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +both_reads_align_clip_marked 163 chr7 302 255 101M = 1 -201 NCGCGGCATCNCGATTTCTTTCCGCAGCTAACCTCCCGACAGATCGGCAGCGCGTCGTGTAGGTTATTATGGTACATCTTGTCGTGCGGCNAGAGCATACA &/15445666651/566666553+2/14/&/555512+3/)-'/-&-'*+))*''13+3)'//++''/'))/3+&*5++)&'2+&+/*&-&&*)&-./1' RG:Z:0 PG:Z:1 NM:i:5 MQ:i:255 OQ:Z:11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +read_2_too_many_gaps 163 chr7 302 255 10M1D10M5I76M = 1 -201 NCGCGGCATCNCGATTTCTTTCCGCAGCTAACCTCCCGACAGATCGGCAGCGCGTCGTGTAGGTTATTATGGTACATCTTGTCGTGCGGCNAGAGCATACA &/15445666651/566666553+2/14/&/555512+3/)-'/-&-'*+))*''13+3)'//++''/'))/3+&*5++)&'2+&+/*&-&&*)&-./1' RG:Z:2 PG:Z:1 NM:i:6 MQ:i:255 OQ:Z:11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +both_reads_present_only_first_aligns 165 * 0 0 * chr7 1 0 NCGCGGCATCNCGATTTCTTTCCGCAGCTAACCTCCCGACAGATCGGCAGCGCGTCGTGTAGGTTATTATGGTACATCTTGTCGTGCGGCNAGAGCATACA &/15445666651/566666553+2/14/&/555512+3/)-'/-&-'*+))*''13+3)'//++''/'))/3+&*5++)&'2+&+/*&-&&*)&-./1' RG:Z:1 PG:Z:1 From 7d31bc2a5c4f7ad79de7fa804898289d6fd556de Mon Sep 17 00:00:00 2001 From: Tim Fennell Date: Mon, 26 Jun 2017 16:14:08 -0400 Subject: [PATCH 48/59] Adding the gitter badge. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1981fd182..c2af30592 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.samtools/htsjdk/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.samtools%22%20AND%20a%3A%22htsjdk%22) [![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/samtools/htsjdk) [![Language](http://img.shields.io/badge/language-java-brightgreen.svg)](https://www.java.com/) +[![Join the chat at https://gitter.im/samtools/htsjdk](https://badges.gitter.im/samtools/htsjdk.svg)](https://gitter.im/samtools/htsjdk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Status of downstream projects automatically built on top of the current htsjdk master branch. See [gatk-jenkins](https://gatk-jenkins.broadinstitute.org/view/HTSJDK%20Release%20Tests/) for detailed logs. Failure may indicate problems in htsjdk, but may also be due to expected incompatibilities between versions, or unrelated failures in downstream projects. - [Picard](https://github.com/broadinstitute/picard): [![Build Status](https://gatk-jenkins.broadinstitute.org/buildStatus/icon?job=picard-on-htsjdk-master)](https://gatk-jenkins.broadinstitute.org/job/picard-on-htsjdk-master/) From 1cd23dacecd6b837a54ba801d4f08a48006d3215 Mon Sep 17 00:00:00 2001 From: Ron Levine Date: Wed, 5 Jul 2017 19:51:35 -0400 Subject: [PATCH 49/59] Make stream seek error message informative (#927) --- .../samtools/util/BlockCompressedInputStream.java | 17 +++++++++++++++-- .../util/BlockCompressedOutputStreamTest.java | 20 +++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java b/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java index e108d1bb3..622ca67ac 100755 --- a/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java +++ b/src/main/java/htsjdk/samtools/util/BlockCompressedInputStream.java @@ -57,10 +57,12 @@ public final static String INCORRECT_HEADER_SIZE_MSG = "Incorrect header size for file: "; public final static String UNEXPECTED_BLOCK_LENGTH_MSG = "Unexpected compressed block length: "; public final static String PREMATURE_END_MSG = "Premature end of file: "; - public final static String CANNOT_SEEK_STREAM_MSG = "Cannot seek on stream based file "; + public final static String CANNOT_SEEK_STREAM_MSG = "Cannot seek a position for a non-file stream"; + public final static String CANNOT_SEEK_CLOSED_STREAM_MSG = "Cannot seek a position for a closed stream"; public final static String INVALID_FILE_PTR_MSG = "Invalid file pointer: "; private InputStream mStream = null; + private boolean mIsClosed = false; private SeekableStream mFile = null; private byte[] mFileBuffer = null; private DecompressedBlock mCurrentBlock = null; @@ -222,6 +224,9 @@ public void close() throws IOException { // Encourage garbage collection mFileBuffer = null; mCurrentBlock = null; + + // Mark as closed + mIsClosed = true; } /** @@ -344,12 +349,20 @@ public int read(final byte[] buffer, int offset, int length) throws IOException * Seek to the given position in the file. Note that pos is a special virtual file pointer, * not an actual byte offset. * - * @param pos virtual file pointer + * @param pos virtual file pointer position + * @throws IOException if stream is closed or not a file based stream */ public void seek(final long pos) throws IOException { + // Must be before the mFile == null check because mFile == null for closed files and streams + if (mIsClosed) { + throw new IOException(CANNOT_SEEK_CLOSED_STREAM_MSG); + } + + // Cannot seek on streams that are not file based if (mFile == null) { throw new IOException(CANNOT_SEEK_STREAM_MSG); } + // Decode virtual file pointer // Upper 48 bits is the byte offset into the compressed stream of a // block. diff --git a/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java b/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java index a678c8dca..35175cd1d 100644 --- a/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java +++ b/src/test/java/htsjdk/samtools/util/BlockCompressedOutputStreamTest.java @@ -81,6 +81,7 @@ public void testBasic() throws Exception { Assert.assertEquals(bcis2.read(buffer), available, "Should read to end of block"); Assert.assertTrue(bcis2.endOfBlock(), "Should be at end of block"); bcis2.close(); + Assert.assertEquals(bcis2.read(buffer), -1, "Should be end of file"); } @DataProvider(name = "seekReadExceptionsData") @@ -89,24 +90,32 @@ public void testBasic() throws Exception { return new Object[][]{ {HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.truncated.gz", FileTruncatedException.class, BlockCompressedInputStream.PREMATURE_END_MSG + System.getProperty("user.dir") + "/" + - HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.truncated.gz", true, false, 0}, + HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.truncated.gz", true, false, false, 0}, {HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.truncated.hdr.gz", IOException.class, BlockCompressedInputStream.INCORRECT_HEADER_SIZE_MSG + System.getProperty("user.dir") + "/" + - HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.truncated.hdr.gz", true, false, 0}, + HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.truncated.hdr.gz", true, false, false, 0}, {HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.gz", IOException.class, - BlockCompressedInputStream.CANNOT_SEEK_STREAM_MSG, false, true, 0}, + BlockCompressedInputStream.CANNOT_SEEK_STREAM_MSG, false, true, false, 0}, + {HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.gz", IOException.class, + BlockCompressedInputStream.CANNOT_SEEK_CLOSED_STREAM_MSG, false, true, true, 0}, {HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.gz", IOException.class, BlockCompressedInputStream.INVALID_FILE_PTR_MSG + 1000 + " for " + System.getProperty("user.dir") + "/" + - HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.gz", true, true, 1000 } + HTSJDK_TRIBBLE_RESOURCES + "vcfexample.vcf.gz", true, true, false, 1000 } }; } @Test(dataProvider = "seekReadExceptionsData") - public void testSeekReadExceptions(final String filePath, final Class c, final String msg, final boolean isFile, final boolean isSeek, final int pos) throws Exception { + public void testSeekReadExceptions(final String filePath, final Class c, final String msg, final boolean isFile, final boolean isSeek, final boolean isClosed, + final int pos) throws Exception { final BlockCompressedInputStream bcis = isFile ? new BlockCompressedInputStream(new File(filePath)) : new BlockCompressedInputStream(new FileInputStream(filePath)); + + if ( isClosed ) { + bcis.close(); + } + boolean haveException = false; try { if ( isSeek ) { @@ -212,5 +221,6 @@ public Deflater makeDeflater(final int compressionLevel, final boolean gzipCompa } bcis.close(); Assert.assertEquals(deflateCalls[0], 3, "deflate calls"); + Assert.assertEquals(reader.readLine(), null); } } From 8d207ae878f9f5136cb6e6ae628659eddef047d6 Mon Sep 17 00:00:00 2001 From: Tim Fennell Date: Wed, 12 Jul 2017 08:14:04 -0400 Subject: [PATCH 50/59] Updates to readme about documentation, communication & license. (#916) --- README.md | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c2af30592..afe901e05 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,19 @@ common file formats, such as [SAM][1] and [VCF][2], used for high-throughput sequencing data. There are also an number of useful utilities for manipulating HTS data. -Please see the [HTSJDK Documentation](http://samtools.github.io/htsjdk) for more information. - > **NOTE: _HTSJDK does not currently support the latest Variant Call Format Specification (VCFv4.3 and BCFv2.2)._** -#### Building HTSJDK +### Documentation & Getting Help + +API documentation for all versions of HTSJDK since `1.128` are available through [javadoc.io](http://www.javadoc.io/doc/com.github.samtools/htsjdk). + +If you believe you have found a bug or have an issue with the library please a) search the open and recently closed issues to ensure it has not already been reported, then b) log an issue. + +The project has a [gitter chat room](https://gitter.im/samtools/htsjdk) if you would like to chat with the developers and others involved in the project. + +To receive announcements of releases and other significant project news please subscribe to the [htsjdk-announce](https://groups.google.com/forum/#!forum/htsjdk-announce) google group. + +### Building HTSJDK HTSJDK is now built using [gradle](http://gradle.org/). @@ -74,7 +82,7 @@ Example gradle usage from the htsjdk root directory: ./gradlew tasks ``` -#### Create an HTSJDK project in IntelliJ +### Create an HTSJDK project in IntelliJ To create a project in IntelliJ IDE for htsjdk do the following: 1. Select fom the menu: `File -> New -> Project from Existing Sources` @@ -83,13 +91,17 @@ To create a project in IntelliJ IDE for htsjdk do the following: From time to time if dependencies change in htsjdk you may need to refresh the project from the `View -> Gradle` menu. -#### Licensing Information +### Licensing Information -Not all sub-packages of htsjdk are subject to the same license, so a license notice is included in each source file or sub-package as appropriate. Please check the relevant license notice whenever you start working with a part of htsjdk that you have not previously worked with to avoid any surprises. +Not all sub-packages of htsjdk are subject to the same license, so a license notice is included in each source file or sub-package as appropriate. +Please check the relevant license notice whenever you start working with a part of htsjdk that you have not previously worked with to avoid any surprises. +Broadly speaking the majority of the code is covered under the MIT license with the following notable exceptions: -#### Java Minimum Version Support Policy +* Much of the CRAM code is under the Apache License, Version 2 +* Core `tribble` code (underlying VCF reading/writing amongst other things) is under LGPL +* Code supporting the reading/writing of SRA format is uncopyrighted & public domain -> **NOTE: _Effective November 24th 2015, HTSJDK has ended support of Java 7 and previous versions. Java 8 is now required_.** +### Java Minimum Version Support Policy We will support all Java SE versions supported by Oracle until at least six months after Oracle's Public Updates period has ended ([see this link](http://www.oracle.com/technetwork/java/eol-135779.html)). @@ -97,9 +109,8 @@ Java SE Major Release | End of Java SE Oracle Public Updates | Proposed End of S ---- | ---- | ---- | ---- 6 | Feb 2013 | Aug 2013 | Oct 2015 7 | Apr 2015 | Oct 2015 | Oct 2015 -8* | Mar 2017 | Sep 2017 | Sep 2017 +8 | Jul 2018 | Jul 2018 | TBD -* to be finalized HTSJDK is migrating to semantic versioning (http://semver.org/). We will eventually adhere to it strictly and bump our major version whenever there are breaking changes to our API, but until we more clearly define what constitutes our official API, clients should assume that every release potentially contains at least minor changes to public methods. From 624a3bf314575769a9e50c04d43bd7a41fd8a81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B3mez-S=C3=A1nchez?= Date: Wed, 19 Jul 2017 16:41:02 +0200 Subject: [PATCH 51/59] Add missing HtsjdkTest marker to test classes (#936) Some tests were being skipped because they weren't marked as HtsjdkTest --- src/test/java/htsjdk/samtools/BAMFileSpanTest.java | 4 +++- src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java | 3 ++- src/test/java/htsjdk/samtools/PathInputResourceTest.java | 4 +++- src/test/java/htsjdk/samtools/SAMProgramRecordTest.java | 3 ++- src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java | 3 ++- src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java | 3 ++- src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java | 3 ++- src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java | 3 ++- 8 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/test/java/htsjdk/samtools/BAMFileSpanTest.java b/src/test/java/htsjdk/samtools/BAMFileSpanTest.java index 4fc39b294..06d1bc9ab 100644 --- a/src/test/java/htsjdk/samtools/BAMFileSpanTest.java +++ b/src/test/java/htsjdk/samtools/BAMFileSpanTest.java @@ -1,11 +1,13 @@ package htsjdk.samtools; import java.util.Arrays; + +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -public class BAMFileSpanTest { +public class BAMFileSpanTest extends HtsjdkTest { @Test(dataProvider = "testRemoveContentsBeforeProvider") public void testRemoveContentsBefore(BAMFileSpan originalSpan, BAMFileSpan cutoff, BAMFileSpan expectedSpan) { diff --git a/src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java b/src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java index 5943535af..d86b697a5 100644 --- a/src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java +++ b/src/test/java/htsjdk/samtools/DuplicateScoringStrategyTest.java @@ -1,10 +1,11 @@ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -public class DuplicateScoringStrategyTest { +public class DuplicateScoringStrategyTest extends HtsjdkTest { @DataProvider public Object [][] compareData() { diff --git a/src/test/java/htsjdk/samtools/PathInputResourceTest.java b/src/test/java/htsjdk/samtools/PathInputResourceTest.java index 1bc2aa161..f82b9a627 100644 --- a/src/test/java/htsjdk/samtools/PathInputResourceTest.java +++ b/src/test/java/htsjdk/samtools/PathInputResourceTest.java @@ -5,10 +5,12 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.function.Function; + +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; -public class PathInputResourceTest { +public class PathInputResourceTest extends HtsjdkTest { final String localBam = "src/test/resources/htsjdk/samtools/BAMFileIndexTest/index_test.bam"; @Test diff --git a/src/test/java/htsjdk/samtools/SAMProgramRecordTest.java b/src/test/java/htsjdk/samtools/SAMProgramRecordTest.java index 5d9a36893..99a26cc38 100644 --- a/src/test/java/htsjdk/samtools/SAMProgramRecordTest.java +++ b/src/test/java/htsjdk/samtools/SAMProgramRecordTest.java @@ -23,13 +23,14 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; /** * Test for SAMReadGroupRecordTest */ -public class SAMProgramRecordTest { +public class SAMProgramRecordTest extends HtsjdkTest { @Test public void testGetSAMString() { diff --git a/src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java b/src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java index c3a7423d3..0801f52a5 100644 --- a/src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java +++ b/src/test/java/htsjdk/samtools/SAMReadGroupRecordTest.java @@ -23,13 +23,14 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.Test; /** * Test for SAMReadGroupRecordTest */ -public class SAMReadGroupRecordTest { +public class SAMReadGroupRecordTest extends HtsjdkTest { @Test public void testGetSAMString() { diff --git a/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java b/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java index e0c73d502..89e6121d2 100644 --- a/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java +++ b/src/test/java/htsjdk/samtools/SAMSequenceRecordTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools; +import htsjdk.HtsjdkTest; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -32,7 +33,7 @@ /** * Test for SAMReadGroupRecordTest */ -public class SAMSequenceRecordTest { +public class SAMSequenceRecordTest extends HtsjdkTest { @Test public void testGetSAMString() { diff --git a/src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java b/src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java index 72e59cff7..c367397a3 100644 --- a/src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java +++ b/src/test/java/htsjdk/samtools/fastq/FastqEncoderTest.java @@ -23,6 +23,7 @@ */ package htsjdk.samtools.fastq; +import htsjdk.HtsjdkTest; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMRecordSetBuilder; import org.testng.Assert; @@ -31,7 +32,7 @@ /** * @author Daniel Gomez-Sanchez (magicDGS) */ -public class FastqEncoderTest { +public class FastqEncoderTest extends HtsjdkTest { @Test public void testAsFastqRecord() throws Exception { diff --git a/src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java b/src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java index 54cfdedfc..d9b3bf84a 100644 --- a/src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java +++ b/src/test/java/htsjdk/samtools/util/CigarElementUnitTest.java @@ -1,11 +1,12 @@ package htsjdk.samtools.util; +import htsjdk.HtsjdkTest; import htsjdk.samtools.CigarElement; import htsjdk.samtools.CigarOperator; import org.testng.annotations.Test; -public class CigarElementUnitTest { +public class CigarElementUnitTest extends HtsjdkTest { @Test(expectedExceptions = IllegalArgumentException.class) public void testNegativeLengthCheck(){ From cfbbe4b8e0e5ad4f011d6aacec2640eab8ff49a0 Mon Sep 17 00:00:00 2001 From: Vadim Zalunin Date: Thu, 20 Jul 2017 13:21:07 +0100 Subject: [PATCH 52/59] fix for ambiguity and lc match/mismatch bases in CRAM (#889) --- .../htsjdk/samtools/CRAMContainerStreamWriter.java | 9 +- .../htsjdk/samtools/cram/build/CramNormalizer.java | 11 +- .../samtools/cram/build/Sam2CramRecordFactory.java | 136 +++++++-------------- .../cram/encoding/readfeatures/Substitution.java | 15 ++- .../htsjdk/samtools/cram/ref/ReferenceSource.java | 9 +- .../java/htsjdk/samtools/util/SequenceUtil.java | 54 +++++++- .../java/htsjdk/samtools/BAMFileWriterTest.java | 39 ++++++ .../java/htsjdk/samtools/CRAMComplianceTest.java | 13 +- .../java/htsjdk/samtools/CRAMSliceMD5Test.java | 136 +++++++++++++++++++++ .../cram/build/Sam2CramRecordFactoryTest.java | 109 +++++++++++++++++ .../samtools/cram/ref/ReferenceSourceTest.java | 33 +++++ .../htsjdk/samtools/util/SequenceUtilTest.java | 57 +++++++++ .../htsjdk/samtools/cram/amb#amb.2.1.cram | Bin 0 -> 11045 bytes .../htsjdk/samtools/cram/amb#amb.3.0.cram | Bin 0 -> 1210 bytes .../resources/htsjdk/samtools/cram/amb#amb.sam | 57 +++++++++ src/test/resources/htsjdk/samtools/cram/amb.fa | 2 + src/test/resources/htsjdk/samtools/cram/amb.fa.fai | 1 + .../htsjdk/samtools/cram/ambiguityCodes.fasta | 2 + .../htsjdk/samtools/cram/ambiguityCodes.fasta.fai | 1 + .../samtoolsSliceMD5WithAmbiguityCodesTest.cram | Bin 0 -> 1826 bytes 20 files changed, 577 insertions(+), 107 deletions(-) create mode 100644 src/test/java/htsjdk/samtools/CRAMSliceMD5Test.java create mode 100644 src/test/java/htsjdk/samtools/cram/build/Sam2CramRecordFactoryTest.java create mode 100644 src/test/java/htsjdk/samtools/cram/ref/ReferenceSourceTest.java create mode 100644 src/test/resources/htsjdk/samtools/cram/amb#amb.2.1.cram create mode 100644 src/test/resources/htsjdk/samtools/cram/amb#amb.3.0.cram create mode 100644 src/test/resources/htsjdk/samtools/cram/amb#amb.sam create mode 100644 src/test/resources/htsjdk/samtools/cram/amb.fa create mode 100644 src/test/resources/htsjdk/samtools/cram/amb.fa.fai create mode 100644 src/test/resources/htsjdk/samtools/cram/ambiguityCodes.fasta create mode 100644 src/test/resources/htsjdk/samtools/cram/ambiguityCodes.fasta.fai create mode 100644 src/test/resources/htsjdk/samtools/cram/samtoolsSliceMD5WithAmbiguityCodesTest.cram diff --git a/src/main/java/htsjdk/samtools/CRAMContainerStreamWriter.java b/src/main/java/htsjdk/samtools/CRAMContainerStreamWriter.java index 4707b7bcc..c588bdb46 100644 --- a/src/main/java/htsjdk/samtools/CRAMContainerStreamWriter.java +++ b/src/main/java/htsjdk/samtools/CRAMContainerStreamWriter.java @@ -17,6 +17,7 @@ import htsjdk.samtools.cram.structure.Slice; import htsjdk.samtools.util.Log; import htsjdk.samtools.util.RuntimeIOException; +import htsjdk.samtools.util.SequenceUtil; import java.io.IOException; import java.io.OutputStream; @@ -437,7 +438,13 @@ protected void flushContainer() throws IllegalArgumentException, IllegalAccessEx final SAMRecord restoredSamRecord = f.create(cramRecords.get(i)); assert (restoredSamRecord.getAlignmentStart() == samRecords.get(i).getAlignmentStart()); assert (restoredSamRecord.getReferenceName().equals(samRecords.get(i).getReferenceName())); - assert (restoredSamRecord.getReadString().equals(samRecords.get(i).getReadString())); + + if (!restoredSamRecord.getReadString().equals(samRecords.get(i).getReadString())) { + // try to fix the original read bases by normalizing them to BAM set: + final byte[] originalReadBases = samRecords.get(i).getReadString().getBytes(); + final String originalReadBasesUpperCaseIupacNoDot = new String(SequenceUtil.toBamReadBasesInPlace(originalReadBases)); + assert (restoredSamRecord.getReadString().equals(originalReadBasesUpperCaseIupacNoDot)); + } assert (restoredSamRecord.getBaseQualityString().equals(samRecords.get(i).getBaseQualityString())); } } diff --git a/src/main/java/htsjdk/samtools/cram/build/CramNormalizer.java b/src/main/java/htsjdk/samtools/cram/build/CramNormalizer.java index 1be1aa546..b2dd67c44 100644 --- a/src/main/java/htsjdk/samtools/cram/build/CramNormalizer.java +++ b/src/main/java/htsjdk/samtools/cram/build/CramNormalizer.java @@ -32,6 +32,7 @@ import htsjdk.samtools.cram.structure.CramCompressionRecord; import htsjdk.samtools.cram.structure.SubstitutionMatrix; import htsjdk.samtools.util.Log; +import htsjdk.samtools.util.SequenceUtil; import java.util.ArrayList; import java.util.Arrays; @@ -242,7 +243,8 @@ public static void restoreQualityScores(final byte defaultQualityScore, } else System.arraycopy(ref, alignmentStart - refOffsetZeroBased, bases, 0, bases.length); - return bases; + + return SequenceUtil.toBamReadBasesInPlace(bases); } final List variations = record.readFeatures; for (final ReadFeature variation : variations) { @@ -256,6 +258,7 @@ public static void restoreQualityScores(final byte defaultQualityScore, final Substitution substitution = (Substitution) variation; byte refBase = getByteOrDefault(ref, alignmentStart + posInSeq - refOffsetZeroBased, (byte) 'N'); + // substitution requires ACGTN only: refBase = Utils.normalizeBase(refBase); final byte base = substitutionMatrix.base(refBase, substitution.getCode()); substitution.setBase(base); @@ -304,11 +307,7 @@ public static void restoreQualityScores(final byte defaultQualityScore, } } - for (int i = 0; i < bases.length; i++) { - bases[i] = Utils.normalizeBase(bases[i]); - } - - return bases; + return SequenceUtil.toBamReadBasesInPlace(bases); } private static byte getByteOrDefault(final byte[] array, final int pos, diff --git a/src/main/java/htsjdk/samtools/cram/build/Sam2CramRecordFactory.java b/src/main/java/htsjdk/samtools/cram/build/Sam2CramRecordFactory.java index b7ffcb13f..6c59e13fd 100644 --- a/src/main/java/htsjdk/samtools/cram/build/Sam2CramRecordFactory.java +++ b/src/main/java/htsjdk/samtools/cram/build/Sam2CramRecordFactory.java @@ -17,50 +17,21 @@ */ package htsjdk.samtools.cram.build; -import htsjdk.samtools.CigarElement; -import htsjdk.samtools.CigarOperator; -import htsjdk.samtools.SAMFileHeader; -import htsjdk.samtools.SAMReadGroupRecord; -import htsjdk.samtools.SAMRecord; +import htsjdk.samtools.*; import htsjdk.samtools.SAMRecord.SAMTagAndValue; -import htsjdk.samtools.SAMTag; import htsjdk.samtools.cram.common.CramVersions; import htsjdk.samtools.cram.common.Version; -import htsjdk.samtools.cram.encoding.readfeatures.BaseQualityScore; -import htsjdk.samtools.cram.encoding.readfeatures.Deletion; -import htsjdk.samtools.cram.encoding.readfeatures.HardClip; -import htsjdk.samtools.cram.encoding.readfeatures.InsertBase; -import htsjdk.samtools.cram.encoding.readfeatures.Padding; -import htsjdk.samtools.cram.encoding.readfeatures.ReadFeature; -import htsjdk.samtools.cram.encoding.readfeatures.RefSkip; -import htsjdk.samtools.cram.encoding.readfeatures.SoftClip; -import htsjdk.samtools.cram.encoding.readfeatures.Substitution; +import htsjdk.samtools.cram.encoding.readfeatures.*; import htsjdk.samtools.cram.structure.CramCompressionRecord; import htsjdk.samtools.cram.structure.ReadTag; import htsjdk.samtools.util.Log; +import htsjdk.samtools.util.SequenceUtil; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; public class Sam2CramRecordFactory { - - public static final String UNKNOWN_READ_GROUP_ID = "UNKNOWN"; - public static final String UNKNOWN_READ_GROUP_SAMPLE = "UNKNOWN"; - - private final static byte QS_asciiOffset = 33; - public final static byte unsetQualityScore = 32; - public final static byte ignorePositionsWithQualityScore = -1; - private byte[] refBases; private final Version version; - private byte[] refSNPs; final private SAMFileHeader header; @@ -68,9 +39,6 @@ private final Map readGroupMap = new HashMap(); - private long landedRefMaskScores = 0; - private long landedTotalScores = 0; - public boolean captureAllTags = false; public boolean preserveReadNames = false; public final Set captureTags = new TreeSet(); @@ -151,8 +119,14 @@ public CramCompressionRecord createCramRecord(final SAMRecord record) { } else cramRecord.readFeatures = Collections.emptyList(); cramRecord.readBases = record.getReadBases(); + + /** + * CRAM read bases are limited to ACGTN, see https://github.com/samtools/hts-specs/blob/master/CRAMv3.pdf passage 10.2 on read bases. + * However, BAM format allows upper case IUPAC codes without a dot, so we follow the same approach to reproduce the behaviour of samtools. + */ + // copy read bases to avoid changing the original record: + cramRecord.readBases = SequenceUtil.toBamReadBasesInPlace(Arrays.copyOf(record.getReadBases(), record.getReadLength())); cramRecord.qualityScores = record.getBaseQualities(); - landedTotalScores += cramRecord.readLength; if (version.compatibleWith(CramVersions.CRAM_v3)) cramRecord.setUnknownBases(record.getReadBases() == SAMRecord.NULL_SEQUENCE); @@ -187,7 +161,7 @@ public CramCompressionRecord createCramRecord(final SAMRecord record) { * A wrapper method to provide better diagnostics for ArrayIndexOutOfBoundsException. * * @param cramRecord CRAM record - * @param samRecord SAM record + * @param samRecord SAM record * @return a list of read features created for the given {@link htsjdk.samtools.SAMRecord} */ private List checkedCreateVariations(final CramCompressionRecord cramRecord, final SAMRecord samRecord) { @@ -247,7 +221,7 @@ public CramCompressionRecord createCramRecord(final SAMRecord record) { case M: case X: case EQ: - addSubstitutionsAndMaskedBases(cramRecord, features, zeroBasedPositionInRead, alignmentStartOffset, + addMismatchReadFeatures(cramRecord.alignmentStart, features, zeroBasedPositionInRead, alignmentStartOffset, cigarElementLength, bases, qualityScore); break; default: @@ -291,57 +265,47 @@ private void addInsertion(final List features, final int zeroBasedP } } - private void addSubstitutionsAndMaskedBases(final CramCompressionRecord cramRecord, final List features, final int fromPosInRead, final int + /** + * Processes a stretch of read bases marked as match or mismatch and emits appropriate read features. + * Briefly the algorithm is: + *