From 78096d547e034aaa82a5e3129bd4860110d0f650 Mon Sep 17 00:00:00 2001
From: delocalizer
Date: Mon, 21 Aug 2023 15:28:09 +1000
Subject: [PATCH 1/5] minimal changes to address #915. Existing tests pass
---
src/main/java/picard/sam/FastqToSam.java | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/src/main/java/picard/sam/FastqToSam.java b/src/main/java/picard/sam/FastqToSam.java
index 4d437cbec9..014dbb3fa6 100644
--- a/src/main/java/picard/sam/FastqToSam.java
+++ b/src/main/java/picard/sam/FastqToSam.java
@@ -328,11 +328,14 @@ protected int doWork() {
final SAMFileWriter writer = new SAMFileWriterFactory().makeWriter(header, false, OUTPUT, REFERENCE_SEQUENCE);
final FastqReader reader1 = fileToFastqReader(FASTQ.toPath());
+ final boolean pipedInput = !Files.isRegularFile(FASTQ.toPath());
if (reader1.hasNext()) {
- // Set the quality format
- QUALITY_FORMAT = FastqToSam.determineQualityFormat(reader1,
- (FASTQ2 == null) ? null : fileToFastqReader(FASTQ2.toPath()),
- QUALITY_FORMAT);
+ if (!pipedInput) {
+ // Set the quality format
+ QUALITY_FORMAT = FastqToSam.determineQualityFormat(reader1,
+ (FASTQ2 == null) ? null : fileToFastqReader(FASTQ2.toPath()),
+ QUALITY_FORMAT);
+ }
} else {
if (ALLOW_EMPTY_FASTQ) {
LOG.warn("Input FASTQ is empty, will write out SAM/BAM file with no reads.");
@@ -360,9 +363,14 @@ protected int doWork() {
}
}
else {
- readers1.add(fileToFastqReader(FASTQ.toPath()));
- if (FASTQ2 != null) {
- readers2.add(fileToFastqReader(FASTQ2.toPath()));
+ if (pipedInput) {
+ // use the already opened reader if input is STDIN or a named pipe
+ readers1.add(reader1);
+ } else {
+ readers1.add(fileToFastqReader(FASTQ.toPath()));
+ if (FASTQ2 != null) {
+ readers2.add(fileToFastqReader(FASTQ2.toPath()));
+ }
}
}
@@ -582,6 +590,7 @@ private String error(final FastqReader freader, final String str) {
protected String[] customCommandLineValidation() {
if (MIN_Q < 0) return new String[]{"MIN_Q must be >= 0"};
if (MAX_Q > SAMUtils.MAX_PHRED_SCORE) return new String[]{"MAX_Q must be <= " + SAMUtils.MAX_PHRED_SCORE};
+ if (!Files.isRegularFile(FASTQ.toPath()) && QUALITY_FORMAT == null) return new String[]{"QUALITY_FORMAT must be specified when input is not a regular file"};
return null;
}
}
From ee2d3e098cf1eebff45cd1a4fe5133b7d474dbf9 Mon Sep 17 00:00:00 2001
From: delocalizer
Date: Mon, 21 Aug 2023 17:11:01 +1000
Subject: [PATCH 2/5] unit tests for FastqToSam stdin input happy & sad paths
---
src/test/java/picard/sam/FastqToSamTest.java | 65 ++++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/src/test/java/picard/sam/FastqToSamTest.java b/src/test/java/picard/sam/FastqToSamTest.java
index 504aae115a..734cb7395c 100644
--- a/src/test/java/picard/sam/FastqToSamTest.java
+++ b/src/test/java/picard/sam/FastqToSamTest.java
@@ -36,8 +36,10 @@
import picard.cmdline.CommandLineProgramTest;
import picard.PicardException;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.io.PrintStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
@@ -47,6 +49,8 @@
*/
public class FastqToSamTest extends CommandLineProgramTest {
private static final File TEST_DATA_DIR = new File("testdata/picard/sam/fastq2bam");
+ private static final String CLASSPATH = "\"" + System.getProperty("java.class.path") + "\" ";
+
public String getCommandLineProgramName() {
return FastqToSam.class.getSimpleName();
@@ -177,6 +181,67 @@ public void testEmptyFastqAllowed() throws IOException {
convertFile(emptyFastq, null, FastqQualityFormat.Illumina, false, false, true);
}
+ @Test
+ public void testStreamInputWithoutQuality() throws IOException {
+ ByteArrayOutputStream stderrStream = new ByteArrayOutputStream();
+ PrintStream newStderr = new PrintStream(stderrStream);
+ PrintStream oldStderr = System.err;
+ System.setErr(newStderr);
+
+ final File tmpFile = File.createTempFile("empty", ".sam");
+
+ final String[] args = {
+ "FASTQ=/dev/stdin",
+ "SAMPLE_NAME=sample001",
+ "OUTPUT=" + tmpFile
+ };
+
+ Assert.assertEquals(runPicardCommandLine(args), 1);
+ System.setErr(oldStderr);
+ Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when input is not a regular file\n"));
+ }
+
+ @Test
+ public void testStreamInput() throws IOException {
+ final File output = newTempSamFile("stdin");
+ String[] command = {
+ "/bin/bash",
+ "-c",
+ "echo -ne '" +
+ "@ERR194147.10008417/1\\n" +
+ "ATTTAATTAAGAAAATGTAAACTAAATGACAGTAGACAGACAAGTATGCCTTTGC\\n" +
+ "+\\n" +
+ "???????????????????????????????????????????????????????\\n'" +
+ "|" +
+ "java -classpath " +
+ CLASSPATH +
+ "picard.cmdline.PicardCommandLine " +
+ "FastqToSam " +
+ "-FASTQ /dev/stdin " +
+ "-QUALITY_FORMAT Standard " +
+ "-SAMPLE_NAME sample001 " +
+ "-OUTPUT " + output
+ };
+
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ processBuilder.inheritIO();
+ Process process = processBuilder.start();
+ Assert.assertEquals(process.waitFor(), 0);
+ } catch (Exception e) {
+ Assert.fail("Failed to pipe data from stdin to FastqToSam", e);
+ }
+ final SamReader samReader = SamReaderFactory.makeDefault().open(output);
+ final SAMRecordIterator iterator = samReader.iterator();
+ int actualCount = 0;
+ while (iterator.hasNext()) {
+ iterator.next();
+ actualCount++;
+ }
+ samReader.close();
+ Assert.assertEquals(actualCount, 1);
+ }
+
private File convertFile(final String filename, final FastqQualityFormat version) throws IOException {
return convertFile(filename, null, version);
}
From 59ca6ea91e06858ce6b470519f87b5e521c327a0 Mon Sep 17 00:00:00 2001
From: delocalizer
Date: Mon, 21 Aug 2023 17:25:48 +1000
Subject: [PATCH 3/5] update docs
---
src/main/java/picard/sam/FastqToSam.java | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/main/java/picard/sam/FastqToSam.java b/src/main/java/picard/sam/FastqToSam.java
index 014dbb3fa6..adb8954fef 100644
--- a/src/main/java/picard/sam/FastqToSam.java
+++ b/src/main/java/picard/sam/FastqToSam.java
@@ -82,7 +82,7 @@
*
*
* By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly
- * using the QUALITY_FORMAT
argument.
+ * using the QUALITY_FORMAT
argument, and must do so if input is not from a regular file.
*
* Output
* A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.
@@ -132,7 +132,7 @@ public class FastqToSam extends CommandLineProgram {
"Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name " +
"(see USE_SEQUENCIAL_FASTQ for details below).
" +
"By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly " +
- "using the QUALITY_FORMAT argument.
" +
+ "using the QUALITY_FORMAT argument, and must do so if input is not from a regular file." +
"Output
" +
"A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.
" +
"Usage examples
" +
@@ -176,7 +176,8 @@ public class FastqToSam extends CommandLineProgram {
@Argument(shortName="V", doc="A value describing how the quality values are encoded in the input FASTQ file. " +
"Either Solexa (phred scaling + 66), Illumina (phred scaling + 64) or Standard (phred scaling + 33). " +
- "If this value is not specified, the quality format will be detected automatically.", optional = true)
+ "If input is from a regular file and this value is not specified, the quality format will be detected automatically. " +
+ "If input is from STDIN or a named pipe, this value is required.", optional = true)
public FastqQualityFormat QUALITY_FORMAT;
@Argument(doc="Output BAM/SAM/CRAM file. ", shortName=StandardOptionDefinitions.OUTPUT_SHORT_NAME)
From 6451e7eef57202bf94d86141d1419e0c85c57b07 Mon Sep 17 00:00:00 2001
From: delocalizer
Date: Tue, 22 Aug 2023 14:28:51 +1000
Subject: [PATCH 4/5] requested changes
---
src/main/java/picard/nio/PicardHtsPath.java | 17 ++++++
src/main/java/picard/sam/FastqToSam.java | 55 +++++++++++---------
src/test/java/picard/sam/FastqToSamTest.java | 51 +++++++++---------
3 files changed, 72 insertions(+), 51 deletions(-)
diff --git a/src/main/java/picard/nio/PicardHtsPath.java b/src/main/java/picard/nio/PicardHtsPath.java
index f9ca70dd93..f82a788dc9 100644
--- a/src/main/java/picard/nio/PicardHtsPath.java
+++ b/src/main/java/picard/nio/PicardHtsPath.java
@@ -30,7 +30,9 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -83,6 +85,21 @@ public static List fromPaths(Collection paths) {
return paths.stream().map(PicardHtsPath::new).collect(Collectors.toList());
}
+ /**
+ * Test if the URI of this object is something other than a regular file, directory, or
+ * symbolic link.
+ *
+ * @return {@code true} if it's a device, socket or named pipe
+ * @throws RuntimeException if an I/O error occurs when creating the file system
+ */
+ public boolean isOther() {
+ try {
+ return Files.readAttributes(IOUtil.getPath(super.getURIString()), BasicFileAttributes.class).isOther();
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
/**
* Resolve the URI of this object to a {@link Path} object.
*
diff --git a/src/main/java/picard/sam/FastqToSam.java b/src/main/java/picard/sam/FastqToSam.java
index adb8954fef..d9807252d0 100644
--- a/src/main/java/picard/sam/FastqToSam.java
+++ b/src/main/java/picard/sam/FastqToSam.java
@@ -23,6 +23,17 @@
*/
package picard.sam;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.broadinstitute.barclay.argparser.Argument;
+import org.broadinstitute.barclay.argparser.CommandLineProgramProperties;
+import org.broadinstitute.barclay.help.DocumentedFeature;
+
import htsjdk.samtools.ReservedTagConstants;
import htsjdk.samtools.SAMException;
import htsjdk.samtools.SAMFileHeader;
@@ -44,24 +55,12 @@
import htsjdk.samtools.util.SequenceUtil;
import htsjdk.samtools.util.SolexaQualityConverter;
import htsjdk.samtools.util.StringUtil;
-import org.broadinstitute.barclay.argparser.Argument;
-import org.broadinstitute.barclay.argparser.CommandLineProgramProperties;
-import org.broadinstitute.barclay.help.DocumentedFeature;
import picard.PicardException;
import picard.cmdline.CommandLineProgram;
import picard.cmdline.StandardOptionDefinitions;
import picard.cmdline.programgroups.ReadDataManipulationProgramGroup;
import picard.nio.PicardHtsPath;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
/**
* Converts a FASTQ file to an unaligned BAM or SAM file.
*
@@ -78,11 +77,14 @@
* These files might be in gzip compressed format (when file name is ending with ".gz").
*
*
- * Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name (see USE_SEQUENCIAL_FASTQ
for details below).
+ * Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name (see USE_SEQUENTIAL_FASTQ
for details below).
*
*
* By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly
- * using the QUALITY_FORMAT
argument, and must do so if input is not from a regular file.
+ * using the QUALITY_FORMAT
argument, and must do so if FASTQ
is not a regular file (e.g. stdin).
+ *
+ *
+ * FASTQ2
input is not supported when FASTQ
is not a regular file: you may consider using upstream tools to merge multiple inputs into a single input stream.
*
* Output
* A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.
@@ -130,9 +132,11 @@ public class FastqToSam extends CommandLineProgram {
"One FASTQ file name for single-end or two for pair-end sequencing input data. " +
"These files might be in gzip compressed format (when file name is ending with \".gz\").
" +
"Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name " +
- "(see USE_SEQUENCIAL_FASTQ for details below).
" +
+ "(see USE_SEQUENTIAL_FASTQ for details below)." +
"By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly " +
- "using the QUALITY_FORMAT argument, and must do so if input is not from a regular file.
" +
+ "using the QUALITY_FORMAT argument, and must do so if FASTQ is not a regular file (e.g. stdin)." +
+ "FASTQ2 input is not supported when FASTQ is not a regular file: you may consider using " +
+ "upstream tools to merge multiple inputs into a single input stream.
" +
"Output
" +
"A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.
" +
"Usage examples
" +
@@ -240,6 +244,8 @@ public class FastqToSam extends CommandLineProgram {
private static final SolexaQualityConverter solexaQualityConverter = SolexaQualityConverter.getSingleton();
+ private Boolean regularFileInput;
+
/**
* Looks at fastq input(s) and attempts to determine the proper quality format
*
@@ -329,9 +335,8 @@ protected int doWork() {
final SAMFileWriter writer = new SAMFileWriterFactory().makeWriter(header, false, OUTPUT, REFERENCE_SEQUENCE);
final FastqReader reader1 = fileToFastqReader(FASTQ.toPath());
- final boolean pipedInput = !Files.isRegularFile(FASTQ.toPath());
if (reader1.hasNext()) {
- if (!pipedInput) {
+ if (regularFileInput) {
// Set the quality format
QUALITY_FORMAT = FastqToSam.determineQualityFormat(reader1,
(FASTQ2 == null) ? null : fileToFastqReader(FASTQ2.toPath()),
@@ -364,7 +369,7 @@ protected int doWork() {
}
}
else {
- if (pipedInput) {
+ if (!regularFileInput) {
// use the already opened reader if input is STDIN or a named pipe
readers1.add(reader1);
} else {
@@ -414,7 +419,7 @@ protected int doUnpaired(final FastqReader freader, final SAMFileWriter writer)
final ProgressLogger progress = new ProgressLogger(LOG);
for ( ; freader.hasNext() ; readCount++) {
final FastqRecord frec = freader.next();
- final SAMRecord srec = createSamRecord(writer.getFileHeader(), SequenceUtil.getSamReadNameFromFastqHeader(frec.getReadHeader()) , frec, false) ;
+ final SAMRecord srec = createSamRecord(writer.getFileHeader(), SequenceUtil.getSamReadNameFromFastqHeader(frec.getReadName()) , frec, false) ;
srec.setReadPairedFlag(false);
writer.addAlignment(srec);
progress.record(srec);
@@ -431,8 +436,8 @@ protected int doPaired(final FastqReader freader1, final FastqReader freader2, f
final FastqRecord frec1 = freader1.next();
final FastqRecord frec2 = freader2.next();
- final String frec1Name = SequenceUtil.getSamReadNameFromFastqHeader(frec1.getReadHeader());
- final String frec2Name = SequenceUtil.getSamReadNameFromFastqHeader(frec2.getReadHeader());
+ final String frec1Name = SequenceUtil.getSamReadNameFromFastqHeader(frec1.getReadName());
+ final String frec2Name = SequenceUtil.getSamReadNameFromFastqHeader(frec2.getReadName());
final String baseName = getBaseName(frec1Name, frec2Name, freader1, freader2);
final SAMRecord srec1 = createSamRecord(writer.getFileHeader(), baseName, frec1, true) ;
@@ -471,7 +476,7 @@ private SAMRecord createSamRecord(final SAMFileHeader header, final String baseN
final int uQual = qual & 0xff;
if (uQual < MIN_Q || uQual > MAX_Q) {
throw new PicardException("Base quality " + uQual + " is not in the range " + MIN_Q + ".." +
- MAX_Q + " for read " + frec.getReadHeader());
+ MAX_Q + " for read " + frec.getReadName());
}
}
srec.setBaseQualities(quals);
@@ -591,7 +596,9 @@ private String error(final FastqReader freader, final String str) {
protected String[] customCommandLineValidation() {
if (MIN_Q < 0) return new String[]{"MIN_Q must be >= 0"};
if (MAX_Q > SAMUtils.MAX_PHRED_SCORE) return new String[]{"MAX_Q must be <= " + SAMUtils.MAX_PHRED_SCORE};
- if (!Files.isRegularFile(FASTQ.toPath()) && QUALITY_FORMAT == null) return new String[]{"QUALITY_FORMAT must be specified when input is not a regular file"};
+ regularFileInput = !FASTQ.isOther();
+ if (!regularFileInput && QUALITY_FORMAT == null) return new String[]{"QUALITY_FORMAT must be specified when FASTQ is not a regular file"};
+ if (!regularFileInput && FASTQ2 != null) return new String[]{"FASTQ2 input is not supported when FASTQ is not a regular file"};
return null;
}
}
diff --git a/src/test/java/picard/sam/FastqToSamTest.java b/src/test/java/picard/sam/FastqToSamTest.java
index 734cb7395c..6a41d99eb6 100644
--- a/src/test/java/picard/sam/FastqToSamTest.java
+++ b/src/test/java/picard/sam/FastqToSamTest.java
@@ -186,33 +186,34 @@ public void testStreamInputWithoutQuality() throws IOException {
ByteArrayOutputStream stderrStream = new ByteArrayOutputStream();
PrintStream newStderr = new PrintStream(stderrStream);
PrintStream oldStderr = System.err;
- System.setErr(newStderr);
-
- final File tmpFile = File.createTempFile("empty", ".sam");
-
- final String[] args = {
- "FASTQ=/dev/stdin",
- "SAMPLE_NAME=sample001",
- "OUTPUT=" + tmpFile
- };
-
- Assert.assertEquals(runPicardCommandLine(args), 1);
- System.setErr(oldStderr);
- Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when input is not a regular file\n"));
+ try {
+ System.setErr(newStderr);
+ final File tmpFile = File.createTempFile("empty", ".sam");
+ final String[] args = {
+ "FASTQ=/dev/stdin",
+ "SAMPLE_NAME=sample001",
+ "OUTPUT=" + tmpFile
+ };
+ Assert.assertEquals(runPicardCommandLine(args), 1);
+ } finally {
+ System.setErr(oldStderr);
+ }
+ Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when FASTQ is not a regular file\n"));
}
@Test
public void testStreamInput() throws IOException {
final File output = newTempSamFile("stdin");
+ String fastq = """
+ @ERR194147.10008417/1
+ ATTTAATTAAGAAAATGTAAACTAAATGACAGTAGACAGACAAGTATGCCTTTGC
+ +
+ ???????????????????????????????????????????????????????
+ """;
String[] command = {
"/bin/bash",
"-c",
- "echo -ne '" +
- "@ERR194147.10008417/1\\n" +
- "ATTTAATTAAGAAAATGTAAACTAAATGACAGTAGACAGACAAGTATGCCTTTGC\\n" +
- "+\\n" +
- "???????????????????????????????????????????????????????\\n'" +
- "|" +
+ "echo -n '" + fastq + "'|" +
"java -classpath " +
CLASSPATH +
"picard.cmdline.PicardCommandLine " +
@@ -231,15 +232,11 @@ public void testStreamInput() throws IOException {
} catch (Exception e) {
Assert.fail("Failed to pipe data from stdin to FastqToSam", e);
}
- final SamReader samReader = SamReaderFactory.makeDefault().open(output);
- final SAMRecordIterator iterator = samReader.iterator();
- int actualCount = 0;
- while (iterator.hasNext()) {
- iterator.next();
- actualCount++;
+
+ try(final SamReader samReader = SamReaderFactory.makeDefault().open(output)) {
+ long actualCount = samReader.iterator().stream().count();
+ Assert.assertEquals(actualCount, 1);
}
- samReader.close();
- Assert.assertEquals(actualCount, 1);
}
private File convertFile(final String filename, final FastqQualityFormat version) throws IOException {
From 94e41f3d9b21ce2e611ded9c85eeadbebb25e989 Mon Sep 17 00:00:00 2001
From: delocalizer
Date: Thu, 24 Aug 2023 19:23:24 +1000
Subject: [PATCH 5/5] requested changes
---
src/main/java/picard/nio/PicardHtsPath.java | 42 +++++++++++---------
src/main/java/picard/sam/FastqToSam.java | 32 ++++++---------
src/test/java/picard/sam/FastqToSamTest.java | 2 +-
3 files changed, 37 insertions(+), 39 deletions(-)
diff --git a/src/main/java/picard/nio/PicardHtsPath.java b/src/main/java/picard/nio/PicardHtsPath.java
index f82a788dc9..3856244f5b 100644
--- a/src/main/java/picard/nio/PicardHtsPath.java
+++ b/src/main/java/picard/nio/PicardHtsPath.java
@@ -24,10 +24,6 @@
package picard.nio;
-import htsjdk.io.HtsPath;
-import htsjdk.samtools.util.IOUtil;
-import htsjdk.samtools.util.RuntimeIOException;
-
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -38,6 +34,11 @@
import java.util.Objects;
import java.util.stream.Collectors;
+import htsjdk.io.HtsPath;
+import htsjdk.io.IOPath;
+import htsjdk.samtools.util.IOUtil;
+import htsjdk.samtools.util.RuntimeIOException;
+
/**
* A Subclass of {@link HtsPath} with conversion to {@link Path} making use of {@link IOUtil}
*/
@@ -85,21 +86,6 @@ public static List fromPaths(Collection paths) {
return paths.stream().map(PicardHtsPath::new).collect(Collectors.toList());
}
- /**
- * Test if the URI of this object is something other than a regular file, directory, or
- * symbolic link.
- *
- * @return {@code true} if it's a device, socket or named pipe
- * @throws RuntimeException if an I/O error occurs when creating the file system
- */
- public boolean isOther() {
- try {
- return Files.readAttributes(IOUtil.getPath(super.getURIString()), BasicFileAttributes.class).isOther();
- } catch (IOException e) {
- throw new RuntimeIOException(e);
- }
- }
-
/**
* Resolve the URI of this object to a {@link Path} object.
*
@@ -125,6 +111,24 @@ public static PicardHtsPath fromPath(final Path path){
return new PicardHtsPath(new HtsPath(path.toUri().toString()));
}
+ /**
+ * Test if {@code ioPath} is something other than a regular file, directory, or symbolic link.
+ *
+ * @return {@code true} if it's a device, named pipe, htsget API URL, etc.
+ * @throws RuntimeException if an I/O error occurs when creating the file system
+ */
+ public static boolean isOther(final IOPath ioPath) {
+ if(ioPath.isPath()) {
+ try {
+ return Files.readAttributes(ioPath.toPath(), BasicFileAttributes.class).isOther();
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ } else {
+ return true;
+ }
+ }
+
/**
* Create a {@link List} from {@link PicardHtsPath}s
* @param picardHtsPaths may NOT be null
diff --git a/src/main/java/picard/sam/FastqToSam.java b/src/main/java/picard/sam/FastqToSam.java
index d9807252d0..063ffca78a 100644
--- a/src/main/java/picard/sam/FastqToSam.java
+++ b/src/main/java/picard/sam/FastqToSam.java
@@ -81,10 +81,7 @@
*
*
* By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly
- * using the QUALITY_FORMAT
argument, and must do so if FASTQ
is not a regular file (e.g. stdin).
- *
- *
- * FASTQ2
input is not supported when FASTQ
is not a regular file: you may consider using upstream tools to merge multiple inputs into a single input stream.
+ * using the QUALITY_FORMAT
argument, and must do so if inputs are not a regular file (e.g. stdin).
*
* Output
* A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.
@@ -134,9 +131,7 @@ public class FastqToSam extends CommandLineProgram {
"Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name " +
"(see USE_SEQUENTIAL_FASTQ for details below).
" +
"By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly " +
- "using the QUALITY_FORMAT argument, and must do so if FASTQ is not a regular file (e.g. stdin).
" +
- "FASTQ2 input is not supported when FASTQ is not a regular file: you may consider using " +
- "upstream tools to merge multiple inputs into a single input stream.
" +
+ "using the QUALITY_FORMAT argument, and must do so if inputs are not a regular file (e.g. stdin)." +
"Output
" +
"A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.
" +
"Usage examples
" +
@@ -181,7 +176,7 @@ public class FastqToSam extends CommandLineProgram {
@Argument(shortName="V", doc="A value describing how the quality values are encoded in the input FASTQ file. " +
"Either Solexa (phred scaling + 66), Illumina (phred scaling + 64) or Standard (phred scaling + 33). " +
"If input is from a regular file and this value is not specified, the quality format will be detected automatically. " +
- "If input is from STDIN or a named pipe, this value is required.", optional = true)
+ "If input is not from a regular file, this value is required.", optional = true)
public FastqQualityFormat QUALITY_FORMAT;
@Argument(doc="Output BAM/SAM/CRAM file. ", shortName=StandardOptionDefinitions.OUTPUT_SHORT_NAME)
@@ -244,6 +239,7 @@ public class FastqToSam extends CommandLineProgram {
private static final SolexaQualityConverter solexaQualityConverter = SolexaQualityConverter.getSingleton();
+ // tested for and set in customCommandLineValidation
private Boolean regularFileInput;
/**
@@ -369,14 +365,10 @@ protected int doWork() {
}
}
else {
- if (!regularFileInput) {
- // use the already opened reader if input is STDIN or a named pipe
- readers1.add(reader1);
- } else {
- readers1.add(fileToFastqReader(FASTQ.toPath()));
- if (FASTQ2 != null) {
- readers2.add(fileToFastqReader(FASTQ2.toPath()));
- }
+ // use the already opened reader1 if input is STDIN or a named pipe
+ readers1.add(regularFileInput ? fileToFastqReader(FASTQ.toPath()) : reader1);
+ if (FASTQ2 != null) {
+ readers2.add(fileToFastqReader(FASTQ2.toPath()));
}
}
@@ -596,9 +588,11 @@ private String error(final FastqReader freader, final String str) {
protected String[] customCommandLineValidation() {
if (MIN_Q < 0) return new String[]{"MIN_Q must be >= 0"};
if (MAX_Q > SAMUtils.MAX_PHRED_SCORE) return new String[]{"MAX_Q must be <= " + SAMUtils.MAX_PHRED_SCORE};
- regularFileInput = !FASTQ.isOther();
- if (!regularFileInput && QUALITY_FORMAT == null) return new String[]{"QUALITY_FORMAT must be specified when FASTQ is not a regular file"};
- if (!regularFileInput && FASTQ2 != null) return new String[]{"FASTQ2 input is not supported when FASTQ is not a regular file"};
+ regularFileInput = !PicardHtsPath.isOther(FASTQ) && (FASTQ2 == null || !PicardHtsPath.isOther(FASTQ2));
+ if (QUALITY_FORMAT == null && !regularFileInput) {
+ return new String[]{"QUALITY_FORMAT must be specified when either of FASTQ or FASTQ2 is not a regular file"};
+ }
return null;
}
+
}
diff --git a/src/test/java/picard/sam/FastqToSamTest.java b/src/test/java/picard/sam/FastqToSamTest.java
index 6a41d99eb6..678d8f2bd2 100644
--- a/src/test/java/picard/sam/FastqToSamTest.java
+++ b/src/test/java/picard/sam/FastqToSamTest.java
@@ -198,7 +198,7 @@ public void testStreamInputWithoutQuality() throws IOException {
} finally {
System.setErr(oldStderr);
}
- Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when FASTQ is not a regular file\n"));
+ Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when either of FASTQ or FASTQ2 is not a regular file\n"));
}
@Test