From 1612ae4060ae1ca685fb94ccdd830eaa787331bd Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Thu, 22 Apr 2021 16:16:40 +0200 Subject: [PATCH 1/4] add classes for vcfreaderfactory --- src/main/java/htsjdk/samtools/Defaults.java | 7 ++ .../htsjdk/variant/vcf/VCFReaderFactory.java | 101 ++++++++++++++++++ .../variant/vcf/VCFReaderFactoryTest.java | 64 +++++++++++ 3 files changed, 172 insertions(+) create mode 100644 src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java create mode 100644 src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java diff --git a/src/main/java/htsjdk/samtools/Defaults.java b/src/main/java/htsjdk/samtools/Defaults.java index e2ecf3d1f7..d432228da8 100644 --- a/src/main/java/htsjdk/samtools/Defaults.java +++ b/src/main/java/htsjdk/samtools/Defaults.java @@ -110,6 +110,12 @@ public class Defaults { */ public static final boolean DISABLE_SNAPPY_COMPRESSOR; + /** Custom VCF reader factory . + * Expected format: + * Default = "". + */ + public static final String CUSTOM_VCF_READER_FACTORY; + public static final String SAMJDK_PREFIX = "samjdk."; static { @@ -134,6 +140,7 @@ public class Defaults { SAM_FLAG_FIELD_FORMAT = SamFlagField.valueOf(getStringProperty("sam_flag_field_format", SamFlagField.DECIMAL.name())); SRA_LIBRARIES_DOWNLOAD = getBooleanProperty("sra_libraries_download", false); DISABLE_SNAPPY_COMPRESSOR = getBooleanProperty(DISABLE_SNAPPY_PROPERTY_NAME, false); + CUSTOM_VCF_READER_FACTORY = getStringProperty("vcf_reader_factory", ""); } /** diff --git a/src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java b/src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java new file mode 100644 index 0000000000..61785fcfc1 --- /dev/null +++ b/src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java @@ -0,0 +1,101 @@ +package htsjdk.variant.vcf; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import htsjdk.samtools.Defaults; +import htsjdk.samtools.util.IOUtil; +import htsjdk.samtools.util.Log; +import htsjdk.samtools.util.RuntimeIOException; +import htsjdk.samtools.util.StringUtil; + +/** + * + * @author Pierre Lindenbaum + * A Factory opening {@link VCFReader} + * + */ +public abstract class VCFReaderFactory { + private final static Log LOG = Log.getInstance(VCFReaderFactory.class); + + private static final VCFReaderFactory DEFAULT_FACTORY; + private static VCFReaderFactory currentFactory; + + static { + DEFAULT_FACTORY = createDefaultVcfReaderFactory(); + currentFactory = DEFAULT_FACTORY; + } + + /** set the current instance of VCFReaderFactory */ + public static void setInstance(final VCFReaderFactory factory) { + currentFactory = factory; + } + + /** get the current instance of VCFReaderFactory */ + public static VCFReaderFactory getInstance() { + return currentFactory; + } + + /** get the default instance of VCFReaderFactory */ + public static VCFReaderFactory getDefaultInstance() { + return DEFAULT_FACTORY; + } + + /** + * Open VCFReader that will or will not assert the presence of an index as + * desired + */ + public abstract VCFReader open(final Path vcfPath, boolean requireIndex); + + /** + * Open VCFReader that will or will not assert the presence of an index as + * desired + */ + public VCFReader open(final File vcfFile, boolean requireIndex) { + return open(IOUtil.toPath(vcfFile), requireIndex); + } + + /** + * Open VCFReader that will or will not assert the presence of an index as + * desired + */ + public VCFReader open(final String vcfUri, boolean requireIndex) { + if (IOUtil.isUrl(vcfUri)) { + throw new RuntimeIOException("VCFReaderFactory(" + this.getClass() + + ") cannot open URI: " + vcfUri); + } + return open(Paths.get(vcfUri), requireIndex); + } + + /** + * creates the default instance of VCFReaderFactory A new instance from a + * specific class it's name is defined by the java property + * {@link Defaults}.CUSTOM_VCF_READER_FACTORY . Otherwise, we return the + * default instance of VCFReaderFactory + */ + private static VCFReaderFactory createDefaultVcfReaderFactory() { + final String factoryClassName = Defaults.CUSTOM_VCF_READER_FACTORY; + if (!StringUtil.isBlank(factoryClassName)) { + try { + LOG.info("Attempting to load factory class " + factoryClassName); + final Class clazz = Class.forName(factoryClassName); + return VCFReaderFactory.class.cast(clazz.newInstance()); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot load " + factoryClassName, e); + } + } + return new DefaultVCFReaderFactory(); + } + + /** + * default instance of a VCFReaderFactory. It returns a + * {@link VCFFileReader} + */ + private static class DefaultVCFReaderFactory extends VCFReaderFactory { + @Override + public VCFReader open(final Path vcfPath, boolean requireIndex) { + return new VCFFileReader(vcfPath, requireIndex); + } + } +} diff --git a/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java b/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java new file mode 100644 index 0000000000..e2fa5412dd --- /dev/null +++ b/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java @@ -0,0 +1,64 @@ +package htsjdk.variant.vcf; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import com.google.common.jimfs.Configuration; +import htsjdk.HtsjdkTest; +import htsjdk.samtools.util.RuntimeIOException; + + +/** + * Created by Pierre Lindenbaum + */ +public class VCFReaderFactoryTest extends HtsjdkTest { + private static final File TEST_DATA_DIR = new File("src/test/resources/htsjdk/variant/"); + + @DataProvider(name = "queryableData") + public Iterator queryableData() throws IOException { + List tests = new ArrayList<>(); + tests.add(new Object[] { new File(TEST_DATA_DIR, "NA12891.fp.vcf"), false }); + tests.add( + new Object[] { new File(TEST_DATA_DIR, "NA12891.vcf"), false }); + tests.add(new Object[] { VCFUtils.createTemporaryIndexedVcfFromInput( + new File(TEST_DATA_DIR, "NA12891.vcf"), + "fingerprintcheckertest.tmp."), true }); + tests.add(new Object[] { VCFUtils.createTemporaryIndexedVcfFromInput( + new File(TEST_DATA_DIR, "NA12891.vcf.gz"), + "fingerprintcheckertest.tmp."), true }); + + return tests.iterator(); + } + + @Test(dataProvider = "queryableData") + public void testIsQueriable(final File vcf, final boolean expectedQueryable) + throws Exception { + try (VCFReader reader = VCFReaderFactory.getInstance().open(vcf, false)) { + Assert.assertEquals(reader.isQueryable(), expectedQueryable); + } + } + + @Test + public void testInstances() { + Assert.assertNotNull(VCFReaderFactory.getInstance()); + Assert.assertNotNull(VCFReaderFactory.getDefaultInstance()); + final VCFReaderFactory custom = new VCFReaderFactory() { + public VCFReader open(Path vcfPath, boolean requireIndex) { + throw new RuntimeIOException("cannot open anything"); + } + }; + VCFReaderFactory.setInstance(custom); + Assert.assertTrue(custom == VCFReaderFactory.getInstance()); + VCFReaderFactory.setInstance(VCFReaderFactory.getDefaultInstance()); + Assert.assertFalse(custom == VCFReaderFactory.getInstance()); + } + +} From 75482ad1083fa9ff7545be96268481abe18bfb13 Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Fri, 23 Apr 2021 10:24:19 +0200 Subject: [PATCH 2/4] adding tests --- .../variant/vcf/VCFReaderFactoryTest.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java b/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java index e2fa5412dd..3a3bd78a21 100644 --- a/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java +++ b/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java @@ -26,26 +26,40 @@ public class VCFReaderFactoryTest extends HtsjdkTest { public Iterator queryableData() throws IOException { List tests = new ArrayList<>(); tests.add(new Object[] { new File(TEST_DATA_DIR, "NA12891.fp.vcf"), false }); - tests.add( - new Object[] { new File(TEST_DATA_DIR, "NA12891.vcf"), false }); + tests.add(new Object[] { new File(TEST_DATA_DIR, "NA12891.vcf"), false }); tests.add(new Object[] { VCFUtils.createTemporaryIndexedVcfFromInput( new File(TEST_DATA_DIR, "NA12891.vcf"), "fingerprintcheckertest.tmp."), true }); tests.add(new Object[] { VCFUtils.createTemporaryIndexedVcfFromInput( new File(TEST_DATA_DIR, "NA12891.vcf.gz"), "fingerprintcheckertest.tmp."), true }); - return tests.iterator(); } @Test(dataProvider = "queryableData") - public void testIsQueriable(final File vcf, final boolean expectedQueryable) + public void testFileIsQueriable(final File vcf, final boolean expectedQueryable) throws Exception { try (VCFReader reader = VCFReaderFactory.getInstance().open(vcf, false)) { Assert.assertEquals(reader.isQueryable(), expectedQueryable); } } + @Test(dataProvider = "queryableData") + public void testPathIsQueriable(final File vcf, final boolean expectedQueryable) + throws Exception { + try (VCFReader reader = VCFReaderFactory.getInstance().open(vcf.toPath(), false)) { + Assert.assertEquals(reader.isQueryable(), expectedQueryable); + } + } + + @Test(dataProvider = "queryableData") + public void testUriIsQueriable(final File vcf, final boolean expectedQueryable) + throws Exception { + try (VCFReader reader = VCFReaderFactory.getInstance().open(vcf.toString(), false)) { + Assert.assertEquals(reader.isQueryable(), expectedQueryable); + } + } + @Test public void testInstances() { Assert.assertNotNull(VCFReaderFactory.getInstance()); From ecb03e6fa957f2572d5a31245abc06edf19cfde4 Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Fri, 23 Apr 2021 11:03:17 +0200 Subject: [PATCH 3/4] add toString, remove import --- src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java | 9 ++++++++- .../java/htsjdk/variant/vcf/VCFReaderFactoryTest.java | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java b/src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java index 61785fcfc1..24e5bb7f68 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java +++ b/src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java @@ -37,7 +37,9 @@ public static VCFReaderFactory getInstance() { return currentFactory; } - /** get the default instance of VCFReaderFactory */ + /** get the default instance of VCFReaderFactory which + * opens VCF files using {@link VCFFileReader} + */ public static VCFReaderFactory getDefaultInstance() { return DEFAULT_FACTORY; } @@ -68,6 +70,11 @@ public VCFReader open(final String vcfUri, boolean requireIndex) { return open(Paths.get(vcfUri), requireIndex); } + @Override + public String toString() { + return "VCFReaderFactory(" + this.getClass() + ")"; + } + /** * creates the default instance of VCFReaderFactory A new instance from a * specific class it's name is defined by the java property diff --git a/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java b/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java index 3a3bd78a21..36cb0878cb 100644 --- a/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java +++ b/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java @@ -11,7 +11,6 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import com.google.common.jimfs.Configuration; import htsjdk.HtsjdkTest; import htsjdk.samtools.util.RuntimeIOException; From a286cc4e5d9b2f01dda87f6104cd3850be9ca0ab Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Fri, 23 Apr 2021 11:16:50 +0200 Subject: [PATCH 4/4] add comment --- src/main/java/htsjdk/variant/vcf/VCFReader.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/htsjdk/variant/vcf/VCFReader.java b/src/main/java/htsjdk/variant/vcf/VCFReader.java index f1d059ae50..3a284fcb47 100644 --- a/src/main/java/htsjdk/variant/vcf/VCFReader.java +++ b/src/main/java/htsjdk/variant/vcf/VCFReader.java @@ -31,6 +31,7 @@ /** * Interface for reading VCF/BCF files. + * @see VCFReaderFactory */ public interface VCFReader extends Closeable, Iterable {