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/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 { 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..24e5bb7f68 --- /dev/null +++ b/src/main/java/htsjdk/variant/vcf/VCFReaderFactory.java @@ -0,0 +1,108 @@ +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 which + * opens VCF files using {@link VCFFileReader} + */ + 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); + } + + @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 + * {@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..36cb0878cb --- /dev/null +++ b/src/test/java/htsjdk/variant/vcf/VCFReaderFactoryTest.java @@ -0,0 +1,77 @@ +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 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 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()); + 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()); + } + +}