diff --git a/src/main/java/htsjdk/samtools/SamInputResource.java b/src/main/java/htsjdk/samtools/SamInputResource.java index f25d97bb6..13ecf5dfd 100644 --- a/src/main/java/htsjdk/samtools/SamInputResource.java +++ b/src/main/java/htsjdk/samtools/SamInputResource.java @@ -39,9 +39,11 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.nio.channels.SeekableByteChannel; import java.nio.file.FileSystemNotFoundException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.function.Function; /** * Describes a SAM-like resource, including its data (where the records are), and optionally an index. @@ -89,7 +91,15 @@ public String toString() { public static SamInputResource of(final File file) { return new SamInputResource(new FileInputResource(file)); } /** Creates a {@link SamInputResource} reading from the provided resource, with no index. */ - public static SamInputResource of(final Path path) { return new SamInputResource(new PathInputResource(path)); } + public static SamInputResource of(final Path path) { + return new SamInputResource(new PathInputResource(path)); + } + + /** Creates a {@link SamInputResource} reading from the provided resource, with no index, + * and with a wrapper to apply to the SeekableByteChannel for custom prefetching/buffering. */ + public static SamInputResource of(final Path path, Function wrapper) { + return new SamInputResource(new PathInputResource(path, wrapper)); + } /** Creates a {@link SamInputResource} reading from the provided resource, with no index. */ public static SamInputResource of(final InputStream inputStream) { return new SamInputResource(new InputStreamInputResource(inputStream)); } @@ -121,7 +131,7 @@ public SamInputResource index(final File file) { /** Updates the index to point at the provided resource, then returns itself. */ public SamInputResource index(final Path path) { - this.index = new PathInputResource(path); + this.index = new PathInputResource(path, Function.identity()); return this; } @@ -268,11 +278,12 @@ public SRAAccession asSRAAccession() { class PathInputResource extends InputResource { final Path pathResource; + final Function wrapper; final Lazy lazySeekableStream = new Lazy(new Lazy.LazyInitializer() { @Override public SeekableStream make() { try { - return new SeekablePathStream(pathResource); + return new SeekablePathStream(pathResource, wrapper); } catch (final IOException e) { throw new RuntimeIOException(e); } @@ -281,8 +292,14 @@ public SeekableStream make() { PathInputResource(final Path pathResource) { + this(pathResource, Function.identity()); + } + + // wrapper applies to the SeekableByteChannel for custom prefetching/buffering. + PathInputResource(final Path pathResource, Function wrapper) { super(Type.PATH); this.pathResource = pathResource; + this.wrapper = wrapper; } @Override diff --git a/src/main/java/htsjdk/samtools/SamReaderFactory.java b/src/main/java/htsjdk/samtools/SamReaderFactory.java index 8769f4879..4c3035433 100644 --- a/src/main/java/htsjdk/samtools/SamReaderFactory.java +++ b/src/main/java/htsjdk/samtools/SamReaderFactory.java @@ -33,9 +33,11 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.channels.SeekableByteChannel; import java.nio.file.Path; import java.util.Collections; import java.util.EnumSet; +import java.util.function.Function; import java.util.zip.GZIPInputStream; /** @@ -74,11 +76,13 @@ public abstract class SamReaderFactory { private static ValidationStringency defaultValidationStringency = ValidationStringency.DEFAULT_STRINGENCY; - + + private Function pathWrapper = Function.identity(); + abstract public SamReader open(final File file); public SamReader open(final Path path) { - final SamInputResource r = SamInputResource.of(path); + final SamInputResource r = SamInputResource.of(path, getPathWrapper()); final Path indexMaybe = SamFiles.findIndex(path); if (indexMaybe != null) r.index(indexMaybe); return open(r); @@ -102,6 +106,25 @@ public SamReader open(final Path path) { /** Sets a specific Option to a boolean value. * */ abstract public SamReaderFactory setOption(final Option option, boolean value); + /** Sets a wrapper to modify the SeekableByteChannel from an opened Path, e.g. to add + * buffering or prefetching. This only works on Path inputs since we need a SeekableByteChannel. + * + * @param wrapper how to modify the SeekableByteChannel (Function.identity to unset) + * @return this + */ + public SamReaderFactory setPathWrapper(Function wrapper) { + this.pathWrapper = wrapper; + return this; + } + + /** Gets the wrapper previously set via setPathWrapper. + * + * @return the wrapper. + */ + public Function getPathWrapper() { + return pathWrapper; + } + /** Sets the specified reference sequence * */ abstract public SamReaderFactory referenceSequence(File referenceSequence); @@ -138,8 +161,8 @@ public static SamReaderFactory makeDefault() { } /** - * Creates an "empty" factory with no enabled {@link Option}s, {@link ValidationStringency#DEFAULT_STRINGENCY}, and - * {@link htsjdk.samtools.DefaultSAMRecordFactory}. + * Creates an "empty" factory with no enabled {@link Option}s, {@link ValidationStringency#DEFAULT_STRINGENCY}, + * no path wrapper, and {@link htsjdk.samtools.DefaultSAMRecordFactory}. */ public static SamReaderFactory make() { return new SamReaderFactoryImpl(EnumSet.noneOf(Option.class), ValidationStringency.DEFAULT_STRINGENCY, DefaultSAMRecordFactory.getInstance()); @@ -155,10 +178,15 @@ public static SamReaderFactory make() { private CRAMReferenceSource referenceSource; private SamReaderFactoryImpl(final EnumSet