diff --git a/qannotate/src/au/edu/qimr/qannotate/Main.java b/qannotate/src/au/edu/qimr/qannotate/Main.java index d27cc1659..a75e40476 100644 --- a/qannotate/src/au/edu/qimr/qannotate/Main.java +++ b/qannotate/src/au/edu/qimr/qannotate/Main.java @@ -5,6 +5,10 @@ */ package au.edu.qimr.qannotate; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + import org.qcmg.common.log.QLogger; import org.qcmg.common.log.QLoggerFactory; @@ -32,36 +36,19 @@ public static void main(final String[] args) { logger = QLoggerFactory.getLogger(Main.class, options.getLogFileName(), options.getLogLevel()); logger.logInitialExecutionStats(options.getPGName(), options.getVersion(), args); - checkOptions(options); - - if (options.getMode() == Options.MODE.dbsnp) { - new DbsnpMode(options); - } else if (options.getMode() == Options.MODE.germline) { - new GermlineMode(options); - } else if (options.getMode() == Options.MODE.snpeff) { - new SnpEffMode(options); - } else if (options.getMode() == Options.MODE.confidence) { - new ConfidenceMode(options); - } else if (options.getMode() == Options.MODE.ccm) { - new CCMMode(options); - } else if (options.getMode() == Options.MODE.vcf2maf) { - new Vcf2maf(options); - } else if (options.getMode() == Options.MODE.cadd) { - new CaddMode(options); - } else if (options.getMode() == Options.MODE.indelconfidence) { - new IndelConfidenceMode(options); - } else if (options.getMode() == Options.MODE.hom) { - new HomopolymersMode(options); - } else if (options.getMode() == Options.MODE.trf) { - new TandemRepeatMode(options); - } else if (options.getMode() == Options.MODE.make_valid) { - new MakeValidMode(options); - } else if (options.getMode() == Options.MODE.overlap) { - new OverlapMode(options); - } else if (options.getMode() == null) { + List modes = options.getModes(); + if (modes.isEmpty()) { throw new IllegalArgumentException("No mode was specified on the commandline - please add the \"-mode\" option"); + } + if (modes.size() > 1 && (modes.contains(Options.MODE.vcf2maf) || modes.contains(Options.MODE.vcf2maftmp))) { + throw new IllegalArgumentException("Multiple mode runs do not support vcf2maf/vcf2maftmp"); + } + + if (modes.size() == 1) { + checkOptions(options); + runMode(options); } else { - throw new IllegalArgumentException("No valid mode are specified on commandline - please run \"qannotate -help\" to see the list of available modes"); + runMultipleModes(args, modes, options.getInputFileName(), options.getOutputFileName()); } logger.logFinalExecutionStats(0); @@ -79,6 +66,129 @@ public static void main(final String[] args) { } } + static void runMultipleModes(String[] args, List modes, String inputFile, String outputFile) throws Exception { + String currentInput = inputFile; + List tempFiles = new ArrayList<>(); + List databaseArgs = extractDatabaseArgs(args); + int databaseIndex = 0; + for (int i = 0 ; i < modes.size() ; i++) { + boolean finalMode = i == modes.size() - 1; + String currentOutput; + if (finalMode) { + currentOutput = outputFile; + } else { + File tmp = File.createTempFile("qannotate-mode-" + i + "-", ".vcf"); + tmp.deleteOnExit(); + tempFiles.add(tmp); + currentOutput = tmp.getAbsolutePath(); + } + List modeDatabaseArgs = new ArrayList<>(); + if (modeUsesDatabase(modes.get(i)) && databaseIndex < databaseArgs.size()) { + modeDatabaseArgs.add(databaseArgs.get(databaseIndex++)); + } + Options modeOptions = new Options(rewriteArgsForMode(args, modes.get(i), currentInput, currentOutput, modeDatabaseArgs)); + checkOptions(modeOptions); + runMode(modeOptions); + currentInput = currentOutput; + } + for (File temp : tempFiles) { + if (temp.exists() && ! temp.delete()) { + temp.deleteOnExit(); + } + } + } + + static String[] rewriteArgsForMode(String[] args, Options.MODE mode, String inputFile, String outputFile, List databaseArgs) { + List newArgs = new ArrayList<>(); + for (int i = 0 ; i < args.length ; i++) { + String arg = args[i]; + if (isOptionWithValue(arg, "mode", "input", "i", "o", "output", "d", "database")) { + i++; + continue; + } + if (arg.startsWith("--mode=") || arg.startsWith("--input=") || arg.startsWith("-i=") + || arg.startsWith("--output=") || arg.startsWith("-o=") || arg.startsWith("-output=") + || arg.startsWith("--database=") || arg.startsWith("-d=")) { + continue; + } + newArgs.add(arg); + } + newArgs.add("--mode"); + newArgs.add(mode.name()); + newArgs.add("-i"); + newArgs.add(inputFile); + newArgs.add("-o"); + newArgs.add(outputFile); + for (String databaseArg : databaseArgs) { + newArgs.add("-d"); + newArgs.add(databaseArg); + } + return newArgs.toArray(new String[0]); + } + + static List extractDatabaseArgs(String[] args) { + List databaseArgs = new ArrayList<>(); + for (int i = 0 ; i < args.length ; i++) { + String arg = args[i]; + if (arg.equals("-d") || arg.equals("--database")) { + if (i + 1 < args.length) { + databaseArgs.add(args[++i]); + } + } else if (arg.startsWith("-d=")) { + databaseArgs.add(arg.substring(3)); + } else if (arg.startsWith("--database=")) { + databaseArgs.add(arg.substring(11)); + } + } + return databaseArgs; + } + + private static boolean modeUsesDatabase(Options.MODE mode) { + return switch (mode) { + case dbsnp, germline, snpeff, cadd, trf, hom, overlap, make_valid, indelconfidence -> true; + default -> false; + }; + } + + private static boolean isOptionWithValue(String arg, String... options) { + for (String option : options) { + if (arg.equals("-" + option) || arg.equals("--" + option)) { + return true; + } + } + return false; + } + + private static void runMode(Options options) throws Exception { + if (options.getMode() == Options.MODE.dbsnp) { + new DbsnpMode(options); + } else if (options.getMode() == Options.MODE.germline) { + new GermlineMode(options); + } else if (options.getMode() == Options.MODE.snpeff) { + new SnpEffMode(options); + } else if (options.getMode() == Options.MODE.confidence) { + new ConfidenceMode(options); + } else if (options.getMode() == Options.MODE.ccm) { + new CCMMode(options); + } else if (options.getMode() == Options.MODE.vcf2maf) { + new Vcf2maf(options); + } else if (options.getMode() == Options.MODE.cadd) { + new CaddMode(options); + } else if (options.getMode() == Options.MODE.indelconfidence) { + new IndelConfidenceMode(options); + } else if (options.getMode() == Options.MODE.hom) { + new HomopolymersMode(options); + } else if (options.getMode() == Options.MODE.trf) { + new TandemRepeatMode(options); + } else if (options.getMode() == Options.MODE.make_valid) { + new MakeValidMode(options); + } else if (options.getMode() == Options.MODE.overlap) { + new OverlapMode(options); + } else { + throw new IllegalArgumentException("No valid mode are specified on commandline - please run \"qannotate -help\" to see the list of available modes"); + } + } + /** * Checks the Options object to see if the minimal options (input, output, database) have been supplied. * Using the switch statements fall through process here. diff --git a/qannotate/src/au/edu/qimr/qannotate/Options.java b/qannotate/src/au/edu/qimr/qannotate/Options.java index 3cf00ec5e..e412ca1ac 100644 --- a/qannotate/src/au/edu/qimr/qannotate/Options.java +++ b/qannotate/src/au/edu/qimr/qannotate/Options.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -35,6 +36,7 @@ public enum MODE {dbsnp, germline, snpeff, confidence, vcf2maf, cadd, indelconfi private final String commandLine; private final Options.MODE mode; + private final List modes; private final OptionParser parser; private final String outputFileName; private final String inputFileName; @@ -83,12 +85,13 @@ public Options(final String[] args) throws IOException { parser = new OptionParser(); OptionSet options = parseArgs(args); - if (options.has("mode")) { - String m = ((String) options.valueOf("mode")).toLowerCase(); - this.mode = MODE.valueOf(m); //already checked the validation of mode - } else { - this.mode = null; + List modeStrings = (List) options.valuesOf("mode"); + List modeList = new ArrayList<>(); + for (String m : modeStrings) { + modeList.add(MODE.valueOf(m.toLowerCase())); } + this.modes = Collections.unmodifiableList(modeList); + this.mode = modeList.isEmpty() ? null : modeList.get(0); if (options.has("h") || options.has("help")) { displayHelp(mode); @@ -113,7 +116,7 @@ public Options(final String[] args) throws IOException { List dbList = (List) options.valuesOf("d"); databaseFiles = dbList.toArray(new String[dbList.size()]); - if (MODE.dbsnp == mode && dbList.isEmpty()) { + if (modes.contains(MODE.dbsnp) && dbList.isEmpty()) { displayHelp(mode); System.exit(0); } @@ -193,43 +196,43 @@ public OptionSet parseArgs(final String[] args) { System.exit(0); } - Options.MODE mm = null; + List selectedModes = new ArrayList<>(); if (options.has("mode")) { - final String m = ((String) options.valueOf("mode")).toLowerCase(); - try { - mm = MODE.valueOf(m); - } catch (IllegalArgumentException | NullPointerException e) { - System.err.println("invalid mode specified: " + m); - System.exit(1); + for (String m : (List) options.valuesOf("mode")) { + try { + selectedModes.add(MODE.valueOf(m.toLowerCase())); + } catch (IllegalArgumentException | NullPointerException e) { + System.err.println("invalid mode specified: " + m); + System.exit(1); + } } } - if (mm == null) { + if (selectedModes.isEmpty()) { /* * used by nanno.Annotate */ parser.accepts("config", Messages.getMessage("NANNO_CONF_FILE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("config file"); } else { - if (mm.equals(MODE.confidence) || mm.equals(MODE.vcf2maf)) { + if (selectedModes.contains(MODE.confidence) || selectedModes.contains(MODE.vcf2maf)) { parser.accepts(test, Messages.getMessage("TUMOUR_SAMPLEID_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("testSample"); parser.accepts(control, Messages.getMessage("NORMAL_SAMPLEID_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("controlSample"); - } else { - parser.acceptsAll(asList("d", "database"), Messages.getMessage("DATABASE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("database file"); } + parser.acceptsAll(asList("d", "database"), Messages.getMessage("DATABASE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("database file"); - if (mm.equals(MODE.snpeff)) { + if (selectedModes.contains(MODE.snpeff)) { parser.accepts("config", Messages.getMessage("CONF_FILE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("config file"); parser.accepts("summaryFile", Messages.getMessage("SUMMARY_FILE_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("stat output"); } - if (mm.equals(MODE.trf)) + if (selectedModes.contains(MODE.trf)) parser.accepts("buffer", "check TRF region on both sides of indel within this nominated size").withRequiredArg().ofType(Integer.class);//.describedAs("integer"); - if (mm.equals(MODE.cadd)) + if (selectedModes.contains(MODE.cadd)) parser.accepts("gap", "adjacent variants size").withRequiredArg().ofType(String.class).describedAs("gap size"); - if (mm.equals(MODE.vcf2maf)) { + if (selectedModes.contains(MODE.vcf2maf)) { parser.accepts("outdir", Messages.getMessage("MAF_OUTPUT_DIRECTORY_OPTION_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("output file location"); parser.accepts("donor", Messages.getMessage("DONOR_ID_DESCRIPTION")).withRequiredArg().ofType(String.class).describedAs("donor id"); parser.accepts("center", "Genome sequencing center").withRequiredArg().ofType(String.class).describedAs("center"); @@ -338,6 +341,10 @@ public MODE getMode() { return mode; } + public List getModes() { + return modes; + } + private void displayHelp(MODE mode) throws IOException { String mess = Messages.getMessage("USAGE"); if (mode != null) { diff --git a/qannotate/test/au/edu/qimr/qannotate/MainTest.java b/qannotate/test/au/edu/qimr/qannotate/MainTest.java new file mode 100644 index 000000000..4d62d96ef --- /dev/null +++ b/qannotate/test/au/edu/qimr/qannotate/MainTest.java @@ -0,0 +1,26 @@ +package au.edu.qimr.qannotate; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +public class MainTest { + + @Test + public void rewriteArgsForModeReplacesModeAndIo() { + String[] args = {"--mode", "dbsnp", "--mode", "germline", "-i", "in.vcf", "-o", "out.vcf", "-d", "db.vcf"}; + String[] rewritten = Main.rewriteArgsForMode(args, Options.MODE.germline, "tmpIn.vcf", "tmpOut.vcf", Collections.singletonList("db2.vcf")); + assertArrayEquals(new String[]{"--mode", "germline", "-i", "tmpIn.vcf", "-o", "tmpOut.vcf", "-d", "db2.vcf"}, rewritten); + } + + @Test + public void extractDatabaseArgsInOrder() { + String[] args = {"--mode", "dbsnp", "-d", "db1.vcf", "--database=db2.vcf", "-d=db3.vcf"}; + List dbs = Main.extractDatabaseArgs(args); + assertEquals(List.of("db1.vcf", "db2.vcf", "db3.vcf"), dbs); + } +} diff --git a/qannotate/test/au/edu/qimr/qannotate/OptionsTest.java b/qannotate/test/au/edu/qimr/qannotate/OptionsTest.java index 1afc3dda2..8df6f1628 100644 --- a/qannotate/test/au/edu/qimr/qannotate/OptionsTest.java +++ b/qannotate/test/au/edu/qimr/qannotate/OptionsTest.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.Optional; import org.junit.Rule; @@ -82,4 +83,14 @@ public void homReportWindow() throws IOException { assertEquals(Optional.of(42), o.getHomoplymersReportWindow()); } + @Test + public void multipleModes() throws IOException { + File i = testFolder.newFile(); + File o = testFolder.newFile(); + File d = testFolder.newFile(); + Options options = new Options(new String[]{"--mode", "dbsnp", "--mode", "germline", "-i", i.getAbsolutePath(), "-o", o.getAbsolutePath(), "-d", d.getAbsolutePath()}); + assertEquals(Arrays.asList(Options.MODE.dbsnp, Options.MODE.germline), options.getModes()); + assertEquals(Options.MODE.dbsnp, options.getMode()); + } + }