Merge branch 'develop' into features/r2_11_2_alphafold/JAL-629
authorBen Soares <b.soares@dundee.ac.uk>
Wed, 17 May 2023 10:19:49 +0000 (11:19 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Wed, 17 May 2023 10:19:49 +0000 (11:19 +0100)
131 files changed:
.gitignore
build.gradle
examples/argfiles/test_fab41-B.txt [new file with mode: 0644]
examples/argfiles/test_fab41-autocounter.txt [new file with mode: 0644]
examples/argfiles/test_fab41.txt [new file with mode: 0644]
examples/test_fab41.result/argfile.txt [new file with mode: 0644]
help/help/help.jhm
help/help/helpTOC.xml
help/help/html/colourSchemes/index.html
help/help/html/features/clarguments-advanced.html [new file with mode: 0644]
help/help/html/features/clarguments-argfiles.html [new file with mode: 0644]
help/help/html/features/clarguments-basic.html [new file with mode: 0644]
help/help/html/features/clarguments-intro.html [new file with mode: 0644]
help/help/html/features/clarguments-old.html [new file with mode: 0644]
help/help/html/features/clarguments.html
help/help/html/features/commandline.html
help/help/html/menus/alignmentMenu.html
help/help/html/menus/alwcolour.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/bin/ArgParser.java [deleted file]
src/jalview/bin/Cache.java
src/jalview/bin/Commands.java
src/jalview/bin/Console.java
src/jalview/bin/Jalview.java
src/jalview/bin/Launcher.java
src/jalview/bin/argparser/Arg.java [new file with mode: 0644]
src/jalview/bin/argparser/ArgParser.java [new file with mode: 0644]
src/jalview/bin/argparser/ArgValue.java [new file with mode: 0644]
src/jalview/bin/argparser/ArgValues.java [new file with mode: 0644]
src/jalview/bin/argparser/ArgValuesMap.java [new file with mode: 0644]
src/jalview/bin/argparser/BootstrapArgs.java [new file with mode: 0644]
src/jalview/bin/argparser/SubVals.java [new file with mode: 0644]
src/jalview/datamodel/ContactMatrix.java
src/jalview/datamodel/ContactMatrixI.java
src/jalview/datamodel/SeqDistanceContactMatrix.java
src/jalview/datamodel/annotations/AnnotationRowBuilder.java
src/jalview/ext/jmol/JmolParser.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/AssociatePdbFileWithSeq.java
src/jalview/gui/Desktop.java
src/jalview/gui/ImageExporter.java
src/jalview/gui/LineartOptions.java
src/jalview/gui/Preferences.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureViewer.java
src/jalview/io/AlignFile.java
src/jalview/io/BackupFiles.java
src/jalview/io/BioJsHTMLOutput.java
src/jalview/io/FileFormat.java
src/jalview/io/FileLoader.java
src/jalview/io/HTMLOutput.java
src/jalview/io/HtmlSvgOutput.java
src/jalview/io/IdentifyFile.java
src/jalview/io/NewickFile.java
src/jalview/io/StructureFile.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GPreferences.java
src/jalview/log/JLogger.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/schemes/ColourSchemes.java
src/jalview/schemes/JalviewColourScheme.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/util/AWTConsole.java
src/jalview/util/ArrayUtils.java
src/jalview/util/FileUtils.java [new file with mode: 0644]
src/jalview/util/ImageMaker.java
src/jalview/util/StringUtils.java
src/jalview/ws/datamodel/alphafold/PAEContactMatrix.java
src/jalview/ws/dbsources/EBIAlfaFold.java
src/mc_view/PDBfile.java
test/jalview/bin/ArgsParserTest.java
test/jalview/bin/CommandLineOperations.java
test/jalview/bin/CommandLineOperationsNG.java [new file with mode: 0644]
test/jalview/bin/CommandsTest.java [new file with mode: 0644]
test/jalview/bin/CommandsTest2.java [new file with mode: 0644]
test/jalview/bin/HiDPISettingTest1.java
test/jalview/bin/argparser/ArgParserTest.java [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/argfile.autocounter [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/argfile0.txt [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/argfile1.txt [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/argfile2.txt [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir1/argfile.txt [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir1/test1.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir1/test2.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir2/argfile.txt [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir2/test1.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir2/test2.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir2/test3.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir3/subdir/subdirfile.txt [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir3/subdir/test0.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir3/subdir/test1.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir3/subdir/test2.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/dir3/subdir/test3.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/test1.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/test2.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/test3.fa [new file with mode: 0644]
test/jalview/bin/argparser/testfiles/testProps.jvprops [new file with mode: 0644]
test/jalview/bin/commandsTest.jvprops [new file with mode: 0644]
test/jalview/bin/commandsTest2.argfile1 [new file with mode: 0644]
test/jalview/bin/commandsTest2.argfile2 [new file with mode: 0644]
test/jalview/bin/commandsTest2.jvprops1 [new file with mode: 0644]
test/jalview/bin/commandsTest2.jvprops2 [new file with mode: 0644]
test/jalview/ext/jmol/JmolViewerTest.java
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/FeatureSettingsTest.java
test/jalview/gui/FreeUpMemoryTest.java
test/jalview/gui/QuitHandlerTest.java
test/jalview/gui/SeqPanelTest.java
test/jalview/gui/StructureChooserTest.java
test/jalview/io/AnnotatedPDBFileInputTest.java
test/jalview/io/BackupFilesTest.java
test/jalview/io/CrossRef2xmlTests.java
test/jalview/io/Jalview2xmlBase.java
test/jalview/io/JalviewExportPropertiesTests.java
test/jalview/io/uniref50-relaxed.phy [new file with mode: 0644]
test/jalview/io/uniref50-strict.phy [new file with mode: 0644]
test/jalview/io/uniref50.phy [new file with mode: 0644]
test/jalview/project/Jalview2xmlTests.java
test/jalview/renderer/seqfeatures/FeatureRendererTest.java
test/jalview/schemes/ColourSchemesTest.java
test/jalview/structure/StructureSelectionManagerTest.java
test/jalview/testProps.jvprops
test/jalview/util/FileUtilsTest.java [new file with mode: 0644]
test/jalview/ws/dbsources/EBIAlphaFoldTest.java
utils/eclipse/jalview
utils/eclipse/launcher [new file with mode: 0755]

index 616d27d..59c4a99 100644 (file)
@@ -34,6 +34,7 @@ TESTNG
 /utils/install4j/jalview-installers-*.install4j
 /utils/install4j/jalview-install4j-conf.install4j
 *.swp
+*.kate-swp
 /bin
 /.j2s
 /template.html
index 87ca397..6a622c3 100644 (file)
@@ -50,7 +50,7 @@ plugins {
   id "com.diffplug.gradle.spotless" version "3.28.0"
   id 'com.github.johnrengelman.shadow' version '4.0.3'
   id 'com.install4j.gradle' version '10.0.3'
-  id 'com.dorongold.task-tree' version '2.1.0' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
+  id 'com.dorongold.task-tree' version '2.1.1' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
   id 'com.palantir.git-version' version '0.13.0' apply false
 }
 
@@ -1064,7 +1064,7 @@ compileJava {
   // JBP->BS should the print statement in doFirst refer to compile_target_compatibility ?
   sourceCompatibility = compile_source_compatibility
   targetCompatibility = compile_target_compatibility
-  options.compilerArgs = additional_compiler_args
+  options.compilerArgs += additional_compiler_args
   options.encoding = "UTF-8"
   doFirst {
     print ("Setting target compatibility to "+compile_target_compatibility+"\n")
@@ -1076,7 +1076,7 @@ compileJava {
 compileTestJava {
   sourceCompatibility = compile_source_compatibility
   targetCompatibility = compile_target_compatibility
-  options.compilerArgs = additional_compiler_args
+  options.compilerArgs += additional_compiler_args
   doFirst {
     print ("Setting target compatibility to "+targetCompatibility+"\n")
   }
@@ -1732,26 +1732,113 @@ task prepare {
 
 compileJava.dependsOn prepare
 run.dependsOn compileJava
-//run.dependsOn prepare
+compileTestJava.dependsOn compileJava
+
 
 
-//testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
 test {
-  dependsOn prepare
+  group = "Verification"
+  description = "Runs all testTaskN tasks)"
 
   if (useClover) {
     dependsOn cloverClasses
-   } else { //?
-    dependsOn compileJava //?
+  } else { //?
+    dependsOn testClasses
+  }
+
+  // not running tests in this task
+  exclude "**/*"
+}
+/* testTask0 is the main test task */
+task testTask0(type: Test) {
+  group = "Verification"
+  description = "The main test task. Runs all non-testTaskN-labelled tests (unless excluded)"
+  useTestNG() {
+    includeGroups testng_groups.split(",")
+    excludeGroups testng_excluded_groups.split(",")
+    tasks.withType(Test).matching {it.name.startsWith("testTask") && it.name != name}.all {t -> excludeGroups t.name}
+    preserveOrder true
+    useDefaultListeners=true
+  }
+}
+
+/* separated tests */
+task testTask1(type: Test) {
+  group = "Verification"
+  description = "Tests that need to be isolated from the main test run"
+  useTestNG() {
+    includeGroups name
+    excludeGroups testng_excluded_groups.split(",")
+    preserveOrder true
+    useDefaultListeners=true
   }
+}
 
+/* insert more testTaskNs here -- change N to next digit or other string */
+/*
+task testTaskN(type: Test) {
+  group = "Verification"
+  description = "Tests that need to be isolated from the main test run"
   useTestNG() {
-    includeGroups testng_groups
-    excludeGroups testng_excluded_groups
+    includeGroups name
+    excludeGroups testng_excluded_groups.split(",")
     preserveOrder true
     useDefaultListeners=true
   }
+}
+*/
+
+/*
+ * adapted from https://medium.com/@wasyl/pretty-tests-summary-in-gradle-744804dd676c
+ * to summarise test results from all Test tasks
+ */
+/* START of test tasks results summary */
+import groovy.time.TimeCategory
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+rootProject.ext.testsResults = [] // Container for tests summaries
+
+tasks.withType(Test).matching {t -> t.getName().startsWith("testTask")}.all { testTask ->
+
+  // from original test task
+  if (useClover) {
+    dependsOn cloverClasses
+  } else { //?
+    dependsOn testClasses //?
+  }
+
+  // run main tests first
+  if (!testTask.name.equals("testTask0"))
+    testTask.mustRunAfter "testTask0"
+
+  testTask.testLogging { logging ->
+    events TestLogEvent.FAILED
+//      TestLogEvent.SKIPPED,
+//      TestLogEvent.STANDARD_OUT,
+//      TestLogEvent.STANDARD_ERROR
+
+    exceptionFormat TestExceptionFormat.FULL
+    showExceptions true
+    showCauses true
+    showStackTraces true
+
+    info.events = [ TestLogEvent.FAILED ]
+  }
+
+
+
+  ignoreFailures = true // Always try to run all tests for all modules
+
+  afterSuite { desc, result ->
+    if (desc.parent)
+      return // Only summarize results for whole modules
+
+    def resultsInfo = [testTask.project.name, testTask.name, result, TimeCategory.minus(new Date(result.endTime), new Date(result.startTime)), testTask.reports.html.entryPoint]
+
+    rootProject.ext.testsResults.add(resultsInfo)
+  }
 
+  // from original test task
   maxHeapSize = "1024m"
 
   workingDir = jalviewDir
@@ -1770,11 +1857,143 @@ test {
   jvmArgs += additional_compiler_args
 
   doFirst {
+    // this is not perfect yet -- we should only add the commandLineIncludePatterns to the
+    // testTasks that include the tests, and exclude all from the others.
+    // get --test argument
+    filter.commandLineIncludePatterns = test.filter.commandLineIncludePatterns
+    // do something with testTask.getCandidateClassFiles() to see if the test should silently finish because of the
+    // commandLineIncludePatterns not matching anything.  Instead we are doing setFailOnNoMatchingTests(false) below
+
+
     if (useClover) {
       println("Running tests " + (useClover?"WITH":"WITHOUT") + " clover")
     }
   }
+
+
+  /* don't fail on no matching tests (so --tests will run across all testTasks) */
+  testTask.filter.setFailOnNoMatchingTests(false)
+
+  /* ensure the "test" task dependsOn all the testTasks */
+  test.dependsOn testTask
+}
+
+gradle.buildFinished {
+    def allResults = rootProject.ext.testsResults
+
+    if (!allResults.isEmpty()) {
+        printResults allResults
+        allResults.each {r ->
+          if (r[2].resultType == TestResult.ResultType.FAILURE)
+            throw new GradleException("Failed tests!")
+        }
+    }
+}
+
+private static String colString(styler, col, colour, text) {
+  return col?"${styler[colour](text)}":text
+}
+
+private static String getSummaryLine(s, pn, tn, rt, rc, rs, rf, rsk, t, col) {
+  def colour = 'black'
+  def text = rt
+  def nocol = false
+  if (rc == 0) {
+    text = "-----"
+    nocol = true
+  } else {
+    switch(rt) {
+      case TestResult.ResultType.SUCCESS:
+        colour = 'green'
+        break;
+      case TestResult.ResultType.FAILURE:
+        colour = 'red'
+        break;
+      default:
+        nocol = true
+        break;
+    }
+  }
+  StringBuilder sb = new StringBuilder()
+  sb.append("${pn}")
+  if (tn != null)
+    sb.append(":${tn}")
+  sb.append(" results: ")
+  sb.append(colString(s, col && !nocol, colour, text))
+  sb.append(" (")
+  sb.append("${rc} tests, ")
+  sb.append(colString(s, col && rs > 0, 'green', rs))
+  sb.append(" successes, ")
+  sb.append(colString(s, col && rf > 0, 'red', rf))
+  sb.append(" failures, ")
+  sb.append("${rsk} skipped) in ${t}")
+  return sb.toString()
+}
+
+private static void printResults(allResults) {
+
+    // styler from https://stackoverflow.com/a/56139852
+    def styler = 'black red green yellow blue magenta cyan white'.split().toList().withIndex(30).collectEntries { key, val -> [(key) : { "\033[${val}m${it}\033[0m" }] }
+
+    def maxLength = 0
+    def failedTests = false
+    def summaryLines = []
+    def totalcount = 0
+    def totalsuccess = 0
+    def totalfail = 0
+    def totalskip = 0
+    def totaltime = TimeCategory.getSeconds(0)
+    // sort on project name then task name
+    allResults.sort {a, b -> a[0] == b[0]? a[1]<=>b[1]:a[0] <=> b[0]}.each {
+      def projectName = it[0]
+      def taskName = it[1]
+      def result = it[2]
+      def time = it[3]
+      def report = it[4]
+      def summaryCol = getSummaryLine(styler, projectName, taskName, result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount, time, true)
+      def summaryPlain = getSummaryLine(styler, projectName, taskName, result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount, time, false)
+      def reportLine = "Report file: ${report}"
+      def ls = summaryPlain.length()
+      def lr = reportLine.length()
+      def m = [ls, lr].max()
+      if (m > maxLength)
+        maxLength = m
+      def info = [ls, summaryCol, reportLine]
+      summaryLines.add(info)
+      failedTests |= result.resultType == TestResult.ResultType.FAILURE
+      totalcount += result.testCount
+      totalsuccess += result.successfulTestCount
+      totalfail += result.failedTestCount
+      totalskip += result.skippedTestCount
+      totaltime += time
+    }
+    def totalSummaryCol = getSummaryLine(styler, "OVERALL", "", failedTests?TestResult.ResultType.FAILURE:TestResult.ResultType.SUCCESS, totalcount, totalsuccess, totalfail, totalskip, totaltime, true)
+    def totalSummaryPlain = getSummaryLine(styler, "OVERALL", "", failedTests?TestResult.ResultType.FAILURE:TestResult.ResultType.SUCCESS, totalcount, totalsuccess, totalfail, totalskip, totaltime, false)
+    def tls = totalSummaryPlain.length()
+    if (tls > maxLength)
+      maxLength = tls
+    def info = [tls, totalSummaryCol, null]
+    summaryLines.add(info)
+
+    def allSummaries = []
+    for(sInfo : summaryLines) {
+      def ls = sInfo[0]
+      def summary = sInfo[1]
+      def report = sInfo[2]
+
+      StringBuilder sb = new StringBuilder()
+      sb.append("│" + summary + " " * (maxLength - ls) + "│")
+      if (report != null) {
+        sb.append("\n│" + report + " " * (maxLength - report.length()) + "│")
+      }
+      allSummaries += sb.toString()
+    }
+
+    println "┌${"${"─" * maxLength}"}┐"
+    println allSummaries.join("\n├${"${"─" * maxLength}"}┤\n")
+    println "└${"${"─" * maxLength}"}┘"
 }
+/* END of test tasks results summary */
 
 
 task compileLinkCheck(type: JavaCompile) {
@@ -3986,3 +4205,4 @@ task jalviewjs {
   description "Build the site"
   dependsOn jalviewjsBuildSite
 }
+
diff --git a/examples/argfiles/test_fab41-B.txt b/examples/argfiles/test_fab41-B.txt
new file mode 100644 (file)
index 0000000..4bf0b31
--- /dev/null
@@ -0,0 +1,4 @@
+--open[B]=./examples/uniref50.fa
+--colour[B]=clustal
+--image[B]=outputB.html
+--close[B]
diff --git a/examples/argfiles/test_fab41-autocounter.txt b/examples/argfiles/test_fab41-autocounter.txt
new file mode 100644 (file)
index 0000000..9981286
--- /dev/null
@@ -0,0 +1,14 @@
+--open[{++n}]=./examples/test_fab41.result/sample.a2m
+--colour[{n}]=clustal
+--structure[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3.pdb
+--paematrix[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3_scores.json
+--structure[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_2_model_4.pdb
+--paematrix[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_2_model_4_scores.json
+--structure[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_3_model_2.pdb
+--paematrix[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_3_model_2_scores.json
+--structure[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_4_model_5.pdb
+--paematrix[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_4_model_5_scores.json
+--structure[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_5_model_1.pdb
+--paematrix[{n}]=./examples/test_fab41.result/test_fab41_unrelaxed_rank_5_model_1_scores.json
+--image[{n}]=output1.html
+--close
diff --git a/examples/argfiles/test_fab41.txt b/examples/argfiles/test_fab41.txt
new file mode 100644 (file)
index 0000000..e6f7627
--- /dev/null
@@ -0,0 +1,13 @@
+--open=./examples/test_fab41.result/sample.a2m
+--colour=gecos:flower
+--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3.pdb
+--paematrix=[label=pAE R1-M3]./examples/test_fab41.result/test_fab41_unrelaxed_rank_1_model_3_scores.json
+--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_2_model_4.pdb
+--paematrix=[label=pAE R2-M4]./examples/test_fab41.result/test_fab41_unrelaxed_rank_2_model_4_scores.json
+--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_3_model_2.pdb
+--paematrix=[label=pAE R3-M2]./examples/test_fab41.result/test_fab41_unrelaxed_rank_3_model_2_scores.json
+--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_4_model_5.pdb
+--paematrix=[label=pAE R4-M5]./examples/test_fab41.result/test_fab41_unrelaxed_rank_4_model_5_scores.json
+--structure=./examples/test_fab41.result/test_fab41_unrelaxed_rank_5_model_1.pdb
+--paematrix=[label=pAE R5-M1]./examples/test_fab41.result/test_fab41_unrelaxed_rank_5_model_1_scores.json
+--image=output1.html
diff --git a/examples/test_fab41.result/argfile.txt b/examples/test_fab41.result/argfile.txt
new file mode 100644 (file)
index 0000000..52d68ee
--- /dev/null
@@ -0,0 +1,24 @@
+--debug
+--nonews
+--nosplash
+--noannotation
+--substitutions
+--open={argfiledirname}/sample.a2m
+--colour=gecos:flower
+--structure={dirname}/test_fab41_unrelaxed_rank_1_model_3.pdb
+--paematrix={dirname}/test_fab41_unrelaxed_rank_1_model_3_scores.json
+--tempfac=plddt
+--structure={dirname}/test_fab41_unrelaxed_rank_2_model_4.pdb
+--paematrix={dirname}/test_fab41_unrelaxed_rank_2_model_4_scores.json
+--tempfac=plddt
+--structure={dirname}/test_fab41_unrelaxed_rank_3_model_2.pdb
+--paematrix={dirname}/test_fab41_unrelaxed_rank_3_model_2_scores.json
+--tempfac=plddt
+--structure={dirname}/test_fab41_unrelaxed_rank_4_model_5.pdb
+--paematrix={dirname}/test_fab41_unrelaxed_rank_4_model_5_scores.json
+--tempfac=plddt
+--structure={dirname}/test_fab41_unrelaxed_rank_5_model_1.pdb
+--tempfac=plddt
+--paematrix={dirname}/test_fab41_unrelaxed_rank_5_model_1_scores.json
+--image={dirname}/{basename}.html
+#--headless
index cd415ea..8e609a6 100755 (executable)
    <mapID target="commandline" url="html/features/commandline.html"/>
    <mapID target="jalviewcltool" url="html/features/commandline.html#script"/>
    <mapID target="clarguments" url="html/features/clarguments.html"/>
+   <mapID target="clarguments-intro" url="html/features/clarguments-intro.html"/>
+   <mapID target="clarguments-basic" url="html/features/clarguments-basic.html"/>
+   <mapID target="clarguments-advanced" url="html/features/clarguments-advanced.html"/>
+   <mapID target="clarguments-argfiles" url="html/features/clarguments-argfiles.html"/>
    <mapID target="jvlfiles" url="html/features/jvlfiles.html"/>
    <mapID target="io" url="html/io/index.html"/>
    <mapID target="io.modellerpir" url="html/io/modellerpir.html"/>
index 877c758..1cdf4d3 100755 (executable)
@@ -29,7 +29,7 @@
                                <tocitem text="Structures via 3D Beacons" target="structuresvia3dbeacons" />
                                <tocitem text="Startup Memory Settings" target="startupprefs" />
                        <tocitem text="The Java Console, Logging and Reporting Bugs" target="logging" />
-                       <tocitem text="Command line launch scripts" target="jalviewcltool"/>
+                       <tocitem text="Command line launch" target="jalviewcltool"/>
                </tocitem>
                
                <tocitem text="Editing Alignments" target="edit" />
                        <tocitem text="Groovy Features Counter example" target="groovy.featurescounter"/>
                </tocitem>
                <tocitem text="Command Line" target="commandline" expand="false">
-                       <tocitem text="Command Line Arguments" target="clarguments" />
-    <tocitem text="Memory Settings" target="memory" expand="false"/>
-    <tocitem text="Jalview Launch Files" target="jvlfiles" expand="false"/>
+                       <tocitem text="Command Line: summary" target="clarguments" />
+                       <tocitem text="Command Line: introduction" target="clarguments-intro" />
+                       <tocitem text="Command Line: basic usage" target="clarguments-basic" />
+                       <tocitem text="Command Line: advanced usage" target="clarguments-advanced" />
+                       <tocitem text="Command Line: argument files" target="clarguments-argfiles" />
+               </tocitem>
+               <tocitem text="Memory Settings" target="memory" expand="false"/>
+               <tocitem text="Jalview Launch Files" target="jvlfiles" expand="false"/>
     </tocitem>
-               <tocitem text="Privacy" target="privacy" />
-       </tocitem>
+       <tocitem text="Privacy" target="privacy" />
        <tocitem text="Useful information" expand="true">
                <tocitem text="Amino Acid Table" target="aminoAcids" />
                <tocitem text="Amino Acid Properties" target="aaProperties" />
index 9ba0578..6b77d91 100755 (executable)
@@ -94,7 +94,8 @@ td {
                                <td>
                                        <table border="1">
                                                <tr>
-                                                       <td nowrap></td>
+                                                       <td nowrap>Name</td>
+                                                       <td nowrap>CLI name</td>
                                                        <td>A</td>
                                                        <td>R</td>
                                                        <td>N</td>
@@ -121,6 +122,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td height="24">Clustal</td>
+                                                       <td>clustal</td>
                                                        <td bgcolor="#80a0f0"></td>
                                                        <td bgcolor="#f01505"></td>
                                                        <td bgcolor="#00ff00"></td>
@@ -147,6 +149,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td height="24">Zappo</td>
+                                                       <td>zappo</td>
                                                        <td bgcolor="#ffafaf"></td>
                                                        <td bgcolor="#6464ff"></td>
                                                        <td bgcolor="#00ff00"></td>
@@ -173,6 +176,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>Taylor</td>
+                                                       <td>taylor</td>
                                                        <td bgcolor="#ccff00"></td>
                                                        <td bgcolor="#0000ff"></td>
                                                        <td bgcolor="#cc00ff"></td>
@@ -199,7 +203,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>gecos Flower</td>
-
+                                                       <td>gecos-flower</td>
                                                        <td bgcolor="#b18a51"></td>
                                                        <td bgcolor="#83bff1"></td>
                                                        <td bgcolor="#0bcec6"></td>
@@ -226,6 +230,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>gecos Blossom</td>
+                                                       <td>gecos-blossom</td>
                                                        <td bgcolor="#8bc4b4"></td>
                                                        <td bgcolor="#fc9502"></td>
                                                        <td bgcolor="#b5c206"></td>
@@ -252,6 +257,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>gecos Sunset</td>
+                                                       <td>gecos-sunset</td>
                                                        <td bgcolor="#fea0fd"></td>
                                                        <td bgcolor="#85746a"></td>
                                                        <td bgcolor="#abc8f5"></td>
@@ -278,6 +284,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>gecos Ocean</td>
+                                                       <td>gecos-ocean</td>
                                                        <td bgcolor="#c6ca9b"></td>
                                                        <td bgcolor="#0ca0a8"></td>
                                                        <td bgcolor="#0adfc3"></td>
@@ -304,6 +311,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>Hydrophobicity</td>
+                                                       <td>hydrophobicity</td>
                                                        <td bgcolor="#ad0052"></td>
                                                        <td bgcolor="#0000ff"></td>
                                                        <td bgcolor="#0c00f3"></td>
@@ -330,6 +338,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>Helix Propensity</td>
+                                                       <td>helix-propensity</td>
                                                        <td bgcolor="#e718e7"></td>
                                                        <td bgcolor="#6f906f"></td>
                                                        <td bgcolor="#1be41b"></td>
@@ -356,6 +365,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td nowrap>Strand Propensity</td>
+                                                       <td>strand-propensity</td>
                                                        <td bgcolor="#5858a7"></td>
                                                        <td bgcolor="#6b6b94"></td>
                                                        <td bgcolor="#64649b"></td>
@@ -382,6 +392,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>Turn Propensity</td>
+                                                       <td>turn-propensity</td>
                                                        <td bgcolor="#2cd3d3"></td>
                                                        <td bgcolor="#708f8f"></td>
                                                        <td bgcolor="#ff0000"></td>
@@ -408,6 +419,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td>Buried Index</td>
+                                                       <td>buried-index</td>
                                                        <td bgcolor="#00a35c"></td>
                                                        <td bgcolor="#00fc03"></td>
                                                        <td bgcolor="#00eb14"></td>
@@ -445,7 +457,8 @@ td {
                                <td>
                                        <table border="1">
                                                <tr>
-                                                       <td nowrap></td>
+                                                       <td nowrap>Name</td>
+                                                       <td nowrap>CLI name</td>
                                                        <td>A</td>
                                                        <!--Adenine-->
                                                        <td>C</td>
@@ -485,6 +498,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td height="24">Nucleotide</td>
+                                                       <td>nucleotide</td>
                                                        <td bgcolor="#64F73F"></td>
                                                        <td bgcolor="#FFB340"></td>
                                                        <td bgcolor="#EB413C"></td>
@@ -506,6 +520,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td height="24">Nucleotide Ambiguity</td>
+                                                       <td>nucleotide-ambiguity</td>
                                                        <td bgcolor="#f0fff0"></td>
                                                        <td bgcolor="#f0fff0"></td>
                                                        <td bgcolor="#f0fff0"></td>
@@ -527,6 +542,7 @@ td {
                                                </tr>
                                                <tr>
                                                        <td height="24">Purine/Pyrimidine</td>
+                                                       <td>purine-pyrimidine</td>
                                                        <td bgcolor="#FF83FA"></td>
                                                        <td bgcolor="#40E0D0"></td>
                                                        <td bgcolor="#FF83FA"></td>
diff --git a/help/help/html/features/clarguments-advanced.html b/help/help/html/features/clarguments-advanced.html
new file mode 100644 (file)
index 0000000..db818d8
--- /dev/null
@@ -0,0 +1,183 @@
+<html>
+<!--
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ -->
+<title>Command Line: advanced usage</title>
+<body>
+
+  <h1>Command Line: advanced usage</h1>
+
+  <p>
+  <a href="clarguments.html">Command Line: summary</a>
+  <br/>
+  <a href="clarguments-intro.html">Command Line: introduction</a>
+  <br/>
+  <a href="clarguments-basic.html">Command Line: basic usage</a>
+  <br/>
+  Command Line: advanced usage
+  <br/>
+  <a href="clarguments-argfiles.html">Command Line: argument files</a>
+  </p>
+
+  <hr/>
+
+  <ul>
+  <li><a href="#alignmentlinkedids">Alignment linked IDs</a></li>
+  <li><a href="#moresubstitutions">More substitutions</a></li>
+  <li><a href="#equalsseparatorandfileglobs">Equals separator and file globs</a></li>
+  <li><a href="#subvaluemodifiers">Sub-value modifiers</a></li>
+  </ul>
+
+
+  <h2><a name="alignmentlinkedids"></a>Alignment linked IDs</h2>
+
+  <p>
+  Jalview's alignment related arguments are linked together using a <em>linked ID</em>.  In all of the basic usage examples this linked ID is assigned using a default formula of <code>JALVIEW:<em>n</em></code> where <em>n</em> starts at 0 and increments every time there is an <code>--open</code>ed file (or a first use of <code>--append</code>) or the <code>--new</code> argument is used.
+  </p>
+
+  <p>
+  When another alignment related argument is used (without a specified linked ID), it is assigned this default linked ID.  When the <code>--all</code> argument is used, following alignment related arguments are applied to all of the linked IDs (made so far).
+  </p>
+
+  <p>
+  You can assign your own linked IDs to alignments that are opened from the command-line.  To do this put the linked ID in square brackets ('[', ']') <em>immediately following the argument</em> (i.e. before any space or equals sign) like this:
+  <pre>
+  jalview --open[myId1] file1.fa --open[myId2] file2.fa --open[myId3] file3.fa --colour[myId1] gecos-flower --colour[myId2] gecos-blossom --colour[myId3] zappo --image "*.png" --headless
+  </pre>
+
+  <p>
+  As you can see, if you specify your own linked IDs, the arguments for one alignment do not need to be adjacent as they are when using the default assigned linked ID.
+  </p>
+
+  <p>
+  A specified linked ID will also take precedence over a wildcard or <code>--all</code> set linked ID. e.g.
+  <pre>
+  jalview --open[myID] examples/uniref50.fa --all --colour taylor --colour[myID] gecos-blossom
+  </pre>
+  will open the alignment using the <code>gecos-blossom</code> colour scheme (thank goodness).
+  </p>
+
+
+  <h2><a name="moresubstitutions"></a>More substitutions (<code>{n}</code>, <code>{++n}</code>, <code>[*]</code>)</h2>
+
+  <p>
+  In the <a href="clarguments-basic.html#substitutions">basic usage document</a> we have a list of special strings that get replaced in output filename values with parts of input filename values.
+  </p>
+
+  <p>
+  There is also an incrementor integer value <code>{n}</code> that can be put into both linked IDs and filenames and works across different linked IDs.  Whenever you use <code>{n}</code> in a linked ID or filename value it is replaced with the current value of <em>n</em>.  The initial value is 0, and it can be incremented by using the argument <code>--npp</code> or <code>--n++</code>, or using a another special substitution <code>{++n}</code> in either a linked ID or filename value which increments the value and is replaced with the new incremented value of <em>n</em>.
+  </p>
+
+  <p>
+  In the same way that the <code>--all</code> argument enables (some) following arguments to apply to all opened alignments so far, the special linked ID <code>*</code> will also apply an individual argument to all opened linked IDs (in fact when you use the <code>--all</code> argument it simply changes the default linked ID to <code>*</code>).
+  <pre>
+  jalview --open[myId1] fileA.fa --open[myId2] fileB.fa --open[myId3] fileC.fa --colour[*] taylor --all --image tmp/image-{++n}.png --headless
+  </pre>
+  The above will produce an image <code>image-0.png</code> for the alignment in <code>fileA.fa</code>,  an image <code>image-1.png</code> for the alignment in <code>fileB.fa</code>, etc.
+  If you suddenly decide to colour the last alignment differently you could add to the command:
+  <pre>
+  jalview --open[myId1] fileA.fa --open[myId2] fileB.fa --open[myId3] fileC.fa --colour[*] taylor --all --image tmp/image-{++n}.png --headless --colour[myId3] gecos-blossom
+  </pre>
+  because all of the command line arguments are read and sorted into their linked IDs before starting to be processed, and the <code>[myId3]</code> specified linked ID takes precedence over the <code>[*]</code> wildcard linked ID.
+  </p>
+  </p>
+
+
+  <h2><a name="equalsseparatorandfileglobs"></a>Equals separator and Java file globs</h2>
+
+  <p>
+  When a Command-line argument requires one (or more) values, you can use the usual space separation, which allows the shell to expand any file "globs" (that is, filenames with wildcard characters allowing it to represent multiple files).  The shell "expands" this file glob and to Jalview it appears like a list of filenames supplied to the argument.
+  </p>
+
+  <p>
+  There is a limitation to shell expansion of file globs, as the length of a single command, or number of files the shell is willing to expand a file glob to, is limited (to different amounts depending on your shell and its configuration).
+  </p>
+
+  <p>
+  Jalview will also recognise values supplied to arguments (that expect one or more value) that are separated by an equals sign ("=").  There should be no spaces around the equals sign, e.g.
+  <pre>
+  jalview --open=examples/uniref50*.fa --colour=clustal --all --image=*.png --scale=2.5 --headless
+  </pre>
+  </p>
+
+  <p>
+  One benefit of this is seen above in the <code>--image</code> argument, the special "alloutput" wildcard filename <code>*.png</code> will not be expanded by the shell, so does not need to be escaped or surrounded with quotation marks.
+  </p>
+
+  <p>
+  A bigger benefit is that the <em>input</em> filename is not expanded by the shell but can instead be expanded by Jalview.  There is no limit to the number of files that this kind of file glob can match since it is expanded by Jalview, not the shell.
+  </p>
+
+  <p>
+  Jalview uses the Java PathMatcher "glob" scheme to match filenames, which is quite similar, though in some ways more powerful, to most shell glob rules.  The usual '<code>*</code>' and '<code>?</code>' characters act the same, but other combinations can be used too.  For the full rules see <a href="https://devdocs.io/openjdk~11/java.base/java/nio/file/filesystem#getPathMatcher(java.lang.String)">java.nio.file.PathMatcher.getPathMatcher</a> javadocs at https://devdocs.io/openjdk~11/java.base/java/nio/file/filesystem#getPathMatcher(java.lang.String).
+  </p>
+
+  <p>
+  One such PathMatcher addition to shell file globs is the use of <code>**</code> to match any number of directories and sub-directories (matching across file separator).  So to convert every Fasta file under the current working directory to BLC format you can do:
+  <pre>
+  jalview --open=./**/*.fa --output=*.blc --close --headless
+  </pre>
+  or to find every Stockholm and Pfam file under </code>/tmp</code> and convert to Fasta and save in your own folder, do
+  <pre>
+  jalview --open=/tmp/**/*.{stk,pfam} --all --output=~/rescued/{basenamedir}.fa --close --headless
+  </pre>
+  </p>
+
+  <h2><a name="subvaluemodifiers"></a>Sub-value modifiers</h2>
+
+  <p>
+  Sub-value modifiers are given in square brackets ('[', ']') <em>immediately before the value</em> (i.e. after any space or equals sign, and with no space between it and the value) like this:
+  <pre>
+  jalview --open=examples/uniref50.fa --image=[scale=1.414]picture.png
+  </pre>
+  </p>
+
+  <p>
+  Some arguments (such as <code>--scale=<em>n</em></code>) are used to modify the behaviour of other "primary" arguments (such as <code>--image=filename</code>).  These arguments can alternatively be specifed as <em>sub-value modifiers</em> of the values given to the primary argument.  If specified as a sub-value modifier, this modifier takes precedence over any following linked argument if given. e.g
+  <pre>
+  jalview --open=[colour=zappo]examples/*.fa
+  </pre>
+  Notice also that if a sub-value modifier is used for a value that is expanded by the application (i.e. using an equals sign ('=') to separate argument and value) then that modifier is applied to all of the values.
+  </p>
+
+  <p>
+  For shell expanded globs this is more problematic since the presence of a sub-value modifier will almost certainly prevent the shell from recognising the file glob, so
+  <pre>
+  jalview --open [colour=zappo]examples/*.fa
+  </pre>
+  will <em>NOT</em> work the same as above.  If you need to use sub-value modifiers on a file glob you should use an equals sign ('=') to separate it from the argument.
+  </p>
+
+  <p>
+  You can specify multiple sub-value modifiers separating them with a comma (',').  If you wish to specify a "boolean" argument, such as <code>--wrap</code> or <code>--nowrap</code> then simply use the argument name without a value, like this:
+  <pre>
+  jalview --open=[colour=gecos-flower,wrap,noannotations,features=examples/plantfdx.features]examples/plantfdx.fa
+  </pre>
+  </p>
+
+
+  <hr/>
+  Continue to <a href="clarguments-argfiles.html">Command Line: argument files</a>.
+  <br/>
+  Return to <a href="clarguments.html">Command Line: summary</a>.
+
+
+</body>
+</html>
diff --git a/help/help/html/features/clarguments-argfiles.html b/help/help/html/features/clarguments-argfiles.html
new file mode 100644 (file)
index 0000000..f4741ee
--- /dev/null
@@ -0,0 +1,168 @@
+<html>
+<!--
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ -->
+<title>Command Line: argument files</title>
+<body>
+
+  <h1>Command Line: argument files</h1>
+
+  <p>
+  <a href="clarguments.html">Command Line: summary</a>
+  <br/>
+  <a href="clarguments-intro.html">Command Line: introduction</a>
+  <br/>
+  <a href="clarguments-basic.html">Command Line: basic usage</a>
+  <br/>
+  <a href="clarguments-advanced.html">Command Line: advanced usage</a>
+  <br/>
+  Command Line: argument files
+  </p>
+
+  <hr/>
+
+  <ul>
+  <li><a href="#argumentfiles">Argument files</a></li>
+  <li><a href="#onlyargumentfiles">Only argument files</a></li>
+  <li><a href="#evenmoresubstitutions">Even more substitutions</a></li>
+  </ul>
+
+  <h2><a name="argumentfiles"></a>Argument files</h2>
+
+  <p>
+  If you want to save a set of arguments to reuse, you can save them in a text file, say <code>argfile.txt</code>, and read them into Jalview with
+  <pre>
+  jalview --argfile=argfile.txt
+  </pre>
+  </p>
+
+  <p>
+  The argument file has one argument and value per line, still using the double-dash ('--') before the argument name, and separating the argument and value with an equals sign ('=').
+  <br/>
+  Because the argument file is read by the application and not read by the shell, you do not need to escape any values -- all spaces will be read as part of the value until the end of the line.
+  <br/>
+  You can add comments to a line by starting the line with an octothorpe (hash, pound-sign '#').
+  <br/>
+  e.g.
+  <table border="1">
+  <tr>
+  <td>
+  File <code>argfile.txt</code>
+  </td>
+  </tr>
+  <tr>
+  <td>
+  <pre>
+--nonews
+--nosplash
+--open=[nowrap,colour=gecos-flower,showannotations]examples/plantfdx.fa
+--features=examples/plantfdx.features
+--annotations=examples/plantfdx.annotations
+--image=images/alignment.png
+--scale=2.5
+#--scale=10
+# let's see what's happening
+#--headless</pre>
+  </td>
+  </tr>
+  </table>
+  </p>
+
+  <p>
+  Because <code>--argfiles</code> takes a filename argument, and multiple <code>--argfiles</code> can be read on the command line, you can also use file globs to specify multiple <code>--argfile</code> values.  If you produce an argument file for each set of alignment files that you wish to associate then you can act on all of them with, e.g.
+  <pre>
+  jalview --argfile=*/argfile.txt --headless
+  </pre>
+  </p>
+
+  <p>
+  You can even read argument files from within argument files, e.g.
+  <pre>
+  jalview --argfile=argfile*.txt --headless
+  </pre>
+  <table border="1">
+  <tr><td>File <code>argfile1.txt</code></td></tr>
+  <tr><td><pre>
+--open=file1.fa
+--argfile=myfavouriteformattingargfile.txt
+--argfile=mysecondfavouriteimageargfile.txt</pre></td></tr>
+  <tr><td>File <code>myfavouriteformattingargfile.txt</code></td></tr>
+  <tr><td><pre>
+--wrap
+--showannotations
+--annotations={dirname}/{basename}.annots</pre></td></tr>
+  <tr><td>File <code>mysecondfavouriteimageargfile.txt</code></td></tr>
+  <tr><td><pre>
+--image=images/{basename}.png
+--width=1920
+--height=1080</pre></td></tr>
+  </table>
+  </p>
+
+  <p>
+  If a "loop" of argument files is detected then Jalview will refuse to play (which is a Good Thing).
+  </p>
+
+
+  <h2><a name="onlyargumentfiles"></a>Only argument files</h2>
+
+  <p>
+  When you use an <code>--argfile</code> argument, all other non-initialising arguments on the command line <em>will be ignored</em>.  Only the initialising arguments and any and all <code>--argfiles</code> arguments on the command line will be used.  You can also set initialising arguments in argument files.
+  </p>
+
+
+  <h2><a name="evenmoresubstitutions"></a>Even more substitutions</h2>
+
+  <p>
+  When adding values that can use substitutions within argument files, there are two additional substitutions that are made:
+  <br/>
+  <code>{argfilebasename}</code> - replaced with the base of the filename of the argument file (i.e. without directory path or file extension).
+  <br/>
+  <code>{argfiledirname}</code> - replaced with the path to the filename of the argument file.
+  </p>
+
+  <p>
+  Another amusing substitution you can make in argument files is the <code>{n}</code> substitution, combined with an <code>-npp</code> increment at the start (or end) of the argument file, which gives the potential of having multiple argument files with the exact same content, or even re-using the same argument file multiple times. e.g.
+
+  <table border="1">
+  <tr><td>File <code>alignment.argfile</code></td></tr>
+  <tr><td><pre>
+--open={argfilebasename}-{n}.fa
+--wrap
+--output={basename}.stk
+--close
+--npp</pre></td></tr>
+  </table>
+  <pre>
+  jalview --argfile alignment.argfile --argfile alignment.argfile --headless
+  </pre>
+  would be processed like
+  <pre>
+  jalview --open=alignment-0.fa --wrap --output=alignment-0.stk --close --open=alignment-1.fa --wrap --output=alignment-1.stk --close --headless
+  </pre>
+  </p>
+
+
+  <hr/>
+  Return to <a href="clarguments.html">Command Line: summary</a>.
+
+
+</body>
+</html>
diff --git a/help/help/html/features/clarguments-basic.html b/help/help/html/features/clarguments-basic.html
new file mode 100644 (file)
index 0000000..edee480
--- /dev/null
@@ -0,0 +1,662 @@
+<html>
+<!--
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ -->
+<title>Command Line: basic usage</title>
+<body>
+
+  <h1>Command Line: basic usage</h1>
+
+  <p>
+  <a href="clarguments.html">Command Line: summary</a>
+  <br/>
+  <a href="clarguments-intro.html">Command Line: introduction</a>
+  <br/>
+  Command Line: basic usage
+  <br/>
+  <a href="clarguments-advanced.html">Command Line: advanced usage</a>
+  <br/>
+  <a href="clarguments-argfiles.html">Command Line: argument files</a>
+  </p>
+
+  <hr/>
+
+  <ul>
+  <li><a href="#openingalignments">Opening alignments</a></li>
+  <li><a href="#alignmentoptions">Alignment options</a></li>
+  <li><a href="#adding3dstructures">Adding 3D structures</a></li>
+  <li><a href="#outputtingalignmentfiles">Outputting/converting alignment files and images</a></li>
+  <li><a href="#filenamesubstitutionsandbatchprocessing">Filename substitutions and batch processing</a></li>
+  <li><a href="#alloutputwildcard">The all output wildcard</a></li>
+  </ul>
+
+  <h2><a name="openingalignments"></a>Opening alignments (<code>--open</code>, <code>--append</code>, <code>--new</code>)</h2>
+
+  <p>
+  To simply open one or more alignment files in different windows just put the filenames as the first arguments:
+  <pre>
+  jalview filename1 filename2 ...
+  </pre>
+  </p>
+
+  <p>
+  You can use shell-expanded wildcards:
+  <pre>
+  jalview this/filename* that/filename* other/filename*
+  </pre>
+  and URLs:
+  <pre>
+  jalview https://rest.uniprot.org/uniprotkb/P00221.fasta
+  </pre>
+  </p>
+
+  <p>
+  (Using initial filenames is the same as using the <code>--open</code> argument, and further arguments can be used
+  after the initial filenames.)
+  </p>
+
+  <h3><a name="open"></a><code>--open</code></h3>
+
+  <p>
+  Use the <code>--open</code> argument to open alignment files each in their own window.
+  </p>
+
+  <p>
+  The following are equivalent:
+  <pre>
+  jalview --open filename1 filename2 ...
+
+  jalview --open filename*
+
+  jalview --open filename1 --open filename2 --open ...
+
+  jalview filename1 filename2 ...
+  </pre>
+  </p>
+
+  <p>
+  Similarly you can open URLs:
+  <pre>
+  jalview --open https://rest.uniprot.org/uniprotkb/P00221.fasta
+  </pre>
+  </p>
+
+  <h3><a name="append"></a><code>--append</code></h3>
+
+  <p>
+  To append several alignment files together use:
+  <pre>
+  jalview --open filename1.fa --append filename2.fa filename3.fa
+  </pre>
+  or, if you haven't previously used <code>--open</code> then you can use --append to open one new window and keep appending each set of alignments:
+  <pre>
+  jalview --append these/filename*.fa --append more/filename*.fa
+
+  jalview --append https://rest.uniprot.org/uniprotkb/P00221.fasta https://www.uniprot.org/uniprotkb/A0A0K9QVB3/entry
+  </pre>
+  </p>
+
+  <p>
+  <strong>Note</strong> that whilst you can include a Jalview Project File (<code>.jvp</code>) as an <code>--append</code> value, the items in the file will always open in their original windows and not append to another.
+  </p>
+
+  <h3><a name="new"></a><code>--new</code></h3>
+
+  <p>
+  To append different sets of alignment files in different windows, use <code>--new</code> to move on to a new alignment window:
+  <pre>
+  jalview --append these/filename*.fa --new --append other/filename*.fa
+  </pre>
+  </p>
+
+  <p>
+  <code>--open</code> is like using <code>--new --append</code> applied to every filename/URL given to <code>--open</code>
+  </p>
+
+
+  <h2><a name="alignmentoptions"></a>Alignment options (<code>--colour</code>, <code>--wrap</code>, <code>--showannotations</code>, <code>--title</code>)</h2>
+
+  <h3><a name="colour"></a><code>--colour</code></h3>
+
+  <p>
+  You can specify a residue/base colouring for the alignment using the <code>--colour</code> option (note spelling -- Jalview is made in Scotland!):
+  <pre>
+  jalview --open examples/uniref50.fa --colour gecos-flower
+  </pre>
+  There are several colour schemes that you can use.  See the <a href="../colourSchemes/index.html">page on Colour Schemes</a> for details.
+  The names to use on the command line for colour schemes are:
+  </p>
+  <p>
+  <code>clustal</code>,
+  <br/>
+  <code>blosum62</code>,
+  <br/>
+  <code>pc-identity</code>,
+  <br/>
+  <code>zappo</code>,
+  <br/>
+  <code>taylor</code>,
+  <br/>
+  <code>gecos-flower</code>,
+  <br/>
+  <code>gecos-blossom</code>,
+  <br/>
+  <code>gecos-sunset</code>,
+  <br/>
+  <code>gecos-ocean</code>,
+  <br/>
+  <code>hydrophobic</code>,
+  <br/>
+  <code>helix-propensity</code>,
+  <br/>
+  <code>strand-propensity</code>,
+  <br/>
+  <code>turn-propensity</code>,
+  <br/>
+  <code>buried-index</code>,
+  <br/>
+  <code>nucleotide</code>,
+  <br/>
+  <code>nucleotide-ambiguity</code>,
+  <br/>
+  <code>purine-pyrimidine</code>,
+  <br/>
+  <code>rna-helices</code>,
+  <br/>
+  <code>t-coffee-scores</code>,
+  <br/>
+  <code>sequence-id</code>
+  </p>
+
+  <h3><a name="wrap"></a><code>--wrap</code></h3>
+  <p>
+  An alignment should open with your usual preferences stored in the <code>.jalview_properties</code> file.  To open an alignment with the sequences (definitely) wrapped, following your <code>--open</code> (or first <code>--append</code>) argument use the argument <code>--wrap</code>:
+  <pre>
+  jalview --open examples/uniref50.fa --wrap
+  </pre>
+  To ensure an alignment is not wrapped use <code>--nowrap</code>:
+  <pre>
+  jalview --open examples/uniref50.fa --nowrap
+  </pre>
+  </p>
+
+  <h3><a name="showannotations"></a><code>--showannotations</code> / <code>--noshowannotations</code></h3>
+
+  <p>
+  You can specify whether the currently opened alignment window should show alignment annotations (e.g. Conservation, Quality, Consensus...) or not with either <code>--showannotations</code> or <code>--noshowannotations</code>.  If you don't specify then your saved preference will be used.
+  <pre>
+  jalview --open examples/uniref50.fa --noshowannotations
+  </pre>
+  </p>
+
+  <h3><a name="title"></a><code>--title</code></h3>
+
+  <p>
+  If you would like to give the alignment window a specific title you can do so with the <code>--title</code> option:
+  <pre>
+  jalview --open examples/uniref50.fa --title "My example alignment"
+  </pre>
+  </p>
+
+
+
+
+  <h2><a name="adding3dstructures"></a>Adding 3D structures (<code>--structure</code>, <code>--seqid</code>, <code>--structureviewer</code>, <code>--paematrix</code>, <code>--tempfac</code>, <code>--showssannotations</code>)</h2>
+
+  <p>
+  </p>
+
+  <h3><a name="structure"></a><code>--structure</code></h3>
+
+  <p>
+  You can add a 3D structure file to a sequence in the current alignment window with the <code>--structure</code> option:
+  <pre>
+  jalview --open examples/uniref50.fa --structure examples/AlphaFold/AF-P00221-F1-model_v4.pdb
+  </pre>
+  By default this attaches to the first sequence in the alignment but most likely you will want to attach it to a specific sequence.
+  </p>
+
+  <h3><a name="seqid"></a><code>--seqid</code></h3>
+
+  <p>
+  The easiest way to specify a sequence ID for your structure is to follow the <code>--structure</code> argument with a <code>--seqid</code> argument with a value of a sequence ID in the alignment.  This does of course require some knowledge of the sequences in the alignment files
+  that have been opened.
+  <br/>
+  Alternatively you can specify a <em>sub-value</em> with the <code>--structure</code> argument value.  You do this by preceding the value with square brackets and <code>seqid=SequenceId</code>,
+  like this:
+  <pre>
+  jalview --open examples/uniref50.fa --structure [seqid=FER1_SPIOL]examples/AlphaFold/AF-P00221-F1-model_v4.pdb
+  </pre>
+  which is equivalent to
+  <pre>
+  jalview --open examples/uniref50.fa --structure examples/AlphaFold/AF-P00221-F1-model_v4.pdb --seqid FER1_SPIOL
+  </pre>
+  </p>
+
+  <p>
+  The sub-value <code>seqid=FER1_SPIOL</code> takes precedence over the following argument <code>--seqid FER1_SPIOL</code> if you accidentally specify both (in which case the argument will probably be completely unused).
+  </p>
+
+  <p>
+  If you don't know the sequence IDs but do know the position of the sequence in the alignment, you can also specify an <em>INDEX</em>
+  in the sub-values to indicate which sequence in the alignment to attach the sequence to (although this is less precise).  This is a zero-indexed value, so to specify the eighth sequence in the alignment use:
+  <pre>
+  jalview --open examples/uniref50.fa --structure [7]examples/AlphaFold/AF-P00221-F1-model_v4.pdb
+  </pre>
+
+  <p>
+  Remember that you might need to escape any spaces in the sequence ID or enclose the ID in quotation marks.
+  </p>
+
+  <h3><a name="structureviewer"></a><code>--structureviewer</code></h3>
+
+  <p>
+  You can specify which structure viewer (or none) to use to open the structure using either the <code>--structureviewer</code> argument or the <code>structureviewer</code> sub-value.  Multiple sub-values can be specified together, separated by a comma ','.  Possible values for the <code>structureviewer</code> are:
+  <br/>
+  <code>none</code>,
+  <br/>
+  <code>jmol</code>,
+  <br/>
+  <code>chimera</code>,
+  <br/>
+  <code>chimerax</code>,
+  <br/>
+  <code>pymol</code>.
+  </p>
+  <p>
+  <code>none</code> and <code>jmol</code> will always be available, but to use the others you must have the appropriate software already set up on your computer and in Jalview.  See the page <a href="../features/viewingpdbs.html">Discovering and Viewing PDB and 3D-Beacons structures</a> for more details.
+  <pre>
+  jalview --open examples/uniref50.fa --structure [seqid=FER1_SPIOL,structureviewer=none]examples/AlphaFold/AF-P00221-F1-model_v4.pdb
+  </pre>
+  or, if you prefer
+  <pre>
+  jalview --open examples/uniref50.fa --structure examples/AlphaFold/AF-P00221-F1-model_v4.pdb --seqid FER1_SPIOL --structureviewer none
+  </pre>
+  </p>
+
+  <h3><a name="paematrix"></a><code>--paematrix</code></h3>
+
+  <p>
+  If you are opening a structure file that has a PAE matrix (provided as a JSON file), such as from an AlphaFold model or an nf-core pipeline, you can add the PAE matrix as an annotation by following the <code>--structure</code> argument with a <code>--paematrix</code> argument with the filename.  You can also specify a <code>paematrix=filename</code> sub-value.
+  <pre>
+  jalview --open examples/uniref50.fa --structure [seqid=FER1+SPIOL,structureviewer=pymol]examples/AlphaFold/AF-P00221-F1-model_v4.pdb --paematrix examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json
+  </pre>
+  </p>
+
+  <h3><a name="tempfac"></a><code>--tempfac</code></h3>
+
+  <p>
+  Structure files may have a temperature factor associated with the structure component positions.  If the temperature factor is a pLDDT confidence score, such as with an AlphaFold model, you can specify this by using a following argument of <code>--tempfac</code> with a value of <code>plddt</code>.  This will enable standard pLDDT colouring of the temperature factor annotation.  Valid values are:
+  <code>default</code>,
+  <code>plddt</code>.
+  More types of temperature factor may be added in future releases of Jalview.
+  <br/>
+  The value can also be specified as a sub-value:
+  <pre>
+  jalview --open examples/uniref50.fa --structure [seqid=FER1+SPIOL,structureviewer=jmol,tempfac=plddt]examples/AlphaFold/AF-P00221-F1-model_v4.pdb
+  </pre>
+  which is equivalent to
+  <pre>
+  jalview --open examples/uniref50.fa --structure examples/AlphaFold/AF-P00221-F1-model_v4.pdb --tempfac plddt --seqid FER1+SPIOL
+   --structureviewer jmol
+  </pre>
+
+  </p>
+
+  <!-- notempfac not yet working. undocumented until then -->
+
+  <h3><a name="showssannotations"></a><code>--showssannotations</code> / <code>--noshowssannotations</code></h3>
+
+  <p>
+  You can specify whether the currently opened alignment window should show secondary structure annotations or not with either <code>--showssannotations</code> or <code>--noshowssannotations</code>.  If you don't specify then your saved preference will be used.
+  <pre>
+  jalview --open examples/uniref50.fa --structure examples/AlphaFold/AF-P00221-F1-model_v4.pdb --noshowssannotations
+  </pre>
+  or you can use a sub-value modifier:
+  <pre>
+  jalview --open examples/uniref50.fa --structure [noshowssannotations]examples/AlphaFold/AF-P00221-F1-model_v4.pdb
+  </pre>
+  </p>
+
+  <h2><a name="outputtingalignmentfiles"></a>Outputting/converting alignment files and images (<code>--output</code>, <code>--format</code>, <code>--image</code>, <code>--type</code>, <code>--textrenderer</code>, <code>--scale</code>, <code>--backups</code>, <code>--overwrite</code>)</h2>
+
+  <p>
+  You can save an alignment as an alignment file, or exported as an image, in different formats.  Jalview's alignment output formats are:
+  fasta,
+  pfam,
+  stockholm,
+  pir,
+  blc,
+  amsa,
+  json,
+  pileup,
+  msf,
+  clustal,
+  phylip,
+  jalview.
+  </p>
+  <p>
+  Alignments can be exported as an image in formats EPS, SVG, HTML, BioJSON (vector formats) or PNG (bitmap format).
+  </p>
+  <p>
+  In vector formats you can specify whether text should be rendered as text (which may have font changes, but will produce a smaller and more usable file) or as lineart (which will retain exact appearance of text, but will be less easy to edit or use to copy text).
+  </p>
+  <p>
+  In bitmap formats (currently only PNG, but what else would you want?!) you can specify a scaling factor to improve the resolution of the output image.
+  </p>
+
+  <h3><a name="output"></a><code>--output</code></h3>
+
+  <p>
+  To save the open alignment in a new alignment file use <code>--output filename</code>.  The format for the file can be found from the extension of <code>filename</code>, or if you are using a non-standard extension you can use a following <code>--format</code> argument, or specify it as a sub-value modifier.
+  </p>
+  <p>
+  Recognised formats and their recognised extensions are:
+  <br/>
+  <code>fasta</code> (<code>fa, fasta, mfa, fastq</code>),
+  <br/>
+  <code>pfam</code> (<code>pfam</code>),
+  <br/>
+  <code>stockholm</code> (<code>sto, stk</code>),
+  <br/>
+  <code>pir</code> (<code>pir</code>),
+  <br/>
+  <code>blc</code> (<code>blc</code>),
+  <br/>
+  <code>amsa</code> (<code>amsa</code>),
+  <br/>
+  <code>json</code> (<code>json</code>),
+  <br/>
+  <code>pileup</code> (<code>pileup</code>),
+  <br/>
+  <code>msf</code> (<code>msf</code>),
+  <br/>
+  <code>clustal</code> (<code>aln</code>),
+  <br/>
+  <code>phylip</code> (<code>phy</code>),
+  <br/>
+  <code>jalview</code> (<code>jvp, jar</code>).
+  <p>
+  For example, to open a FASTA file, append another FASTA file and then save the concatenation as a Stockholm file, do
+  <pre>
+  jalview --open alignment1.fa --append alignment2.fa --output bothalignments.stk
+  </pre>
+  or
+  <pre>
+  jalview --append alignment*.fa --output bigballofstring.txt --format stockholm
+  </pre>
+  or
+  <pre>
+  jalview --append alignment*.fa --output [format=stockholm]bigballofstring.txt
+  </pre>
+  </p>
+
+  <h3><a name="format"></a><code>--format</code></h3>
+
+  <p>
+  To specify the format of the output file (if using an unrecognised file extension) use the <code>--format</code> argument to specify a value (see above).  A sub-value modifier on the <code>--output</code> value can also be used.
+  </p>
+
+  <h3><a name="image"></a><code>--image</code></h3>
+  To export the open alignment window as an image, use the <code>--image</code> argument, which will give an image of the alignment and annotations as it appears (or would appear if not in <code>--headless</code> mode) in the alignment window if it was large enough for the whole alignment, including colour choice and features.
+  <p>
+  <pre>
+  jalview --open examples/plantfdx.fa --colour gecos-blossom --features examples/plantfdx.features --annotations examples/plantfdx.annotations --image plantfdx.png --headless
+  </pre>
+  </p>
+
+  <p>
+  This by default produces a PNG image of screen or webpage resolution, which you may want to improve upon.  There are two ways of doing this with Jalview: increasing the scale of the PNG image, or using a vector based image format (EPS, SVG, HTML).
+  <p>
+
+  <h3><a name="bitmap"></a>Bitmap image types (<code>png</code>)</h3>
+
+  <p>
+  Bitmap images are composed of an array of pixels.  Bitmap images with a low resolution that are blown up to a larger size appear "blocky", so it is important to get the resolution for your purpose correct.  Older screens only require a modest resolution, whilst newer HiDPI screens look better with a higher resolution.  For print you will want an even higher resolution although in this case you would probably want to use a vector image format.  In general creating a bitmap image that has a large resolution means you can scale the image down if needed, although if you are running a batch process this will take more time and resources.
+  </p>
+  <p>
+  Jalview only produces a PNG bitmap image type.  This is a high-colour lossless format which can also use lossless compression so is suitable for alignment figures.
+  </p>
+  <p>
+  Let's increase the resolution of the PNG image:
+  </p>
+
+  <h3><a name="scale"></a><code>--scale</code></h3>
+
+  <p>
+  We can increase the size of the PNG image by a factor of <em>S</em> by following the <code>--image</code> argument with a <code>--scale <em>S</em></code> argument and value.  The value doesn't have to be an integer and should be given as an integer or floating point formatted number, e.g.
+  <pre>
+  jalview --open examples/uniref50.fa --colour gecos-ocean --image mypic.png --scale 5.5 --headless
+  </pre>
+  which will produce a PNG image 5.5 times larger (and more detailed) than without the <code>--scale</code> argument.
+  </p>
+  <p>
+  However, since you won't necessarily already know the size of an alignment's exported image you can specify either an exact width or height (in pixels) with either one of the
+  <code>--width</code> and <code>--height</code> arguments:
+
+  <h3><a name="width"></a><code>--width</code></h3>
+
+  <p>
+  Specify an exact width of an exported PNG image with <code>--width</code>:
+  <pre>
+  jalview --headless --open https://www.ebi.ac.uk/interpro/api/entry/pfam/PF03760/?annotation=alignment%3Aseed --noshowannotations --colour gecos-sunset --image wallpaper.png --width 3840
+  </pre>
+  </p>
+
+  <h3><a name="height"></a><code>--height</code></h3>
+
+  <p>
+  Alternatively specify an exact height with the <code>--height</code> argument:
+  <pre>
+  jalview --headless --open https://www.ebi.ac.uk/interpro/api/entry/pfam/PF03760/?annotation=alignment%3Aseed --noshowannotations --colour gecos-ocean --image wallpaper.png --height 2160
+  </pre>
+  </p>
+
+  <p>
+  You can specify two or all of <code>--scale</code>, <code>--width</code> and <code>--height</code> as limits to the size of the image (think of one or two bounding boxes) and the one which produces the smallest scale of image is used.  You can also specify each of these as sub-value modifiers to the <code>--image</code> value:
+  <pre>
+  jalview --headless --open https://www.ebi.ac.uk/interpro/api/entry/pfam/PF03760/?annotation=alignment%3Aseed --noshowannotations --colour gecos-flower --image [scale=0.25,width=320,height=240]thumbnail.png
+  </pre>
+  </p>
+
+  <p>
+  Next we look at vector image formats, which maintain detail at all resolutions.
+  </p>
+
+  <h3><a name="vector"></a>Vector image export</h3>
+
+  <p>
+  Jalview can export an alignment in Encapsulated PostScript (<code>eps</code>), Scalable Vector Graphics (<code>svg</code>), HTML (<code>html</code>) or BioJSON -- another HTML format (<code>biojs</code>), by using, e.g.
+  <pre>
+  jalview --open examples/uniref50.fa --colour gecos-flower --image printable.eps
+  </pre>
+  The image format can be specified with the <code>--type</code> argument or as a sub-value modifier on the <code>--image</code> value.  If neither is used the <code>type</code> will be guessed from the image file extension.  The following three examples should produce the same contents:
+  <pre>
+  jalview --open examples/uniref50.fa --colour gecos-flower --image printable.eps
+  jalview --open examples/uniref50.fa --colour gecos-flower --image printable.postscript --type eps
+  jalview --open examples/uniref50.fa --colour gecos-flower --image [type=eps]printable.postscript
+  </pre>
+  </p>
+
+  <h3><a name="textrenderer"></a><code>--textrenderer</code></h3>
+
+  <p>
+  In a vector format any text that appears on the page (including residue/base labels) can be saved in the image file either as <code>text</code> or as <code>lineart</code> using the <code>--textrenderer</code> argument.  This is only available for <code>eps</code>, <code>svg</code> and <code>html</code> formats.
+  </p>
+
+  <p>
+  The difference is potentially in the appearance of the file, and definitely in the filesize!  Saving with <code>text</code> requires the font used to display the text characters in the alignment to be present on the viewing platform to look exactly the same.  If it isn't then another suitable font will probably be used.  The filesize using <code>text</code> is relatively small.
+  </p>
+  <p>
+  When using <code>lineart</code> each individual character that appears in the alignment (including names/titles and resisdues/bases) is stored in the image with its own vector lines.  This means that the appearance of the text is retained exactly independent of installed fonts, but the filesize is increased.  You will also be unable to copy what appears to be text as text.
+  </p>
+
+  <p>
+  The type of <code>--textrenderer</code> can be specified with an argument following <code>--image</code> or as a sub-value modifier:
+  <pre>
+  jalview --open examples/uniref50.fa --colour gecos-flower --image printable.html --type biojs
+  jalview --open examples/uniref50.fa --colour gecos-flower --image [type=eps,textrenderer=lineart]printable.ps
+  </pre>
+  </p>
+
+
+  <h2><a name="filenamesubstitutionsandbatchprocessing"></a>Filename substitutions and batch processing (<code>--substitutions</code>, <code>--close</code>, <code>--all</code>)</h2>
+
+  <p>
+  One of the more powerful aspects of Jalview's command line operations is that stores all of the different opened alignment arguments (before opening them) and can apply some arguments to <em>all</em> of the alignments as they are opened.  This includes display and output arguments.
+  </p>
+
+  <p>
+  When outputting a file you will obviously want to use a different filename for each of the alignments that are opened.  There are several ways you can specify how that filename differs, but the easiest way is to refer to the input filename and change the extension.  In the case of an alignment where multiple files are appended, the filename of the first file to be appended or opened is used.
+  </p>
+
+  <p>
+  To refer to different parts of the opening filename, you can use the strings
+  <ul>
+  <li><code>{dirname}</code> -- is replaced by the directory path to the opened file.</li>
+  <li><code>{basename}</code> -- is replaced by the base of the filename of the opened file. This is without the path or file extension (if there is one).</li>
+  <li><code>{extension}</code> -- is replaced by the extension of the filename of the opened file.</li>
+  </ul>
+  The braces (curly brackets '{', '}') are important!
+  </p>
+
+  <p>
+  These filename substitutions are on by default, but if for whatever reason you wish to disable the substitutions, they can be turned off (or back on again) through the list of arguments with:
+  </p>
+
+  <h3><a name="substitutions"></a><code>--substitutions / --nosubstitutions</code></h3>
+
+  <p>
+  Enable (or disable) filename substitutions in the following argument values and sub-value modifier values.
+  <pre>
+  jalview --open exampes/uniref50.fa --nosubstitutions --output I_Want_A_Weird_Output_Filname_With_{basename}_Curly_Brackets_In_And_A_Sensible_One_Next.stk --substitutions --output tmp/{basename}.stk --headless
+  </pre>
+  </p>
+
+  <p>
+  For opening single files this is less useful, since you could obviously just type the output filename, but for multiple opened alignments you can also use these substituted values and they will be replaced by the relevant part of the filename given for each opened alignment window.  Normally an <code>--output</code> or <code>--image</code> argument will only be applied to the latest opened alignment window, but you can tell Jalview to apply some arguments to all alignments that have been opened (so far) by using the <code>--all</code> argument.
+  </p>
+
+  <h3><a name="all"></a><code>--all / -noall</code></h3>
+
+  <p>
+  When using the <code>--all</code> argument, following arguments will apply to all of the previously opened alignment windows.  You can turn this behaviour off again for following arguments using the <code>--noall</code> argument.  The arguments that can apply to all previously opened alignments are:
+  <br/>
+  <code>--colour</code>
+  <br/>
+  <code>--sortbytree</code>
+  <br/>
+  <code>--showannotations</code>
+  <br/>
+  <code>--wrap</code>
+  <br/>
+  <code>--nostructure</code>
+  <br/>
+  <code>--notempfac</code>
+  <br/>
+  <code>--showssannotations</code>
+  <br/>
+  <code>--image</code>
+  <br/>
+  <code>--type</code>
+  <br/>
+  <code>--textrenderer</code>
+  <br/>
+  <code>--scale</code>
+  <br/>
+  <code>--width</code>
+  <br/>
+  <code>--height</code>
+  <br/>
+  <code>--output</code>
+  <br/>
+  <code>--format</code>
+  <br/>
+  <code>--groovy</code>
+  <br/>
+  <code>--backups</code>
+  <br/>
+  <code>--overwrite</code>
+  <br/>
+  <code>--close</code>
+  </p>
+  <p>
+  In particular, for our example above, we can use <code>-all</code> and <code>--output</code> like this (<code>--close</code> will be explained in a moment):
+  <pre>
+  jalview --open all_my_fasta_files/*.fa --all --output all_my_converted_stockholm_files/{basename}.stk --close --headless
+  </pre>
+  </p>
+
+  <p>
+  Another example would be to create a print quality coloured postscript image for every Fasta file in several directories and place the image next to the alignment:
+  <pre>
+  jalview --open */*.fa --all --colour gecos-flower --image {dirname}/{basename}.eps --textrenderer text --close --headless
+  </pre>
+  or thumbnails for every Fasta file with
+  <pre>
+  jalview --open */*.fa --all --colour gecos-flower --image {dirname}/{basename}.png --width 256 --height 256 --close --headless
+  </pre>
+  </p>
+
+  <h3><a name="close"></a><code>--close</code></h3>
+
+  <p>
+  The <code>--close</code> tag is used to close an alignment window after all output files or image exports are performed.  This reduces memory use, especially if an <code>--open</code> value is set to open many files.  These will be opened, formatted and output sequentially so since they are closed before the next one is opened memory use will not build up over a large number of alignments.
+  <pre>
+  </pre>
+  </p>
+
+
+  <h2><a name="alloutputwildcard"></a>The all output wildcard: <code>--output "*.ext"</code>,  <code>--image "*.ext"</code></h2>
+
+  <p>
+  Purely as an intuitive syntactic sweetener, you can use the <code>--output</code> wildcard <code>*</code> <em>at the beginning of the output filename</em> as shorthand for <code>--all --output {dirname}/{basename}</code> followed by whatever you put after the '<code>*</code>'.  For example, to achieve the same as the thumbnails example above, you could use
+  <pre>
+  jalview --open */*.fa --image "*.png" --colour gecos-flower --width 256 --height 256 --close --headless
+  </pre>
+  Here we move the <code>--colour</code> argument after the <code>--output</code> argument (it will still be applied before the image export or file output) so that it is included after the implied <code>--all</code> argument.  The thumbnails will be placed in the same directory as the alignment file with the same filename except for a different extension of <code>.png</code>.
+  </p>
+
+  <p>
+  For a simple conversion of many fasta files into Stockholm format, simply use
+  <pre>
+  jalview --open all_my_fasta_files/*.fa --output "*.stk" --close --headless
+  </pre>
+  </p>
+
+  <p>
+  <strong>Important!</strong>  Please note that using a bareword <code>*.ext</code> (i.e. without an escape or quotation marks) in a shell command line will potentially be expanded by the shell to a list of all the files in the current directory ending with <code>.ext</code> <em>before</em> this value is passed to Jalview.  This will result in the first of these files being overwritten (multiple times)!  If you shell-escape the <code>*</code> (usually with a backslash '\') or enclose the value in quotation marks (<code>"*.ext"</code> - that's including the quotation marks) then the shell will not expand the wildcard.
+  </p>
+
+  <p>
+  An alternative is to use an equals sign ('=') with no spaces between the argument and the value, <code>--output=*.ext</code>, which Jalview will interpret the same, but the shell will not automatically expand before it is sent to Jalview, e.g.
+  <pre>
+  jalview --open all_my_fasta_files/*.fa --output=*.stk --close --headless
+  </pre>
+  </p>
+
+  <hr/>
+  Continue to <a href="clarguments-advanced.html">Command Line: advanced usage</a>.
+  <br/>
+  Return to <a href="clarguments.html">Command Line: summary</a>.
+
+
+</body>
+</html>
diff --git a/help/help/html/features/clarguments-intro.html b/help/help/html/features/clarguments-intro.html
new file mode 100644 (file)
index 0000000..6fddf0c
--- /dev/null
@@ -0,0 +1,88 @@
+<html>
+<!--
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ -->
+<title>Command Line: introduction</title>
+<body>
+
+  <h1>Command Line: introduction</h1>
+
+  <p>
+  <a href="clarguments.html">Command Line: summary</a>
+  <br/>
+  Command Line: introduction
+  <br/>
+  <a href="clarguments-basic.html">Command Line: basic usage</a>
+  <br/>
+  <a href="clarguments-advanced.html">Command Line: advanced usage</a>
+  <br/>
+  <a href="clarguments-argfiles.html">Command Line: argument files</a>
+  </p>
+
+  <hr/>
+
+  <p>
+  From version 2.11.3.0 Jalview processes a new set of command line arguments
+  which allow more powerful and flexible combinations of arguments, though can
+  also be used for very simple use cases too.
+  </p>
+
+  <p>
+  These new arguments are all accessed with a <code>--doubledash</code> form of
+  command line argument (with the one exception where simply opening one or more
+  files can be performed without any arguments other than the filenames).
+  </p>
+
+  <p>
+  The old command line arguments can still be used (see
+  <a href="clarguments-old.html">the old page on command line arguments</a>) so
+  existing scripts utilising them should not break.
+  <br/>
+  <strong>These are now deprecated and will be removed</strong> in a future version of Jalview.
+  </p>
+
+  <p>
+  However, you cannot mix old and new style arguments, so if you use any
+  <code>-singledash</code> arguments (with the exception of <code>-help</code> or <code>-h</code>), they will all be interpreted as
+  old style arguments with the new <code>--doubledash</code>
+  arguments being ignored.  If you have a script
+  that uses the old arguments without any dashes, and uses the bare-word
+  <code>open</code> then these will also be interpreted as old style arguments.
+  </p>
+  <p>
+  <strong>Warning!</strong> If you use command line arguments without any dashes and
+  <em>don't</em> use the bare-word argument <code>open</code> then all
+  your arguments will be interpreted as alignment files to be opened by the
+  new command line argument process!
+  </p>
+
+  <p>
+  To launch Jalview from the command line, see
+  <a href="commandline.html">running Jalview from the command line</a>.
+  </p>
+
+  <hr/>
+  Continue to <a href="clarguments-basic.html">Command Line: basic usage</a>.
+  <br/>
+  Return to <a href="clarguments.html">Command Line: summary</a>.
+
+
+</body>
+</html>
diff --git a/help/help/html/features/clarguments-old.html b/help/help/html/features/clarguments-old.html
new file mode 100644 (file)
index 0000000..c0f4c42
--- /dev/null
@@ -0,0 +1,276 @@
+<html>
+<!--
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ -->
+<title>Jalview Command Line Arguments</title>
+<body>
+  <p>
+    <strong>The Jalview Executable's Command Line Arguments</strong>
+  </p>
+  See
+  <a href="commandline.html">running Jalview from the command line</a>
+  for more information.
+  <br />
+  <br />Jalview processes arguments on the command line sequentially. If
+  you would like to pass a <a href="jvlfiles.html">'JVL' file</a> containing
+  <a href="../memory.html">memory settings</a> or any other launch
+  parameters, then include it at the beginning of the command line to
+  ensure they are processed before any remaining arguments.
+  <br>
+  Typical command line execution follows the following pattern:
+  <pre>
+  jalview -open &lt;Alignment File/URL&gt; [additional import arguments] [export arguments]
+  </pre>
+  
+  <table width="100%" border="1" cellspacing="0" cellpadding="0">
+    <tr>
+      <td width="27%"><div align="center">-nodisplay</div></td>
+      <td width="73%"><div align="left">Run Jalview without
+          User Interface. (automatically disables questionnaire, version
+          and usage stats checks)</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-nowebservicediscovery</div></td>
+      <td><div align="left">Do not query configured servers to
+          discover web services (<em>Since 2.11.2.0</em>)</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-open FILE/URL</div></td>
+      <td><div align="left">Specify the alignment file to
+          open or process by providing additional arguments.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-props FILE/URL</div></td>
+      <td><div align="left">Use the given Jalview properties
+          file instead of users default.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-setprop PROPERTY=value</div></td>
+      <td><div align="left">(JalviewJS ONLY) sets the given
+          property to the given value</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-features FILE/URL</div></td>
+      <td><div align="left">
+          <p>
+            Use the given file to add sequence features to an alignment.
+            See <a href="featuresFormat.html" target="NEW">Features
+              File</a> (Known as Groups file prior to 2.08) description.
+          </p>
+
+        </div></td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-colour COLOURSCHEME</div>
+      </td>
+      <td>Set the colourscheme for the alignment. This can be any
+        of the built-in colourschemes, a name of a predefined
+        colourscheme (defined in the Jalview properties file), or an
+        'inline' colourscheme (see the applet's colour parameter for
+        more information).</td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-annotations FILE/URL</div>
+      </td>
+      <td>Add precalculated annotations to the alignment. See <a
+        href="annotationsFormat.html" target="NEW">Annotation
+          File</a> description.
+      </td>
+    </tr>
+        <tr>
+      <td>
+        <div align="center">-no-annotation</div>
+      </td>
+      <td>Do not display annotation below the alignment. 
+      </td>
+    </tr>
+    
+    <tr>
+      <td>
+        <div align="center">-tree FILE/URL</div>
+      <td>
+        <div align="left">Load the given newick format tree file
+          onto the alignment</div>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-questionnaire URL</div>
+      <td>
+        <div align="left">Queries the given URL for information
+          about any Jalview user questionnaires</div>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-noquestionnaire</div>
+      <td>
+        <div align="left">Turn off questionnaire check</div>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-nonews</div>
+      <td>
+        <div align="left">
+          Disable check for <a href="../webServices/newsreader.html">Jalview
+            news</a> on startup (not recommended other than for classroom /
+          demo usage)
+        </div>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-nousagestats</div>
+      <td>
+        <div align="left">Turn off google analytics usage tracking</div>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-[no]sortbytree</div>
+      <td>
+        <div align="left">Enable or disable automatic sorting of
+          associated view when a new tree is displayed</div>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-groovy FILE/URL</div>
+      <td>
+        <div align="left">Execute groovy script in FILE (where
+          FILE may be 'STDIN' to read from the standard input) after all
+          other arguments have been processed</div>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-jabaws URL</div>
+      <td>
+        <div align="left">Specify the URL of the preferred JABAWS
+          server</div>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        <div align="center">-fasta FILE</div>
+      </td>
+
+      <td>
+        <div align="left">Create alignment file FILE in Fasta
+          format.</div>
+      </td>
+    </tr>
+    <tr>
+      <td><div align="center">-clustal FILE</div></td>
+      <td><div align="left">Create alignment file FILE in
+          Clustal format.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-msf FILE</div></td>
+
+      <td><div align="left">Create alignment file FILE in MSF
+          format.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-pileup FILE</div></td>
+      <td><div align="left">Create alignment file FILE in
+          Pileup format.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-pir FILE</div></td>
+
+      <td><div align="left">Create alignment file FILE in PIR
+          format.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-pfam FILE</div></td>
+      <td><div align="left">Create alignment file FILE in
+          PFAM format.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-blc FILE</div></td>
+      <td><div align="left">Create alignment file FILE in BLC
+          format.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-json FILE</div></td>
+      <td><div align="left">Create alignment file FILE in
+          JSON format.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-jalview FILE</div></td>
+
+      <td><div align="left">Create alignment file FILE in
+          Jalview format.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-png FILE</div></td>
+      <td><div align="left">Create PNG image FILE from
+          alignment.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-imgMap FILE</div></td>
+
+      <td><div align="left">Create HTML file FILE with image
+          map of PNG image.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-eps FILE</div></td>
+      <td><div align="left">Create EPS file FILE from
+          alignment.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-svg FILE</div></td>
+      <td><div align="left">Create Scalable Vector Graphics
+          file FILE from alignment.</div></td>
+    </tr>
+    <tr>
+      <td><div align="center">-biojsMSA FILE</div></td>
+      <td><div align="left">Write an HTML page to display
+          the alignment with the <a href="biojsmsa.html">
+          BioJS MSAviewer MSA</a>
+          </div>
+      </td>
+    </tr>
+    <tr>
+      <td><div align="center">-jvmmempc=PERCENT</div></td>
+      <td><div align="left"><em>Only available with standalone executable jar or jalview.bin.Launcher.</em>
+          Limit maximum heap size (memory) to PERCENT% of total physical memory detected.
+         This defaults to 90 if total physical memory can be detected.
+         See <a href="../memory.html">Memory usage settings for Jalview</a> for more details.
+          </div>
+      </td>
+    </tr>
+    <tr>
+      <td><div align="center">-jvmmemmax=MAXMEMORY</div></td>
+      <td><div align="left"><em>Only available with standalone executable jar or jalview.bin.Launcher.</em>
+          Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m),
+         gigabytes(g) or if you're lucky enough, terabytes(t).
+         This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected.
+         See <a href="../memory.html">Memory usage settings for Jalview</a> for more details.
+          </div>
+      </td>
+    </tr>
+  </table>
+</body>
+</html>
index c0f4c42..437392f 100644 (file)
@@ -1,15 +1,5 @@
 <html>
 <!--
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
  * Jalview is distributed in the hope that it will be useful, but 
  * WITHOUT ANY WARRANTY; without even the implied warranty 
  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  -->
-<title>Jalview Command Line Arguments</title>
+<title>Command Line: summary</title>
 <body>
+
+  <h1>Command Line: summary</h1>
+
   <p>
-    <strong>The Jalview Executable's Command Line Arguments</strong>
+  Command Line: summary
+  <br/>
+  <a href="clarguments-intro.html">Command Line: introduction</a>
+  <br/>
+  <a href="clarguments-basic.html">Command Line: basic usage</a>
+  <br/>
+  <a href="clarguments-advanced.html">Command Line: advanced usage</a>
+  <br/>
+  <a href="clarguments-argfiles.html">Command Line: argument files</a>
   </p>
-  See
-  <a href="commandline.html">running Jalview from the command line</a>
-  for more information.
-  <br />
-  <br />Jalview processes arguments on the command line sequentially. If
-  you would like to pass a <a href="jvlfiles.html">'JVL' file</a> containing
-  <a href="../memory.html">memory settings</a> or any other launch
-  parameters, then include it at the beginning of the command line to
-  ensure they are processed before any remaining arguments.
-  <br>
-  Typical command line execution follows the following pattern:
-  <pre>
-  jalview -open &lt;Alignment File/URL&gt; [additional import arguments] [export arguments]
-  </pre>
-  
-  <table width="100%" border="1" cellspacing="0" cellpadding="0">
-    <tr>
-      <td width="27%"><div align="center">-nodisplay</div></td>
-      <td width="73%"><div align="left">Run Jalview without
-          User Interface. (automatically disables questionnaire, version
-          and usage stats checks)</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-nowebservicediscovery</div></td>
-      <td><div align="left">Do not query configured servers to
-          discover web services (<em>Since 2.11.2.0</em>)</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-open FILE/URL</div></td>
-      <td><div align="left">Specify the alignment file to
-          open or process by providing additional arguments.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-props FILE/URL</div></td>
-      <td><div align="left">Use the given Jalview properties
-          file instead of users default.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-setprop PROPERTY=value</div></td>
-      <td><div align="left">(JalviewJS ONLY) sets the given
-          property to the given value</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-features FILE/URL</div></td>
-      <td><div align="left">
-          <p>
-            Use the given file to add sequence features to an alignment.
-            See <a href="featuresFormat.html" target="NEW">Features
-              File</a> (Known as Groups file prior to 2.08) description.
-          </p>
-
-        </div></td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-colour COLOURSCHEME</div>
-      </td>
-      <td>Set the colourscheme for the alignment. This can be any
-        of the built-in colourschemes, a name of a predefined
-        colourscheme (defined in the Jalview properties file), or an
-        'inline' colourscheme (see the applet's colour parameter for
-        more information).</td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-annotations FILE/URL</div>
-      </td>
-      <td>Add precalculated annotations to the alignment. See <a
-        href="annotationsFormat.html" target="NEW">Annotation
-          File</a> description.
-      </td>
-    </tr>
-        <tr>
-      <td>
-        <div align="center">-no-annotation</div>
-      </td>
-      <td>Do not display annotation below the alignment. 
-      </td>
-    </tr>
-    
-    <tr>
-      <td>
-        <div align="center">-tree FILE/URL</div>
-      <td>
-        <div align="left">Load the given newick format tree file
-          onto the alignment</div>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-questionnaire URL</div>
-      <td>
-        <div align="left">Queries the given URL for information
-          about any Jalview user questionnaires</div>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-noquestionnaire</div>
-      <td>
-        <div align="left">Turn off questionnaire check</div>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-nonews</div>
-      <td>
-        <div align="left">
-          Disable check for <a href="../webServices/newsreader.html">Jalview
-            news</a> on startup (not recommended other than for classroom /
-          demo usage)
-        </div>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-nousagestats</div>
-      <td>
-        <div align="left">Turn off google analytics usage tracking</div>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-[no]sortbytree</div>
-      <td>
-        <div align="left">Enable or disable automatic sorting of
-          associated view when a new tree is displayed</div>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-groovy FILE/URL</div>
-      <td>
-        <div align="left">Execute groovy script in FILE (where
-          FILE may be 'STDIN' to read from the standard input) after all
-          other arguments have been processed</div>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-jabaws URL</div>
-      <td>
-        <div align="left">Specify the URL of the preferred JABAWS
-          server</div>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <div align="center">-fasta FILE</div>
-      </td>
-
-      <td>
-        <div align="left">Create alignment file FILE in Fasta
-          format.</div>
-      </td>
-    </tr>
-    <tr>
-      <td><div align="center">-clustal FILE</div></td>
-      <td><div align="left">Create alignment file FILE in
-          Clustal format.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-msf FILE</div></td>
-
-      <td><div align="left">Create alignment file FILE in MSF
-          format.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-pileup FILE</div></td>
-      <td><div align="left">Create alignment file FILE in
-          Pileup format.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-pir FILE</div></td>
-
-      <td><div align="left">Create alignment file FILE in PIR
-          format.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-pfam FILE</div></td>
-      <td><div align="left">Create alignment file FILE in
-          PFAM format.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-blc FILE</div></td>
-      <td><div align="left">Create alignment file FILE in BLC
-          format.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-json FILE</div></td>
-      <td><div align="left">Create alignment file FILE in
-          JSON format.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-jalview FILE</div></td>
-
-      <td><div align="left">Create alignment file FILE in
-          Jalview format.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-png FILE</div></td>
-      <td><div align="left">Create PNG image FILE from
-          alignment.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-imgMap FILE</div></td>
-
-      <td><div align="left">Create HTML file FILE with image
-          map of PNG image.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-eps FILE</div></td>
-      <td><div align="left">Create EPS file FILE from
-          alignment.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-svg FILE</div></td>
-      <td><div align="left">Create Scalable Vector Graphics
-          file FILE from alignment.</div></td>
-    </tr>
-    <tr>
-      <td><div align="center">-biojsMSA FILE</div></td>
-      <td><div align="left">Write an HTML page to display
-          the alignment with the <a href="biojsmsa.html">
-          BioJS MSAviewer MSA</a>
-          </div>
-      </td>
-    </tr>
-    <tr>
-      <td><div align="center">-jvmmempc=PERCENT</div></td>
-      <td><div align="left"><em>Only available with standalone executable jar or jalview.bin.Launcher.</em>
-          Limit maximum heap size (memory) to PERCENT% of total physical memory detected.
-         This defaults to 90 if total physical memory can be detected.
-         See <a href="../memory.html">Memory usage settings for Jalview</a> for more details.
-          </div>
-      </td>
-    </tr>
-    <tr>
-      <td><div align="center">-jvmmemmax=MAXMEMORY</div></td>
-      <td><div align="left"><em>Only available with standalone executable jar or jalview.bin.Launcher.</em>
-          Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m),
-         gigabytes(g) or if you're lucky enough, terabytes(t).
-         This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected.
-         See <a href="../memory.html">Memory usage settings for Jalview</a> for more details.
-          </div>
-      </td>
+
+  <hr/>
+
+
+  <h2>Initialising arguments</h2>
+
+  <table border="1" cellpadding="3">
+    <tr valign="top">
+    <td><strong>argument</strong></td>
+    <td><strong>action</strong></td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;help / -h</code></td>
+    <td>Display a help statement</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;headless</code></td>
+    <td>Run Jalview in headless mode.  No GUI interface will be created and Jalview will quit after all arguments have been processed.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;jabaws&nbsp;<em>URL</em></code></td>
+    <td>Set a different URL to connect to a JABAWS server.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;news / &#8209;&#8209;nonews</code></td>
+    <td>Show (or don't show) the news feed.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;splash / &#8209;&#8209;nosplash</code></td>
+    <td>Show (or don't show) the About Jalview splash screen.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;questionnaire / &#8209;&#8209;noquestionnaire</code></td>
+    <td>Show (or don't show) the questionnaire if one is available.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;usagestats / &#8209;&#8209;nousagestats</code></td>
+    <td>Send (or don't send) initial launch usage stats. <em>Note: usage stats are useful for future funding for Jalview!</em></td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;webservicediscovery / &#8209;&#8209;nowebservicediscovery</code></td>
+    <td>Attempt (or don't attempt) to connect to JABAWS web services.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;props&nbsp;<em>filename</em></code></td>
+    <td>Use file <em>filename</em> as the preferences file <em>instead</em> of the usual <code>~/.jalview_properties</code> file.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;debug</code></td>
+    <td>Start Jalview in debug log level.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;quiet</code></td>
+    <td>Stop all output to STDOUT (after the Java Virtual Machine has started).  Use <code>&#8209;&#8209;quiet</code> a second time to stop all output to STDERR.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;initsubstitutions / &#8209;&#8209;noinitsubstitutions</code></td>
+    <td>Set <code>&#8209;&#8209;substitutions</code> to be initially enabled (or initially disabled).</td>
+    </tr>
+
+<!--
+    <tr valign="top">
+    <td><code>&#8209;&#8209;threads <em>NUMBER</em></code></td>
+    <td>When opening multiple alignment windows, set a limit of <em>NUMBER</em> alignments being processed at one time.  The default is 3.</td>
+-->
+
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;jvmmempc=<em>PERCENT</em></code></td>
+    <td>
+      Limit maximum heap size (memory) to <em>PERCENT</em>% of total physical memory detected.
+      This defaults to 90 if total physical memory can be detected.
+      <br/>
+      The equals sign ("=") separator must be used with no spaces.
+      <br/>
+      See <a href="../memory.html">Memory usage settings for Jalview</a> for more details.
+    </td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;jvmmemmax=<em>MAXMEMORY</em></code></td>
+    <td>
+      Limit maximum heap size (memory) to <em>MAXMEMORY</em>. <em>MAXMEMORY</em> can be specified in bytes, kilobytes(k), megabytes(m),
+      gigabytes(g) or if you're lucky enough, terabytes(t).
+      This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected.
+      <br/>
+      The equals sign ("=") separator must be used with no spaces.
+      <br/>
+      See <a href="../memory.html">Memory usage settings for Jalview</a> for more details.
+    </td>
+    </tr>
+
+  </table>
+
+
+  <h2>Opening an alignment</h2>
+
+  <table border="1" cellpadding="3">
+    <tr valign="top">
+    <td><strong>argument</strong></td>
+    <td><strong>action</strong></td>
+    <td><strong>sub-value modifiers</strong> (optional)</td>
+    <td><strong>linked</strong> (optional)</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;open&nbsp;<em>filename/URL ...</em></code></td>
+    <td>
+    Opens one or more alignment files <em>filename</em> or URLs <em>URL</em> in new alignment windows.
+    </td>
+    <td>
+      <code>
+        colour=<em>name</em>,
+        <br/>
+        title=<em>string</em>,
+        <br/>
+        features=<em>filename</em>,
+        <br/>
+        annotations=<em>filename</em>,
+        <br/>
+        tree=<em>filename</em>,
+        <br/>
+        showannotations,
+        <br/>
+        showssannotations,
+        <br/>
+        sortbytree,
+        <br/>
+        wrap
+      </code>
+    </td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;append&nbsp;<em>filename/URL ...</em></code></td>
+    <td>Appends one or more alignment files <em>filename</em> or URLs <em>URL</em> to the open alignment window (or opens a new alignment if none already open).</td>
+    <td>
+    <code>
+        colour=<em>name</em>,
+        <br/>
+        title=<em>string</em>,
+        <br/>
+        features=<em>filename</em>,
+        <br/>
+        annotations=<em>filename</em>,
+        <br/>
+        tree=<em>filename</em>,
+        <br/>
+        showannotations,
+        <br/>
+        showssannotations,
+        <br/>
+        sortbytree,
+        <br/>
+        wrap
+      </code>
+    </td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;title&nbsp;<em>"string""</em></code></td>
+    <td>Specifies the title for the open alignment window as <em>string</em>.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;colour&nbsp;<em>name</em></code></td>
+    <td>Applies the colour scheme <em>name</em> to the open alignment window.  Valid values for <em>name</em> are:
+    <br/>
+    <code>clustal</code>,
+    <br/>
+    <code>blosum62</code>,
+    <br/>
+    <code>pc-identity</code>,
+    <br/>
+    <code>zappo</code>,
+    <br/>
+    <code>taylor</code>,
+    <br/>
+    <code>gecos-flower</code>,
+    <br/>
+    <code>gecos-blossom</code>,
+    <br/>
+    <code>gecos-sunset</code>,
+    <br/>
+    <code>gecos-ocean</code>,
+    <br/>
+    <code>hydrophobic</code>,
+    <br/>
+    <code>helix-propensity</code>,
+    <br/>
+    <code>strand-propensity</code>,
+    <br/>
+    <code>turn-propensity</code>,
+    <br/>
+    <code>buried-index</code>,
+    <br/>
+    <code>nucleotide</code>,
+    <br/>
+    <code>nucleotide-ambiguity</code>,
+    <br/>
+    <code>purine-pyrimidine</code>,
+    <br/>
+    <code>rna-helices</code>,
+    <br/>
+    <code>t-coffee-scores</code>,
+    <br/>
+    <code>sequence-id</code>.
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;features&nbsp;<em>filename/URL</em></code></td>
+    <td>Add a feature file <em>filename</em> or URL <em>URL</em> to the open alignment.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;tree&nbsp;<em>filename/URL</em></code></td>
+    <td>Add a tree file <em>filename</em> or URL <em>URL</em> to the open alignment.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;sortbytree / &#8209;&#8209;nosortbytree</code></td>
+    <td>Enforces sorting (or not sorting) the alignment in the order of an attached phylogenetic tree.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;annotations&nbsp;<em>filename/URL</em></code></td>
+    <td>Add an annotations file <em>filename</em> or URL <em>URL</em> to the open alignment.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;showannotations / &#8209;&#8209;noshowannotations</code></td>
+    <td>Enforces showing (or not showing) alignment annotations.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;wrap / &#8209;&#8209;nowrap</code></td>
+    <td>Enforces wrapped (or not wrapped) alignment formatting.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;nostructure</code></td>
+    <td>Do not open or process any 3D structure in the <code>&#8209;&#8209;open</code> or <code>&#8209;&#8209;append</code> files.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+  </table>
+
+
+  <h2>Adding a 3D structure</h2>
+
+  <table border="1" cellpadding="3">
+    <tr valign="top">
+    <td><strong>argument</strong></td>
+    <td><strong>action</strong></td>
+    <td><strong>sub-value modifiers</strong> (optional)</td>
+    <td><strong>linked</strong> (optional)</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;structure&nbsp;<em>filename/URL</em></code></td>
+    <td>Load a structure file <em>filename</em> or URL <em>URL</em> associated with a sequence in the open alignment.  The sequence to be associated with can be specified with a following <code>&#8209;&#8209;seqid</code> argument, or the sub-value modifier <code>seqid=<em>ID</em></code> can be used.  A sub-value <em>INDEX</em> can also be used to specify the <em>INDEX-th</em> sequence in the open alignment.</td>
+    <td>
+      <code>
+        seqid=<em>id</em></code> or <code><em>INDEX</em>,
+        <br/>
+        paefile=<em>filename</em>,
+        <br/>
+        tempfac=<em>name</em>,
+        <br/>
+        showssannotations,
+        <!--
+        <br/>
+        notempfac,
+        -->
+        <br/>
+        structureviewer=<em>name</em>
+      </code></td>
+    <td align="center">&#x2713;</td>
     </tr>
+
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;seqid&nbsp;<em>ID</em></code></td>
+    <td>Specify the sequence name for the preceding <code>&#8209;&#8209;structure</code> to be associated with.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;paematrix&nbsp;<em>filename</em></code></td>
+    <td>Add a PAE json matrix file <em>filename</em> to the preceding <code>&#8209;&#8209;structure</code>.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;tempfac&nbsp;<em>name</em></code></td>
+    <td>Set the type of temperature factor.  Valid values for <em>name</em> are:
+      <br/>
+      <code>default</code>,
+      <br/>
+      <code>plddt</code>
+    </td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;structureviewer&nbsp;<em>name</em></code></td>
+    <td>Set the structure viewer to use to open the 3d structure file specified in previous <code>&#8209;&#8209;structure</code> to <em>name</em>.  Valid values of <em>name</em> are:
+    <br/>
+    <code>none</code>,
+    <br/>
+    <code>jmol</code>,
+    <br/>
+    <code>chimera</code> <em>- requires installation, might need configuring in Preferences</em>,
+    <br/>
+    <code>chimerax</code> <em>- requires installation, might need configuring in Preferences</em>,
+    <br/>
+    <code>pymol</code> <em>- requires installation, might need configuring in Preferences</em>
+    </td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+
+    <!--
+    <tr valign="top">
+    <td><code>&#8209;&#8209;notempfac</code></td>
+    <td>Do not show the temperature factor annotation for the preceding <code>&#8209;&#8209;structure</code></td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+    -->
+
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;showssannotations / &#8209;&#8209;noshowssannotations</code></td>
+    <td>Do not show secondary structure annotations for the preceding <code>&#8209;&#8209;structure</code></td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
   </table>
+
+
+  <h2>Outputting files</h2>
+
+  <table border="1" cellpadding="3">
+    <tr valign="top">
+    <td><strong>argument</strong></td>
+    <td><strong>action</strong></td>
+    <td><strong>sub-value modifiers</strong> (optional)</td>
+    <td><strong>linked</strong> (optional)</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;output&nbsp;<em>filename</em></code></td>
+    <td>Export the open alignment to file <em>filename</em>.  The format <em>name</em> is specified by the sub-value modifier <code>format=<em>name</em></code>, a following <code>&#8209;&#8209;format <em>name</em></code> argument or guessed from the file extension.  Valid format names (and file extensions) are:
+    <br/>
+    <code>fasta</code> (<code>fa, fasta, mfa, fastq</code>),
+    <br/>
+    <code>pfam</code> (<code>pfam</code>),
+    <br/>
+    <code>stockholm</code> (<code>sto, stk</code>),
+    <br/>
+    <code>pir</code> (<code>pir</code>),
+    <br/>
+    <code>blc</code> (<code>blc</code>),
+    <br/>
+    <code>amsa</code> (<code>amsa</code>),
+    <br/>
+    <code>json</code> (<code>json</code>),
+    <br/>
+    <code>pileup</code> (<code>pileup</code>),
+    <br/>
+    <code>msf</code> (<code>msf</code>),
+    <br/>
+    <code>clustal</code> (<code>aln</code>),
+    <br/>
+    <code>phylip</code> (<code>phy</code>),
+    <br/>
+    <code>jalview</code> (<code>jvp, jar</code>).
+    </td>
+    <td><code>format=<em>name</em></code></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;format&nbsp;<em>name</em></code></td>
+    <td>Sets the format for the preceding <code>&#8209;&#8209;output</code> file.  Valid formats are:
+    <br/>
+    <code>fasta</code>,
+    <br/>
+    <code>pfam</code>,
+    <br/>
+    <code>stockholm</code>,
+    <br/>
+    <code>pir</code>,
+    <br/>
+    <code>blc</code>,
+    <br/>
+    <code>amsa</code>,
+    <br/>
+    <code>json</code>,
+    <br/>
+    <code>pileup</code>,
+    <br/>
+    <code>msf</code>,
+    <br/>
+    <code>clustal</code>,
+    <br/>
+    <code>phylip</code>,
+    <br/>
+    <code>jalview</code>.
+    </td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;image&nbsp;<em>filename</em></code></td>
+    <td>Output an image of the open alignment window.  Format is specified by the sub-value modifier, a following <code>&#8209;&#8209;type</code> argument or guessed from the file extension.  Valid formats/extensions are:
+    <br/>
+    <code>svg</code>,
+    <br/>
+    <code>png</code>,
+    <br/>
+    <code>eps</code>,
+    <br/>
+    <code>html</code>,
+    <br/>
+    <code>biojs</code>.
+    </td>
+    <td>
+      <code>type=<em>name</em>,
+      <code>textrenderer=<em>name</em>,
+      <code>scale=<em>number</em>,
+      <code>width=<em>number</em>,
+      <code>height=<em>number</em>
+    </td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;type&nbsp;<em>name</em></code></td>
+    <td>Set the image format for the preceding <code>&#8209;&#8209;image</code> to <em>name</em>.  Valid values for <em>name</em> are:
+    <br/>
+    <code>svg</code>,
+    <br/>
+    <code>png</code>,
+    <br/>
+    <code>eps</code>,
+    <br/>
+    <code>html</code>,
+    <br/>
+    <code>biojs</code>.
+    </td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;textrenderer&nbsp;<em>name</em></code></td>
+    <td>Sets whether text in a vector image format (SVG, HTML, EPS) should be rendered as text or vector line-art.  Valid values for <em>name</em> are:
+    <br/>
+    <code>text</code>,
+    <br/>
+    <code>lineart</code>.
+    </td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;scale&nbsp;<em>number</em></code></td>
+    <td>Sets a scaling for bitmap image format (PNG).  Should be given as a floating point number.  This can also be set as a sub-value modifier to the <code>--image</code> value.  If used in conjunction with <code>--width</code> and <code>--height</code> then the smallest scaling will be used (<code>scale</code>, <code>width</code> and <code>height</code> provide bounds for the image).</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;width&nbsp;<em>number</em></code></td>
+    <td>Sets a width for bitmap image format (PNG) with the height maintaining the aspect ratio.  Should be given as a positive integer.  This can also be set as a sub-value modifier to the <code>--image</code> value.  If used in conjunction with <code>--scale</code> and <code>--height</code> then the smallest scaling will be used (<code>scale</code>, <code>width</code> and <code>height</code> provide bounds for the image).</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;height&nbsp;<em>number</em></code></td>
+    <td>Sets a height for bitmap image format (PNG) with the width maintaining the aspect ratio.  Should be given as a positive integer.  This can also be set as a sub-value modifier to the <code>--image</code> value.  If used in conjunction with <code>--scale</code> and <code>--width</code> then the smallest scaling will be used (<code>scale</code>, <code>width</code> and <code>height</code> provide bounds for the image).</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;groovy&nbsp;<em>filename</em></code></td>
+    <td>Process a groovy script in the file for the open alignment.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;backups / &#8209;&#8209;nobackups</code></td>
+    <td>Enable (or disable) writing backup files when saving an <code>&#8209;&#8209;output</code> file.  This applies to the current open alignment -- to apply to all <code>&#8209;&#8209;output</code> and <code>&#8209;&#8209;image</code> files, use after <code>&#8209;&#8209;all</code>.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;overwrite / &#8209;&#8209;nooverwrite</code></td>
+    <td>Enable (or disable) overwriting of output files without backups enabled.  This applies to the current open alignment -- to apply to all <code>&#8209;&#8209;output</code> and <code>&#8209;&#8209;image</code> files, use after <code>&#8209;&#8209;all</code>.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;close</code></td>
+    <td>Close the current open alignment window.  This occurs after other output arguments.  This applies to the current open alignment -- to apply to all <code>&#8209;&#8209;output</code> and <code>&#8209;&#8209;image</code> files, use after <code>&#8209;&#8209;all</code>.</td>
+    <td></td>
+    <td align="center">&#x2713;</td>
+    </tr>
+
+  </table>
+
+
+  <h2>Controlling flow of arguments</h2>
+
+  <table border="1" cellpadding="3">
+    <tr valign="top">
+    <td><strong>argument</strong></td>
+    <td><strong>action</strong></td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;new</code></td>
+    <td>
+    Move on to a new alignment window.  This will ensure <code>&#8209;&#8209;append</code> will start a new alignment window and other linked arguments will apply to the new alignment window.
+    <br/>
+    <em>Note</em> that <code>--open</code> already starts a new alignment window for each file it opens.
+    </td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;substitutions / &#8209;&#8209;nosubstitutions</code></td>
+    <td>The following argument values allow (or don't allow) subsituting filename parts.  This is initially true.  Valid substitutions are
+    <code>{basename}</code> - the filename-without-extension of the currently <code>&#8209;&#8209;open</code>ed file (or first <code>&#8209;&#8209;append</code>ed file),
+    <br/>
+    <code>{dirname}</code>, - the directory (folder) name of the currently <code>&#8209;&#8209;open</code>ed file (or first <code>&#8209;&#8209;append</code>ed file),
+    <br/>
+    <code>{argfilebasename}</code> - the filename-without-extension of the current <code>&#8209;&#8209;argfile</code>,
+    <br/>
+    <code>{argfiledirname}</code> - the directory (folder) name of the current <code>&#8209;&#8209;argfile</code>,
+    <br/>
+    <code>{n}</code> - the value of the index counter (starting at 0).
+    <br/>
+    <code>{++n}</code> - increase and substitute the value of the index counter,
+    <br/>
+    <code>{}</code> - the value of the current alignment window <em>default</em> index.
+    </td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;argfile&nbsp;<em>filename</em></code></td>
+    <td>
+    Open one or more files <em>filename</em> and read, line-by-line, as arguments to Jalview.
+    <br/>
+    Values in an argfile should be given with an equals sign ("=") separator with no spaces.
+    <br/>
+    <strong>Note</strong> that if you use one or more <code>&#8209;&#8209;argfile</code> arguments then all other non-initialising arguments will be ignored.
+    </td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;npp</code></td>
+    <td>Increase the index counter used in argument value substitutions.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;all / &#8209;&#8209;noall</code></td>
+    <td>Apply (or stop applying) the following output arguments to <em>all</em> sets of linked arguments.</td>
+    </tr>
+
+    <tr valign="top">
+    <td><code>&#8209;&#8209;quit</code></td>
+    <td>After all files have been opened, appended and output, quit Jalview.  In <code>&#8209;&#8209;headless</code> mode this already happens.</td>
+    </tr>
+
+  </table>
+
 </body>
 </html>
index 381478f..b162740 100644 (file)
@@ -46,8 +46,7 @@
       <li>For older versions of Jalview, call the <a href="#olderinstalls">native launch program directly</a>.
   </ul>
   <p>
-    <strong><a name="script">Jalview's command line launch
-        script</a></strong>
+    <strong><a name="script">Jalview's command line launch</a></strong>
   <p>Since version 2.11.2, the Jalview native application includes a <strong>launching shell script</strong>. This is the easiest way to
   launch an installed Jalview application from the command line. </p><p>To run the <strong>launch script</strong>, simply open a Terminal (or Command prompt on Windows), and type:<pre>
   jalview</pre>
     included in the source distribution.
   </p>
   <p>
-    Use '-help' to get more information on the <a
+    Use '--help' to get more information on the <a
       href="clarguments.html">command line arguments</a> that Jalview
     accepts.
   </p>
index 6534d2d..fef5bb9 100755 (executable)
           and the intensity threshold for transition between them. </em></li>
       <li>Colour Scheme options: <strong>None, ClustalX,
           Blosum62 Score, Percentage Identity, Zappo, Taylor,
-         gecos:flower, gecos:blossom, gecos:sunset, gecos:ocean,
+         gecos-flower, gecos-blossom, gecos-sunset, gecos-ocean,
           Hydrophobicity, Helix Propensity, Strand Propensity, Turn
           Propensity, Buried Index, Nucleotide, Nucleotide Ambiguity, Purine/Pyrimidine, User
           Defined<br>
index 5b8d00b..33da749 100755 (executable)
@@ -38,7 +38,7 @@
         the intensity threshold for transition between them. </em></li>
     <li>Colour Scheme options: <strong>None, ClustalX,
         Blosum62 Score, Percentage Identity, Zappo, Taylor,
-       gecos:flower, gecos:blossom, gecos:sunset, gecos:ocean,
+       gecos-flower, gecos-blossom, gecos-sunset, gecos-ocean,
         Hydrophobicity, Helix Propensity, Strand Propensity, Turn
         Propensity, Buried Index, Nucleotide, Nucleotide Ambiguity, Purine/Pyrimidine, User
         Defined<br>
index 271454a..006886f 100644 (file)
@@ -220,10 +220,10 @@ label.colourScheme_nucleotideambiguity = Nucleotide Ambiguity
 label.colourScheme_t-coffeescores = T-Coffee Scores
 label.colourScheme_rnahelices = By RNA Helices
 label.colourScheme_sequenceid = Sequence ID Colour
-label.colourScheme_gecos\:flower = gecos Flower
-label.colourScheme_gecos\:blossom = gecos Blossom
-label.colourScheme_gecos\:sunset = gecos Sunset
-label.colourScheme_gecos\:ocean = gecos Ocean
+label.colourScheme_gecos-flower = gecos Flower
+label.colourScheme_gecos-blossom = gecos Blossom
+label.colourScheme_gecos-sunset = gecos Sunset
+label.colourScheme_gecos-ocean = gecos Ocean
 label.blc = BLC
 label.fasta = Fasta
 label.msf = MSF
index c427104..4ac298f 100644 (file)
@@ -211,10 +211,10 @@ label.colourScheme_nucleotideambiguity = Ambig
 label.colourScheme_t-coffeescores = Puntuación del T-Coffee
 label.colourScheme_rnahelices = Por hélices de RNA
 label.colourScheme_sequenceid = Color de ID de secuencia
-label.colourScheme_gecos\:flower = gecos Flower
-label.colourScheme_gecos\:blossom = gecos Blossom
-label.colourScheme_gecos\:sunset = gecos Sunset
-label.colourScheme_gecos\:ocean = gecos Ocean
+label.colourScheme_gecos-flower = gecos Flower
+label.colourScheme_gecos-blossom = gecos Blossom
+label.colourScheme_gecos-sunset = gecos Sunset
+label.colourScheme_gecos-ocean = gecos Ocean
 label.blc = BLC
 label.fasta = Fasta
 label.msf = MSF
diff --git a/src/jalview/bin/ArgParser.java b/src/jalview/bin/ArgParser.java
deleted file mode 100644 (file)
index c7527c8..0000000
+++ /dev/null
@@ -1,1107 +0,0 @@
-/*
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
- * PURPOSE.  See the GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.bin;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URLDecoder;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import jalview.util.Platform;
-
-public class ArgParser
-{
-  private static final String NEGATESTRING = "no";
-
-  private static final String DEFAULTLINKEDID = "";
-
-  private static enum Opt
-  {
-    BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES
-  }
-
-  // These bootstrap args are simply parsed before a full parse of arguments and
-  // so are accessible at an earlier stage to (e.g.) set debug log leve, provide
-  // a props file (that might set log level), run headlessly, read an argfile
-  // instead of other args.
-  private static final Collection<Arg> bootstrapArgs = new ArrayList(
-          Arrays.asList(Arg.PROPS, Arg.DEBUG, Arg.HEADLESS, Arg.ARGFILE));
-
-  public enum Arg
-  {
-    /*
-    NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
-    FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
-    NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
-    OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
-    VSESS;
-    */
-    HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
-    COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
-    ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
-    USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
-    VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
-    TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
-    NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, DEBUG("d"), ARGFILE;
-
-    static
-    {
-      HELP.setOptions(Opt.UNARY);
-      CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
-                                                 // expecting "--nocalculation"
-      MENUBAR.setOptions(true, Opt.BOOLEAN);
-      STATUS.setOptions(true, Opt.BOOLEAN);
-      SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
-      ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
-      COLOUR.setOptions(Opt.STRING, Opt.LINKED);
-      FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
-      GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
-      GROUPS.setOptions(Opt.STRING, Opt.LINKED);
-      HEADLESS.setOptions(Opt.UNARY);
-      JABAWS.setOptions(Opt.STRING);
-      ANNOTATION.setOptions(true, Opt.BOOLEAN);
-      ANNOTATION2.setOptions(true, Opt.BOOLEAN);
-      DISPLAY.setOptions(true, Opt.BOOLEAN);
-      GUI.setOptions(true, Opt.BOOLEAN);
-      NEWS.setOptions(true, Opt.BOOLEAN);
-      NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
-                                             // expects a string value
-      SORTBYTREE.setOptions(true, Opt.BOOLEAN);
-      USAGESTATS.setOptions(true, Opt.BOOLEAN);
-      OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
-      OPEN2.setOptions(Opt.STRING, Opt.LINKED);
-      PROPS.setOptions(Opt.STRING);
-      QUESTIONNAIRE.setOptions(Opt.STRING);
-      SETPROP.setOptions(Opt.STRING);
-      TREE.setOptions(Opt.STRING);
-
-      VDOC.setOptions(Opt.UNARY);
-      VSESS.setOptions(Opt.UNARY);
-
-      OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
-      OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
-
-      SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
-      NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
-      TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
-      TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
-      TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
-      TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
-      TITLE.setOptions(Opt.STRING, Opt.LINKED);
-      PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
-      NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
-      STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
-      WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
-      IMAGE.setOptions(Opt.STRING, Opt.LINKED);
-      QUIT.setOptions(Opt.UNARY);
-      DEBUG.setOptions(Opt.BOOLEAN);
-      ARGFILE.setOptions(Opt.STRING);
-    }
-
-    private final String[] argNames;
-
-    private Opt[] argOptions;
-
-    private boolean defaultBoolValue = false;
-
-    public String toLongString()
-    {
-      StringBuilder sb = new StringBuilder();
-      sb.append("Arg: ").append(this.name());
-      for (String name : getNames())
-      {
-        sb.append(", '").append(name).append("'");
-      }
-      sb.append("\nOptions: ");
-      boolean first = true;
-      for (Opt o : argOptions)
-      {
-        if (!first)
-        {
-          sb.append(", ");
-        }
-        sb.append(o.toString());
-        first = false;
-      }
-      sb.append("\n");
-      return sb.toString();
-    }
-
-    private Arg()
-    {
-      this(new String[0]);
-    }
-
-    private Arg(String... names)
-    {
-      int length = (names == null || names.length == 0
-              || (names.length == 1 && names[0] == null)) ? 1
-                      : names.length + 1;
-      this.argNames = new String[length];
-      this.argNames[0] = this.getName();
-      if (length > 1)
-        System.arraycopy(names, 0, this.argNames, 1, names.length);
-    }
-
-    public String[] getNames()
-    {
-      return argNames;
-    }
-
-    public String getName()
-    {
-      return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
-    }
-
-    @Override
-    public final String toString()
-    {
-      return getName();
-    }
-
-    public boolean hasOption(Opt o)
-    {
-      if (argOptions == null)
-        return false;
-      for (Opt option : argOptions)
-      {
-        if (o == option)
-          return true;
-      }
-      return false;
-    }
-
-    protected void setOptions(Opt... options)
-    {
-      setOptions(false, options);
-    }
-
-    protected void setOptions(boolean defaultBoolValue, Opt... options)
-    {
-      this.defaultBoolValue = defaultBoolValue;
-      argOptions = options;
-    }
-
-    protected boolean getDefaultBoolValue()
-    {
-      return defaultBoolValue;
-    }
-  }
-
-  public static class ArgValues
-  {
-    private static final String ID = "id";
-
-    private Arg arg;
-
-    private int argCount = 0;
-
-    private boolean boolValue = false;
-
-    private boolean negated = false;
-
-    private int boolIndex = -1;
-
-    private List<Integer> argsIndexes;
-
-    private List<ArgValue> argValueList;
-
-    private Map<String, ArgValue> idMap = new HashMap<>();
-
-    protected ArgValues(Arg a)
-    {
-      this.arg = a;
-      this.argValueList = new ArrayList<ArgValue>();
-      this.boolValue = arg.getDefaultBoolValue();
-    }
-
-    public Arg arg()
-    {
-      return arg;
-    }
-
-    protected int getCount()
-    {
-      return argCount;
-    }
-
-    protected void incrementCount()
-    {
-      argCount++;
-    }
-
-    protected void setNegated(boolean b)
-    {
-      this.negated = b;
-    }
-
-    protected boolean isNegated()
-    {
-      return this.negated;
-    }
-
-    protected void setBoolean(boolean b, int i)
-    {
-      this.boolValue = b;
-      this.boolIndex = i;
-    }
-
-    protected boolean getBoolean()
-    {
-      return this.boolValue;
-    }
-
-    @Override
-    public String toString()
-    {
-      if (argValueList == null)
-        return null;
-      StringBuilder sb = new StringBuilder();
-      sb.append(arg.toLongString());
-      if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
-        sb.append("Boolean: ").append(boolValue).append("; Default: ")
-                .append(arg.getDefaultBoolValue()).append("; Negated: ")
-                .append(negated).append("\n");
-      if (arg.hasOption(Opt.STRING))
-      {
-        sb.append("Values:");
-        boolean first = true;
-        for (ArgValue av : argValueList)
-        {
-          String v = av.getValue();
-          if (!first)
-            sb.append(",");
-          sb.append("\n  '");
-          sb.append(v).append("'");
-          first = false;
-        }
-        sb.append("\n");
-      }
-      sb.append("Count: ").append(argCount).append("\n");
-      return sb.toString();
-    }
-
-    protected void addValue()
-    {
-      addValue(null, -1);
-    }
-
-    protected void addValue(String val, int argIndex)
-    {
-      addArgValue(new ArgValue(val, argIndex));
-    }
-
-    protected void addArgValue(ArgValue av)
-    {
-      if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
-              || (arg.hasOption(Opt.NODUPLICATEVALUES)
-                      && argValueList.contains(av.getValue())))
-        return;
-      if (argValueList == null)
-      {
-        argValueList = new ArrayList<ArgValue>();
-      }
-      SubVals sv = ArgParser.getSubVals(av.getValue());
-      if (sv.has(ID))
-      {
-        String id = sv.get(ID);
-        av.setId(id);
-        idMap.put(id, av);
-      }
-      argValueList.add(av);
-    }
-
-    protected boolean hasValue(String val)
-    {
-      return argValueList.contains(val);
-    }
-
-    protected ArgValue getArgValue()
-    {
-      if (arg.hasOption(Opt.MULTI))
-        Console.warn("Requesting single value for multi value argument");
-      return argValueList.size() > 0 ? argValueList.get(0) : null;
-    }
-
-    protected List<ArgValue> getArgValueList()
-    {
-      return argValueList;
-    }
-
-    protected boolean hasId(String id)
-    {
-      return idMap.containsKey(id);
-    }
-
-    protected ArgValue getId(String id)
-    {
-      return idMap.get(id);
-    }
-  }
-
-  // old style
-  private List<String> vargs = null;
-
-  private boolean isApplet;
-
-  // private AppletParams appletParams;
-
-  public boolean isApplet()
-  {
-    return isApplet;
-  }
-
-  public String nextValue()
-  {
-    return vargs.remove(0);
-  }
-
-  public int getSize()
-  {
-    return vargs.size();
-  }
-
-  public String getValue(String arg)
-  {
-    return getValue(arg, false);
-  }
-
-  public String getValue(String arg, boolean utf8decode)
-  {
-    int index = vargs.indexOf(arg);
-    String dc = null;
-    String ret = null;
-    if (index != -1)
-    {
-      ret = vargs.get(index + 1).toString();
-      vargs.remove(index);
-      vargs.remove(index);
-      if (utf8decode && ret != null)
-      {
-        try
-        {
-          dc = URLDecoder.decode(ret, "UTF-8");
-          ret = dc;
-        } catch (Exception e)
-        {
-          // TODO: log failure to decode
-        }
-      }
-    }
-    return ret;
-  }
-
-  /*
-  public Object getAppletValue(String key, String def, boolean asString)
-  {
-    Object value;
-    return (appletParams == null ? null
-            : (value = appletParams.get(key.toLowerCase())) == null ? def
-                    : asString ? "" + value : value);
-  }
-  */
-
-  // new style
-  private static final Map<String, Arg> argMap;
-
-  private Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
-
-  private List<String> linkedOrder = null;
-
-  private List<Arg> argList;
-
-  static
-  {
-    argMap = new HashMap<>();
-    for (Arg a : EnumSet.allOf(Arg.class))
-    {
-      ARGNAME: for (String argName : a.getNames())
-      {
-        if (argMap.containsKey(argName))
-        {
-          Console.warn("Trying to add argument name multiple times: '"
-                  + argName + "'"); // RESTORE THIS WHEN MERGED
-          if (argMap.get(argName) != a)
-          {
-            Console.error(
-                    "Trying to add argument name multiple times for different Args: '"
-                            + argMap.get(argName).getName() + ":" + argName
-                            + "' and '" + a.getName() + ":" + argName
-                            + "'");
-          }
-          continue ARGNAME;
-        }
-        argMap.put(argName, a);
-      }
-    }
-  }
-
-  public ArgParser(String[] args)
-  {
-    // old style
-    vargs = new ArrayList<>();
-    isApplet = (args.length > 0 && args[0].startsWith("<applet"));
-    if (isApplet)
-    {
-      // appletParams = AppletParams.getAppletParams(args, vargs);
-    }
-    else
-    {
-      if (Platform.isJS())
-
-      {
-        isApplet = true;
-        // appletParams =
-        // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
-      }
-      for (int i = 0; i < args.length; i++)
-      {
-        String arg = args[i].trim();
-        if (arg.charAt(0) == '-')
-        {
-          arg = arg.substring(1);
-        }
-        vargs.add(arg);
-      }
-    }
-
-    // new style
-    Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
-    int argIndex = 0;
-    while (argE.hasMoreElements())
-    {
-      String arg = argE.nextElement();
-      String argName = null;
-      String val = null;
-      String linkedId = null;
-      if (arg.startsWith("--"))
-      {
-        int equalPos = arg.indexOf('=');
-        if (equalPos > -1)
-        {
-          argName = arg.substring(2, equalPos);
-          val = arg.substring(equalPos + 1);
-        }
-        else
-        {
-          argName = arg.substring(2);
-        }
-        int idOpen = argName.indexOf('[');
-        int idClose = argName.indexOf(']');
-
-        if (idOpen > -1 && idClose == argName.length() - 1)
-        {
-          linkedId = argName.substring(idOpen + 1, idClose);
-          argName = argName.substring(0, idOpen);
-        }
-
-        Arg a = argMap.get(argName);
-        // check for boolean prepended by "no"
-        boolean negated = false;
-        if (a == null && argName.startsWith(NEGATESTRING) && argMap
-                .containsKey(argName.substring(NEGATESTRING.length())))
-        {
-          argName = argName.substring(NEGATESTRING.length());
-          a = argMap.get(argName);
-          negated = true;
-        }
-
-        // check for config errors
-        if (a == null)
-        {
-          // arg not found
-          Console.error("Argument '" + arg + "' not recognised. Ignoring.");
-          continue;
-        }
-        if (!a.hasOption(Opt.BOOLEAN) && negated)
-        {
-          // used "no" with a non-boolean option
-          Console.error("Argument '--" + NEGATESTRING + argName
-                  + "' not a boolean option. Ignoring.");
-          continue;
-        }
-        if (!a.hasOption(Opt.STRING) && equalPos > -1)
-        {
-          // set --argname=value when arg does not accept values
-          Console.error("Argument '--" + argName
-                  + "' does not expect a value (given as '" + arg
-                  + "').  Ignoring.");
-          continue;
-        }
-        if (!a.hasOption(Opt.LINKED) && linkedId != null)
-        {
-          // set --argname[linkedId] when arg does not use linkedIds
-          Console.error("Argument '--" + argName
-                  + "' does not expect a linked id (given as '" + arg
-                  + "'). Ignoring.");
-          continue;
-        }
-
-        if (a.hasOption(Opt.STRING) && equalPos == -1)
-        {
-          // take next arg as value if required, and '=' was not found
-          if (!argE.hasMoreElements())
-          {
-            // no value to take for arg, which wants a value
-            Console.error("Argument '" + a.getName()
-                    + "' requires a value, none given. Ignoring.");
-            continue;
-          }
-          val = argE.nextElement();
-        }
-
-        // use default linkedId for linked arguments
-        if (a.hasOption(Opt.LINKED) && linkedId == null)
-          linkedId = DEFAULTLINKEDID;
-
-        if (!linkedArgs.containsKey(linkedId))
-          linkedArgs.put(linkedId, new ArgValuesMap());
-
-        ArgValuesMap avm = linkedArgs.get(linkedId);
-
-        if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
-        {
-          Console.error("Argument '--" + argName
-                  + "' cannot contain a duplicate value ('" + val
-                  + "'). Ignoring this and subsequent occurrences.");
-          continue;
-        }
-
-        // check for unique id
-        SubVals sv = ArgParser.getSubVals(val);
-        String id = sv.get(ArgValues.ID);
-        if (id != null && avm.hasId(a, id))
-        {
-          Console.error("Argument '--" + argName + "' has a duplicate id ('"
-                  + id + "'). Ignoring.");
-          continue;
-        }
-
-        ArgValues avs = avm.getOrCreateArgValues(a);
-        if (avs == null)
-        {
-          avs = new ArgValues(a);
-        }
-        // store appropriate value
-        if (a.hasOption(Opt.STRING))
-        {
-          avs.addValue(val, argIndex);
-        }
-        else if (a.hasOption(Opt.BOOLEAN))
-        {
-          avs.setBoolean(!negated, argIndex);
-          avs.setNegated(negated);
-        }
-        else if (a.hasOption(Opt.UNARY))
-        {
-          avs.setBoolean(true, argIndex);
-        }
-        avs.incrementCount();
-
-        // store in appropriate place
-        if (a.hasOption(Opt.LINKED))
-        {
-          // allow a default linked id for single usage
-          if (linkedId == null)
-            linkedId = DEFAULTLINKEDID;
-          // store the order of linkedIds
-          if (linkedOrder == null)
-            linkedOrder = new ArrayList<>();
-          if (!linkedOrder.contains(linkedId))
-            linkedOrder.add(linkedId);
-        }
-
-        // store arg in the list of args used
-        if (argList == null)
-          argList = new ArrayList<>();
-        if (!argList.contains(a))
-          argList.add(a);
-      }
-    }
-  }
-
-  public boolean isSet(Arg a)
-  {
-    return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
-  }
-
-  public boolean isSet(String linkedId, Arg a)
-  {
-    ArgValuesMap avm = linkedArgs.get(linkedId);
-    return avm == null ? false : avm.containsArg(a);
-  }
-
-  public boolean getBool(Arg a)
-  {
-    if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
-    {
-      Console.warn("Getting boolean from non boolean Arg '" + a.getName()
-              + "'.");
-    }
-    return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
-  }
-
-  public boolean getBool(String linkedId, Arg a)
-  {
-    ArgValuesMap avm = linkedArgs.get(linkedId);
-    if (avm == null)
-      return a.getDefaultBoolValue();
-    ArgValues avs = avm.getArgValues(a);
-    return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
-  }
-
-  public List<String> linkedIds()
-  {
-    return linkedOrder;
-  }
-
-  public ArgValuesMap linkedArgs(String id)
-  {
-    return linkedArgs.get(id);
-  }
-
-  @Override
-  public String toString()
-  {
-    StringBuilder sb = new StringBuilder();
-    sb.append("UNLINKED\n");
-    sb.append(argValuesMapToString(linkedArgs.get(null)));
-    if (linkedIds() != null)
-    {
-      sb.append("LINKED\n");
-      for (String id : linkedIds())
-      {
-        // already listed these as UNLINKED args
-        if (id == null)
-          continue;
-
-        ArgValuesMap avm = linkedArgs(id);
-        sb.append("ID: '").append(id).append("'\n");
-        sb.append(argValuesMapToString(avm));
-      }
-    }
-    return sb.toString();
-  }
-
-  private static String argValuesMapToString(ArgValuesMap avm)
-  {
-    if (avm == null)
-      return null;
-    StringBuilder sb = new StringBuilder();
-    for (Arg a : avm.getArgKeys())
-    {
-      ArgValues v = avm.getArgValues(a);
-      sb.append(v.toString());
-      sb.append("\n");
-    }
-    return sb.toString();
-  }
-
-  public static SubVals getSubVals(String item)
-  {
-    return new SubVals(item);
-  }
-
-  /**
-   * A helper class to keep an index of argument position with argument values
-   */
-  public static class ArgValue
-  {
-    private int argIndex;
-
-    private String value;
-
-    private String id;
-
-    protected ArgValue(String value, int argIndex)
-    {
-      this.value = value;
-      this.argIndex = argIndex;
-    }
-
-    protected String getValue()
-    {
-      return value;
-    }
-
-    protected int getArgIndex()
-    {
-      return argIndex;
-    }
-
-    protected void setId(String i)
-    {
-      id = i;
-    }
-
-    protected String getId()
-    {
-      return id;
-    }
-  }
-
-  /**
-   * A helper class to parse a string of the possible forms "content"
-   * "[index]content", "[keyName=keyValue]content" and return the integer index,
-   * the strings keyName and keyValue, and the content after the square brackets
-   * (if present). Values not set `will be -1 or null.
-   */
-  public static class SubVals
-  {
-    private static int NOTSET = -1;
-
-    private int index = NOTSET;
-
-    private Map<String, String> subVals = null;
-
-    private static char SEPARATOR = ';';
-
-    private String content = null;
-
-    public SubVals(String item)
-    {
-      this.parseVals(item);
-    }
-
-    public void parseVals(String item)
-    {
-      if (item == null)
-        return;
-      if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
-      {
-        int openBracket = item.indexOf('[');
-        int closeBracket = item.indexOf(']');
-        String subvalsString = item.substring(openBracket + 1,
-                closeBracket);
-        this.content = item.substring(closeBracket + 1);
-        boolean setIndex = false;
-        for (String subvalString : subvalsString
-                .split(Character.toString(SEPARATOR)))
-        {
-          int equals = subvalString.indexOf('=');
-          if (equals > -1)
-          {
-            if (subVals == null)
-              subVals = new HashMap<>();
-            subVals.put(subvalString.substring(0, equals),
-                    subvalString.substring(equals + 1));
-          }
-          else
-          {
-            try
-            {
-              this.index = Integer.parseInt(subvalString);
-              setIndex = true;
-            } catch (NumberFormatException e)
-            {
-              Console.warn("Failed to obtain subvalue or index from '"
-                      + item + "'. Setting index=0 and using content='"
-                      + content + "'.");
-            }
-          }
-        }
-        if (!setIndex)
-          this.index = NOTSET;
-      }
-      else
-      {
-        this.content = item;
-      }
-    }
-
-    public boolean notSet()
-    {
-      // notSet is true if content present but nonsensical
-      return index == NOTSET && subVals == null;
-    }
-
-    public String get(String key)
-    {
-      return subVals == null ? null : subVals.get(key);
-    }
-
-    public boolean has(String key)
-    {
-      return subVals == null ? false : subVals.containsKey(key);
-    }
-
-    public int getIndex()
-    {
-      return index;
-    }
-
-    public String getContent()
-    {
-      return content;
-    }
-  }
-
-  /**
-   * Helper class to allow easy extraction of information about specific
-   * argument values (without having to check for null etc all the time)
-   */
-  protected static class ArgValuesMap
-  {
-    protected Map<Arg, ArgValues> m;
-
-    protected ArgValuesMap()
-    {
-      this.newMap();
-    }
-
-    protected ArgValuesMap(Map<Arg, ArgValues> map)
-    {
-      this.m = map;
-    }
-
-    private Map<Arg, ArgValues> getMap()
-    {
-      return m;
-    }
-
-    private void newMap()
-    {
-      m = new HashMap<Arg, ArgValues>();
-    }
-
-    private void newArg(Arg a)
-    {
-      if (m == null)
-        newMap();
-      if (!containsArg(a))
-        m.put(a, new ArgValues(a));
-    }
-
-    protected void addArgValue(Arg a, ArgValue av)
-    {
-      if (getMap() == null)
-        m = new HashMap<Arg, ArgValues>();
-
-      if (!m.containsKey(a))
-        m.put(a, new ArgValues(a));
-      ArgValues avs = m.get(a);
-      avs.addArgValue(av);
-    }
-
-    protected ArgValues getArgValues(Arg a)
-    {
-      return m == null ? null : m.get(a);
-    }
-
-    protected ArgValues getOrCreateArgValues(Arg a)
-    {
-      ArgValues avs = m.get(a);
-      if (avs == null)
-        newArg(a);
-      return getArgValues(a);
-    }
-
-    protected List<ArgValue> getArgValueList(Arg a)
-    {
-      ArgValues avs = getArgValues(a);
-      return avs == null ? new ArrayList<>() : avs.getArgValueList();
-    }
-
-    protected ArgValue getArgValue(Arg a)
-    {
-      List<ArgValue> vals = getArgValueList(a);
-      return (vals == null || vals.size() == 0) ? null : vals.get(0);
-    }
-
-    protected String getValue(Arg a)
-    {
-      ArgValue av = getArgValue(a);
-      return av == null ? null : av.getValue();
-    }
-
-    protected boolean containsArg(Arg a)
-    {
-      if (m == null || !m.containsKey(a))
-        return false;
-      return a.hasOption(Opt.STRING) ? getArgValue(a) != null
-              : this.getBoolean(a);
-    }
-
-    protected boolean hasValue(Arg a, String val)
-    {
-      if (m == null || !m.containsKey(a))
-        return false;
-      for (ArgValue av : getArgValueList(a))
-      {
-        String avVal = av.getValue();
-        if ((val == null && avVal == null)
-                || (val != null && val.equals(avVal)))
-        {
-          return true;
-        }
-      }
-      return false;
-    }
-
-    protected boolean getBoolean(Arg a)
-    {
-      ArgValues av = getArgValues(a);
-      return av == null ? false : av.getBoolean();
-    }
-
-    protected Set<Arg> getArgKeys()
-    {
-      return m.keySet();
-    }
-
-    protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
-            Arg a)
-    {
-      ArgValue closestAv = null;
-      int thisArgIndex = thisAv.getArgIndex();
-      ArgValues compareAvs = this.getArgValues(a);
-      int closestPreviousIndex = -1;
-      for (ArgValue av : compareAvs.getArgValueList())
-      {
-        int argIndex = av.getArgIndex();
-        if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
-        {
-          closestPreviousIndex = argIndex;
-          closestAv = av;
-        }
-      }
-      return closestAv;
-    }
-
-    protected ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
-    {
-      // this looks for the *next* arg that *might* be referring back to
-      // a thisAv. Such an arg would have no subValues (if it does it should
-      // specify an id in the subValues so wouldn't need to be guessed).
-      ArgValue closestAv = null;
-      int thisArgIndex = thisAv.getArgIndex();
-      ArgValues compareAvs = this.getArgValues(a);
-      int closestNextIndex = Integer.MAX_VALUE;
-      for (ArgValue av : compareAvs.getArgValueList())
-      {
-        int argIndex = av.getArgIndex();
-        if (argIndex > thisArgIndex && argIndex < closestNextIndex)
-        {
-          closestNextIndex = argIndex;
-          closestAv = av;
-        }
-      }
-      return closestAv;
-    }
-
-    protected ArgValue[] getArgValuesReferringTo(String key, String value,
-            Arg a)
-    {
-      // this looks for the *next* arg that *might* be referring back to
-      // a thisAv. Such an arg would have no subValues (if it does it should
-      // specify an id in the subValues so wouldn't need to be guessed).
-      List<ArgValue> avList = new ArrayList<>();
-      Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
-              : new Arg[]
-              { a };
-      for (Arg keyArg : args)
-      {
-        for (ArgValue av : this.getArgValueList(keyArg))
-        {
-
-        }
-      }
-      return (ArgValue[]) avList.toArray();
-    }
-
-    protected boolean hasId(Arg a, String id)
-    {
-      ArgValues avs = this.getArgValues(a);
-      return avs == null ? false : avs.hasId(id);
-    }
-
-    protected ArgValue getId(Arg a, String id)
-    {
-      ArgValues avs = this.getArgValues(a);
-      return avs == null ? null : avs.getId(id);
-    }
-  }
-
-  public static Map<Arg, String> bootstrapArgs(String[] args)
-  {
-    Map<Arg, String> bootstrapArgMap = new HashMap<>();
-    if (args == null)
-      return bootstrapArgMap;
-    Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
-    while (argE.hasMoreElements())
-    {
-      String arg = argE.nextElement();
-      String argName = null;
-      String val = null;
-      if (arg.startsWith("--"))
-      {
-        int equalPos = arg.indexOf('=');
-        if (equalPos > -1)
-        {
-          argName = arg.substring(2, equalPos);
-          val = arg.substring(equalPos + 1);
-        }
-        else
-        {
-          argName = arg.substring(2);
-        }
-        Arg a = argMap.get(argName);
-        if (a != null && bootstrapArgs.contains(a))
-          bootstrapArgMap.put(a, val);
-      }
-    }
-    return bootstrapArgMap;
-  }
-
-  public static ArgParser parseArgFile(String argFilename)
-  {
-    List<String> argsList = null;
-    File argFile = new File(argFilename);
-    if (!argFile.exists())
-    {
-      System.err.println("--" + Arg.ARGFILE.name().toLowerCase(Locale.ROOT)
-              + "=\"" + argFilename + "\": File does not exist.");
-      System.exit(2);
-    }
-    try
-    {
-      argsList = Files.readAllLines(Paths.get(argFilename));
-    } catch (IOException e)
-    {
-      System.err.println("--" + Arg.ARGFILE.name().toLowerCase(Locale.ROOT)
-              + "=\"" + argFilename + "\": File could not be read.");
-      System.exit(3);
-    }
-    return new ArgParser((String[]) argsList.toArray());
-  }
-}
\ No newline at end of file
index 698cbb8..f4c8854 100755 (executable)
@@ -244,6 +244,9 @@ public class Cache
    */
   public static final String JALVIEWLOGLEVEL = "logs.Jalview.level";
 
+  // for tests
+  public static final String BOOTSTRAP_TEST = "BOOTSTRAP_TEST";
+
   /**
    * Sifts settings
    */
@@ -380,10 +383,13 @@ public class Cache
         {
           // props file provided as URL
           fis = new URL(propertiesFile).openStream();
-          System.out.println(
-                  "Loading jalview properties from : " + propertiesFile);
-          System.out.println(
-                  "Disabling Jalview writing to user's local properties file.");
+          if (!Jalview.quiet())
+          {
+            System.out.println(
+                    "Loading jalview properties from : " + propertiesFile);
+            System.out.println(
+                    "Disabling Jalview writing to user's local properties file.");
+          }
           propsAreReadOnly = true;
         } catch (Exception ex)
         {
@@ -411,7 +417,8 @@ public class Cache
         fis.close();
       } catch (Exception ex)
       {
-        System.out.println("Error reading properties file: " + ex);
+        if (!Jalview.quiet())
+          System.out.println("Error reading properties file: " + ex);
       }
     }
 
@@ -467,7 +474,8 @@ public class Cache
       }
     } catch (Exception ex)
     {
-      System.out.println("Error reading author details: " + ex);
+      if (!Jalview.quiet())
+        System.out.println("Error reading author details: " + ex);
       authorDetails = null;
     }
     if (authorDetails == null)
@@ -530,8 +538,8 @@ public class Cache
           if (orgtimeout == null)
           {
             orgtimeout = "30";
-            System.out.println("# INFO: Setting default net timeout to "
-                    + orgtimeout + " seconds.");
+            Console.debug("Setting default net timeout to " + orgtimeout
+                    + " seconds.");
           }
           String remoteVersion = null;
           if (remoteBuildPropertiesUrl.startsWith("http"))
@@ -551,10 +559,13 @@ public class Cache
               remoteVersion = remoteBuildProperties.getProperty("VERSION");
             } catch (Exception ex)
             {
-              System.out.println(
-                      "Non-fatal exception when checking version at "
-                              + remoteBuildPropertiesUrl + ":");
-              System.out.println(ex);
+              if (!Jalview.quiet())
+              {
+                System.out.println(
+                        "Non-fatal exception when checking version at "
+                                + remoteBuildPropertiesUrl + ":");
+                System.out.println(ex);
+              }
               remoteVersion = getProperty("VERSION");
             }
           }
@@ -653,7 +664,8 @@ public class Cache
       }
     } catch (Exception ex)
     {
-      System.out.println("Error reading build details: " + ex);
+      if (!Jalview.quiet())
+        System.out.println("Error reading build details: " + ex);
       applicationProperties.remove("VERSION");
     }
     String codeVersion = getProperty("VERSION");
@@ -674,7 +686,7 @@ public class Cache
     if (printVersion && reportVersion)
     {
       System.out.println(ChannelProperties.getProperty("app_name")
-              + " Version: " + codeVersion + codeInstallation);
+              + " version: " + codeVersion + codeInstallation);
     }
   }
 
@@ -735,8 +747,9 @@ public class Cache
         def = Integer.parseInt(string);
       } catch (NumberFormatException e)
       {
-        System.out.println("Error parsing int property '" + property
-                + "' with value '" + string + "'");
+        if (!Jalview.quiet())
+          System.out.println("Error parsing int property '" + property
+                  + "' with value '" + string + "'");
       }
     }
 
@@ -777,8 +790,9 @@ public class Cache
       }
     } catch (Exception ex)
     {
-      System.out.println(
-              "Error setting property: " + key + " " + obj + "\n" + ex);
+      if (!Jalview.quiet())
+        System.out.println(
+                "Error setting property: " + key + " " + obj + "\n" + ex);
     }
     return oldValue;
   }
@@ -808,7 +822,8 @@ public class Cache
         out.close();
       } catch (Exception ex)
       {
-        System.out.println("Error saving properties: " + ex);
+        if (!Jalview.quiet())
+          System.out.println("Error saving properties: " + ex);
       }
     }
   }
@@ -1146,7 +1161,8 @@ public class Cache
         }
       } catch (Exception ex)
       {
-        System.out.println("Error loading User ColourFile\n" + ex);
+        if (!Jalview.quiet())
+          System.out.println("Error loading User ColourFile\n" + ex);
       }
     }
     if (!files.equals(coloursFound.toString()))
@@ -1622,7 +1638,7 @@ public class Cache
   }
 
   private static final Collection<String> bootstrapProperties = new ArrayList<>(
-          Arrays.asList(JALVIEWLOGLEVEL));
+          Arrays.asList(JALVIEWLOGLEVEL, BOOTSTRAP_TEST));
 
   public static Properties bootstrapProperties(String filename)
   {
@@ -1648,7 +1664,9 @@ public class Cache
       file = new File(releasePropertiesFilename);
     }
 
-    if (filename == null || !file.exists())
+    if (filename == null)
+      return null;
+    if (!file.exists())
     {
       System.err.println("Could not load bootstrap preferences file '"
               + filename + "'");
index b42f08e..50ff7c3 100644 (file)
@@ -2,9 +2,11 @@ package jalview.bin;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -12,138 +14,144 @@ import java.util.Locale;
 import java.util.Map;
 
 import jalview.analysis.AlignmentUtils;
-import jalview.api.AlignmentViewPanel;
-import jalview.bin.ArgParser.Arg;
-import jalview.bin.ArgParser.ArgValue;
-import jalview.bin.ArgParser.ArgValuesMap;
-import jalview.bin.ArgParser.SubVals;
-import jalview.datamodel.AlignmentAnnotation;
+import jalview.bin.argparser.Arg;
+import jalview.bin.argparser.ArgParser;
+import jalview.bin.argparser.ArgParser.Position;
+import jalview.bin.argparser.ArgValue;
+import jalview.bin.argparser.ArgValues;
+import jalview.bin.argparser.ArgValuesMap;
+import jalview.bin.argparser.SubVals;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.annotations.AlphaFoldAnnotationRowBuilder;
 import jalview.gui.AlignFrame;
 import jalview.gui.AlignmentPanel;
-import jalview.gui.AssociatePdbFileWithSeq;
 import jalview.gui.Desktop;
 import jalview.gui.Preferences;
 import jalview.gui.StructureChooser;
 import jalview.gui.StructureViewer;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.AppletFormatAdapter;
+import jalview.io.BackupFiles;
+import jalview.io.BioJsHTMLOutput;
 import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
 import jalview.io.FileFormatException;
 import jalview.io.FileFormatI;
+import jalview.io.FileFormats;
 import jalview.io.FileLoader;
 import jalview.io.HtmlSvgOutput;
 import jalview.io.IdentifyFile;
-import jalview.schemes.AnnotationColourGradient;
-import jalview.structure.StructureImportSettings;
+import jalview.io.NewickFile;
 import jalview.structure.StructureImportSettings.TFType;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.HttpUtils;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
-import jalview.ws.dbsources.EBIAlfaFold;
-import mc_view.PDBChain;
 
 public class Commands
 {
   Desktop desktop;
 
-  private static boolean headless;
+  private boolean headless;
 
-  private static ArgParser argParser;
+  private ArgParser argParser;
 
   private Map<String, AlignFrame> afMap;
 
-  private static boolean commandArgsProvided = false;
+  private boolean commandArgsProvided = false;
 
-  public static boolean commandArgsProvided()
+  private boolean argsWereParsed = false;
+
+  public Commands(ArgParser argparser, boolean headless)
   {
-    return commandArgsProvided;
+    this(Desktop.instance, argparser, headless);
   }
 
-  public static boolean processArgs(ArgParser ap, boolean h)
+  public Commands(Desktop d, ArgParser argparser, boolean h)
   {
-    argParser = ap;
+    argParser = argparser;
     headless = h;
-    boolean argsWereParsed = true;
-    if (headless)
+    desktop = d;
+    afMap = new HashMap<String, AlignFrame>();
+    if (argparser != null)
     {
-      System.setProperty("java.awt.headless", "true");
+      processArgs(argparser, headless);
     }
+  }
 
-    if (argParser != null && argParser.linkedIds() != null)
+  private boolean processArgs(ArgParser argparser, boolean h)
+  {
+    argParser = argparser;
+    headless = h;
+    boolean theseArgsWereParsed = false;
+
+    if (argParser != null && argParser.getLinkedIds() != null)
     {
-      for (String id : argParser.linkedIds())
+      for (String id : argParser.getLinkedIds())
       {
-        Commands cmds = new Commands();
-        if (id == null)
-        {
-          cmds.processUnlinked(id);
-          argsWereParsed &= cmds.wereParsed();
-        }
-        else
+        ArgValuesMap avm = argParser.getLinkedArgs(id);
+        theseArgsWereParsed = true;
+        theseArgsWereParsed &= processLinked(id);
+        processGroovyScript(id);
+        boolean processLinkedOkay = theseArgsWereParsed;
+        theseArgsWereParsed &= processImages(id);
+        if (processLinkedOkay)
+          theseArgsWereParsed &= processOutput(id);
+
+        // close ap
+        if (avm.getBoolean(Arg.CLOSE))
         {
-          cmds.processLinked(id);
-          argsWereParsed &= cmds.wereParsed();
+          AlignFrame af = afMap.get(id);
+          if (af != null)
+          {
+            af.closeMenuItem_actionPerformed(true);
+          }
         }
-        cmds.processImages(id);
-        argsWereParsed &= cmds.wereParsed();
+
       }
 
     }
-    if (argParser.getBool(Arg.QUIT))
+    if (argParser.getBoolean(Arg.QUIT))
     {
       Jalview.getInstance().quit();
       return true;
     }
     // carry on with jalview.bin.Jalview
+    argsWereParsed = theseArgsWereParsed;
     return argsWereParsed;
   }
 
-  boolean argsWereParsed = true; // set false as soon as an arg is found
-
-  private boolean wereParsed()
-  {
-    return argsWereParsed;
-  }
-
-  public Commands()
+  public boolean commandArgsProvided()
   {
-    this(Desktop.instance);
+    return commandArgsProvided;
   }
 
-  public Commands(Desktop d)
+  public boolean argsWereParsed()
   {
-    this.desktop = d;
-    afMap = new HashMap<String, AlignFrame>();
+    return argsWereParsed;
   }
 
-  protected void processUnlinked(String id)
+  protected boolean processUnlinked(String id)
   {
-    processLinked(id);
+    return processLinked(id);
   }
 
-  protected void processLinked(String id)
+  protected boolean processLinked(String id)
   {
-    ArgValuesMap avm = argParser.linkedArgs(id);
+    boolean theseArgsWereParsed = false;
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
     if (avm == null)
-      return;
-    else
-      argsWereParsed = false;
+      return true;
 
     /*
-    // script to execute after all loading is completed one way or another
-    String groovyscript = m.get(Arg.GROOVY) == null ? null
-            : m.get(Arg.GROOVY).getValue();
-    String file = m.get(Arg.OPEN) == null ? null
-            : m.get(Arg.OPEN).getValue();
-    String data = null;
-    FileFormatI format = null;
-    DataSourceType protocol = null;
-    */
-    if (avm.containsArg(Arg.OPEN))
+     * // script to execute after all loading is completed one way or another String
+     * groovyscript = m.get(Arg.GROOVY) == null ? null :
+     * m.get(Arg.GROOVY).getValue(); String file = m.get(Arg.OPEN) == null ? null :
+     * m.get(Arg.OPEN).getValue(); String data = null; FileFormatI format = null;
+     * DataSourceType protocol = null;
+     */
+    if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
     {
       commandArgsProvided = true;
       long progress = -1;
@@ -151,13 +159,22 @@ public class Commands
       boolean first = true;
       boolean progressBarSet = false;
       AlignFrame af;
-      for (ArgValue av : avm.getArgValueList(Arg.OPEN))
+      // Combine the APPEND and OPEN files into one list, along with whether it
+      // was APPEND or OPEN
+      List<ArgValue> openAvList = new ArrayList<>();
+      openAvList.addAll(avm.getArgValueList(Arg.OPEN));
+      openAvList.addAll(avm.getArgValueList(Arg.APPEND));
+      // sort avlist based on av.getArgIndex()
+      Collections.sort(openAvList);
+      for (ArgValue av : openAvList)
       {
+        Arg a = av.getArg();
+        SubVals sv = av.getSubVals();
         String openFile = av.getValue();
         if (openFile == null)
           continue;
 
-        argsWereParsed = true;
+        theseArgsWereParsed = true;
         if (first)
         {
           first = false;
@@ -200,106 +217,108 @@ public class Commands
         }
 
         af = afMap.get(id);
-        if (af == null)
+        // When to open a new AlignFrame
+        if (af == null || "true".equals(av.getSubVal("new"))
+                || a == Arg.OPEN || format == FileFormat.Jalview)
         {
-          /*
-           * this approach isn't working yet
-          // get default annotations before opening AlignFrame
-          if (m.get(Arg.SSANNOTATION) != null)
-          {
-            Console.debug("***** SSANNOTATION="
-                    + m.get(Arg.SSANNOTATION).getBoolean());
-          }
-          if (m.get(Arg.NOTEMPFAC) != null)
-          {
-            Console.debug(
-                    "***** NOTEMPFAC=" + m.get(Arg.NOTEMPFAC).getBoolean());
-          }
-          boolean showSecondaryStructure = (m.get(Arg.SSANNOTATION) != null)
-                  ? m.get(Arg.SSANNOTATION).getBoolean()
-                  : false;
-          boolean showTemperatureFactor = (m.get(Arg.NOTEMPFAC) != null)
-                  ? !m.get(Arg.NOTEMPFAC).getBoolean()
-                  : false;
-          Console.debug("***** tempfac=" + showTemperatureFactor
-                  + ", showSS=" + showSecondaryStructure);
-          StructureSelectionManager ssm = StructureSelectionManager
-                  .getStructureSelectionManager(Desktop.instance);
-          if (ssm != null)
-          {
-            ssm.setAddTempFacAnnot(showTemperatureFactor);
-            ssm.setProcessSecondaryStructure(showSecondaryStructure);
-          }
-           */
-
-          // get kind of temperature factor annotation
-          StructureImportSettings.TFType tempfacType = TFType.DEFAULT;
-          if ((!avm.getBoolean(Arg.NOTEMPFAC))
-                  && avm.containsArg(Arg.TEMPFAC))
+          if (a == Arg.OPEN)
           {
-            try
-            {
-              tempfacType = StructureImportSettings.TFType
-                      .valueOf(avm.getArgValue(Arg.TEMPFAC).getValue()
-                              .toUpperCase(Locale.ROOT));
-              Console.debug("Obtained Temperature Factor type of '"
-                      + tempfacType + "'");
-            } catch (IllegalArgumentException e)
-            {
-              // Just an error message!
-              StringBuilder sb = new StringBuilder().append("Cannot set --")
-                      .append(Arg.TEMPFAC.getName()).append(" to '")
-                      .append(tempfacType)
-                      .append("', ignoring.  Valid values are: ");
-              Iterator<StructureImportSettings.TFType> it = Arrays
-                      .stream(StructureImportSettings.TFType.values())
-                      .iterator();
-              while (it.hasNext())
-              {
-                sb.append(it.next().toString().toLowerCase(Locale.ROOT));
-                if (it.hasNext())
-                  sb.append(", ");
-              }
-              Console.warn(sb.toString());
-            }
+            Jalview.testoutput(argParser, Arg.OPEN, "examples/uniref50.fa",
+                    openFile);
           }
 
           Console.debug(
                   "Opening '" + openFile + "' in new alignment frame");
           FileLoader fileLoader = new FileLoader(!headless);
 
-          StructureImportSettings.setTemperatureFactorType(tempfacType);
-
           af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
                   format);
 
           // wrap alignment?
-          if (avm.getBoolean(Arg.WRAP))
+          boolean wrap = ArgParser.getFromSubValArgOrPref(avm, Arg.WRAP, sv,
+                  null, "WRAP_ALIGNMENT", false);
+          af.getCurrentView().setWrapAlignment(wrap);
+
+          // colour alignment?
+          String colour = ArgParser.getFromSubValArgOrPref(avm, av,
+                  Arg.COLOUR, sv, null, "DEFAULT_COLOUR_PROT", "");
+          if ("" != colour)
           {
-            af.getCurrentView().setWrapAlignment(true);
+            af.changeColour_actionPerformed(colour);
+            Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
           }
 
-          // colour aligment?
-          if (avm.containsArg(Arg.COLOUR))
+          // Change alignment frame title
+          String title = ArgParser.getFromSubValArgOrPref(avm, av,
+                  Arg.TITLE, sv, null, null, null);
+          if (title != null)
           {
-            af.changeColour_actionPerformed(avm.getValue(Arg.COLOUR));
+            af.setTitle(title);
+            Jalview.testoutput(argParser, Arg.TITLE, "test title", title);
           }
 
-          // change alignment frame title
-          if (avm.containsArg(Arg.TITLE))
-            af.setTitle(avm.getValue(Arg.TITLE));
+          // Add features
+          String featuresfile = ArgParser.getValueFromSubValOrArg(avm, av,
+                  Arg.FEATURES, sv);
+          if (featuresfile != null)
+          {
+            af.parseFeaturesFile(featuresfile,
+                    AppletFormatAdapter.checkProtocol(featuresfile));
+            Jalview.testoutput(argParser, Arg.FEATURES,
+                    "examples/testdata/plantfdx.features", featuresfile);
+          }
 
-          /* hacky approach to hiding the annotations */
-          // show secondary structure annotations?
-          if (avm.getBoolean(Arg.SSANNOTATION))
+          // Add annotations from file
+          String annotationsfile = ArgParser.getValueFromSubValOrArg(avm,
+                  av, Arg.ANNOTATIONS, sv);
+          if (annotationsfile != null)
           {
-            // do this better (annotation types?)
-            AlignmentUtils.showOrHideSequenceAnnotations(
-                    af.getCurrentView().getAlignment(),
-                    Collections.singleton("Secondary Structure"), null,
-                    false, false);
+            af.loadJalviewDataFile(annotationsfile, null, null, null);
+            Jalview.testoutput(argParser, Arg.ANNOTATIONS,
+                    "examples/testdata/plantfdx.annotations",
+                    annotationsfile);
+          }
+
+          // Set or clear the sortbytree flag
+          boolean sortbytree = ArgParser.getBoolFromSubValOrArg(avm,
+                  Arg.SORTBYTREE, sv);
+          if (sortbytree)
+          {
+            af.getViewport().setSortByTree(true);
+            Jalview.testoutput(argParser, Arg.SORTBYTREE);
           }
 
+          // Load tree from file
+          String treefile = ArgParser.getValueFromSubValOrArg(avm, av,
+                  Arg.TREE, sv);
+          if (treefile != null)
+          {
+            try
+            {
+              NewickFile nf = new NewickFile(treefile,
+                      AppletFormatAdapter.checkProtocol(treefile));
+              af.getViewport().setCurrentTree(
+                      af.showNewickTree(nf, treefile).getTree());
+              Jalview.testoutput(argParser, Arg.TREE,
+                      "examples/testdata/uniref50_test_tree", treefile);
+            } catch (IOException e)
+            {
+              Console.warn("Couldn't add tree " + treefile, e);
+            }
+          }
+
+          // Show secondary structure annotations?
+          boolean showSSAnnotations = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.SHOWSSANNOTATIONS, av.getSubVals(), null,
+                  "STRUCT_FROM_PDB", true);
+          af.setAnnotationsVisibility(showSSAnnotations, true, false);
+
+          // Show sequence annotations?
+          boolean showAnnotations = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.SHOWANNOTATIONS, av.getSubVals(), null,
+                  "SHOW_ANNOTATIONS", true);
+          af.setAnnotationsVisibility(showAnnotations, false, true);
+
           // show temperature factor annotations?
           if (avm.getBoolean(Arg.NOTEMPFAC))
           {
@@ -311,30 +330,6 @@ public class Commands
                     af.getCurrentView().getAlignment(), hideThese, null,
                     false, false);
           }
-          else
-          /* comment out hacky approach up to here and add this line:
-           if (showTemperatureFactor)
-             */
-          {
-            if (avm.containsArg(Arg.TEMPFAC_LABEL))
-            {
-              AlignmentAnnotation aa = AlignmentUtils
-                      .getFirstSequenceAnnotationOfType(
-                              af.getCurrentView().getAlignment(),
-                              AlignmentAnnotation.LINE_GRAPH);
-              String label = avm.getValue(Arg.TEMPFAC_LABEL);
-              if (aa != null)
-              {
-                aa.label = label;
-              }
-              else
-              {
-                Console.info(
-                        "Could not find annotation to apply tempfac_label '"
-                                + label);
-              }
-            }
-          }
 
           // store the AlignFrame for this id
           afMap.put(id, af);
@@ -346,25 +341,29 @@ public class Commands
                     .getStructureSelectionManager(Desktop.instance);
             SequenceI seq = af.alignPanel.getAlignment().getSequenceAt(0);
             ssm.computeMapping(false, new SequenceI[] { seq }, null,
-                    openFile, DataSourceType.FILE, null, null, null);
+                    openFile, DataSourceType.FILE, null, null, null, false);
           }
         }
         else
         {
           Console.debug(
                   "Opening '" + openFile + "' in existing alignment frame");
-          af.getCurrentView().addFile(new File(openFile), format);
+          DataSourceType dst = HttpUtils.startsWithHttpOrHttps(openFile)
+                  ? DataSourceType.URL
+                  : DataSourceType.FILE;
+          FileLoader fileLoader = new FileLoader(!headless);
+          fileLoader.LoadFile(af.getCurrentView(), openFile, dst, null,
+                  false);
         }
 
-        Console.debug("Command " + Arg.OPEN + " executed successfully!");
+        Console.debug("Command " + Arg.APPEND + " executed successfully!");
 
       }
       if (first) // first=true means nothing opened
       {
         if (headless)
         {
-          Console.error("Could not open any files in headless mode");
-          System.exit(1);
+          Jalview.exit("Could not open any files in headless mode", 1);
         }
         else
         {
@@ -386,38 +385,42 @@ public class Commands
         for (ArgValue av : avm.getArgValueList(Arg.STRUCTURE))
         {
           String val = av.getValue();
-          SubVals subId = new SubVals(val);
-          SequenceI seq = getSpecifiedSequence(af, subId);
+          SubVals subVals = av.getSubVals();
+          SequenceI seq = getSpecifiedSequence(af, avm, av);
+          if (seq == null)
+          {
+            // Could not find sequence from subId, let's assume the first
+            // sequence in the alignframe
+            AlignmentI al = af.getCurrentView().getAlignment();
+            seq = al.getSequenceAt(0);
+          }
+
           if (seq == null)
           {
-            Console.warn("Could not find sequence for argument --"
-                    + Arg.STRUCTURE + "=" + val);
+            Console.warn("Could not find sequence for argument "
+                    + Arg.STRUCTURE.argString() + "=" + val);
             // you probably want to continue here, not break
             // break;
             continue;
           }
           File structureFile = null;
-          if (subId.getContent() != null
-                  && subId.getContent().length() != 0)
+          if (subVals.getContent() != null
+                  && subVals.getContent().length() != 0)
           {
-            structureFile = new File(subId.getContent());
+            structureFile = new File(subVals.getContent());
             Console.debug("Using structure file (from argument) '"
                     + structureFile.getAbsolutePath() + "'");
           }
-
           // TRY THIS
           /*
-           PDBEntry fileEntry = new AssociatePdbFileWithSeq()
-                  .associatePdbWithSeq(selectedPdbFileName,
-                          DataSourceType.FILE, selectedSequence, true,
-                          Desktop.instance);
-                          
-           sViewer = launchStructureViewer(ssm, new PDBEntry[] { fileEntry },
-                  ap, new SequenceI[]
-                  { selectedSequence });
-          
+           * PDBEntry fileEntry = new AssociatePdbFileWithSeq()
+           * .associatePdbWithSeq(selectedPdbFileName, DataSourceType.FILE,
+           * selectedSequence, true, Desktop.instance);
+           * 
+           * sViewer = launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap, new
+           * SequenceI[] { selectedSequence });
+           * 
            */
-
           /* THIS DOESN'T WORK */
           else if (seq.getAllPDBEntries() != null
                   && seq.getAllPDBEntries().size() > 0)
@@ -444,9 +447,13 @@ public class Commands
           Console.debug("Using structure file "
                   + structureFile.getAbsolutePath());
 
+          // ##### Does this need to happen? Follow
+          // openStructureFileForSequence() below
+          /*
           PDBEntry fileEntry = new AssociatePdbFileWithSeq()
                   .associatePdbWithSeq(structureFile.getAbsolutePath(),
                           DataSourceType.FILE, seq, true, Desktop.instance);
+                          */
 
           // open structure view
           AlignmentPanel ap = af.alignPanel;
@@ -456,77 +463,109 @@ public class Commands
                     StructureViewer.ViewerType.JMOL.toString());
           }
 
-          // get tft, paeFilename, label?
-          /*
-          ArgValue tftAv = avm.getArgValuesReferringTo("structid", structId,
-                  Arg.TEMPFAC);
-           */
-          StructureChooser.openStructureFileForSequence(null, null, ap, seq,
-                  false, structureFile.getAbsolutePath(), null, null); // tft,
-                                                                       // paeFilename);
-        }
-      }
-    }
+          String structureFilepath = structureFile.getAbsolutePath();
 
-    // load a pAE file if given
-    if (avm.containsArg(Arg.PAEMATRIX))
-    {
-      AlignFrame af = afMap.get(id);
-      if (af != null)
-      {
-        for (ArgValue av : avm.getArgValueList(Arg.PAEMATRIX))
-        {
-          String val = av.getValue();
-          SubVals subVals = ArgParser.getSubVals(val);
-          File paeFile = new File(subVals.getContent());
-          String paePath = null;
-          try
-          {
-            paePath = paeFile.getCanonicalPath();
-          } catch (IOException e)
-          {
-            paePath = paeFile.getAbsolutePath();
-            Console.warn(
-                    "Problem with the PAE file path: '" + paePath + "'");
-          }
-          String structId = subVals.get("structid");
-          if (subVals.notSet())
+          // get PAEMATRIX file and label from subvals or Arg.PAEMATRIX
+          String paeFilepath = ArgParser
+                  .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
+                          Arg.PAEMATRIX, Position.AFTER, av, subVals, null,
+                          null, null);
+          if (paeFilepath != null)
           {
-            // take structid from pdbfilename
-          }
-          if (subVals.has("structfile"))
-          {
-            Console.info("***** Attaching paeFile '" + paePath + "' to "
-                    + "structfile=" + subVals.get("structfile"));
-            EBIAlfaFold.addAlphaFoldPAE(af.getCurrentView().getAlignment(),
-                    paeFile, subVals.getIndex(), subVals.get("structfile"),
-                    true, false);
+            File paeFile = new File(paeFilepath);
+
+            try
+            {
+              paeFilepath = paeFile.getCanonicalPath();
+            } catch (IOException e)
+            {
+              paeFilepath = paeFile.getAbsolutePath();
+              Console.warn("Problem with the PAE file path: '"
+                      + paeFile.getPath() + "'");
+            }
           }
-          else if (subVals.has("structid"))
+
+          // showing annotations from structure file or not
+          boolean ssFromStructure = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.SHOWSSANNOTATIONS, subVals, null, "STRUCT_FROM_PDB",
+                  true);
+
+          // get TEMPFAC type from subvals or Arg.TEMPFAC in case user Adds
+          // reference annotations
+          String tftString = ArgParser
+                  .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
+                          Arg.TEMPFAC, Position.AFTER, av, subVals, null,
+                          null, null);
+          boolean notempfac = ArgParser.getBoolFromSubValOrArg(avm,
+                  Arg.NOTEMPFAC, subVals);
+          TFType tft = notempfac ? null : TFType.DEFAULT;
+          /*
+          String tftString = subVals.get("tempfac");
+          ArgValue tftAv = getArgAssociatedWithStructure(Arg.TEMPFAC, avm,
+                  af, structureFilepath);
+          if (tftString == null && tftAv != null)
           {
-            Console.info("***** Attaching paeFile '" + paePath + "' to "
-                    + "structid=" + subVals.get("structid"));
-            EBIAlfaFold.addAlphaFoldPAE(af.getCurrentView().getAlignment(),
-                    paeFile, subVals.getIndex(), subVals.get("structid"),
-                    true, true);
+            tftString = tftAv.getSubVals().getContent();
           }
-          else
+          */
+          if (tftString != null && !notempfac)
           {
-            Console.debug("***** Attaching paeFile '" + paePath
-                    + "' to sequence index " + subVals.getIndex());
-            EBIAlfaFold.addAlphaFoldPAE(af.getCurrentView().getAlignment(),
-                    paeFile, subVals.getIndex(), null, false, false);
-            // required to readjust the height and position of the pAE
-            // annotation
+            // get kind of temperature factor annotation
+            try
+            {
+              tft = TFType.valueOf(tftString.toUpperCase(Locale.ROOT));
+              Console.debug("Obtained Temperature Factor type of '" + tft
+                      + "' for structure '" + structureFilepath + "'");
+            } catch (IllegalArgumentException e)
+            {
+              // Just an error message!
+              StringBuilder sb = new StringBuilder().append("Cannot set ")
+                      .append(Arg.TEMPFAC.argString()).append(" to '")
+                      .append(tft)
+                      .append("', ignoring.  Valid values are: ");
+              Iterator<TFType> it = Arrays.stream(TFType.values())
+                      .iterator();
+              while (it.hasNext())
+              {
+                sb.append(it.next().toString().toLowerCase(Locale.ROOT));
+                if (it.hasNext())
+                  sb.append(", ");
+              }
+              Console.warn(sb.toString());
+            }
           }
-          for (AlignmentViewPanel ap : af.getAlignPanels())
+
+          String sViewer = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.STRUCTUREVIEWER, Position.AFTER, av, subVals, null,
+                  null, "jmol");
+          ViewerType viewerType = null;
+          if (!"none".equals(sViewer))
           {
-            ap.adjustAnnotationHeight();
+            for (ViewerType v : EnumSet.allOf(ViewerType.class))
+            {
+              String name = v.name().toLowerCase(Locale.ROOT)
+                      .replaceAll(" ", "");
+              if (sViewer.equals(name))
+              {
+                viewerType = v;
+                break;
+              }
+            }
           }
+
+          boolean addTempFac = notempfac ? false
+                  : ((tft != null)
+                          || Cache.getDefault("ADD_TEMPFACT_ANN", false));
+
+          // TODO use ssFromStructure
+          StructureChooser.openStructureFileForSequence(null, null, ap, seq,
+                  false, structureFilepath, tft, paeFilepath, false,
+                  ssFromStructure, false, viewerType);
         }
       }
     }
 
+    /*
     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
     if (doShading)
     {
@@ -541,11 +580,14 @@ public class Commands
         Console.info("Changed colour " + acg.toString());
       }
     }
+    */
+
+    return theseArgsWereParsed;
   }
 
-  protected void processImages(String id)
+  protected void processGroovyScript(String id)
   {
-    ArgValuesMap avm = argParser.linkedArgs(id);
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
     AlignFrame af = afMap.get(id);
 
     if (af == null)
@@ -554,22 +596,92 @@ public class Commands
       return;
     }
 
+    if (avm.containsArg(Arg.GROOVY))
+    {
+      String groovyscript = avm.getValue(Arg.GROOVY);
+      if (groovyscript != null)
+      {
+        // Execute the groovy script after we've done all the rendering stuff
+        // and before any images or figures are generated.
+        Console.info("Executing script " + groovyscript);
+        Jalview.getInstance().executeGroovyScript(groovyscript, af);
+      }
+    }
+  }
+
+  protected boolean processImages(String id)
+  {
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
+    AlignFrame af = afMap.get(id);
+
+    if (af == null)
+    {
+      Console.warn("Did not have an alignment window for id=" + id);
+      return false;
+    }
+
     if (avm.containsArg(Arg.IMAGE))
     {
       for (ArgValue av : avm.getArgValueList(Arg.IMAGE))
       {
         String val = av.getValue();
-        SubVals subVal = new SubVals(val);
-        String type = "png"; // default
+        SubVals subVal = av.getSubVals();
         String fileName = subVal.getContent();
         File file = new File(fileName);
-        if (subVal.has("type"))
+        String name = af.getName();
+        String renderer = ArgParser.getValueFromSubValOrArg(avm, av,
+                Arg.TEXTRENDERER, subVal);
+        if (renderer == null)
+          renderer = "text";
+        String type = "png"; // default
+
+        float bitmapscale = 0.0f;
+        int bitmapwidth = 0;
+        int bitmapheight = 0;
+        String scale = ArgParser.getValueFromSubValOrArg(avm, av, Arg.SCALE,
+                subVal);
+        if (scale != null)
+        {
+          try
+          {
+            bitmapscale = Float.parseFloat(scale);
+          } catch (NumberFormatException e)
+          {
+            Console.warn("Did not understand scale '" + scale
+                    + "', won't be used.");
+          }
+        }
+        String width = ArgParser.getValueFromSubValOrArg(avm, av, Arg.WIDTH,
+                subVal);
+        if (width != null)
+        {
+          try
+          {
+            bitmapwidth = Integer.parseInt(width);
+          } catch (NumberFormatException e)
+          {
+            Console.warn("Did not understand width '" + width
+                    + "', won't be used.");
+          }
+        }
+        String height = ArgParser.getValueFromSubValOrArg(avm, av,
+                Arg.HEIGHT, subVal);
+        if (height != null)
         {
-          type = subVal.get("type");
+          try
+          {
+            bitmapheight = Integer.parseInt(height);
+          } catch (NumberFormatException e)
+          {
+            Console.warn("Did not understand height '" + height
+                    + "', won't be used.");
+          }
         }
-        else if (fileName != null)
+
+        type = ArgParser.getValueFromSubValOrArg(avm, av, Arg.TYPE, subVal);
+        if (type == null && fileName != null)
         {
-          for (String ext : new String[] { "svg", "png", "html" })
+          for (String ext : new String[] { "svg", "png", "html", "eps" })
           {
             if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
             {
@@ -581,40 +693,253 @@ public class Commands
         Cache.setPropsAreReadOnly(true);
         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
 
+        Console.info("Writing " + file);
+
         switch (type)
         {
+
         case "svg":
           Console.debug("Outputting type '" + type + "' to " + fileName);
-          af.createSVG(file);
+          af.createSVG(file, renderer);
           break;
+
         case "png":
           Console.debug("Outputting type '" + type + "' to " + fileName);
-          af.createPNG(file);
+          af.createPNG(file, null, bitmapscale, bitmapwidth, bitmapheight);
           break;
+
         case "html":
           Console.debug("Outputting type '" + type + "' to " + fileName);
           HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
-          htmlSVG.exportHTML(fileName);
+          htmlSVG.exportHTML(fileName, renderer);
+          break;
+
+        case "biojs":
+          try
+          {
+            BioJsHTMLOutput.refreshVersionInfo(
+                    BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
+          } catch (URISyntaxException e)
+          {
+            e.printStackTrace();
+          }
+          BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
+          bjs.exportHTML(fileName);
+          Console.debug("Creating BioJS MSA Viwer HTML file: " + fileName);
           break;
+
+        case "eps":
+          af.createEPS(file, name);
+          Console.debug("Creating EPS file: " + fileName);
+          break;
+
+        case "imagemap":
+          af.createImageMap(file, name);
+          Console.debug("Creating ImageMap file: " + fileName);
+          break;
+
         default:
-          Console.warn("--image type '" + type + "' not known. Ignoring");
+          Console.warn(Arg.IMAGE.argString() + " type '" + type
+                  + "' not known. Ignoring");
           break;
         }
       }
     }
+    return true;
   }
 
-  private SequenceI getSpecifiedSequence(AlignFrame af, SubVals subId)
+  protected boolean processOutput(String id)
   {
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
+    AlignFrame af = afMap.get(id);
+
+    if (af == null)
+    {
+      Console.warn("Did not have an alignment window for id=" + id);
+      return false;
+    }
+
+    if (avm.containsArg(Arg.OUTPUT))
+    {
+      for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
+      {
+        String val = av.getValue();
+        SubVals subVals = av.getSubVals();
+        String fileName = subVals.getContent();
+        File file = new File(fileName);
+        boolean overwrite = ArgParser.getFromSubValArgOrPref(avm,
+                Arg.OVERWRITE, subVals, null, "OVERWRITE_OUTPUT", false);
+        // backups. Use the Arg.BACKUPS or subval "backups" setting first,
+        // otherwise if headless assume false, if not headless use the user
+        // preference with default true.
+        boolean backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS,
+                subVals, null,
+                Platform.isHeadless() ? null : BackupFiles.ENABLED,
+                !Platform.isHeadless());
+
+        // if backups is not true then --overwrite must be specified
+        if (file.exists() && !(overwrite || backups))
+        {
+          Console.error("Won't overwrite file '" + fileName + "' without "
+                  + Arg.OVERWRITE.argString() + " or "
+                  + Arg.BACKUPS.argString() + " set");
+          return false;
+        }
+
+        String name = af.getName();
+        String format = ArgParser.getValueFromSubValOrArg(avm, av,
+                Arg.FORMAT, subVals);
+        FileFormats ffs = FileFormats.getInstance();
+        List<String> validFormats = ffs.getWritableFormats(false);
+
+        FileFormatI ff = null;
+        if (format == null && fileName != null)
+        {
+          FORMAT: for (String fname : validFormats)
+          {
+            FileFormatI tff = ffs.forName(fname);
+            String[] extensions = tff.getExtensions().split(",");
+            for (String ext : extensions)
+            {
+              if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
+              {
+                ff = tff;
+                format = ff.getName();
+                break FORMAT;
+              }
+            }
+          }
+        }
+        if (ff == null && format != null)
+        {
+          ff = ffs.forName(format);
+        }
+        if (ff == null)
+        {
+          StringBuilder validSB = new StringBuilder();
+          for (String f : validFormats)
+          {
+            if (validSB.length() > 0)
+              validSB.append(", ");
+            validSB.append(f);
+            FileFormatI tff = ffs.forName(f);
+            validSB.append(" (");
+            validSB.append(tff.getExtensions());
+            validSB.append(")");
+          }
+
+          Jalview.exit("No valid format specified for "
+                  + Arg.OUTPUT.argString() + ". Valid formats are "
+                  + validSB.toString() + ".", 1);
+          // this return really shouldn't happen
+          return false;
+        }
+
+        String savedBackupsPreference = Cache
+                .getDefault(BackupFiles.ENABLED, null);
+        Console.debug("Setting backups to " + backups);
+        Cache.applicationProperties.put(BackupFiles.ENABLED,
+                Boolean.toString(backups));
+
+        Console.info("Writing " + fileName);
+
+        af.saveAlignment(fileName, ff);
+        Console.debug("Returning backups to " + savedBackupsPreference);
+        if (savedBackupsPreference != null)
+          Cache.applicationProperties.put(BackupFiles.ENABLED,
+                  savedBackupsPreference);
+        if (af.isSaveAlignmentSuccessful())
+        {
+          Console.debug("Written alignment '" + name + "' in "
+                  + ff.getName() + " format to " + file);
+        }
+        else
+        {
+          Console.warn("Error writing file " + file + " in " + ff.getName()
+                  + " format!");
+        }
+
+      }
+    }
+    return true;
+  }
+
+  private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
+          ArgValue av)
+  {
+    SubVals subVals = av.getSubVals();
+    ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID);
+    SequenceI seq = null;
+    if (subVals == null && idAv == null)
+      return null;
     AlignmentI al = af.getCurrentView().getAlignment();
-    if (-1 < subId.getIndex()
-            && subId.getIndex() < al.getSequences().size())
+    if (al == null)
+      return null;
+    if (subVals != null)
+    {
+      if (subVals.has(Arg.SEQID.getName()))
+      {
+        seq = al.findName(subVals.get(Arg.SEQID.getName()));
+      }
+      else if (-1 < subVals.getIndex()
+              && subVals.getIndex() < al.getSequences().size())
+      {
+        seq = al.getSequenceAt(subVals.getIndex());
+      }
+    }
+    else if (idAv != null)
     {
-      return al.getSequenceAt(subId.getIndex());
+      seq = al.findName(idAv.getValue());
     }
-    else if (subId.has("seqid"))
+    return seq;
+  }
+
+  // returns the first Arg value intended for the structure structFilename
+  // (in the given AlignFrame from the ArgValuesMap)
+  private ArgValue getArgAssociatedWithStructure(Arg arg, ArgValuesMap avm,
+          AlignFrame af, String structFilename)
+  {
+    if (af != null)
     {
-      return al.findName(subId.get("seqid"));
+      for (ArgValue av : avm.getArgValueList(arg))
+      {
+        SubVals subVals = av.getSubVals();
+        String structid = subVals.get("structid");
+        String structfile = subVals.get("structfile");
+
+        // let's find a structure
+        if (structfile == null && structid == null)
+        {
+          ArgValue likelyStructure = avm.getClosestPreviousArgValueOfArg(av,
+                  Arg.STRUCTURE);
+          if (likelyStructure != null)
+          {
+            SubVals sv = likelyStructure.getSubVals();
+            if (sv != null && sv.has(ArgValues.ID))
+            {
+              structid = sv.get(ArgValues.ID);
+            }
+            else
+            {
+              structfile = likelyStructure.getValue();
+            }
+          }
+        }
+
+        if (structfile == null && structid != null)
+        {
+          StructureSelectionManager ssm = StructureSelectionManager
+                  .getStructureSelectionManager(Desktop.instance);
+          if (ssm != null)
+          {
+            structfile = ssm.findFileForPDBId(structid);
+          }
+        }
+        if (structfile != null && structfile.equals(structFilename))
+        {
+          return av;
+        }
+      }
     }
     return null;
   }
index b868e7b..4b18484 100644 (file)
@@ -242,8 +242,11 @@ public class Console
 
       if (!Platform.isJS())
       {
-        System.err
-                .println("Setting initial log level to " + logLevel.name());
+        if (!Jalview.quiet())
+        {
+          System.err.println(
+                  "Setting initial log level to " + logLevel.name());
+        }
         Log4j.init(logLevel);
       }
       // log output
index c792a96..cf73c81 100755 (executable)
@@ -26,7 +26,9 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URI;
@@ -37,7 +39,9 @@ import java.security.CodeSource;
 import java.security.PermissionCollection;
 import java.security.Permissions;
 import java.security.Policy;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
@@ -62,7 +66,10 @@ import com.threerings.getdown.util.LaunchUtil;
 //import edu.stanford.ejalbert.launching.IBrowserLaunching;
 import groovy.lang.Binding;
 import groovy.util.GroovyScriptEngine;
-import jalview.bin.ArgParser.Arg;
+import jalview.bin.argparser.Arg;
+import jalview.bin.argparser.Arg.Opt;
+import jalview.bin.argparser.ArgParser;
+import jalview.bin.argparser.BootstrapArgs;
 import jalview.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
@@ -123,8 +130,21 @@ public class Jalview
 
   private Desktop desktop;
 
+  protected Commands cmds;
+
   public static AlignFrame currentAlignFrame;
 
+  public ArgParser argparser = null;
+
+  public BootstrapArgs bootstrapArgs = null;
+
+  private boolean QUIET = false;
+
+  public static boolean quiet()
+  {
+    return Jalview.getInstance() != null && Jalview.getInstance().QUIET;
+  }
+
   static
   {
     if (!Platform.isJS())
@@ -275,37 +295,90 @@ public class Jalview
    */
   void doMain(String[] args)
   {
-
     if (!Platform.isJS())
     {
       System.setSecurityManager(null);
     }
 
-    // get args needed before proper ArgParser
-    Map<ArgParser.Arg, String> bootstrapArgs = ArgParser
-            .bootstrapArgs(args);
+    if (args == null)
+      args = new String[] {};
 
-    System.out
-            .println("Java version: " + System.getProperty("java.version"));
-    System.out.println("Java Home: " + System.getProperty("java.home"));
-    System.out.println(System.getProperty("os.arch") + " "
-            + System.getProperty("os.name") + " "
-            + System.getProperty("os.version"));
+    // get args needed before proper ArgParser
+    bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
 
-    String val = System.getProperty("sys.install4jVersion");
-    if (val != null)
+    if (!Platform.isJS())
     {
-      System.out.println("Install4j version: " + val);
+      // are we being --quiet ?
+      if (bootstrapArgs.contains(Arg.QUIET))
+      {
+        QUIET = true;
+        OutputStream devNull = new OutputStream()
+        {
+
+          @Override
+          public void write(int b)
+          {
+            // DO NOTHING
+          }
+        };
+        System.setOut(new PrintStream(devNull));
+        // redirecting stderr not working
+        if (bootstrapArgs.getList(Arg.QUIET).size() > 1)
+        {
+          System.setErr(new PrintStream(devNull));
+        }
+      }
+
+      if (bootstrapArgs.contains(Arg.HELP)
+              || bootstrapArgs.contains(Arg.VERSION))
+      {
+        QUIET = true;
+      }
     }
-    val = System.getProperty("installer_template_version");
-    if (val != null)
+
+    // Move any new getdown-launcher-new.jar into place over old
+    // getdown-launcher.jar
+    String appdirString = System.getProperty("getdownappdir");
+    if (appdirString != null && appdirString.length() > 0)
     {
-      System.out.println("Install4j template version: " + val);
+      final File appdir = new File(appdirString);
+      new Thread()
+      {
+        @Override
+        public void run()
+        {
+          LaunchUtil.upgradeGetdown(
+                  new File(appdir, "getdown-launcher-old.jar"),
+                  new File(appdir, "getdown-launcher.jar"),
+                  new File(appdir, "getdown-launcher-new.jar"));
+        }
+      }.start();
     }
-    val = System.getProperty("launcher_version");
-    if (val != null)
+
+    if (!quiet() || bootstrapArgs.contains(Arg.VERSION))
     {
-      System.out.println("Launcher version: " + val);
+      System.out.println(
+              "Java version: " + System.getProperty("java.version"));
+      System.out.println("Java home: " + System.getProperty("java.home"));
+      System.out.println("Java arch: " + System.getProperty("os.arch") + " "
+              + System.getProperty("os.name") + " "
+              + System.getProperty("os.version"));
+
+      String val = System.getProperty("sys.install4jVersion");
+      if (val != null)
+      {
+        System.out.println("Install4j version: " + val);
+      }
+      val = System.getProperty("installer_template_version");
+      if (val != null)
+      {
+        System.out.println("Install4j template version: " + val);
+      }
+      val = System.getProperty("launcher_version");
+      if (val != null)
+      {
+        System.out.println("Launcher version: " + val);
+      }
     }
 
     if (Platform.isLinux() && LaunchUtils.getJavaVersion() < 11)
@@ -318,7 +391,14 @@ public class Jalview
             .bootstrapProperties(bootstrapArgs.get(Arg.PROPS));
 
     // report Jalview version
-    Cache.loadBuildProperties(true);
+    Cache.loadBuildProperties(
+            !quiet() || bootstrapArgs.contains(Arg.VERSION));
+
+    // stop now if only after --version
+    if (bootstrapArgs.contains(Arg.VERSION))
+    {
+      Jalview.exit(null, 0);
+    }
 
     // old ArgsParser
     ArgsParser aparser = new ArgsParser(args);
@@ -330,8 +410,15 @@ public class Jalview
 
     try
     {
-      String logLevel = bootstrapArgs.containsKey(Arg.DEBUG) ? "DEBUG"
-              : null;
+      String logLevel = null;
+      if (bootstrapArgs.contains(Arg.TRACE))
+      {
+        logLevel = "TRACE";
+      }
+      else if (bootstrapArgs.contains(Arg.DEBUG))
+      {
+        logLevel = "DEBUG";
+      }
       if (logLevel == null && !(bootstrapProperties == null))
       {
         logLevel = bootstrapProperties.getProperty(Cache.JALVIEWLOGLEVEL);
@@ -340,9 +427,9 @@ public class Jalview
     } catch (NoClassDefFoundError error)
     {
       error.printStackTrace();
-      System.out.println("\nEssential logging libraries not found."
-              + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview");
-      System.exit(0);
+      String message = "\nEssential logging libraries not found."
+              + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
+      Jalview.exit(message, 0);
     }
 
     // register SIGTERM listener
@@ -368,26 +455,33 @@ public class Jalview
       }
     });
 
-    String usrPropsFile = bootstrapArgs.containsKey(Arg.PROPS)
+    String usrPropsFile = bootstrapArgs.contains(Arg.PROPS)
             ? bootstrapArgs.get(Arg.PROPS)
             : aparser.getValue("props");
+    // if usrPropsFile == null, loadProperties will use the Channel
+    // preferences.file
     Cache.loadProperties(usrPropsFile);
     if (usrPropsFile != null)
     {
       System.out.println(
               "CMD [-props " + usrPropsFile + "] executed successfully!");
+      testoutput(bootstrapArgs, Arg.PROPS,
+              "test/jalview/bin/testProps.jvprops", usrPropsFile);
     }
 
-    // new ArgParser
-    ArgParser argparser;
     // --argfile=... -- OVERRIDES ALL NON-BOOTSTRAP ARGS
-    if (bootstrapArgs.containsKey(Arg.ARGFILE))
+    if (bootstrapArgs.contains(Arg.ARGFILE))
     {
-      argparser = ArgParser.parseArgFile(bootstrapArgs.get(Arg.ARGFILE));
+      argparser = ArgParser.parseArgFiles(
+              bootstrapArgs.getList(Arg.ARGFILE),
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
+              bootstrapArgs);
     }
     else
     {
-      argparser = new ArgParser(args);
+      argparser = new ArgParser(args,
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
+              bootstrapArgs);
     }
 
     if (!Platform.isJS())
@@ -397,18 +491,26 @@ public class Jalview
      * @j2sIgnore
      */
     {
-      if (aparser.contains("help") || aparser.contains("h")
-              || argparser.getBool(Arg.HELP))
+      if (bootstrapArgs.contains(Arg.HELP))
       {
+        System.out.println(Arg.usage());
+        Jalview.exit(null, 0);
+      }
+      if (aparser.contains("help") || aparser.contains("h"))
+      {
+        /*
+         * Now using new usage statement.
         showUsage();
-        System.exit(0);
+        */
+        System.out.println(Arg.usage());
+        Jalview.exit(null, 0);
       }
 
-      if (argparser.isSet(Arg.HEADLESS))
+      if (bootstrapArgs.contains(Arg.HEADLESS))
       {
         System.setProperty("java.awt.headless", "true");
         // new
-        headlessArg = argparser.getBool(Arg.HEADLESS);
+        headlessArg = bootstrapArgs.getBoolean(Arg.HEADLESS);
       }
       if (aparser.contains("nodisplay") || aparser.contains("nogui")
               || aparser.contains("headless"))
@@ -422,7 +524,9 @@ public class Jalview
       // allow https handshakes to download intermediate certs if necessary
       System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
 
-      final String jabawsUrl = aparser.getValue("jabaws");
+      String jabawsUrl = bootstrapArgs.get(Arg.JABAWS);
+      if (jabawsUrl == null)
+        jabawsUrl = aparser.getValue("jabaws");
       if (jabawsUrl != null)
       {
         try
@@ -430,6 +534,8 @@ public class Jalview
           Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
           System.out.println(
                   "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
+          testoutput(bootstrapArgs, Arg.JABAWS,
+                  "http://www.compbio.dundee.ac.uk/jabaws", jabawsUrl);
         } catch (MalformedURLException e)
         {
           System.err.println(
@@ -438,26 +544,40 @@ public class Jalview
       }
     }
 
-    String defs = aparser.getValue("setprop");
-    while (defs != null)
+    List<String> setprops = new ArrayList<>();
+    if (bootstrapArgs.contains(Arg.SETPROP))
+    {
+      setprops = bootstrapArgs.getList(Arg.SETPROP);
+    }
+    else
+    {
+      String sp = aparser.getValue("setprop");
+      while (sp != null)
+      {
+        setprops.add(sp);
+        sp = aparser.getValue("setprop");
+      }
+    }
+    for (String setprop : setprops)
     {
-      int p = defs.indexOf('=');
+      int p = setprop.indexOf('=');
       if (p == -1)
       {
-        System.err.println("Ignoring invalid setprop argument : " + defs);
+        System.err
+                .println("Ignoring invalid setprop argument : " + setprop);
       }
       else
       {
-        System.out.println("Executing setprop argument: " + defs);
+        System.out.println("Executing setprop argument: " + setprop);
         if (Platform.isJS())
         {
-          Cache.setProperty(defs.substring(0, p), defs.substring(p + 1));
+          Cache.setProperty(setprop.substring(0, p),
+                  setprop.substring(p + 1));
         }
         // DISABLED FOR SECURITY REASONS
         // TODO: add a property to allow properties to be overriden by cli args
-        // Cache.setProperty(defs.substring(0,p), defs.substring(p+1));
+        // Cache.setProperty(setprop.substring(0,p), setprop.substring(p+1));
       }
-      defs = aparser.getValue("setprop");
     }
     if (System.getProperty("java.awt.headless") != null
             && System.getProperty("java.awt.headless").equals("true"))
@@ -475,9 +595,9 @@ public class Jalview
     NoClassDefFoundError error)
     {
       error.printStackTrace();
-      System.out.println("\nEssential logging libraries not found."
-              + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview");
-      System.exit(0);
+      String message = "\nEssential logging libraries not found."
+              + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
+      Jalview.exit(message, 0);
     }
     desktop = null;
 
@@ -496,7 +616,9 @@ public class Jalview
 
     if (!(headless || headlessArg))
     {
-      Desktop.nosplash = aparser.contains("nosplash");
+      Desktop.nosplash = "false".equals(bootstrapArgs.get(Arg.SPLASH))
+              || aparser.contains("nosplash")
+              || Cache.getDefault("SPLASH", "true").equals("false");
       desktop = new Desktop();
       desktop.setInBatchMode(true); // indicate we are starting up
 
@@ -556,20 +678,37 @@ public class Jalview
           }
         }
 
-        if (!aparser.contains("nowebservicediscovery"))
+        boolean webservicediscovery = bootstrapArgs
+                .getBoolean(Arg.WEBSERVICEDISCOVERY);
+        if (aparser.contains("nowebservicediscovery"))
+          webservicediscovery = false;
+        if (webservicediscovery)
         {
           desktop.startServiceDiscovery();
         }
-        if (!aparser.contains("nousagestats"))
+        else
+        {
+          testoutput(argparser, Arg.WEBSERVICEDISCOVERY);
+        }
+
+        boolean usagestats = bootstrapArgs.getBoolean(Arg.USAGESTATS);
+        if (aparser.contains("nousagestats"))
+          usagestats = false;
+        if (usagestats)
         {
           startUsageStats(desktop);
+          testoutput(argparser, Arg.USAGESTATS);
         }
         else
         {
-          System.err.println("CMD [-nousagestats] executed successfully!");
+          System.out.println("CMD [-nousagestats] executed successfully!");
+          testoutput(argparser, Arg.USAGESTATS);
         }
 
-        if (!aparser.contains("noquestionnaire"))
+        boolean questionnaire = bootstrapArgs.getBoolean(Arg.QUESTIONNAIRE);
+        if (aparser.contains("noquestionnaire"))
+          questionnaire = false;
+        if (questionnaire)
         {
           String url = aparser.getValue("questionnaire");
           if (url != null)
@@ -599,36 +738,44 @@ public class Jalview
         }
         else
         {
-          System.err
+          System.out
                   .println("CMD [-noquestionnaire] executed successfully!");
+          testoutput(argparser, Arg.QUESTIONNAIRE);
         }
 
-        if (!aparser.contains("nonews")
-                || Cache.getProperty("NONEWS") == null)
+        if ((!aparser.contains("nonews")
+                && Cache.getProperty("NONEWS") == null
+                && !"false".equals(bootstrapArgs.get(Arg.NEWS)))
+                || "true".equals(bootstrapArgs.get(Arg.NEWS)))
         {
           desktop.checkForNews();
         }
 
         if (!aparser.contains("nohtmltemplates")
-                || Cache.getProperty("NOHTMLTEMPLATES") == null)
+                && Cache.getProperty("NOHTMLTEMPLATES") == null)
         {
           BioJsHTMLOutput.updateBioJS();
         }
       }
     }
     // Run Commands from cli
-    boolean commandsSuccess = Commands.processArgs(argparser, headlessArg);
+    cmds = new Commands(argparser, headlessArg);
+    boolean commandsSuccess = cmds.argsWereParsed();
     if (commandsSuccess)
     {
-      Console.info("Successfully completed commands");
       if (headlessArg)
-        System.exit(0);
+      {
+        Jalview.exit("Successfully completed commands in headless mode", 0);
+      }
+      Console.info("Successfully completed commands");
     }
     else
     {
-      Console.warn("Error when running commands");
       if (headlessArg)
-        System.exit(1);
+      {
+        Jalview.exit("Error when running Commands in headless mode", 1);
+      }
+      Console.warn("Error when running commands");
     }
 
     // Check if JVM and compile version might cause problems and log if it
@@ -641,25 +788,6 @@ public class Jalview
               + LaunchUtils.getJavaCompileVersion() + ".");
     }
 
-    // Move any new getdown-launcher-new.jar into place over old
-    // getdown-launcher.jar
-    String appdirString = System.getProperty("getdownappdir");
-    if (appdirString != null && appdirString.length() > 0)
-    {
-      final File appdir = new File(appdirString);
-      new Thread()
-      {
-        @Override
-        public void run()
-        {
-          LaunchUtil.upgradeGetdown(
-                  new File(appdir, "getdown-launcher-old.jar"),
-                  new File(appdir, "getdown-launcher.jar"),
-                  new File(appdir, "getdown-launcher-new.jar"));
-        }
-      }.start();
-    }
-
     String file = null, data = null;
 
     FileFormatI format = null;
@@ -674,10 +802,9 @@ public class Jalview
     groovyscript = aparser.getValue("groovy", true);
     file = aparser.getValue("open", true);
 
-    if (file == null && desktop == null)
+    if (file == null && desktop == null && !commandsSuccess)
     {
-      System.out.println("No files to open!");
-      System.exit(1);
+      Jalview.exit("No files to open!", 1);
     }
 
     long progress = -1;
@@ -704,11 +831,12 @@ public class Jalview
         {
           if (!(new File(file)).exists())
           {
-            System.out.println("Can't find " + file);
             if (headless)
             {
-              System.exit(1);
+              Jalview.exit(
+                      "Can't find file '" + file + "' in headless mode", 1);
             }
+            Console.warn("Can't find file'" + file + "'");
           }
         }
       }
@@ -947,7 +1075,7 @@ public class Jalview
 
     if (!Platform.isJS() && !headless && file == null
             && Cache.getDefault("SHOW_STARTUP_FILE", true)
-            && !Commands.commandArgsProvided())
+            && !cmds.commandArgsProvided())
     // don't open the startup file if command line args have been processed
     // (&& !Commands.commandArgsProvided())
     /**
@@ -1416,7 +1544,7 @@ public class Jalview
    *          the Jalview Desktop object passed in to the groovy binding as the
    *          'Jalview' object.
    */
-  private void executeGroovyScript(String groovyscript, AlignFrame af)
+  protected void executeGroovyScript(String groovyscript, AlignFrame af)
   {
     /**
      * for scripts contained in files
@@ -1548,7 +1676,7 @@ public class Jalview
   public void quit()
   {
     // System.exit will run the shutdownHook first
-    System.exit(0);
+    Jalview.exit("Quitting now. Bye!", 0);
   }
 
   public static AlignFrame getCurrentAlignFrame()
@@ -1560,4 +1688,128 @@ public class Jalview
   {
     Jalview.currentAlignFrame = currentAlignFrame;
   }
+
+  protected Commands getCommands()
+  {
+    return cmds;
+  }
+
+  public static void exit(String message, int exitcode)
+  {
+    Console.debug("Using Jalview.exit");
+    if (message != null)
+      if (exitcode == 0)
+        Console.info(message);
+      else
+        Console.error(message);
+    if (exitcode > -1)
+      System.exit(exitcode);
+  }
+
+  /*
+   * testoutput for string values
+   */
+  protected static void testoutput(ArgParser ap, Arg a, String s1,
+          String s2)
+  {
+    BootstrapArgs bsa = ap.getBootstrapArgs();
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
+    {
+      Console.debug("testoutput with unmatching values '" + s1 + "' and '"
+              + s2 + "' for arg " + a.argString());
+      return;
+    }
+    boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
+            : ap.isSet(a);
+    if (!isset)
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(true, a, s1, s2);
+  }
+
+  protected static void testoutput(BootstrapArgs bsa, Arg a, String s1,
+          String s2)
+  {
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
+    {
+      Console.debug("testoutput with unmatching values '" + s1 + "' and '"
+              + s2 + "' for arg " + a.argString());
+      return;
+    }
+    if (!a.hasOption(Opt.BOOTSTRAP))
+    {
+      Console.error("Non-bootstrap Arg '" + a.getName()
+              + "' given to testoutput(BootstrapArgs bsa, Arg a, String s1, String s2) with only BootstrapArgs");
+    }
+    if (!bsa.contains(a))
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(true, a, s1, s2);
+  }
+
+  private static void testoutput(boolean yes, Arg a, String s1, String s2)
+  {
+    if (yes && ((s1 == null && s2 == null)
+            || (s1 != null && s1.equals(s2))))
+    {
+      System.out.println("[TESTOUTPUT] arg " + a.argString() + "='" + s1
+              + "' was set");
+    }
+  }
+
+  /*
+   * testoutput for boolean values
+   */
+  protected static void testoutput(ArgParser ap, Arg a)
+  {
+    if (ap == null)
+      return;
+    BootstrapArgs bsa = ap.getBootstrapArgs();
+    if (bsa == null)
+      return;
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    boolean val = a.hasOption(Opt.BOOTSTRAP) ? bsa.getBoolean(a)
+            : ap.getBoolean(a);
+    boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
+            : ap.isSet(a);
+    if (!isset)
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(val, a);
+  }
+
+  protected static void testoutput(BootstrapArgs bsa, Arg a)
+  {
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!a.hasOption(Opt.BOOTSTRAP))
+    {
+      Console.warn("Non-bootstrap Arg '" + a.getName()
+              + "' given to testoutput(BootstrapArgs bsa, Arg a) with only BootstrapArgs");
+
+    }
+    if (!bsa.contains(a))
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(bsa.getBoolean(a), a);
+  }
+
+  private static void testoutput(boolean yes, Arg a)
+  {
+    System.out.println("[TESTOUTPUT] arg "
+            + (yes ? a.argString() : a.negateArgString()) + " was set");
+  }
 }
index a55146d..61b87e3 100644 (file)
@@ -108,6 +108,21 @@ public class Launcher
         jvmmemmax = arg.substring(
                 MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME.length() + 2);
       }
+      // --doubledash versions
+      else if (arg.startsWith("--"
+              + MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "="))
+      {
+        jvmmempc = arg.substring(
+                MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME.length()
+                        + 3);
+      }
+      else if (arg.startsWith(
+              "--" + MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME + "="))
+      {
+        jvmmemmax = arg.substring(
+                MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME.length() + 3);
+      }
+      // retain arg
       else
       {
         arguments.add(arg);
@@ -181,6 +196,12 @@ public class Launcher
         // Leaving it in in case it gets fixed
         command.add(
                 "-Xdock:name=" + ChannelProperties.getProperty("app_name"));
+        // this launches WITHOUT an icon in the macOS dock. Could be useful for
+        // getdown?
+        // command.add("-Dapple.awt.UIElement=false");
+        // This also does not work for the dock
+        command.add("-Dcom.apple.mrj.application.apple.menu.about.name="
+                + ChannelProperties.getProperty("app_name"));
       }
     }
 
@@ -208,6 +229,8 @@ public class Launcher
 
     if (Boolean.parseBoolean(System.getProperty("launcherstop", "false")))
     {
+      System.out.println(
+              "System property 'launcherstop' is set and not 'false'. Exiting.");
       System.exit(0);
     }
     try
@@ -252,7 +275,6 @@ public class Launcher
     {
       e.printStackTrace();
     }
-    // System.exit(0);
   }
 
 }
diff --git a/src/jalview/bin/argparser/Arg.java b/src/jalview/bin/argparser/Arg.java
new file mode 100644 (file)
index 0000000..a18057c
--- /dev/null
@@ -0,0 +1,515 @@
+package jalview.bin.argparser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import jalview.bin.Cache;
+import jalview.util.ChannelProperties;
+
+public enum Arg
+{
+
+  // Initialising arguments (BOOTSTRAP)
+  HELP("h", "Display this help statement", Opt.UNARY, Opt.BOOTSTRAP),
+  VERSION("v",
+          "Display the version of "
+                  + ChannelProperties.getProperty("app_name"),
+          Opt.UNARY, Opt.BOOTSTRAP),
+  HEADLESS(
+          "Run Jalview in headless mode. No GUI interface will be created and Jalview will quit after all arguments have been processed.",
+          Opt.UNARY, Opt.BOOTSTRAP),
+  JABAWS("Set a different URL to connect to a JABAWS server.", Opt.STRING,
+          Opt.BOOTSTRAP),
+  NEWS("Show (or don't show) the news feed.", true, Opt.BOOLEAN,
+          Opt.BOOTSTRAP),
+  SPLASH("Show (or don't show) the About Jalview splash screen.", true,
+          Opt.BOOLEAN, Opt.BOOTSTRAP),
+  QUESTIONNAIRE(
+          "Show (or don't show) the questionnaire if one is available.",
+          true, Opt.BOOLEAN, Opt.BOOTSTRAP),
+  USAGESTATS("Send (or don't send) initial launch usage stats.", true,
+          Opt.BOOLEAN, Opt.BOOTSTRAP),
+  WEBSERVICEDISCOVERY(
+          "Attempt (or don't attempt) to connect to JABAWS web services.",
+          true, Opt.BOOLEAN, Opt.BOOTSTRAP),
+  PROPS("Use a file as the preferences file instead of the usual ~/"
+          + ChannelProperties.getProperty("preferences.filename")
+          + " file.", Opt.STRING, Opt.BOOTSTRAP),
+  DEBUG("d", "Start Jalview in debug log level.", Opt.BOOLEAN,
+          Opt.BOOTSTRAP),
+  TRACE("Start Jalview in trace log level.", Opt.BOOLEAN, Opt.BOOTSTRAP,
+          Opt.SECRET),
+  QUIET("q",
+          "Stop all output to STDOUT (after the Java Virtual Machine has started). Use ‑‑quiet a second time to stop all output to STDERR.",
+          Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP),
+  INITSUBSTITUTIONS(
+          "Set ‑‑substitutions to be initially enabled (or initially disabled).",
+          true, Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION),
+
+  // Opening an alignment
+  OPEN("Opens one or more alignment files or URLs in new alignment windows.",
+          Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER, Opt.MULTI,
+          Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, Opt.STORED),
+  APPEND("Appends one or more alignment files or URLs to the open alignment window (or opens a new alignment if none already open).",
+          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
+          Opt.ALLOWSUBSTITUTIONS, Opt.INPUT),
+  TITLE("Specifies the title for the open alignment window as string.",
+          Opt.STRING, Opt.LINKED),
+  COLOUR("color", // being a bit soft on the Americans!
+          "Applies the colour scheme to the open alignment window. Valid values are:\n"
+                  + "clustal,\n" + "blosum62,\n" + "pc-identity,\n"
+                  + "zappo,\n" + "taylor,\n" + "gecos-flower,\n"
+                  + "gecos-blossom,\n" + "gecos-sunset,\n"
+                  + "gecos-ocean,\n" + "hydrophobic,\n"
+                  + "helix-propensity,\n" + "strand-propensity,\n"
+                  + "turn-propensity,\n" + "buried-index,\n"
+                  + "nucleotide,\n" + "nucleotide-ambiguity,\n"
+                  + "purine-pyrimidine,\n" + "rna-helices,\n"
+                  + "t-coffee-scores,\n" + "sequence-id.",
+          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+  FEATURES("Add a feature file or URL to the open alignment.", Opt.STRING,
+          Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+  TREE("Add a tree file or URL to the open alignment.", Opt.STRING,
+          Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+  SORTBYTREE(
+          "Enforces sorting (or not sorting) the open alignment in the order of an attached phylogenetic tree.",
+          true, Opt.LINKED, Opt.BOOLEAN, Opt.ALLOWALL),
+  ANNOTATIONS("Add an annotations file or URL to the open alignment.",
+          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+  SHOWANNOTATIONS(
+          "Enforces showing (or not showing) alignment annotations.",
+          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+  WRAP("Enforces wrapped (or not wrapped) alignment formatting.",
+          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+  NOSTRUCTURE(
+          "Do not open or process any 3D structure in the ‑‑open or ‑‑append files.",
+          Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
+
+  // Adding a 3D structure
+  STRUCTURE(
+          "Load a structure file or URL associated with a sequence in the open alignment.\n"
+                  + "The sequence to be associated with can be specified with a following --seqid argument, or the subval modifier seqid=ID can be used. A subval INDEX can also be used to specify the INDEX-th sequence in the open alignment.",
+          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+  SEQID("Specify the sequence name for the preceding --structure to be associated with.",
+          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+  PAEMATRIX("Add a PAE json matrix file to the preceding --structure.",
+          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+  TEMPFAC("Set the type of temperature factor. Possible values are:\n"
+          + "default,\n" + "plddt.", Opt.STRING, Opt.LINKED),
+  STRUCTUREVIEWER(
+          "Set the structure viewer to use to open the 3d structure file specified in previous --structure to name. Possible values of name are:\n"
+                  + "none,\n" + "jmol,\n" + "chimera,\n" + "chimerax,\n"
+                  + "pymol.",
+          Opt.STRING, Opt.LINKED, Opt.MULTI),
+  NOTEMPFAC(
+          "Do not show the temperature factor annotation for the preceding --structure.",
+          Opt.UNARY, Opt.LINKED, Opt.ALLOWALL, Opt.SECRET), // keep this secret
+                                                            // until it
+  // works!
+  SHOWSSANNOTATIONS(null, Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+
+  // Outputting files
+  IMAGE("Output an image of the open alignment window. Format is specified by the subval modifier, a following --type argument or guessed from the file extension. Valid formats/extensions are:\n"
+          + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
+          Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
+          Opt.REQUIREINPUT, Opt.OUTPUT),
+  TYPE("Set the image format for the preceding --image to name. Valid values for name are: svg,\n"
+          + "png,\n" + "eps,\n" + "html,\n" + "biojs.", Opt.STRING,
+          Opt.LINKED, Opt.ALLOWALL),
+  TEXTRENDERER(
+          "Sets whether text in a vector image format (SVG, HTML, EPS) should be rendered as text or vector line-art. Possible values for name are:\n"
+                  + "text,\n" + "lineart.",
+          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+  SCALE("Sets a scaling for bitmap image format (PNG). Should be given as a floating point number. If used in conjunction with --width and --height then the smallest scaling will be used (scale, width and height provide bounds for the image).",
+          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+  WIDTH("Sets a width for bitmap image format (PNG) with the height maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --scale and --height then the smallest scaling will be used (scale, width and height provide bounds for the image).",
+          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+  HEIGHT("Sets a height for bitmap image format (PNG) with the width maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --scale and --width then the smallest scaling will be used (scale, width and height provide bounds for the image).",
+          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+  OUTPUT("Export the open alignment to file filename. The format name is specified by the subval modifier format=name, a following --format name argument or guessed from the file extension. Valid format names (and file extensions) are:\n"
+          + "fasta (fa, fasta, mfa, fastq),\n" + "pfam (pfam),\n"
+          + "stockholm (sto, stk),\n" + "pir (pir),\n" + "blc (blc),\n"
+          + "amsa (amsa),\n" + "json (json),\n" + "pileup (pileup),\n"
+          + "msf (msf),\n" + "clustal (aln),\n" + "phylip (phy),\n"
+          + "jalview (jvp, jar).", Opt.STRING, Opt.LINKED,
+          Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL, Opt.REQUIREINPUT,
+          Opt.OUTPUT),
+  FORMAT("Sets the format for the preceding --output file. Valid formats are:\n"
+          + "fasta,\n" + "pfam,\n" + "stockholm,\n" + "pir,\n" + "blc,\n"
+          + "amsa,\n" + "json,\n" + "pileup,\n" + "msf,\n" + "clustal,\n"
+          + "phylip,\n" + "jalview.", Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+  GROOVY("Process a groovy script in the file for the open alignment.",
+          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS,
+          Opt.ALLOWALL),
+  BACKUPS("Enable (or disable) writing backup files when saving an ‑‑output file. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
+          true, Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+  OVERWRITE(
+          "Enable (or disable) overwriting of output files without backups enabled. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
+          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+  CLOSE("Close the current open alignment window. This occurs after other output arguments. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
+          Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
+
+  // controlling flow of arguments
+  NEW("Move on to a new alignment window. This will ensure --append will start a new alignment window and other linked arguments will apply to the new alignment window.",
+          Opt.UNARY, Opt.MULTI, Opt.NOACTION, Opt.INCREMENTDEFAULTCOUNTER),
+  SUBSTITUTIONS(
+          "The following argument values allow (or don't allow) subsituting filename parts. This is initially true. Valid substitutions are {basename} - the filename-without-extension of the currently --opened file (or first --appended file),\n"
+                  + "{dirname}, - the directory (folder) name of the currently --opened file (or first --appended file),\n"
+                  + "{argfilebasename} - the filename-without-extension of the current --argfile,\n"
+                  + "{argfiledirname} - the directory (folder) name of the current --argfile,\n"
+                  + "{n} - the value of the index counter (starting at 0).\n"
+                  + "{++n} - increase and substitute the value of the index counter,\n"
+                  + "{} - the value of the current alignment window default index.",
+          true, Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
+  ARGFILE("Open one or more files filename and read, line-by-line, as arguments to Jalview.\n"
+          + "Values in an argfile should be given with an equals sign (\"=\") separator with no spaces.\n"
+          + "Note that if you use one or more --argfile arguments then all other non-initialising arguments will be ignored.",
+          Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
+          Opt.ALLOWSUBSTITUTIONS),
+  NPP("n++",
+          "Increase the index counter used in argument value substitutions.",
+          Opt.UNARY, Opt.MULTI, Opt.NOACTION),
+  ALL("Apply the following output arguments to all sets of linked arguments.",
+          Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
+  OPENED("Apply the following output arguments to all of the last --open'ed set of linked arguments.",
+          Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
+  QUIT("After all files have been opened, appended and output, quit Jalview. In ‑‑headless mode this already happens.",
+          Opt.UNARY),
+
+  // secret options
+  TESTOUTPUT(
+          "Allow specific stdout information.  For testing purposes only.",
+          Opt.UNARY, Opt.BOOTSTRAP, Opt.SECRET), // do not show this to the user
+  SETPROP("Set an individual Java System property.", Opt.STRING, Opt.MULTI,
+          Opt.BOOTSTRAP, Opt.SECRET), // not in use yet
+  NIL("This argument does nothing on its own, but can be used with linkedIds.",
+          Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION, Opt.SECRET),
+
+  // private options (inserted during arg processing)
+  SETARGFILE(
+          "Sets the current value of the argfilename.  Inserted before argfilecontents.",
+          Opt.UNARY, Opt.LINKED, Opt.STRING, Opt.MULTI, Opt.PRIVATE,
+          Opt.NOACTION),
+  UNSETARGFILE(
+          "Unsets the current value of the argfilename.  Inserted after argfile contents.",
+          Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.PRIVATE, Opt.NOACTION),
+
+  // these last two have no purpose in the normal Jalview application but are
+  // used by jalview.bin.Launcher to set memory settings. They are not used by
+  // argparser but are here for Usage statement reasons.
+  JVMMEMPC(
+          "Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected.\n"
+                  + "The equals sign (\"=\") separator must be used with no spaces.",
+          Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING),
+  JVMMEMMAX(
+          "Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m), gigabytes(g) or if you're lucky enough, terabytes(t). This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected.\n"
+                  + "The equals sign (\"=\") separator must be used with no spaces.",
+          Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING),
+
+  ;
+
+  public static enum Opt
+  {
+    BOOLEAN, // This Arg can be specified as --arg or --noarg to give true or
+             // false. A default can be given with setOptions(bool, Opt....).
+             // Use ArgParser.isSet(Arg) to see if this arg was not specified.
+    STRING, // This Arg can accept a value either through --arg=value or --arg
+            // value.
+    UNARY, // This Arg is a boolean value, true if present, false if not. Like
+           // BOOLEAN but without the --noarg option.
+    MULTI, // This Arg can be specified multiple times. Multiple values are
+           // stored in the ArgValuesMap (along with their positional index) for
+           // each linkedId.
+    LINKED, // This Arg can be linked to others through a --arg[linkedId] or
+            // --arg[linkedId]=value. If no linkedId is specified then the
+            // current default linkedId will be used.
+    NODUPLICATEVALUES, // This Arg can only have one value (per linkedId). The
+                       // first value will be used and subsequent values ignored
+                       // with a warning.
+    BOOTSTRAP, // This Arg value(s) can be determined at an earlier stage than
+               // non-BOOTSTRAP Args. Substitutions do not happen in BOOTSTRAP
+               // Args and they cannot be linked or contain SubVals. See
+               // jalview.bin.argparser.BootstrapArgs.
+    GLOB, // This Arg can expand wildcard filename "globs" (e.g.
+          // path/*/filename*). If the Arg value is given as --arg filename*
+          // then the shell will have expanded the glob already, but if
+          // specified as --arg=filename* then the Java glob expansion method
+          // will be used (see FileUtils.getFilenamesFromGlob()). Note that this
+          // might be different from the shell expansion rules.
+    NOACTION, // This Arg does not perform a data task, usually used to control
+              // flow in ArgParser.parse(args).
+    ALLOWSUBSTITUTIONS, // This Arg allows substitutions in its linkedId,
+                        // SubVals and values.
+    PRIVATE, // This Arg is used internally, and cannot be specified by the
+             // user.
+    SECRET, // This Arg is used by development processes and although it can be
+            // set by the user, it is not displayed to the user.
+    ALLOWALL, // This Arg can use the '*' linkedId to apply to all known
+              // linkedIds
+    INCREMENTDEFAULTCOUNTER, // If an Arg has this option and the default
+                             // linkedId is used, the defaultLinkedIdCounter is
+                             // incremented *first*.
+    INPUT, // This Arg counts as an input for REQUIREINPUT
+    REQUIREINPUT, // This Arg can only be applied via --all if there is an
+                  // input (i.e. --open or --append)
+    OUTPUT, // This Arg provides an output filename. With Opt.ALLOWALL *.ext is
+            // shorthand for --all --output={basename}.ext
+    STORED, // This Arg resets and creates a new set of "opened" linkedIds
+  }
+
+  private final String[] argNames;
+
+  private Opt[] argOptions;
+
+  private boolean defaultBoolValue = false;
+
+  private String description = null;
+
+  private Arg(String description, Opt... options)
+  {
+    this(null, description, false, options);
+  }
+
+  private Arg(String description, boolean defaultBoolean, Opt... options)
+  {
+    this(null, description, defaultBoolean, options);
+  }
+
+  private Arg(String alternativeName, String description, Opt... options)
+  {
+    this(alternativeName, description, false, options);
+  }
+
+  private Arg(String alternativeName, String description,
+          boolean defaultBoolean, Opt... options)
+  {
+    this.argNames = alternativeName != null
+            ? new String[]
+            { this.getName(), alternativeName }
+            : new String[]
+            { this.getName() };
+    this.description = description;
+    this.defaultBoolValue = defaultBoolean;
+    this.setOptions(options);
+  }
+
+  public String argString()
+  {
+    return argString(false);
+  }
+
+  public String negateArgString()
+  {
+    return argString(true);
+  }
+
+  private String argString(boolean negate)
+  {
+    StringBuilder sb = new StringBuilder(ArgParser.DOUBLEDASH);
+    if (negate && hasOption(Opt.BOOLEAN))
+      sb.append(ArgParser.NEGATESTRING);
+    sb.append(getName());
+    return sb.toString();
+  }
+
+  public String toLongString()
+  {
+    StringBuilder sb = new StringBuilder();
+    sb.append(this.getClass().getName()).append('.').append(this.name());
+    sb.append('(');
+    if (getNames().length > 0)
+      sb.append('"');
+    sb.append(String.join("\", \"", getNames()));
+    if (getNames().length > 0)
+      sb.append('"');
+    sb.append(")\n");
+    sb.append("\nOpt: ");
+    // map List<Opt> to List<String> for the String.join
+    List<String> optList = Arrays.asList(argOptions).stream()
+            .map(opt -> opt.name()).collect(Collectors.toList());
+    sb.append(String.join(", ", optList));
+    sb.append("\n");
+    return sb.toString();
+  }
+
+  public String[] getNames()
+  {
+    return argNames;
+  }
+
+  public String getName()
+  {
+    return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
+  }
+
+  @Override
+  public final String toString()
+  {
+    return getName();
+  }
+
+  public boolean hasOption(Opt o)
+  {
+    if (argOptions == null)
+      return false;
+    for (Opt option : argOptions)
+    {
+      if (o == option)
+        return true;
+    }
+    return false;
+  }
+
+  protected void setOptions(Opt... options)
+  {
+    this.argOptions = options;
+  }
+
+  protected boolean getDefaultBoolValue()
+  {
+    return defaultBoolValue;
+  }
+
+  protected String getDescription()
+  {
+    return description;
+  }
+
+  public static String booleanArgString(Arg a)
+  {
+    StringBuilder sb = new StringBuilder(a.argString());
+    if (a.hasOption(Opt.BOOLEAN))
+    {
+      sb.append('/');
+      sb.append(a.negateArgString());
+    }
+    return sb.toString();
+  }
+
+  public static final String usage()
+  {
+    StringBuilder sb = new StringBuilder();
+
+    sb.append(ChannelProperties.getProperty("app_name"));
+    String version = Cache.getDefault("VERSION", null);
+    if (version != null)
+    {
+      sb.append(" version ");
+      sb.append(Cache.getDefault("VERSION", "unknown"));
+    }
+    sb.append(System.lineSeparator());
+    sb.append("Usage: jalview [files...] [args]");
+    sb.append(System.lineSeparator());
+    sb.append(System.lineSeparator());
+
+    int maxArgLength = 0;
+    for (Arg a : EnumSet.allOf(Arg.class))
+    {
+      if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
+        continue;
+
+      StringBuilder argSb = new StringBuilder();
+      argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
+              : a.argString());
+      if (a.hasOption(Opt.STRING))
+        argSb.append("=value");
+      if (argSb.length() > maxArgLength)
+        maxArgLength = argSb.length();
+    }
+
+    // might want to sort these
+    for (Arg a : EnumSet.allOf(Arg.class))
+    {
+      if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
+        continue;
+      StringBuilder argSb = new StringBuilder();
+      argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
+              : a.argString());
+      if (a.hasOption(Opt.STRING))
+        argSb.append("=value");
+      Iterator<String> descLines = null;
+      if (a.getDescription() != null)
+      {
+        descLines = Arrays.stream(a.getDescription().split("\\n"))
+                .iterator();
+      }
+      sb.append(String.format("%-" + maxArgLength + "s", argSb.toString()));
+      boolean first = true;
+      if (descLines != null)
+      {
+        while (descLines.hasNext())
+        {
+          if (first)
+            sb.append(" - ");
+          else
+            sb.append(" ".repeat(maxArgLength + 3));
+          sb.append(descLines.next());
+          sb.append(System.lineSeparator());
+          first = false;
+        }
+      }
+
+      List<String> options = new ArrayList<>();
+
+      if (a.hasOption(Opt.BOOLEAN))
+      {
+        options.add("default " + (a.getDefaultBoolValue() ? a.argString()
+                : a.negateArgString()));
+      }
+
+      if (a.hasOption(Opt.MULTI))
+      {
+        options.add("multiple");
+      }
+
+      if (a.hasOption(Opt.LINKED))
+      {
+        options.add("can be linked");
+      }
+
+      if (a.hasOption(Opt.GLOB))
+      {
+        options.add("allows file globs");
+      }
+
+      if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
+      {
+        options.add("allows substitutions");
+      }
+
+      if (a.hasOption(Opt.ALLOWALL))
+      {
+        options.add("can be applied to all linked arguments");
+      }
+
+      if (a.hasOption(Opt.PRIVATE))
+      {
+        options.add("for internal use only");
+      }
+
+      if (a.hasOption(Opt.SECRET))
+      {
+        options.add("for development use only");
+      }
+
+      if (options.size() > 0)
+      {
+        if (first)
+          sb.append(" - ");
+        else
+          sb.append(" ".repeat(maxArgLength + 3));
+        sb.append("(");
+        sb.append(String.join("; ", options));
+        sb.append(')');
+        sb.append(System.lineSeparator());
+      }
+      sb.append(System.lineSeparator());
+    }
+    return sb.toString();
+  }
+}
\ No newline at end of file
diff --git a/src/jalview/bin/argparser/ArgParser.java b/src/jalview/bin/argparser/ArgParser.java
new file mode 100644 (file)
index 0000000..e6bd917
--- /dev/null
@@ -0,0 +1,1170 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.bin.argparser;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.bin.Jalview;
+import jalview.bin.argparser.Arg.Opt;
+import jalview.util.FileUtils;
+import jalview.util.HttpUtils;
+
+public class ArgParser
+{
+  protected static final String DOUBLEDASH = "--";
+
+  protected static final char EQUALS = '=';
+
+  protected static final String NEGATESTRING = "no";
+
+  // the default linked id prefix used for no id (not even square braces)
+  protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
+
+  // the linkedId string used to match all linkedIds seen so far
+  protected static final String MATCHALLLINKEDIDS = "*";
+
+  // the linkedId string used to match all of the last --open'ed linkedIds
+  protected static final String MATCHOPENEDLINKEDIDS = "open*";
+
+  // the counter added to the default linked id prefix
+  private int defaultLinkedIdCounter = 0;
+
+  // the substitution string used to use the defaultLinkedIdCounter
+  private static final String DEFAULTLINKEDIDCOUNTER = "{}";
+
+  // the counter added to the default linked id prefix. NOW using
+  // linkedIdAutoCounter
+  // private int openLinkedIdCounter = 0;
+
+  // the linked id prefix used for --open files. NOW the same as DEFAULT
+  protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
+
+  // the counter used for {n} substitutions
+  private int linkedIdAutoCounter = 0;
+
+  // the linked id substitution string used to increment the idCounter (and use
+  // the incremented value)
+  private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
+
+  // the linked id substitution string used to use the idCounter
+  private static final String LINKEDIDAUTOCOUNTER = "{n}";
+
+  // the linked id substitution string used to use the filename extension of
+  // --append
+  // or --open
+  private static final String LINKEDIDEXTENSION = "{extension}";
+
+  // the linked id substitution string used to use the base filename of --append
+  // or --open
+  private static final String LINKEDIDBASENAME = "{basename}";
+
+  // the linked id substitution string used to use the dir path of --append
+  // or --open
+  private static final String LINKEDIDDIRNAME = "{dirname}";
+
+  // the current argfile
+  private String argFile = null;
+
+  // the linked id substitution string used to use the dir path of the latest
+  // --argfile name
+  private static final String ARGFILEBASENAME = "{argfilebasename}";
+
+  // the linked id substitution string used to use the dir path of the latest
+  // --argfile name
+  private static final String ARGFILEDIRNAME = "{argfiledirname}";
+
+  // an output file wildcard to signify --output=*.ext is really --all --output
+  // {basename}.ext
+  private static final String OUTPUTWILDCARD = "*.";
+
+  // flag to say whether {n} subtitutions in output filenames should be made.
+  // Turn on and off with --substitutions and --nosubstitutions
+  // Start with it on
+  private boolean substitutions = true;
+
+  // flag to say whether the default linkedId is the current default linked id
+  // or ALL linkedIds
+  private boolean allLinkedIds = false;
+
+  // flag to say whether the default linkedId is the current default linked id
+  // or OPENED linkedIds
+  private boolean openedLinkedIds = false;
+
+  protected static final Map<String, Arg> argMap;
+
+  protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
+
+  protected List<String> linkedOrder = new ArrayList<>();
+
+  protected List<String> storedLinkedIds = new ArrayList<>();
+
+  protected List<Arg> argList = new ArrayList<>();
+
+  private static final char ARGFILECOMMENT = '#';
+
+  private int argIndex = 0;
+
+  private BootstrapArgs bootstrapArgs = null;
+
+  static
+  {
+    argMap = new HashMap<>();
+    for (Arg a : EnumSet.allOf(Arg.class))
+    {
+      for (String argName : a.getNames())
+      {
+        if (argMap.containsKey(argName))
+        {
+          Console.warn("Trying to add argument name multiple times: '"
+                  + argName + "'"); // RESTORE THIS WHEN
+          // MERGED
+          if (argMap.get(argName) != a)
+          {
+            Console.error(
+                    "Trying to add argument name multiple times for different Args: '"
+                            + argMap.get(argName).getName() + ":" + argName
+                            + "' and '" + a.getName() + ":" + argName
+                            + "'");
+          }
+          continue;
+        }
+        argMap.put(argName, a);
+      }
+    }
+  }
+
+  public ArgParser(String[] args)
+  {
+    this(args, false, null);
+  }
+
+  public ArgParser(String[] args, boolean initsubstitutions,
+          BootstrapArgs bsa)
+  {
+    // Make a mutable new ArrayList so that shell globbing parser works.
+    // (When shell file globbing is used, there are a sequence of non-Arg
+    // arguments (which are the expanded globbed filenames) that need to be
+    // consumed by the --append/--argfile/etc Arg which is most easily done by
+    // removing these filenames from the list one at a time. This can't be done
+    // with an ArrayList made with only Arrays.asList(String[] args). )
+    this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
+            bsa);
+  }
+
+  public ArgParser(List<String> args, boolean initsubstitutions)
+  {
+    this(args, initsubstitutions, false, null);
+  }
+
+  public ArgParser(List<String> args, boolean initsubstitutions,
+          boolean allowPrivate, BootstrapArgs bsa)
+  {
+    // do nothing if there are no "--" args and (some "-" args || >0 arg is
+    // "open")
+    boolean d = false;
+    boolean dd = false;
+    for (String arg : args)
+    {
+      if (arg.startsWith(DOUBLEDASH))
+      {
+        dd = true;
+        break;
+      }
+      else if (arg.startsWith("-") || arg.equals("open"))
+      {
+        d = true;
+      }
+    }
+    if (d && !dd)
+    {
+      // leave it to the old style -- parse an empty list
+      parse(new ArrayList<String>(), false, false);
+      return;
+    }
+    if (bsa != null)
+      this.bootstrapArgs = bsa;
+    else
+      this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
+    parse(args, initsubstitutions, allowPrivate);
+  }
+
+  private void parse(List<String> args, boolean initsubstitutions,
+          boolean allowPrivate)
+  {
+    this.substitutions = initsubstitutions;
+    boolean openEachInitialFilenames = true;
+    for (int i = 0; i < args.size(); i++)
+    {
+      String arg = args.get(i);
+
+      // If the first arguments do not start with "--" or "-" or is not "open"
+      // and` is a filename that exists it is probably a file/list of files to
+      // open so we fake an Arg.OPEN argument and when adding files only add the
+      // single arg[i] and increment the defaultLinkedIdCounter so that each of
+      // these files is opened separately.
+      if (openEachInitialFilenames && !arg.startsWith(DOUBLEDASH)
+              && !arg.startsWith("-") && !arg.equals("open")
+              && (new File(arg).exists()
+                      || HttpUtils.startsWithHttpOrHttps(arg)))
+      {
+        arg = Arg.OPEN.argString();
+      }
+      else
+      {
+        openEachInitialFilenames = false;
+      }
+
+      String argName = null;
+      String val = null;
+      List<String> globVals = null; // for Opt.GLOB only
+      SubVals globSubVals = null; // also for use by Opt.GLOB only
+      String linkedId = null;
+      if (arg.startsWith(DOUBLEDASH))
+      {
+        int equalPos = arg.indexOf(EQUALS);
+        if (equalPos > -1)
+        {
+          argName = arg.substring(DOUBLEDASH.length(), equalPos);
+          val = arg.substring(equalPos + 1);
+        }
+        else
+        {
+          argName = arg.substring(DOUBLEDASH.length());
+        }
+        int idOpen = argName.indexOf('[');
+        int idClose = argName.indexOf(']');
+
+        if (idOpen > -1 && idClose == argName.length() - 1)
+        {
+          linkedId = argName.substring(idOpen + 1, idClose);
+          argName = argName.substring(0, idOpen);
+        }
+
+        Arg a = argMap.get(argName);
+        // check for boolean prepended by "no"
+        boolean negated = false;
+        if (a == null && argName.startsWith(NEGATESTRING) && argMap
+                .containsKey(argName.substring(NEGATESTRING.length())))
+        {
+          argName = argName.substring(NEGATESTRING.length());
+          a = argMap.get(argName);
+          negated = true;
+        }
+
+        // check for config errors
+        if (a == null)
+        {
+          // arg not found
+          Console.error("Argument '" + arg + "' not recognised.  Exiting.");
+          Jalview.exit("Invalid argument used." + System.lineSeparator()
+                  + "Use" + System.lineSeparator() + "jalview "
+                  + Arg.HELP.argString() + System.lineSeparator()
+                  + "for a usage statement.", 13);
+          continue;
+        }
+        if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
+        {
+          Console.error(
+                  "Argument '" + a.argString() + "' is private. Ignoring.");
+          continue;
+        }
+        if (!a.hasOption(Opt.BOOLEAN) && negated)
+        {
+          // used "no" with a non-boolean option
+          Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
+                  + "' not a boolean option. Ignoring.");
+          continue;
+        }
+        if (!a.hasOption(Opt.STRING) && equalPos > -1)
+        {
+          // set --argname=value when arg does not accept values
+          Console.error("Argument '" + a.argString()
+                  + "' does not expect a value (given as '" + arg
+                  + "').  Ignoring.");
+          continue;
+        }
+        if (!a.hasOption(Opt.LINKED) && linkedId != null)
+        {
+          // set --argname[linkedId] when arg does not use linkedIds
+          Console.error("Argument '" + a.argString()
+                  + "' does not expect a linked id (given as '" + arg
+                  + "'). Ignoring.");
+          continue;
+        }
+
+        // String value(s)
+        if (a.hasOption(Opt.STRING))
+        {
+          if (equalPos >= 0)
+          {
+            if (a.hasOption(Opt.GLOB))
+            {
+              // strip off and save the SubVals to be added individually later
+              globSubVals = new SubVals(val);
+              // make substitutions before looking for files
+              String fileGlob = makeSubstitutions(globSubVals.getContent(),
+                      linkedId);
+              globVals = FileUtils.getFilenamesFromGlob(fileGlob);
+            }
+            else
+            {
+              // val is already set -- will be saved in the ArgValue later in
+              // the normal way
+            }
+          }
+          else
+          {
+            // There is no "=" so value is next arg or args (possibly shell
+            // glob-expanded)
+            if ((openEachInitialFilenames ? i : i + 1) >= args.size())
+            {
+              // no value to take for arg, which wants a value
+              Console.error("Argument '" + a.getName()
+                      + "' requires a value, none given. Ignoring.");
+              continue;
+            }
+            // deal with bash globs here (--arg val* is expanded before reaching
+            // the JVM). Note that SubVals cannot be used in this case.
+            // If using the --arg=val then the glob is preserved and Java globs
+            // will be used later. SubVals can be used.
+            if (a.hasOption(Opt.GLOB))
+            {
+              // if this is the first argument with a file list at the start of
+              // the args we add filenames from index i instead of i+1
+              globVals = getShellGlobbedFilenameValues(a, args,
+                      openEachInitialFilenames ? i : i + 1);
+            }
+            else
+            {
+              val = args.get(i + 1);
+            }
+          }
+        }
+
+        // make NOACTION adjustments
+        // default and auto counter increments
+        if (a == Arg.NPP)
+        {
+          linkedIdAutoCounter++;
+        }
+        else if (a == Arg.SUBSTITUTIONS)
+        {
+          substitutions = !negated;
+        }
+        else if (a == Arg.SETARGFILE)
+        {
+          argFile = val;
+        }
+        else if (a == Arg.UNSETARGFILE)
+        {
+          argFile = null;
+        }
+        else if (a == Arg.ALL)
+        {
+          allLinkedIds = !negated;
+          openedLinkedIds = false;
+        }
+        else if (a == Arg.OPENED)
+        {
+          openedLinkedIds = !negated;
+          allLinkedIds = false;
+        }
+
+        if (a.hasOption(Opt.STORED))
+        {
+          // reset the lastOpenedLinkedIds list
+          this.storedLinkedIds = new ArrayList<>();
+        }
+
+        // this is probably only Arg.NEW and Arg.OPEN
+        if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
+        {
+          // use the next default prefixed OPENLINKEDID
+          defaultLinkedId(true);
+        }
+
+        String autoCounterString = null;
+        boolean usingAutoCounterLinkedId = false;
+        String defaultLinkedId = defaultLinkedId(false);
+        boolean usingDefaultLinkedId = false;
+        if (a.hasOption(Opt.LINKED))
+        {
+          if (linkedId == null)
+          {
+            if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
+                    && val.startsWith(MATCHALLLINKEDIDS))
+            {
+              // --output=*.ext is shorthand for --all --output {basename}.ext
+              // (or --image=*.ext)
+              allLinkedIds = true;
+              openedLinkedIds = false;
+              linkedId = MATCHALLLINKEDIDS;
+              val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
+                      + val.substring(MATCHALLLINKEDIDS.length());
+            }
+            else if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
+                    && val.startsWith(MATCHOPENEDLINKEDIDS))
+            {
+              // --output=open*.ext is shorthand for --opened --output
+              // {basename}.ext
+              // (or --image=open*.ext)
+              openedLinkedIds = true;
+              allLinkedIds = false;
+              linkedId = MATCHOPENEDLINKEDIDS;
+              val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
+                      + val.substring(MATCHOPENEDLINKEDIDS.length());
+            }
+            else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
+            {
+              linkedId = MATCHALLLINKEDIDS;
+            }
+            else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
+            {
+              linkedId = MATCHOPENEDLINKEDIDS;
+            }
+            else
+            {
+              // use default linkedId for linked arguments
+              linkedId = defaultLinkedId;
+              usingDefaultLinkedId = true;
+              Console.debug("Changing linkedId to '" + linkedId + "' from "
+                      + arg);
+            }
+          }
+          else if (linkedId.contains(LINKEDIDAUTOCOUNTER))
+          {
+            // turn {n} to the autoCounter
+            autoCounterString = Integer.toString(linkedIdAutoCounter);
+            linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
+                    autoCounterString);
+            usingAutoCounterLinkedId = true;
+            Console.debug(
+                    "Changing linkedId to '" + linkedId + "' from " + arg);
+          }
+          else if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
+          {
+            // turn {++n} to the incremented autoCounter
+            autoCounterString = Integer.toString(++linkedIdAutoCounter);
+            linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
+                    autoCounterString);
+            usingAutoCounterLinkedId = true;
+            Console.debug(
+                    "Changing linkedId to '" + linkedId + "' from " + arg);
+          }
+        }
+
+        // do not continue in this block for NOACTION args
+        if (a.hasOption(Opt.NOACTION))
+          continue;
+
+        ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
+
+        // not dealing with both NODUPLICATEVALUES and GLOB
+        if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
+        {
+          Console.error("Argument '" + a.argString()
+                  + "' cannot contain a duplicate value ('" + val
+                  + "'). Ignoring this and subsequent occurrences.");
+          continue;
+        }
+
+        // check for unique id
+        SubVals idsv = new SubVals(val);
+        String id = idsv.get(ArgValues.ID);
+        if (id != null && avm.hasId(a, id))
+        {
+          Console.error("Argument '" + a.argString()
+                  + "' has a duplicate id ('" + id + "'). Ignoring.");
+          continue;
+        }
+
+        /* TODO
+         * Change all avs.addValue() avs.setBoolean avs.setNegated() avs.incrementCount calls to checkfor linkedId == "*"
+         * DONE, need to check
+         */
+        ArgValues avs = avm.getOrCreateArgValues(a);
+
+        // store appropriate String value(s)
+        if (a.hasOption(Opt.STRING))
+        {
+          if (a.hasOption(Opt.GLOB) && globVals != null
+                  && globVals.size() > 0)
+          {
+            Enumeration<String> gve = Collections.enumeration(globVals);
+            while (gve.hasMoreElements())
+            {
+              String v = gve.nextElement();
+              SubVals vsv = new SubVals(globSubVals, v);
+              addValue(linkedId, avs, vsv, v, argIndex++, true);
+              // if we're using defaultLinkedId and the arg increments the
+              // counter:
+              if (gve.hasMoreElements() && usingDefaultLinkedId
+                      && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
+              {
+                // increment the default linkedId
+                linkedId = defaultLinkedId(true);
+                // get new avm and avs
+                avm = linkedArgs.get(linkedId);
+                avs = avm.getOrCreateArgValues(a);
+              }
+            }
+          }
+          else
+          {
+            addValue(linkedId, avs, val, argIndex, true);
+          }
+        }
+        else if (a.hasOption(Opt.BOOLEAN))
+        {
+          setBoolean(linkedId, avs, !negated, argIndex);
+          setNegated(linkedId, avs, negated);
+        }
+        else if (a.hasOption(Opt.UNARY))
+        {
+          setBoolean(linkedId, avs, true, argIndex);
+        }
+
+        // remove the '*' or 'open*' linkedId that should be empty if it was
+        // created
+        if ((MATCHALLLINKEDIDS.equals(linkedId)
+                && linkedArgs.containsKey(linkedId))
+                || (MATCHOPENEDLINKEDIDS.equals(linkedId)
+                        && linkedArgs.containsKey(linkedId)))
+        {
+          linkedArgs.remove(linkedId);
+        }
+      }
+    }
+  }
+
+  private void finaliseStoringArgValue(String linkedId, ArgValues avs)
+  {
+    Arg a = avs.arg();
+    incrementCount(linkedId, avs);
+    argIndex++;
+
+    // store in appropriate place
+    if (a.hasOption(Opt.LINKED))
+    {
+      // store the order of linkedIds
+      if (!linkedOrder.contains(linkedId))
+        linkedOrder.add(linkedId);
+    }
+
+    // store arg in the list of args used
+    if (!argList.contains(a))
+      argList.add(a);
+  }
+
+  private String defaultLinkedId(boolean increment)
+  {
+    String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
+            .append(Integer.toString(defaultLinkedIdCounter)).toString();
+    if (increment)
+    {
+      while (linkedArgs.containsKey(defaultLinkedId))
+      {
+        defaultLinkedIdCounter++;
+        defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
+                .append(Integer.toString(defaultLinkedIdCounter))
+                .toString();
+      }
+    }
+    getOrCreateLinkedArgValuesMap(defaultLinkedId);
+    return defaultLinkedId;
+  }
+
+  public String makeSubstitutions(String val, String linkedId)
+  {
+    if (!this.substitutions || val == null)
+      return val;
+
+    String subvals;
+    String rest;
+    if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
+    {
+      int closeBracket = val.indexOf(']');
+      if (val.length() == closeBracket)
+        return val;
+      subvals = val.substring(0, closeBracket + 1);
+      rest = val.substring(closeBracket + 1);
+    }
+    else
+    {
+      subvals = "";
+      rest = val;
+    }
+    if (rest.contains(LINKEDIDAUTOCOUNTER))
+      rest = rest.replace(LINKEDIDAUTOCOUNTER,
+              String.valueOf(linkedIdAutoCounter));
+    if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
+      rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
+              String.valueOf(++linkedIdAutoCounter));
+    if (rest.contains(DEFAULTLINKEDIDCOUNTER))
+      rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
+              String.valueOf(defaultLinkedIdCounter));
+    ArgValuesMap avm = linkedArgs.get(linkedId);
+    if (avm != null)
+    {
+      if (rest.contains(LINKEDIDBASENAME))
+      {
+        rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
+      }
+      if (rest.contains(LINKEDIDEXTENSION))
+      {
+        rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
+      }
+      if (rest.contains(LINKEDIDDIRNAME))
+      {
+        rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
+      }
+    }
+    if (argFile != null)
+    {
+      if (rest.contains(ARGFILEBASENAME))
+      {
+        rest = rest.replace(ARGFILEBASENAME,
+                FileUtils.getBasename(new File(argFile)));
+      }
+      if (rest.contains(ARGFILEDIRNAME))
+      {
+        rest = rest.replace(ARGFILEDIRNAME,
+                FileUtils.getDirname(new File(argFile)));
+      }
+    }
+
+    return new StringBuilder(subvals).append(rest).toString();
+  }
+
+  /*
+   * A helper method to take a list of String args where we're expecting
+   * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
+   * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
+   * "file2", "file3"} *and remove these from the original list object* so that
+   * processing can continue from where it has left off, e.g. args has become
+   * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
+   * carries on from the next --arg if available.
+   */
+  protected static List<String> getShellGlobbedFilenameValues(Arg a,
+          List<String> args, int i)
+  {
+    List<String> vals = new ArrayList<>();
+    while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
+    {
+      vals.add(FileUtils.substituteHomeDir(args.remove(i)));
+      if (!a.hasOption(Opt.GLOB))
+        break;
+    }
+    return vals;
+  }
+
+  public BootstrapArgs getBootstrapArgs()
+  {
+    return bootstrapArgs;
+  }
+
+  public boolean isSet(Arg a)
+  {
+    return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
+  }
+
+  public boolean isSetAtAll(Arg a)
+  {
+    for (String linkedId : linkedOrder)
+    {
+      if (isSet(linkedId, a))
+        return true;
+    }
+    return false;
+  }
+
+  public boolean isSet(String linkedId, Arg a)
+  {
+    ArgValuesMap avm = linkedArgs.get(linkedId);
+    return avm == null ? false : avm.containsArg(a);
+  }
+
+  public boolean getBoolean(Arg a)
+  {
+    if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
+    {
+      Console.warn("Getting boolean from non boolean Arg '" + a.getName()
+              + "'.");
+    }
+    return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
+  }
+
+  public boolean getBool(String linkedId, Arg a)
+  {
+    ArgValuesMap avm = linkedArgs.get(linkedId);
+    if (avm == null)
+      return a.getDefaultBoolValue();
+    ArgValues avs = avm.getArgValues(a);
+    return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
+  }
+
+  public List<String> getLinkedIds()
+  {
+    return linkedOrder;
+  }
+
+  public ArgValuesMap getLinkedArgs(String id)
+  {
+    return linkedArgs.get(id);
+  }
+
+  @Override
+  public String toString()
+  {
+    StringBuilder sb = new StringBuilder();
+    sb.append("UNLINKED\n");
+    sb.append(argValuesMapToString(linkedArgs.get(null)));
+    if (getLinkedIds() != null)
+    {
+      sb.append("LINKED\n");
+      for (String id : getLinkedIds())
+      {
+        // already listed these as UNLINKED args
+        if (id == null)
+          continue;
+
+        ArgValuesMap avm = getLinkedArgs(id);
+        sb.append("ID: '").append(id).append("'\n");
+        sb.append(argValuesMapToString(avm));
+      }
+    }
+    return sb.toString();
+  }
+
+  private static String argValuesMapToString(ArgValuesMap avm)
+  {
+    if (avm == null)
+      return null;
+    StringBuilder sb = new StringBuilder();
+    for (Arg a : avm.getArgKeys())
+    {
+      ArgValues v = avm.getArgValues(a);
+      sb.append(v.toString());
+      sb.append("\n");
+    }
+    return sb.toString();
+  }
+
+  public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
+          boolean initsubstitutions, BootstrapArgs bsa)
+  {
+    List<File> argFiles = new ArrayList<>();
+
+    for (String pattern : argFilenameGlobs)
+    {
+      // I don't think we want to dedup files, making life easier
+      argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
+    }
+
+    return parseArgFileList(argFiles, initsubstitutions, bsa);
+  }
+
+  public static ArgParser parseArgFileList(List<File> argFiles,
+          boolean initsubstitutions, BootstrapArgs bsa)
+  {
+    List<String> argsList = new ArrayList<>();
+    for (File argFile : argFiles)
+    {
+      if (!argFile.exists())
+      {
+        String message = Arg.ARGFILE.argString() + EQUALS + "\""
+                + argFile.getPath() + "\": File does not exist.";
+        Jalview.exit(message, 2);
+      }
+      try
+      {
+        String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
+                .append(EQUALS).append(argFile.getCanonicalPath())
+                .toString();
+        argsList.add(setargfile);
+        argsList.addAll(readArgFile(argFile));
+        argsList.add(Arg.UNSETARGFILE.argString());
+      } catch (IOException e)
+      {
+        String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
+                + "\": File could not be read.";
+        Jalview.exit(message, 3);
+      }
+    }
+    // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
+    // --unsetargfile
+    return new ArgParser(argsList, initsubstitutions, true, bsa);
+  }
+
+  protected static List<String> readArgFile(File argFile)
+  {
+    List<String> args = new ArrayList<>();
+    if (argFile != null && argFile.exists())
+    {
+      try
+      {
+        for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
+        {
+          if (line != null && line.length() > 0
+                  && line.charAt(0) != ARGFILECOMMENT)
+            args.add(line);
+        }
+      } catch (IOException e)
+      {
+        String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
+                + "\": File could not be read.";
+        Console.debug(message, e);
+        Jalview.exit(message, 3);
+      }
+    }
+    return args;
+  }
+
+  public static enum Position
+  {
+    FIRST, BEFORE, AFTER
+  }
+
+  // get from following Arg of type a or subval of same name (lowercase)
+  public static String getValueFromSubValOrArg(ArgValuesMap avm,
+          ArgValue av, Arg a, SubVals sv)
+  {
+    return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
+  }
+
+  // get from following Arg of type a or subval key or preference pref or
+  // default def
+  public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
+          Arg a, SubVals sv, String key, String pref, String def)
+  {
+    return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
+            def);
+  }
+
+  // get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
+  // Arg of type a or subval key or preference pref or default def
+  public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
+          Position pos, ArgValue av, SubVals sv, String key, String pref,
+          String def)
+  {
+    return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
+            sv, key, pref, def);
+  }
+
+  public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
+          ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
+          String key, String pref, String def)
+  {
+    if (key == null)
+      key = a.getName();
+    String value = null;
+    if (sv != null && sv.has(key) && sv.get(key) != null)
+    {
+      value = ap == null ? sv.get(key)
+              : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
+    }
+    else if (avm != null && avm.containsArg(a))
+    {
+      if (pos == Position.FIRST && avm.getValue(a) != null)
+        value = avm.getValue(a);
+      else if (pos == Position.BEFORE
+              && avm.getClosestPreviousArgValueOfArg(av, a) != null)
+        value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
+      else if (pos == Position.AFTER
+              && avm.getClosestNextArgValueOfArg(av, a) != null)
+        value = avm.getClosestNextArgValueOfArg(av, a).getValue();
+    }
+    else
+    {
+      value = pref != null ? Cache.getDefault(pref, def) : def;
+    }
+    return value;
+  }
+
+  public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
+          SubVals sv)
+  {
+    return getFromSubValArgOrPref(avm, a, sv, null, null, false);
+  }
+
+  public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
+          SubVals sv, String key, String pref, boolean def)
+  {
+    if ((key == null && a == null) || (sv == null && a == null))
+      return false;
+
+    boolean usingArgKey = false;
+    if (key == null)
+    {
+      key = a.getName();
+      usingArgKey = true;
+    }
+
+    String nokey = ArgParser.NEGATESTRING + key;
+
+    // look for key or nokey in subvals first (if using Arg check options)
+    if (sv != null)
+    {
+      // check for true boolean
+      if (sv.has(key) && sv.get(key) != null)
+      {
+        if (usingArgKey)
+        {
+          if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
+          {
+            Console.debug(
+                    "Looking for boolean in subval from non-boolean/non-unary Arg "
+                            + a.getName());
+            return false;
+          }
+        }
+        return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
+      }
+
+      // check for negative boolean (subval "no..." will be "true")
+      if (sv.has(nokey) && sv.get(nokey) != null)
+      {
+        if (usingArgKey)
+        {
+          if (!(a.hasOption(Opt.BOOLEAN)))
+          {
+            Console.debug(
+                    "Looking for negative boolean in subval from non-boolean Arg "
+                            + a.getName());
+            return false;
+          }
+        }
+        return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
+      }
+    }
+
+    // check argvalues
+    if (avm != null && avm.containsArg(a))
+      return avm.getBoolean(a);
+
+    // return preference or default
+    return pref != null ? Cache.getDefault(pref, def) : def;
+  }
+
+  // the following methods look for the "*" linkedId and add the argvalue to all
+  // linkedId ArgValues if it does.
+  // This version inserts the subvals sv into all created values
+  private void addValue(String linkedId, ArgValues avs, SubVals sv,
+          String v, int argIndex, boolean doSubs)
+  {
+    this.argValueOperation(Op.ADDVALUE, linkedId, avs, sv, v, false,
+            argIndex, doSubs);
+  }
+
+  private void addValue(String linkedId, ArgValues avs, String v,
+          int argIndex, boolean doSubs)
+  {
+    this.argValueOperation(Op.ADDVALUE, linkedId, avs, null, v, false,
+            argIndex, doSubs);
+  }
+
+  private void setBoolean(String linkedId, ArgValues avs, boolean b,
+          int argIndex)
+  {
+    this.argValueOperation(Op.SETBOOLEAN, linkedId, avs, null, null, b,
+            argIndex, false);
+  }
+
+  private void setNegated(String linkedId, ArgValues avs, boolean b)
+  {
+    this.argValueOperation(Op.SETNEGATED, linkedId, avs, null, null, b, 0,
+            false);
+  }
+
+  private void incrementCount(String linkedId, ArgValues avs)
+  {
+    this.argValueOperation(Op.INCREMENTCOUNT, linkedId, avs, null, null,
+            false, 0, false);
+  }
+
+  private enum Op
+  {
+    ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
+  }
+
+  // The following operations look for the "*" and "open*" linkedIds and add the
+  // argvalue to all appropriate linkedId ArgValues if it does.
+  // If subvals are supplied, they are inserted into all new set values.
+  private void argValueOperation(Op op, String linkedId, ArgValues avs,
+          SubVals sv, String v, boolean b, int argIndex, boolean doSubs)
+  {
+    Arg a = avs.arg();
+
+    List<String> wildcardLinkedIds = null;
+    if (a.hasOption(Opt.ALLOWALL))
+    {
+      switch (linkedId)
+      {
+      case MATCHALLLINKEDIDS:
+        wildcardLinkedIds = getLinkedIds();
+        break;
+      case MATCHOPENEDLINKEDIDS:
+        wildcardLinkedIds = this.storedLinkedIds;
+        break;
+      }
+    }
+
+    // if we're not a wildcard linkedId and the arg is marked to be stored, add
+    // to storedLinkedIds
+    if (linkedId != null && wildcardLinkedIds == null
+            && a.hasOption(Opt.STORED)
+            && !storedLinkedIds.contains(linkedId))
+    {
+      storedLinkedIds.add(linkedId);
+    }
+
+    // if we are a wildcard linkedId, apply the arg and value to all appropriate
+    // linkedIds
+    if (wildcardLinkedIds != null)
+    {
+      for (String id : wildcardLinkedIds)
+      {
+        // skip incorrectly stored wildcard ids!
+        if (id == null || MATCHALLLINKEDIDS.equals(id)
+                || MATCHOPENEDLINKEDIDS.equals(id))
+          continue;
+        ArgValuesMap avm = linkedArgs.get(id);
+        // don't set an output if there isn't an input
+        if (a.hasOption(Opt.REQUIREINPUT)
+                && !avm.hasArgWithOption(Opt.INPUT))
+          continue;
+
+        ArgValues tavs = avm.getOrCreateArgValues(a);
+        switch (op)
+        {
+
+        case ADDVALUE:
+          String val = v;
+          if (sv != null)
+          {
+            if (doSubs)
+            {
+              val = makeSubstitutions(v, id);
+              sv = new SubVals(sv, val);
+            }
+            tavs.addValue(sv, val, argIndex, true);
+          }
+          else
+          {
+            if (doSubs)
+            {
+              val = makeSubstitutions(v, id);
+            }
+            tavs.addValue(val, argIndex, true);
+          }
+          finaliseStoringArgValue(id, tavs);
+          break;
+
+        case SETBOOLEAN:
+          tavs.setBoolean(b, argIndex, true);
+          finaliseStoringArgValue(id, tavs);
+          break;
+
+        case SETNEGATED:
+          tavs.setNegated(b, true);
+          break;
+
+        case INCREMENTCOUNT:
+          tavs.incrementCount();
+          break;
+
+        default:
+          break;
+
+        }
+
+      }
+    }
+    else // no wildcard linkedId -- do it simpler
+    {
+      switch (op)
+      {
+      case ADDVALUE:
+        String val = v;
+        if (sv != null)
+        {
+          if (doSubs)
+          {
+            val = makeSubstitutions(v, linkedId);
+            sv = new SubVals(sv, val);
+          }
+          avs.addValue(sv, val, argIndex, false);
+        }
+        else
+        {
+          if (doSubs)
+          {
+            val = makeSubstitutions(v, linkedId);
+          }
+          avs.addValue(val, argIndex, false);
+        }
+        finaliseStoringArgValue(linkedId, avs);
+        break;
+
+      case SETBOOLEAN:
+        avs.setBoolean(b, argIndex, false);
+        finaliseStoringArgValue(linkedId, avs);
+        break;
+
+      case SETNEGATED:
+        avs.setNegated(b, false);
+        break;
+
+      case INCREMENTCOUNT:
+        avs.incrementCount();
+        break;
+
+      default:
+        break;
+      }
+    }
+  }
+
+  private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
+  {
+    if (linkedArgs.containsKey(linkedId)
+            && linkedArgs.get(linkedId) != null)
+      return linkedArgs.get(linkedId);
+
+    linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
+    return linkedArgs.get(linkedId);
+  }
+
+}
\ No newline at end of file
diff --git a/src/jalview/bin/argparser/ArgValue.java b/src/jalview/bin/argparser/ArgValue.java
new file mode 100644 (file)
index 0000000..1643713
--- /dev/null
@@ -0,0 +1,83 @@
+package jalview.bin.argparser;
+
+/**
+ * A helper class to keep an index of argument position with argument values
+ */
+public class ArgValue implements Comparable<ArgValue>
+{
+  private Arg arg;
+
+  private int argIndex;
+
+  private String value;
+
+  // This id is set by a subVal id= to identify the product of this ArgValue
+  // later. Set but not currently used.
+  private String id;
+
+  private SubVals subVals;
+
+  protected ArgValue(Arg a, SubVals sv, String content, int argIndex)
+  {
+    this.arg = a;
+    this.value = content;
+    this.argIndex = argIndex;
+    this.subVals = sv == null ? new SubVals("") : sv;
+  }
+
+  protected ArgValue(Arg a, String value, int argIndex)
+  {
+    this.arg = a;
+    this.argIndex = argIndex;
+    this.subVals = new SubVals(value);
+    this.value = getSubVals().getContent();
+  }
+
+  public Arg getArg()
+  {
+    return arg;
+  }
+
+  public String getValue()
+  {
+    return value;
+  }
+
+  public int getArgIndex()
+  {
+    return argIndex;
+  }
+
+  protected void setId(String i)
+  {
+    id = i;
+  }
+
+  public String getId()
+  {
+    return id;
+  }
+
+  public SubVals getSubVals()
+  {
+    return subVals;
+  }
+
+  public String getSubVal(String key)
+  {
+    if (subVals == null || !subVals.has(key))
+      return null;
+    return subVals.get(key);
+  }
+
+  protected void putSubVal(String key, String val)
+  {
+    this.subVals.put(key, val);
+  }
+
+  @Override
+  public int compareTo(ArgValue o)
+  {
+    return this.getArgIndex() - o.getArgIndex();
+  }
+}
\ No newline at end of file
diff --git a/src/jalview/bin/argparser/ArgValues.java b/src/jalview/bin/argparser/ArgValues.java
new file mode 100644 (file)
index 0000000..f2c299c
--- /dev/null
@@ -0,0 +1,201 @@
+package jalview.bin.argparser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import jalview.bin.Console;
+import jalview.bin.argparser.Arg.Opt;
+
+public class ArgValues
+{
+  public static final String ID = "id";
+
+  private Arg arg;
+
+  private int argCount = 0;
+
+  private boolean boolValue = false;
+
+  private boolean negated = false;
+
+  private boolean setByWildcard = false;
+
+  private int boolIndex = -1;
+
+  private List<Integer> argsIndexes;
+
+  private List<ArgValue> argValueList;
+
+  private Map<String, ArgValue> idMap = new HashMap<>();
+
+  protected ArgValues(Arg a)
+  {
+    this.arg = a;
+    this.argValueList = new ArrayList<ArgValue>();
+    this.boolValue = arg.getDefaultBoolValue();
+  }
+
+  protected boolean setByWildcard()
+  {
+    return setByWildcard;
+  }
+
+  protected void setSetByWildcard(boolean b)
+  {
+    setByWildcard = b;
+  }
+
+  public Arg arg()
+  {
+    return arg;
+  }
+
+  protected int getCount()
+  {
+    return argCount;
+  }
+
+  protected void incrementCount()
+  {
+    argCount++;
+  }
+
+  protected void setNegated(boolean b, boolean beingSetByWildcard)
+  {
+    // don't overwrite a wildcard set boolean with a non-wildcard set boolean
+    if (boolIndex >= 0 && !this.setByWildcard && beingSetByWildcard)
+      return;
+    this.negated = b;
+  }
+
+  protected boolean isNegated()
+  {
+    return this.negated;
+  }
+
+  protected void setBoolean(boolean b, int i, boolean beingSetByWildcard)
+  {
+    // don't overwrite a wildcard set boolean with a non-wildcard set boolean
+    if (boolIndex >= 0 && !this.setByWildcard && beingSetByWildcard)
+      return;
+    this.boolValue = b;
+    this.boolIndex = i;
+    this.setSetByWildcard(beingSetByWildcard);
+  }
+
+  protected boolean getBoolean()
+  {
+    return this.boolValue;
+  }
+
+  @Override
+  public String toString()
+  {
+    if (argValueList == null)
+      return null;
+    StringBuilder sb = new StringBuilder();
+    sb.append(arg.toLongString());
+    if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
+      sb.append("Boolean: ").append(boolValue).append("; Default: ")
+              .append(arg.getDefaultBoolValue()).append("; Negated: ")
+              .append(negated).append("\n");
+    if (arg.hasOption(Opt.STRING))
+    {
+      sb.append("Values:");
+      sb.append("'")
+              .append(String
+                      .join("',\n  '",
+                              argValueList.stream().map(av -> av.getValue())
+                                      .collect(Collectors.toList())))
+              .append("'");
+      sb.append("\n");
+    }
+    sb.append("Count: ").append(argCount).append("\n");
+    return sb.toString();
+  }
+
+  protected void addValue(String val, int argIndex, boolean wildcard)
+  {
+    addArgValue(new ArgValue(arg(), val, argIndex), wildcard);
+  }
+
+  protected void addValue(SubVals sv, String content, int argIndex,
+          boolean wildcard)
+  {
+    addArgValue(new ArgValue(arg(), sv, content, argIndex), wildcard);
+  }
+
+  protected void addArgValue(ArgValue av, boolean beingSetByWildcard)
+  {
+    // allow a non-wildcard value to overwrite a wildcard set single value
+    boolean overwrite = !arg.hasOption(Opt.MULTI) && setByWildcard
+            && !beingSetByWildcard;
+    if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
+            && !overwrite)
+      return;
+    if (arg.hasOption(Opt.NODUPLICATEVALUES)
+            && this.containsValue(av.getValue()))
+      return;
+    // new or overwrite if single valued
+    if (argValueList == null || overwrite)
+    {
+      argValueList = new ArrayList<ArgValue>();
+    }
+    SubVals sv = new SubVals(av.getValue());
+    if (sv.has(ID))
+    {
+      String id = sv.get(ID);
+      av.setId(id);
+      idMap.put(id, av);
+    }
+    argValueList.add(av);
+    this.setSetByWildcard(beingSetByWildcard);
+  }
+
+  protected boolean hasValue(String val)
+  {
+    return argValueList.contains(val);
+  }
+
+  protected ArgValue getArgValue()
+  {
+    if (arg.hasOption(Opt.MULTI))
+      Console.warn("Requesting single value for multi value argument");
+    return argValueList.size() > 0 ? argValueList.get(0) : null;
+  }
+
+  protected List<ArgValue> getArgValueList()
+  {
+    return argValueList;
+  }
+
+  protected boolean hasId(String id)
+  {
+    return idMap.containsKey(id);
+  }
+
+  protected ArgValue getId(String id)
+  {
+    return idMap.get(id);
+  }
+
+  private boolean containsValue(String v)
+  {
+    if (argValueList == null)
+      return false;
+    for (ArgValue av : argValueList)
+    {
+      String val = av.getValue();
+      if (v == null && val == null)
+        return true;
+      if (v == null)
+        continue;
+      if (v.equals(val))
+        return true;
+    }
+    return false;
+  }
+}
\ No newline at end of file
diff --git a/src/jalview/bin/argparser/ArgValuesMap.java b/src/jalview/bin/argparser/ArgValuesMap.java
new file mode 100644 (file)
index 0000000..085099a
--- /dev/null
@@ -0,0 +1,256 @@
+package jalview.bin.argparser;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import jalview.bin.argparser.Arg.Opt;
+import jalview.util.FileUtils;
+
+/**
+ * Helper class to allow easy extraction of information about specific argument
+ * values (without having to check for null etc all the time)
+ */
+public class ArgValuesMap
+{
+  protected Map<Arg, ArgValues> m;
+
+  private String linkedId;
+
+  protected ArgValuesMap(String linkedId)
+  {
+    this.linkedId = linkedId;
+    this.newMap();
+  }
+
+  protected ArgValuesMap(String linkedId, Map<Arg, ArgValues> map)
+  {
+    this.linkedId = linkedId;
+    this.m = map;
+  }
+
+  public String getLinkedId()
+  {
+    return linkedId;
+  }
+
+  private Map<Arg, ArgValues> getMap()
+  {
+    return m;
+  }
+
+  private void newMap()
+  {
+    m = new HashMap<Arg, ArgValues>();
+  }
+
+  private void newArg(Arg a)
+  {
+    if (m == null)
+      newMap();
+    if (!containsArg(a))
+      m.put(a, new ArgValues(a));
+  }
+
+  public ArgValues getArgValues(Arg a)
+  {
+    return m == null ? null : m.get(a);
+  }
+
+  public ArgValues getOrCreateArgValues(Arg a)
+  {
+    ArgValues avs = m.get(a);
+    if (avs == null)
+      newArg(a);
+    return getArgValues(a);
+  }
+
+  public List<ArgValue> getArgValueList(Arg a)
+  {
+    ArgValues avs = getArgValues(a);
+    return avs == null ? new ArrayList<>() : avs.getArgValueList();
+  }
+
+  public ArgValue getArgValue(Arg a)
+  {
+    List<ArgValue> vals = getArgValueList(a);
+    return (vals == null || vals.size() == 0) ? null : vals.get(0);
+  }
+
+  public String getValue(Arg a)
+  {
+    ArgValue av = getArgValue(a);
+    return av == null ? null : av.getValue();
+  }
+
+  public boolean containsArg(Arg a)
+  {
+    if (m == null || !m.containsKey(a))
+      return false;
+    return a.hasOption(Opt.STRING) ? getArgValue(a) != null : true;
+  }
+
+  public boolean hasValue(Arg a, String val)
+  {
+    if (m == null || !m.containsKey(a))
+      return false;
+    for (ArgValue av : getArgValueList(a))
+    {
+      String avVal = av.getValue();
+      if ((val == null && avVal == null)
+              || (val != null && val.equals(avVal)))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public boolean getBoolean(Arg a)
+  {
+    ArgValues av = getArgValues(a);
+    return av == null ? false : av.getBoolean();
+  }
+
+  public Set<Arg> getArgKeys()
+  {
+    return m.keySet();
+  }
+
+  public ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv, Arg a)
+  {
+    ArgValue closestAv = null;
+    int thisArgIndex = thisAv.getArgIndex();
+    ArgValues compareAvs = this.getArgValues(a);
+    int closestPreviousIndex = -1;
+    for (ArgValue av : compareAvs.getArgValueList())
+    {
+      int argIndex = av.getArgIndex();
+      if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
+      {
+        closestPreviousIndex = argIndex;
+        closestAv = av;
+      }
+    }
+    return closestAv;
+  }
+
+  public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
+  {
+    // this looks for the *next* arg that *might* be referring back to
+    // a thisAv. Such an arg would have no subValues (if it does it should
+    // specify an id in the subValues so wouldn't need to be guessed).
+    ArgValue closestAv = null;
+    int thisArgIndex = thisAv.getArgIndex();
+    if (!containsArg(a))
+      return null;
+    ArgValues compareAvs = this.getArgValues(a);
+    int closestNextIndex = Integer.MAX_VALUE;
+    for (ArgValue av : compareAvs.getArgValueList())
+    {
+      int argIndex = av.getArgIndex();
+      if (argIndex > thisArgIndex && argIndex < closestNextIndex)
+      {
+        closestNextIndex = argIndex;
+        closestAv = av;
+      }
+    }
+    return closestAv;
+  }
+
+  public ArgValue[] getArgValuesReferringTo(String key, String value, Arg a)
+  {
+    // this looks for the *next* arg that *might* be referring back to
+    // a thisAv. Such an arg would have no subValues (if it does it should
+    // specify an id in the subValues so wouldn't need to be guessed).
+    List<ArgValue> avList = new ArrayList<>();
+    Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
+            : new Arg[]
+            { a };
+    for (Arg keyArg : args)
+    {
+      for (ArgValue av : this.getArgValueList(keyArg))
+      {
+
+      }
+    }
+    return (ArgValue[]) avList.toArray();
+  }
+
+  public boolean hasId(Arg a, String id)
+  {
+    ArgValues avs = this.getArgValues(a);
+    return avs == null ? false : avs.hasId(id);
+  }
+
+  public ArgValue getId(Arg a, String id)
+  {
+    ArgValues avs = this.getArgValues(a);
+    return avs == null ? null : avs.getId(id);
+  }
+
+  /*
+   * This method returns the basename of the first --append or --open value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public String getBasename()
+  {
+    return getDirBasenameOrExtension(false, false);
+  }
+
+  /*
+   * This method returns the basename of the first --append or --open value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public String getExtension()
+  {
+    return getDirBasenameOrExtension(false, true);
+  }
+
+  /*
+   * This method returns the dirname of the first --append or --open value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public String getDirname()
+  {
+    return getDirBasenameOrExtension(true, false);
+  }
+
+  public String getDirBasenameOrExtension(boolean dirname,
+          boolean extension)
+  {
+    String filename = null;
+    String appendVal = getValue(Arg.APPEND);
+    String openVal = getValue(Arg.OPEN);
+    if (appendVal != null)
+      filename = appendVal;
+    if (filename == null && openVal != null)
+      filename = openVal;
+    if (filename == null)
+      return null;
+
+    File file = new File(filename);
+    if (dirname)
+    {
+      return FileUtils.getDirname(file);
+    }
+    return extension ? FileUtils.getExtension(file)
+            : FileUtils.getBasename(file);
+  }
+
+  /*
+   * Checks if there is an Arg with Opt
+   */
+  public boolean hasArgWithOption(Opt o)
+  {
+    for (Arg a : getArgKeys())
+    {
+      if (a.hasOption(o))
+        return true;
+    }
+    return false;
+  }
+}
diff --git a/src/jalview/bin/argparser/BootstrapArgs.java b/src/jalview/bin/argparser/BootstrapArgs.java
new file mode 100644 (file)
index 0000000..14b7fe6
--- /dev/null
@@ -0,0 +1,214 @@
+package jalview.bin.argparser;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import jalview.bin.argparser.Arg.Opt;
+import jalview.util.FileUtils;
+
+public class BootstrapArgs
+{
+  // only need one
+  private Map<Arg, List<String>> bootstrapArgMap = new HashMap<>();
+
+  private Set<File> argFiles = new HashSet<>();
+
+  public static BootstrapArgs getBootstrapArgs(String[] args)
+  {
+    List<String> argList = new ArrayList<>(Arrays.asList(args));
+    return new BootstrapArgs(argList);
+  }
+
+  public static BootstrapArgs getBootstrapArgs(List<String> args)
+  {
+    return new BootstrapArgs(args);
+  }
+
+  private BootstrapArgs(List<String> args)
+  {
+    parse(args, null);
+  }
+
+  private void parse(List<String> args, File inArgFile)
+  {
+    if (args == null)
+      return;
+    // avoid looping argFiles
+    if (inArgFile != null)
+    {
+      if (argFiles.contains(inArgFile))
+      {
+        System.err.println(
+                "Looped argfiles detected: '" + inArgFile.getPath() + "'");
+        return;
+      }
+      argFiles.add(inArgFile);
+    }
+
+    for (int i = 0; i < args.size(); i++)
+    {
+      String arg = args.get(i);
+      String argName = null;
+      String val = null;
+      if (arg.startsWith(ArgParser.DOUBLEDASH))
+      {
+        // remove "--"
+        arg = arg.substring(ArgParser.DOUBLEDASH.length());
+        int equalPos = arg.indexOf(ArgParser.EQUALS);
+        if (equalPos > -1
+                && ArgParser.argMap.containsKey(arg.substring(0, equalPos)))
+        {
+          argName = arg.substring(0, equalPos);
+          val = arg.substring(equalPos + 1);
+        }
+        // check for boolean prepended by "no"
+        else if (arg.startsWith(ArgParser.NEGATESTRING)
+                && ArgParser.argMap.containsKey(
+                        arg.substring(ArgParser.NEGATESTRING.length())))
+        {
+          argName = arg.substring(ArgParser.NEGATESTRING.length());
+          val = "false";
+        }
+        else if (ArgParser.argMap.containsKey(arg))
+        {
+          argName = arg;
+          val = "true";
+        }
+
+        Arg a = ArgParser.argMap.get(argName);
+
+        if (a == null || !a.hasOption(Opt.BOOTSTRAP))
+        {
+          // not a valid bootstrap arg
+          continue;
+        }
+
+        if (a.hasOption(Opt.STRING))
+        {
+          List<String> vals = null;
+          if (equalPos == -1)
+          {
+            vals = ArgParser.getShellGlobbedFilenameValues(a, args, i + 1);
+          }
+          else
+          {
+            if (a.hasOption(Opt.GLOB))
+            {
+              vals = FileUtils.getFilenamesFromGlob(val);
+            }
+            else
+            {
+              vals = new ArrayList<>();
+              vals.add(val);
+            }
+          }
+          addAll(a, vals);
+
+          if (a == Arg.ARGFILE)
+          {
+            for (String filename : vals)
+            {
+              File argFile = new File(filename);
+              parse(ArgParser.readArgFile(argFile), argFile);
+            }
+          }
+        }
+        else
+        {
+          add(a, val);
+        }
+      }
+    }
+  }
+
+  public boolean contains(Arg a)
+  {
+    return bootstrapArgMap.containsKey(a);
+  }
+
+  public List<String> getList(Arg a)
+  {
+    return bootstrapArgMap.get(a);
+  }
+
+  private List<String> getOrCreateList(Arg a)
+  {
+    List<String> l = getList(a);
+    if (l == null)
+    {
+      l = new ArrayList<>();
+      putList(a, l);
+    }
+    return l;
+  }
+
+  private void putList(Arg a, List<String> l)
+  {
+    bootstrapArgMap.put(a, l);
+  }
+
+  /*
+   * Creates a new list if not used before,
+   * adds the value unless the existing list is non-empty
+   * and the arg is not MULTI (so first expressed value is
+   * retained).
+   */
+  private void add(Arg a, String s)
+  {
+    List<String> l = getOrCreateList(a);
+    if (a.hasOption(Opt.MULTI) || l.size() == 0)
+    {
+      l.add(s);
+    }
+  }
+
+  private void addAll(Arg a, List<String> al)
+  {
+    List<String> l = getOrCreateList(a);
+    if (a.hasOption(Opt.MULTI))
+    {
+      l.addAll(al);
+    }
+    else if (l.size() == 0 && al.size() > 0)
+    {
+      l.add(al.get(0));
+    }
+  }
+
+  /*
+   * Retrieves the first value even if MULTI.
+   * A convenience for non-MULTI args.
+   */
+  public String get(Arg a)
+  {
+    if (!bootstrapArgMap.containsKey(a))
+      return null;
+    List<String> aL = bootstrapArgMap.get(a);
+    return (aL == null || aL.size() == 0) ? null : aL.get(0);
+  }
+
+  public boolean getBoolean(Arg a, boolean d)
+  {
+    if (!bootstrapArgMap.containsKey(a))
+      return d;
+    return Boolean.parseBoolean(get(a));
+  }
+
+  public boolean getBoolean(Arg a)
+  {
+    if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
+    {
+      return false;
+    }
+    if (bootstrapArgMap.containsKey(a))
+      return Boolean.parseBoolean(get(a));
+    else
+      return a.getDefaultBoolValue();
+  }
+}
diff --git a/src/jalview/bin/argparser/SubVals.java b/src/jalview/bin/argparser/SubVals.java
new file mode 100644 (file)
index 0000000..a03ec15
--- /dev/null
@@ -0,0 +1,155 @@
+package jalview.bin.argparser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jalview.bin.Console;
+
+/**
+ * A helper class to parse a string of the possible forms "content"
+ * "[index]content", "[keyName=keyValue]content" and return the integer index,
+ * the strings keyName and keyValue, and the content after the square brackets
+ * (if present). Values not set `will be -1 or null.
+ */
+public class SubVals
+{
+  public static int NOTSET = -1;
+
+  private int index = NOTSET;
+
+  private Map<String, String> subValMap;
+
+  private static char SEPARATOR = ',';
+
+  private static char EQUALS = '=';
+
+  private String content = null;
+
+  protected SubVals(SubVals sv, String c)
+  {
+    if (sv == null)
+    {
+      this.subValMap = new HashMap<>();
+    }
+    else
+    {
+      this.subValMap = sv == null ? new HashMap<>() : sv.getSubValMap();
+      this.index = sv.getIndex();
+    }
+    this.content = c;
+  }
+
+  protected SubVals(String item)
+  {
+    if (subValMap == null)
+      subValMap = new HashMap<>();
+    this.parseVals(item);
+  }
+
+  public void parseVals(String item)
+  {
+    if (item == null)
+      return;
+    if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
+    {
+      int openBracket = 0;
+      int closeBracket = item.indexOf(']');
+      String subvalsString = item.substring(openBracket + 1, closeBracket);
+      this.content = item.substring(closeBracket + 1);
+      boolean setIndex = false;
+      for (String subvalString : subvalsString
+              .split(Character.toString(SEPARATOR)))
+      {
+        int equals = subvalString.indexOf(EQUALS);
+        if (equals > -1)
+        {
+          this.put(subvalString.substring(0, equals),
+                  subvalString.substring(equals + 1));
+        }
+        else
+        {
+          try
+          {
+            this.index = Integer.parseInt(subvalString);
+            setIndex = true;
+          } catch (NumberFormatException e)
+          {
+            // store this non-numeric key as a "true" value
+            this.put(subvalString, "true");
+          }
+        }
+      }
+      if (!setIndex)
+        this.index = NOTSET;
+      else
+        Console.debug("SubVals from '" + subvalsString + "' has index "
+                + this.index + " set");
+    }
+    else
+    {
+      this.content = item;
+    }
+  }
+
+  protected void put(String key, String val)
+  {
+    subValMap.put(key, val);
+  }
+
+  public boolean notSet()
+  {
+    // notSet is true if content present but nonsensical
+    return index == NOTSET && (subValMap == null || subValMap.size() == 0);
+  }
+
+  public String getWithSubstitutions(ArgParser ap, String id, String key)
+  {
+    return ap.makeSubstitutions(subValMap.get(key), id);
+  }
+
+  public String get(String key)
+  {
+    return subValMap.get(key);
+  }
+
+  public boolean has(String key)
+  {
+    return subValMap.containsKey(key);
+  }
+
+  public int getIndex()
+  {
+    return index;
+  }
+
+  public String getContent()
+  {
+    return content;
+  }
+
+  protected Map<String, String> getSubValMap()
+  {
+    return subValMap;
+  }
+
+  public String toString()
+  {
+    if (subValMap == null && getIndex() == NOTSET)
+      return "";
+
+    StringBuilder sb = new StringBuilder();
+    List<String> entries = new ArrayList<>();
+    subValMap.entrySet().stream().forEachOrdered(
+            m -> entries.add(m.getValue().equals("true") ? m.getKey()
+                    : new StringBuilder().append(m.getKey()).append(EQUALS)
+                            .append(m.getValue()).toString()));
+    if (getIndex() != NOTSET)
+      entries.add(Integer.toString(getIndex()));
+    sb.append('[');
+    sb.append(String.join(Character.toString(SEPARATOR), entries));
+    sb.append(']');
+    return sb.toString();
+  }
+}
\ No newline at end of file
index 1b1889e..8434b4a 100644 (file)
@@ -1,12 +1,10 @@
 package jalview.datamodel;
 
 import java.awt.Color;
-import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Spliterator;
 import java.util.StringTokenizer;
 
 import jalview.bin.Console;
@@ -174,27 +172,33 @@ public abstract class ContactMatrix implements ContactMatrixI
   {
     return "Contact Matrix";
   }
-  List<BitSet> groups=null;
+
+  List<BitSet> groups = null;
+
   @Override
   public void updateGroups(List<BitSet> colGroups)
   {
     groups = colGroups;
-    colorMap=new HashMap<>();
+    colorMap = new HashMap<>();
   }
+
   @Override
   public boolean hasGroups()
   {
-    return groups!=null && groups.size()>0;
+    return groups != null && groups.size() > 0;
   }
+
   @Override
   public List<BitSet> getGroups()
   {
     return groups;
   }
+
   @Override
   public BitSet getGroupsFor(int column)
   {
-    for (BitSet gp:groups) {
+    for (BitSet gp : groups)
+    {
       if (gp.get(column))
       {
         return gp;
@@ -202,39 +206,46 @@ public abstract class ContactMatrix implements ContactMatrixI
     }
     return ContactMatrixI.super.getGroupsFor(column);
   }
-  HashMap<BitSet,Color> colorMap = new HashMap<>();
-  @Override 
+
+  HashMap<BitSet, Color> colorMap = new HashMap<>();
+
+  @Override
   public Color getColourForGroup(BitSet bs)
   {
-    if (bs==null) {
+    if (bs == null)
+    {
       return Color.white;
     }
-    Color groupCol=colorMap.get(bs);
-    if (groupCol==null)
+    Color groupCol = colorMap.get(bs);
+    if (groupCol == null)
     {
       return Color.white;
     }
     return groupCol;
   }
-  @Override 
-  public void setColorForGroup(BitSet bs,Color color)
+
+  @Override
+  public void setColorForGroup(BitSet bs, Color color)
   {
-    colorMap.put(bs,color);
+    colorMap.put(bs, color);
   }
+
   public static String contactToFloatString(ContactMatrixI cm)
   {
     StringBuilder sb = new StringBuilder();
-    for (int c=0;c<cm.getWidth();c++)
+    for (int c = 0; c < cm.getWidth(); c++)
     {
-      ContactListI cl=cm.getContactList(c);
-      if (cl!=null) {
-      for (int h=0;h<=cl.getContactHeight();h++)
+      ContactListI cl = cm.getContactList(c);
+      if (cl != null)
       {
-        if (sb.length()>0) {
-          sb.append('\t');
+        for (int h = 0; h <= cl.getContactHeight(); h++)
+        {
+          if (sb.length() > 0)
+          {
+            sb.append('\t');
+          }
+          sb.append(cl.getContactAt(h));
         }
-        sb.append(cl.getContactAt(h));
-      }
       }
     }
     return sb.toString();
@@ -244,29 +255,30 @@ public abstract class ContactMatrix implements ContactMatrixI
           int rows)
   {
     float[][] vals = new float[cols][rows];
-    StringTokenizer tabsep = new StringTokenizer(values,""+'\t');
-    int c=0,r=0;
-    
+    StringTokenizer tabsep = new StringTokenizer(values, "" + '\t');
+    int c = 0, r = 0;
+
     while (tabsep.hasMoreTokens())
     {
       double elem = Double.valueOf(tabsep.nextToken());
-      vals[c][r++]=(float) elem;
-      if (r>=vals[c].length)
+      vals[c][r++] = (float) elem;
+      if (r >= vals[c].length)
       {
-        r=0;
+        r = 0;
         c++;
       }
-      if (c>=vals.length)
+      if (c >= vals.length)
       {
-        
+
         break;
       }
     }
     if (tabsep.hasMoreElements())
     {
-      Console.warn("Ignoring additional elements for Float string to contact matrix parsing.");
+      Console.warn(
+              "Ignoring additional elements for Float string to contact matrix parsing.");
     }
-      
+
     return vals;
   }
 }
index 1c169ef..3510ed1 100644 (file)
@@ -23,12 +23,15 @@ public interface ContactMatrixI
   String getAnnotLabel();
 
   /**
-   * string indicating how the contactMatrix should be rendered - stored in calcId
-   * @return 
+   * string indicating how the contactMatrix should be rendered - stored in
+   * calcId
+   * 
+   * @return
    */
   String getType();
 
   int getWidth();
+
   int getHeight();
   
   default boolean hasGroups() {
index c3f3670..e37a5b2 100644 (file)
@@ -14,6 +14,7 @@ import java.util.List;
 public class SeqDistanceContactMatrix implements ContactMatrixI
 {
   private static final String SEQUENCE_DISTANCE = "SEQUENCE_DISTANCE";
+
   private int width = 0;
 
   public SeqDistanceContactMatrix(int width)
index b3d567a..c7e4549 100644 (file)
@@ -1,7 +1,6 @@
 package jalview.datamodel.annotations;
 
 import jalview.datamodel.Annotation;
-import jalview.structure.StructureImportSettings;
 import jalview.structure.StructureImportSettings.TFType;
 
 public class AnnotationRowBuilder
@@ -18,14 +17,15 @@ public class AnnotationRowBuilder
   /**
    * the type of temperature factor plot (if it is one)
    */
-  private StructureImportSettings.TFType tfType = StructureImportSettings.TFType.DEFAULT;
+  // private TFType tfType = TFType.DEFAULT;
+  private TFType tfType = null;
 
-  public void setTFType(StructureImportSettings.TFType t)
+  public void setTFType(TFType t)
   {
     tfType = t;
   }
 
-  public StructureImportSettings.TFType getTFType()
+  public TFType getTFType()
   {
     return tfType;
   }
@@ -97,7 +97,7 @@ public class AnnotationRowBuilder
     name = string;
   }
 
-  public AnnotationRowBuilder(String name, float min, float max, StructureImportSettings.TFType tft)
+  public AnnotationRowBuilder(String name, float min, float max, TFType tft)
   {
     this(name, min, max);
     setTFType(tft);
index f0e477c..b7c83c6 100644 (file)
@@ -88,6 +88,11 @@ public class JmolParser extends StructureFile implements JmolStatusListener
     super(inFile, sourceType, tempfacType);
   }
 
+  public JmolParser(FileParse fp, boolean doXferSettings) throws IOException
+  {
+    super(fp, doXferSettings);
+  }
+
   public JmolParser(FileParse fp) throws IOException
   {
     super(fp);
@@ -108,6 +113,12 @@ public class JmolParser extends StructureFile implements JmolStatusListener
   @Override
   public void parse() throws IOException
   {
+    parse(true);
+  }
+
+  @Override
+  public void parse(boolean doXferSettings) throws IOException
+  {
     setChains(new Vector<PDBChain>());
     Viewer jmolModel = getJmolData();
     jmolModel.openReader(getDataName(), getDataName(), getReader());
@@ -132,7 +143,7 @@ public class JmolParser extends StructureFile implements JmolStatusListener
                       ? PDBEntry.Type.MMCIF.toString()
                       : "PDB");
 
-      transformJmolModelToJalview(jmolModel.ms);
+      transformJmolModelToJalview(jmolModel.ms, doXferSettings);
     }
   }
 
@@ -202,7 +213,8 @@ public class JmolParser extends StructureFile implements JmolStatusListener
     return false;
   }
 
-  public void transformJmolModelToJalview(ModelSet ms) throws IOException
+  public void transformJmolModelToJalview(ModelSet ms,
+          boolean localDoXferSettings) throws IOException
   {
     try
     {
@@ -260,7 +272,7 @@ public class JmolParser extends StructureFile implements JmolStatusListener
         }
         lastID = tmpatom.resNumIns.trim();
       }
-      if (isParseImmediately())
+      if (isParseImmediately() && localDoXferSettings)
       {
         // configure parsing settings from the static singleton
         xferSettings();
@@ -292,7 +304,7 @@ public class JmolParser extends StructureFile implements JmolStatusListener
       {
         try
         {
-          Console.info("retrieving pAE for " + pdbId);
+          Console.info("Retrieving PAE for " + pdbId);
           File paeFile = EBIAlfaFold.fetchAlphaFoldPAE(pdbId, null);
           this.setPAEMatrix(paeFile.getAbsolutePath());
         } catch (Throwable t)
@@ -305,7 +317,7 @@ public class JmolParser extends StructureFile implements JmolStatusListener
       {
         Alignment al = new Alignment(prot.toArray(new SequenceI[0]));
         EBIAlfaFold.addAlphaFoldPAE(al, new File(this.getPAEMatrix()), 0,
-                null, false, false);
+                null, false, false, null);
 
         if (al.getAlignmentAnnotation() != null)
         {
index 2d02e79..71905c1 100644 (file)
@@ -1480,7 +1480,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void createPNG(File f)
   {
-    alignPanel.makeAlignmentImage(TYPE.PNG, f);
+    createPNG(f, null, 0.0f, 0, 0);
+  }
+
+  public void createPNG(File f, String renderer, float bitmapscale,
+          int bitmapwidth, int bitmapheight)
+  {
+    alignPanel.makeAlignmentImage(TYPE.PNG, f, renderer, bitmapscale,
+            bitmapwidth, bitmapheight);
   }
 
   /**
@@ -1492,7 +1499,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void createEPS(File f)
   {
-    alignPanel.makeAlignmentImage(TYPE.EPS, f);
+    createEPS(f, null);
+  }
+
+  public void createEPS(File f, String renderer)
+  {
+    alignPanel.makeAlignmentImage(TYPE.EPS, f, renderer);
   }
 
   /**
@@ -1504,7 +1516,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void createSVG(File f)
   {
-    alignPanel.makeAlignmentImage(TYPE.SVG, f);
+    createSVG(f, null);
+  }
+
+  public void createSVG(File f, String renderer)
+  {
+    alignPanel.makeAlignmentImage(TYPE.SVG, f, renderer);
   }
 
   @Override
@@ -3440,8 +3457,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void overviewMenuItem_actionPerformed(ActionEvent e)
   {
-    boolean showHiddenRegions = Cache.getDefault(Preferences.SHOW_OV_HIDDEN_AT_START,
-                false);
+    boolean showHiddenRegions = Cache
+            .getDefault(Preferences.SHOW_OV_HIDDEN_AT_START, false);
     openOverviewPanel(showHiddenRegions);
   }
 
@@ -3452,14 +3469,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return alignPanel.overviewPanel;
     }
     JInternalFrame frame = new JInternalFrame();
-    final OverviewPanel overview = new OverviewPanel(alignPanel, frame, showHidden);
+    final OverviewPanel overview = new OverviewPanel(alignPanel, frame,
+            showHidden);
     frame.setContentPane(overview);
-    Desktop.addInternalFrame(frame, "", true, frame.getWidth(), frame.getHeight(),
-            true, true);
+    Desktop.addInternalFrame(frame, "", true, frame.getWidth(),
+            frame.getHeight(), true, true);
     frame.setFrameIcon(null);
     frame.pack();
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
-    final AlignmentPanel thePanel = this.alignPanel; 
+    final AlignmentPanel thePanel = this.alignPanel;
     frame.addInternalFrameListener(
             new javax.swing.event.InternalFrameAdapter()
             {
@@ -3478,7 +3496,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     alignPanel.setOverviewPanel(overview);
     alignPanel.setOverviewTitle(this);
-    
+
     return overview;
   }
 
@@ -4133,7 +4151,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     return showNewickTree(nf, treeTitle, null, w, h, x, y);
   }
 
-
   /**
    * Add a treeviewer for the tree extracted from a Newick file object to the
    * current alignment view
@@ -4184,13 +4201,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     return tp;
   }
 
-
   public void showContactMapTree(AlignmentAnnotation aa,
           PAEContactMatrix cm)
   {
     int x = 4, y = 5;
     int w = 400, h = 500;
-    
+
     try
     {
       NewickFile fin = new NewickFile(
@@ -4198,42 +4214,43 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       String title = "PAE Matrix Tree for "
               + cm.getReferenceSeq().getDisplayId(false);
 
-      showColumnWiseTree(fin, aa, title, w,h, x,y);
+      showColumnWiseTree(fin, aa, title, w, h, x, y);
     } catch (Throwable xx)
     {
       Console.error("Unexpected exception showing tree for contact matrix",
               xx);
     }
   }
-  public TreePanel showColumnWiseTree(NewickFile nf, AlignmentAnnotation aa, String treeTitle,
-           int w, int h, int x, int y)
+
+  public TreePanel showColumnWiseTree(NewickFile nf, AlignmentAnnotation aa,
+          String treeTitle, int w, int h, int x, int y)
   {
-      try
+    try
+    {
+      nf.parse();
+      if (nf.getTree() == null)
       {
-        nf.parse();
-        if (nf.getTree() == null)
-        {
-          return null;
-        }
-        TreePanel tp = new TreePanel(alignPanel, nf, aa, title);
+        return null;
+      }
+      TreePanel tp = new TreePanel(alignPanel, nf, aa, title);
 
-        tp.setSize(w, h);
+      tp.setSize(w, h);
 
-        if (x > 0 && y > 0)
-        {
-          tp.setLocation(x, y);
-        }
-
-        Desktop.addInternalFrame(tp, title, w, h);
-        return tp;
-      } catch (Throwable xx)
+      if (x > 0 && y > 0)
       {
-        Console.error("Unexpected exception showing tree for contact matrix",
-                xx);
+        tp.setLocation(x, y);
       }
-      return null;
+
+      Desktop.addInternalFrame(tp, title, w, h);
+      return tp;
+    } catch (Throwable xx)
+    {
+      Console.error("Unexpected exception showing tree for contact matrix",
+              xx);
+    }
+    return null;
   }
-  
+
   private boolean buildingMenu = false;
 
   /**
@@ -5027,7 +5044,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
        * to add view name "Original" if necessary
        */
       alignPanel.setOverviewTitle(this);
-      
+
       /*
        * switch panels and set Overview title (if there is one
        * because it was opened automatically)
@@ -5698,7 +5715,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    *          update non-sequence-related annotations
    */
   @Override
-  protected void setAnnotationsVisibility(boolean visible,
+  public void setAnnotationsVisibility(boolean visible,
           boolean forSequences, boolean forAlignment)
   {
     AlignmentAnnotation[] anns = alignPanel.getAlignment()
index 90f627e..3b7420f 100644 (file)
@@ -340,7 +340,7 @@ public class AlignViewport extends AlignmentViewport
     viewStyle.setFontName(font.getName());
     viewStyle.setFontStyle(font.getStyle());
     viewStyle.setFontSize(font.getSize());
-    
+
     validCharWidth = true;
   }
 
@@ -745,6 +745,11 @@ public class AlignViewport extends AlignmentViewport
    */
   public void addFile(File file, FileFormatI format)
   {
+    addFile(file, format, true);
+  }
+
+  public void addFile(File file, FileFormatI format, boolean async)
+  {
     DataSourceType protocol = AppletFormatAdapter.checkProtocol(file);
 
     if (format == null)
@@ -769,7 +774,8 @@ public class AlignViewport extends AlignmentViewport
       }
     }
 
-    new FileLoader().LoadFile(this, file, DataSourceType.FILE, format);
+    new FileLoader().LoadFile(this, file, DataSourceType.FILE, format,
+            async);
   }
 
   public void addFile(File file)
index 2d057bb..a5a1aff 100644 (file)
  */
 package jalview.gui;
 
-import jalview.analysis.AnnotationSorter;
-import jalview.api.AlignViewportI;
-import jalview.api.AlignmentViewPanel;
-import jalview.bin.Cache;
-import jalview.bin.Console;
-import jalview.bin.Jalview;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.SearchResultsI;
-import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.gui.ImageExporter.ImageWriterI;
-import jalview.io.HTMLOutput;
-import jalview.jbgui.GAlignmentPanel;
-import jalview.math.AlignmentDimension;
-import jalview.schemes.ResidueProperties;
-import jalview.structure.StructureSelectionManager;
-import jalview.util.Comparison;
-import jalview.util.ImageMaker;
-import jalview.util.MessageManager;
-import jalview.viewmodel.ViewportListenerI;
-import jalview.viewmodel.ViewportRanges;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Container;
@@ -68,6 +44,30 @@ import java.util.List;
 
 import javax.swing.SwingUtilities;
 
+import jalview.analysis.AnnotationSorter;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.bin.Jalview;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SearchResultsI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.gui.ImageExporter.ImageWriterI;
+import jalview.io.HTMLOutput;
+import jalview.jbgui.GAlignmentPanel;
+import jalview.math.AlignmentDimension;
+import jalview.schemes.ResidueProperties;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.Comparison;
+import jalview.util.ImageMaker;
+import jalview.util.MessageManager;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
+
 /**
  * DOCUMENT ME!
  * 
@@ -229,8 +229,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     // set idCanvas bufferedImage to null
     // to prevent drawing old image
     FontMetrics fm = getFontMetrics(av.getFont());
-    
-    // update the flag controlling whether the grid is too small to render the font
+
+    // update the flag controlling whether the grid is too small to render the
+    // font
     av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
 
     scalePanelHolder.setPreferredSize(
@@ -1045,6 +1046,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
        */
       alignmentGraphics.translate(alignmentGraphicsOffset,
               alignmentDrawnHeight);
+      updateLayout();
       getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
               alignmentGraphics, -1, startRes, endRes + 1);
     }
@@ -1171,14 +1173,22 @@ public class AlignmentPanel extends GAlignmentPanel implements
     return (w > 0 ? w : calculateIdWidth().width);
   }
 
+  void makeAlignmentImage(ImageMaker.TYPE type, File file, String renderer)
+  {
+    makeAlignmentImage(type, file, renderer, 0.0f, 0, 0);
+  }
+
   /**
    * Builds an image of the alignment of the specified type (EPS/PNG/SVG) and
    * writes it to the specified file
    * 
    * @param type
    * @param file
+   * @param textrenderer
+   * @param bitmapscale
    */
-  void makeAlignmentImage(ImageMaker.TYPE type, File file)
+  void makeAlignmentImage(ImageMaker.TYPE type, File file, String renderer,
+          float bitmapscale, int bitmapwidth, int bitmapheight)
   {
     final int borderBottomOffset = 5;
 
@@ -1208,7 +1218,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int imageWidth = aDimension.getWidth();
     int imageHeight = aDimension.getHeight() + borderBottomOffset;
     String of = MessageManager.getString("label.alignment");
-    exporter.doExport(file, this, imageWidth, imageHeight, of);
+    exporter.doExport(file, this, imageWidth, imageHeight, of, renderer,
+            bitmapscale, bitmapwidth, bitmapheight);
   }
 
   /**
index eeda585..f1a8af7 100755 (executable)
@@ -605,7 +605,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     }
     else
     {
-      // no row (or row that can be adjusted) was pressed. Simulate a ruler click
+      // no row (or row that can be adjusted) was pressed. Simulate a ruler
+      // click
       ap.getScalePanel().mousePressed(evt);
     }
   }
@@ -752,6 +753,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     av.sendSelection();
     return true;
   }
+
   /**
    * Construct and display a context menu at the right-click position
    * 
@@ -1016,7 +1018,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     }
     if (rowIndex[0] != toRowIndex[0])
     {
-      jalview.bin.Console.trace("Drag went to another row. needs to be clipped");
+      jalview.bin.Console
+              .trace("Drag went to another row. needs to be clipped");
     }
 
     // rectangular selection on matrix style annotation
@@ -1043,9 +1046,9 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
               rowIndex[1] - deltaY);
 
       // mark rectangular region formed by drag
-      jalview.bin.Console.trace("Matrix Selection from last(" + fromXc + ",["
-              + lastXci.cStart + "," + lastXci.cEnd + "]) to cur(" + toXc
-              + ",[" + cXci.cStart + "," + cXci.cEnd + "])");
+      jalview.bin.Console.trace("Matrix Selection from last(" + fromXc
+              + ",[" + lastXci.cStart + "," + lastXci.cEnd + "]) to cur("
+              + toXc + ",[" + cXci.cStart + "," + cXci.cEnd + "])");
       int fr, to;
       fr = Math.min(lastXci.cStart, lastXci.cEnd);
       to = Math.max(lastXci.cStart, lastXci.cEnd);
@@ -1404,8 +1407,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         return;
       }
     }
-    imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
-            + 1) * av.getCharWidth();
+    updateFadedImageWidth();
     if (imgWidth < 1)
     {
       return;
@@ -1473,6 +1475,13 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     g.drawImage(image, 0, 0, this);
   }
 
+  public void updateFadedImageWidth()
+  {
+    imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
+            + 1) * av.getCharWidth();
+
+  }
+
   /**
    * set true to enable redraw timing debug output on stderr
    */
@@ -1648,6 +1657,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   @Override
   public int getFadedImageWidth()
   {
+    updateFadedImageWidth();
     return imgWidth;
   }
 
index 420e2cb..c9951f5 100644 (file)
@@ -49,19 +49,20 @@ public class AssociatePdbFileWithSeq
           StructureSelectionManagerProvider ssmp)
   {
     return associatePdbWithSeq(choice, file, sequence, prompt, ssmp,
-            TFType.DEFAULT, null);
+            TFType.DEFAULT, null, true);
   }
 
   public PDBEntry associatePdbWithSeq(String choice, DataSourceType file,
           SequenceI sequence, boolean prompt,
           StructureSelectionManagerProvider ssmp, TFType tft,
-          String paeFilename)
+          String paeFilename, boolean doXferSettings)
   {
     PDBEntry entry = new PDBEntry();
     StructureFile pdbfile = StructureSelectionManager
             .getStructureSelectionManager(ssmp)
             .setMapping(false, new SequenceI[]
-            { sequence }, null, choice, file, tft, paeFilename);
+            { sequence }, null, choice, file, tft, paeFilename,
+                    doXferSettings);
     if (pdbfile == null)
     {
       // stacktrace already thrown so just return
index 1521d0a..fb3bbc0 100644 (file)
@@ -435,7 +435,11 @@ public class Desktop extends jalview.jbgui.GDesktop
     {
       if (LaunchUtils.getJavaVersion() >= 11)
       {
-        jalview.bin.Console.info(
+        /*
+         * Send this message to stderr as the warning that follows (due to
+         * reflection) also goes to stderr.
+         */
+        System.err.println(
                 "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
       }
       try
@@ -808,7 +812,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     // TODO - write tests and fix AlignFrame.paste() which doesn't track if
     // clipboard has come from a different alignment window than the one where
     // paste has been called! JAL-4151
-    
+
     if (Desktop.jalviewClipboard != null)
     {
       // The clipboard was filled from within Jalview, we must use the
@@ -835,30 +839,32 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
 
       Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
-            AlignFrame.DEFAULT_HEIGHT);
+              AlignFrame.DEFAULT_HEIGHT);
 
-    } else {
-    try
+    }
+    else
     {
-      Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
-      Transferable contents = c.getContents(this);
-
-      if (contents != null)
+      try
       {
-        String file = (String) contents
-                .getTransferData(DataFlavor.stringFlavor);
+        Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
+        Transferable contents = c.getContents(this);
 
-        FileFormatI format = new IdentifyFile().identify(file,
-                DataSourceType.PASTE);
+        if (contents != null)
+        {
+          String file = (String) contents
+                  .getTransferData(DataFlavor.stringFlavor);
+
+          FileFormatI format = new IdentifyFile().identify(file,
+                  DataSourceType.PASTE);
 
-        new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
+          new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
 
+        }
+      } catch (Exception ex)
+      {
+        System.out.println(
+                "Unable to paste alignment from system clipboard:\n" + ex);
       }
-    } catch (Exception ex)
-    {
-      System.out.println(
-              "Unable to paste alignment from system clipboard:\n" + ex);
-    }
     }
   }
 
@@ -1476,7 +1482,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     // this will run the shutdownHook but QuitHandler.getQuitResponse() should
     // not run a second time if gotQuitResponse flag has been set (i.e. user
     // confirmed quit of some kind).
-    System.exit(0);
+    Jalview.exit("Desktop exiting.", 0);
   }
 
   private void storeLastKnownDimensions(String string, Rectangle jc)
index d849ba2..785d206 100644 (file)
@@ -105,6 +105,13 @@ public class ImageExporter
   public void doExport(File file, Component parent, int width, int height,
           String imageSource)
   {
+    doExport(file, parent, width, height, imageSource, null, 0.0f, 0, 0);
+  }
+
+  public void doExport(File file, Component parent, int width, int height,
+          String imageSource, String renderer, float bitmapscale,
+          int bitmapwidth, int bitmapheight)
+  {
     final long messageId = System.currentTimeMillis();
     setStatus(
             MessageManager.formatMessage(
@@ -142,9 +149,10 @@ public class ImageExporter
      * for this as EPS_RENDERING / SVG_RENDERING
      * Always set to Text for JalviewJS as Lineart (glyph fonts) not available
      */
-    String renderStyle = Cache.getDefault(
-            imageType.getName() + "_RENDERING",
-            LineartOptions.PROMPT_EACH_TIME);
+    String renderStyle = renderer == null
+            ? Cache.getDefault(imageType.getName() + "_RENDERING",
+                    LineartOptions.PROMPT_EACH_TIME)
+            : renderer;
     if (Platform.isJS())
     {
       renderStyle = "Text";
@@ -158,7 +166,7 @@ public class ImageExporter
       final File chosenFile = file;
       Callable<Void> okAction = () -> {
         exportImage(chosenFile, !textSelected.get(), width, height,
-                messageId);
+                messageId, bitmapscale, bitmapwidth, bitmapheight);
         return null;
       };
       LineartOptions epsOption = new LineartOptions(TYPE.EPS.getName(),
@@ -184,7 +192,8 @@ public class ImageExporter
        * character rendering not required, or preference already set 
        * - just do the export
        */
-      exportImage(file, !textSelected.get(), width, height, messageId);
+      exportImage(file, !textSelected.get(), width, height, messageId,
+              bitmapscale, bitmapwidth, bitmapheight);
     }
   }
 
@@ -200,7 +209,8 @@ public class ImageExporter
    * @param messageId
    */
   protected void exportImage(File chosenFile, boolean asLineart, int width,
-          int height, long messageId)
+          int height, long messageId, float bitmapscale, int bitmapwidth,
+          int bitmapheight)
   {
     String type = imageType.getName();
     try
@@ -210,7 +220,7 @@ public class ImageExporter
       // "status.exporting_alignment_as_x_file", type),
       // messageId);
       ImageMaker im = new ImageMaker(imageType, width, height, chosenFile,
-              title, asLineart);
+              title, asLineart, bitmapscale, bitmapwidth, bitmapheight);
       imageWriter.exportImage(im.getGraphics());
       im.writeImage();
       setStatus(
index 8a530ac..ff4ff6a 100644 (file)
@@ -42,7 +42,7 @@ import jalview.util.MessageManager;
  */
 public class LineartOptions extends JPanel
 {
-  static final String PROMPT_EACH_TIME = "Prompt each time";
+  public static final String PROMPT_EACH_TIME = "Prompt each time";
 
   JvOptionPane dialog;
 
index 493deee..35fdf6a 100755 (executable)
@@ -188,7 +188,7 @@ public class Preferences extends GPreferences
 
   private OptionsParam promptEachTimeOpt = new OptionsParam(
           MessageManager.getString("label.prompt_each_time"),
-          "Prompt each time");
+          LineartOptions.PROMPT_EACH_TIME);
 
   private OptionsParam lineArtOpt = new OptionsParam(
           MessageManager.getString("label.lineart"), "Lineart");
index 3fce931..35972b0 100644 (file)
@@ -28,9 +28,11 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 
@@ -45,11 +47,16 @@ import javax.swing.table.AbstractTableModel;
 
 import com.stevesoft.pat.Regex;
 
+import jalview.analysis.AlignmentUtils;
+import jalview.api.AlignmentViewPanel;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.bin.Console;
 import jalview.bin.Jalview;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
 import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.ext.jmol.JmolParser;
 import jalview.fts.api.FTSData;
@@ -60,6 +67,7 @@ import jalview.fts.core.FTSRestRequest;
 import jalview.fts.core.FTSRestResponse;
 import jalview.fts.service.pdb.PDBFTSRestClient;
 import jalview.fts.service.threedbeacons.TDB_FTSData;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.gui.structurechooser.PDBStructureChooserQuerySource;
 import jalview.gui.structurechooser.StructureChooserQuerySource;
 import jalview.gui.structurechooser.ThreeDBStructureChooserQuerySource;
@@ -1277,7 +1285,8 @@ public class StructureChooser extends GStructureChooser
           String pdbFilename = selectedPdbFileName;
 
           StructureChooser.openStructureFileForSequence(ssm, sc, ap,
-                  selectedSequence, true, pdbFilename, tft, paeFilename);
+                  selectedSequence, true, pdbFilename, tft, paeFilename,
+                  true);
         }
         SwingUtilities.invokeLater(new Runnable()
         {
@@ -1336,6 +1345,15 @@ public class StructureChooser extends GStructureChooser
           StructureSelectionManager ssm, final PDBEntry[] pdbEntriesToView,
           final AlignmentPanel alignPanel, SequenceI[] sequences)
   {
+    return launchStructureViewer(ssm, pdbEntriesToView, alignPanel,
+            sequences, null);
+  }
+
+  private StructureViewer launchStructureViewer(
+          StructureSelectionManager ssm, final PDBEntry[] pdbEntriesToView,
+          final AlignmentPanel alignPanel, SequenceI[] sequences,
+          ViewerType viewerType)
+  {
     long progressId = sequences.hashCode();
     setProgressBar(MessageManager
             .getString("status.launching_3d_structure_viewer"), progressId);
@@ -1400,7 +1418,8 @@ public class StructureChooser extends GStructureChooser
               MessageManager.getString(
                       "status.fetching_3d_structures_for_selected_entries"),
               progressId);
-      theViewer.viewStructures(pdbEntriesToView, sequences, alignPanel);
+      theViewer.viewStructures(pdbEntriesToView, sequences, alignPanel,
+              viewerType);
     }
     else
     {
@@ -1408,7 +1427,8 @@ public class StructureChooser extends GStructureChooser
               "status.fetching_3d_structures_for",
               pdbEntriesToView[0].getId()), progressId);
       // Can we pass a pre-computeMappinged pdbFile?
-      theViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
+      theViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel,
+              viewerType);
     }
     setProgressBar(null, progressId);
     // remember the last viewer we used...
@@ -1709,12 +1729,25 @@ public class StructureChooser extends GStructureChooser
   public static void openStructureFileForSequence(
           StructureSelectionManager ssm, StructureChooser sc,
           AlignmentPanel ap, SequenceI seq, boolean prompt,
-          String sFilename, TFType tft, String paeFilename)
+          String sFilename, TFType tft, String paeFilename,
+          boolean doXferSettings)
   {
-    boolean headless = false;
+    openStructureFileForSequence(ssm, sc, ap, seq, prompt, sFilename, tft,
+            paeFilename, false, true, doXferSettings, null);
+  }
+
+  public static void openStructureFileForSequence(
+          StructureSelectionManager ssm, StructureChooser sc,
+          AlignmentPanel ap, SequenceI seq, boolean prompt,
+          String sFilename, TFType tft, String paeFilename,
+          boolean forceHeadless, boolean showRefAnnotations,
+          boolean doXferSettings, ViewerType viewerType)
+  {
+    boolean headless = forceHeadless;
     if (sc == null)
     {
-      headless = true;
+      // headless = true;
+      prompt = false;
       sc = new StructureChooser(new SequenceI[] { seq }, seq, ap, false);
     }
     if (ssm == null)
@@ -1722,13 +1755,41 @@ public class StructureChooser extends GStructureChooser
 
     PDBEntry fileEntry = new AssociatePdbFileWithSeq().associatePdbWithSeq(
             sFilename, DataSourceType.FILE, seq, prompt, Desktop.instance,
-            tft, paeFilename);
+            tft, paeFilename, doXferSettings);
 
-    StructureViewer sViewer = sc.launchStructureViewer(ssm,
-            new PDBEntry[]
-            { fileEntry }, ap, new SequenceI[] { seq });
+    // if headless, "false" in the sc constructor above will avoid GUI behaviour
+    // in sc.launchStructureViewer()
+    if (!headless && !(viewerType == null))
+      sc.launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap,
+              new SequenceI[]
+              { seq }, viewerType);
 
     if (headless)
       sc.mainFrame.dispose();
+
+    if (showRefAnnotations)
+      showReferenceAnnotationsForSequence(ap.alignFrame, seq);
+  }
+
+  public static void showReferenceAnnotationsForSequence(AlignFrame af,
+          SequenceI sequence)
+  {
+    AlignViewport av = af.getCurrentView();
+    AlignmentI al = av.getAlignment();
+
+    List<SequenceI> forSequences = new ArrayList<>();
+    forSequences.add(sequence);
+    final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<>();
+    AlignmentUtils.findAddableReferenceAnnotations(forSequences, null,
+            candidates, al);
+    final SequenceGroup selectionGroup = av.getSelectionGroup();
+    AlignmentUtils.addReferenceAnnotations(candidates, al, selectionGroup);
+    for (AlignmentViewPanel ap : af.getAlignPanels())
+    {
+      // required to readjust the height and position of the PAE
+      // annotation
+      ap.adjustAnnotationHeight();
+    }
+
   }
 }
index 5effa1a..0c12eb2 100644 (file)
@@ -119,6 +119,12 @@ public class StructureViewer
   public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
           SequenceI[] seqs, AlignmentPanel ap)
   {
+    return viewStructures(pdbs, seqs, ap, null);
+  }
+
+  public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
+          SequenceI[] seqs, AlignmentPanel ap, ViewerType viewerType)
+  {
     JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
     if (viewer != null)
     {
@@ -128,7 +134,8 @@ public class StructureViewer
       return viewer;
     }
 
-    ViewerType viewerType = getViewerType();
+    if (viewerType == null)
+      viewerType = getViewerType();
 
     Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
             seqs);
@@ -299,6 +306,12 @@ public class StructureViewer
   public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
           SequenceI[] seqsForPdb, AlignmentPanel ap)
   {
+    return viewStructures(pdb, seqsForPdb, ap, null);
+  }
+
+  public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
+          SequenceI[] seqsForPdb, AlignmentPanel ap, ViewerType viewerType)
+  {
     if (sview != null)
     {
       sview.setAlignAddedStructures(superposeAdded);
@@ -311,7 +324,8 @@ public class StructureViewer
       sview.raiseViewer();
       return sview;
     }
-    ViewerType viewerType = getViewerType();
+    if (viewerType == null)
+      viewerType = getViewerType();
     if (viewerType.equals(ViewerType.JMOL))
     {
       sview = new AppJmol(pdb, seqsForPdb, null, ap);
index 1233940..b2cf262 100755 (executable)
@@ -82,6 +82,8 @@ public abstract class AlignFile extends FileParse
 
   private boolean dataClosed = false;
 
+  private boolean doXferSettings = true;
+
   /**
    * @return if doParse() was called at construction time
    */
@@ -152,6 +154,12 @@ public abstract class AlignFile extends FileParse
    * @param source
    * @throws IOException
    */
+  public AlignFile(FileParse source, boolean doXferSettings)
+          throws IOException
+  {
+    this(true, source, true, doXferSettings);
+  }
+
   public AlignFile(FileParse source) throws IOException
   {
     this(true, source);
@@ -174,12 +182,19 @@ public abstract class AlignFile extends FileParse
   public AlignFile(boolean parseImmediately, FileParse source,
           boolean closeData) throws IOException
   {
+    this(parseImmediately, source, closeData, true);
+  }
+
+  public AlignFile(boolean parseImmediately, FileParse source,
+          boolean closeData, boolean doXferSettings) throws IOException
+  {
     super(source);
     initData();
 
     // stash flag in case parse needs to know if it has to autoconfigure or was
     // configured after construction
     this.parseImmediately = parseImmediately;
+    this.doXferSettings = doXferSettings;
 
     if (parseImmediately)
     {
@@ -206,7 +221,7 @@ public abstract class AlignFile extends FileParse
                       + "Need to call initData() again before parsing can be reattempted.");
     }
     parseCalled = true;
-    parse();
+    parse(this.doXferSettings);
     if (closeData && !dataClosed)
     {
       dataIn.close();
@@ -371,6 +386,18 @@ public abstract class AlignFile extends FileParse
   public abstract void parse() throws IOException;
 
   /**
+   * This method is only overridden by JmolParser because of its use in
+   * StructureFile to parse annotations
+   * 
+   * @param doXferSettings
+   * @throws IOException
+   */
+  public void parse(boolean doXferSettings) throws IOException
+  {
+    parse();
+  }
+
+  /**
    * A general parser for ids.
    * 
    * @String id Id to be parsed
@@ -452,4 +479,14 @@ public abstract class AlignFile extends FileParse
   {
     seqs.add(seq);
   }
+
+  public void setDoXferSettings(boolean b)
+  {
+    doXferSettings = b;
+  }
+
+  public boolean getDoXferSettings()
+  {
+    return doXferSettings;
+  }
 }
index 14c1260..af9df86 100644 (file)
@@ -232,6 +232,11 @@ public class BackupFiles
   public BackupFiles(File file)
   {
     classInit();
+    if (file.getParentFile() == null)
+    {
+      // filename probably in pwd represented with no parent -- fix this!
+      file = file.getAbsoluteFile();
+    }
     this.file = file;
 
     // add this file from the save in progress stack
@@ -252,7 +257,7 @@ public class BackupFiles
       if (file != null)
       {
         String tempfilename = file.getName();
-        File tempdir = file.getParentFile();
+        File tempdir = file.getAbsoluteFile().getParentFile();
         tempdir.mkdirs();
         Console.trace(
                 "BACKUPFILES [file!=null] attempting to create temp file for "
@@ -283,7 +288,7 @@ public class BackupFiles
     this.setTempFile(temp);
   }
 
-  public static void classInit()
+  private static void classInit()
   {
     Console.initLogger();
     Console.trace("BACKUPFILES classInit");
index c88d8eb..6779892 100644 (file)
  */
 package jalview.io;
 
-import jalview.bin.Cache;
-import jalview.gui.AlignmentPanel;
-import jalview.gui.OOMWarning;
-import jalview.json.binding.biojs.BioJSReleasePojo;
-import jalview.json.binding.biojs.BioJSRepositoryPojo;
-import jalview.util.MessageManager;
-
 import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.File;
@@ -39,6 +32,13 @@ import java.net.URL;
 import java.util.Objects;
 import java.util.TreeMap;
 
+import jalview.bin.Cache;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.OOMWarning;
+import jalview.json.binding.biojs.BioJSReleasePojo;
+import jalview.json.binding.biojs.BioJSRepositoryPojo;
+import jalview.util.MessageManager;
+
 public class BioJsHTMLOutput extends HTMLOutput
 {
   private static File currentBJSTemplateFile;
@@ -276,4 +276,10 @@ public class BioJsHTMLOutput extends HTMLOutput
 
   }
 
+  @Override
+  public void run(String s)
+  {
+    run();
+  }
+
 }
index b701963..43c6dcf 100644 (file)
@@ -30,7 +30,7 @@ import jalview.structure.StructureImportSettings;
 
 public enum FileFormat implements FileFormatI
 {
-  Fasta("Fasta", "fa, fasta, mfa, fastq", true, true)
+  Fasta("Fasta", "fa,fasta,mfa,fastq", true, true)
   {
     @Override
     public AlignmentFileReaderI getReader(FileParse source)
@@ -92,7 +92,7 @@ public enum FileFormat implements FileFormatI
       return new PIRFile();
     }
   },
-  BLC("BLC", "BLC", true, true)
+  BLC("BLC", "blc", true, true)
   {
     @Override
     public AlignmentFileReaderI getReader(FileParse source)
@@ -244,7 +244,7 @@ public enum FileFormat implements FileFormatI
       return new PhylipFile();
     }
   },
-  GenBank("GenBank Flatfile", "gb, gbk", true, false)
+  GenBank("GenBank Flatfile", "gb,gbk", true, false)
   {
     @Override
     public AlignmentFileReaderI getReader(FileParse source)
@@ -379,7 +379,7 @@ public enum FileFormat implements FileFormatI
       return true;
     }
   },
-  Jalview("Jalview", "jvp, jar", true, true)
+  Jalview("Jalview", "jvp,jar", true, true)
   {
     @Override
     public AlignmentFileReaderI getReader(FileParse source)
index 449c685..a2585b3 100755 (executable)
@@ -96,32 +96,51 @@ public class FileLoader implements Runnable
   public void LoadFile(AlignViewport viewport, Object file,
           DataSourceType protocol, FileFormatI format)
   {
+    LoadFile(viewport, file, protocol, format, true);
+  }
+
+  public void LoadFile(AlignViewport viewport, Object file,
+          DataSourceType protocol, FileFormatI format, boolean async)
+  {
     this.viewport = viewport;
     if (file instanceof File)
     {
       this.selectedFile = (File) file;
       file = selectedFile.getPath();
     }
-    LoadFile(file.toString(), protocol, format);
+    LoadFile(file.toString(), protocol, format, async);
   }
 
   public void LoadFile(String file, DataSourceType protocol,
           FileFormatI format)
   {
+    LoadFile(file, protocol, format, true);
+  }
+
+  public void LoadFile(String file, DataSourceType protocol,
+          FileFormatI format, boolean async)
+  {
     this.file = file;
     this.protocol = protocol;
     this.format = format;
 
-    final Thread loader = new Thread(this);
-
-    SwingUtilities.invokeLater(new Runnable()
+    if (async)
     {
-      @Override
-      public void run()
+      final Thread loader = new Thread(this);
+
+      SwingUtilities.invokeLater(new Runnable()
       {
-        loader.start();
-      }
-    });
+        @Override
+        public void run()
+        {
+          loader.start();
+        }
+      });
+    }
+    else
+    {
+      this.run();
+    }
   }
 
   /**
@@ -313,7 +332,8 @@ public class FileLoader implements Runnable
                   MessageManager.getString("label.couldnt_read_data"),
                   JvOptionPane.WARNING_MESSAGE);
         }
-        this.setShouldBeSaved();
+        // don't set shouldBeSaved if didn't load anything
+        // this.setShouldBeSaved();
         return;
       }
       // TODO: cache any stream datasources as a temporary file (eg. PDBs
index 2745420..ed80eb9 100644 (file)
@@ -304,6 +304,11 @@ public abstract class HTMLOutput implements Runnable
 
   public void exportHTML(String outputFile)
   {
+    exportHTML(outputFile, null);
+  }
+
+  public void exportHTML(String outputFile, String renderer)
+  {
     setProgressMessage(MessageManager.formatMessage(
             "status.exporting_alignment_as_x_file", getDescription()));
     try
@@ -332,7 +337,7 @@ public abstract class HTMLOutput implements Runnable
     }
     if (Jalview.isHeadlessMode())
     {
-      this.run();
+      this.run(renderer);
     }
     else
     {
@@ -351,4 +356,7 @@ public abstract class HTMLOutput implements Runnable
   {
     return description;
   }
+
+  // used to pass an option such as render to run
+  public abstract void run(String string);
 }
\ No newline at end of file
index 9fb3720..c5ce35b 100644 (file)
@@ -25,6 +25,7 @@ import java.awt.print.PrinterException;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Locale;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -202,12 +203,20 @@ public class HtmlSvgOutput extends HTMLOutput
   @Override
   public void run()
   {
+    run(null);
+  }
+
+  @Override
+  public void run(String renderer)
+  {
     try
     {
-      String renderStyle = Cache.getDefault("HTML_RENDERING",
-              "Prompt each time");
+      String renderStyle = renderer == null
+              ? Cache.getDefault("HTML_RENDERING",
+                      LineartOptions.PROMPT_EACH_TIME)
+              : renderer;
       AtomicBoolean textOption = new AtomicBoolean(
-              !"Lineart".equals(renderStyle));
+              !"lineart".equals(renderStyle.toLowerCase(Locale.ROOT)));
 
       /*
        * configure the action to run on OK in the dialog
@@ -220,7 +229,8 @@ public class HtmlSvgOutput extends HTMLOutput
       /*
        * Prompt for character rendering style if preference is not set
        */
-      if (renderStyle.equalsIgnoreCase("Prompt each time") && !isHeadless())
+      if (renderStyle.equalsIgnoreCase(LineartOptions.PROMPT_EACH_TIME)
+              && !isHeadless())
       {
         LineartOptions svgOption = new LineartOptions("HTML", textOption);
         svgOption.setResponseAction(1, () -> {
index c21127e..ea87058 100755 (executable)
  */
 package jalview.io;
 
-import java.util.Locale;
-
 import java.io.File;
 import java.io.IOException;
+import java.util.Locale;
+
+import jalview.bin.Console;
 
 /**
  * DOCUMENT ME!
@@ -58,8 +59,7 @@ public class IdentifyFile
       }
     } catch (Exception e)
     {
-      System.err.println("Error whilst identifying " + file);
-      e.printStackTrace(System.err);
+      Console.error("Error whilst identifying " + file, e);
       emessage = e.getMessage();
     }
     if (parser != null)
@@ -94,8 +94,7 @@ public class IdentifyFile
       }
     } catch (Exception e)
     {
-      System.err.println("Error whilst identifying " + file);
-      e.printStackTrace(System.err);
+      Console.error("Error whilst identifying " + file, e);
       emessage = e.getMessage();
     }
     if (parser != null)
@@ -408,16 +407,15 @@ public class IdentifyFile
       }
     } catch (Exception ex)
     {
-      System.err.println("File Identification failed!\n" + ex);
+      Console.error("File Identification failed!\n" + ex);
       throw new FileFormatException(source.errormessage);
     }
     if (trimmedLength == 0)
     {
-      System.err.println(
-              "File Identification failed! - Empty file was read.");
+      Console.error("File Identification failed! - Empty file was read.");
       throw new FileFormatException("EMPTY DATA FILE");
     }
-    System.out.println("File format identified as " + reply.toString());
+    Console.debug("File format identified as " + reply.toString());
     return reply;
   }
 
@@ -485,15 +483,15 @@ public class IdentifyFile
         type = ider.identify(args[i], DataSourceType.FILE);
       } catch (FileFormatException e)
       {
-        System.err.println(
+        Console.error(
                 String.format("Error '%s' identifying file type for %s",
                         args[i], e.getMessage()));
       }
-      System.out.println("Type of " + args[i] + " is " + type);
+      Console.debug("Type of " + args[i] + " is " + type);
     }
     if (args == null || args.length == 0)
     {
-      System.err.println("Usage: <Filename> [<Filename> ...]");
+      Console.error("Usage: <Filename> [<Filename> ...]");
     }
   }
 
index 269ffb3..b3c0011 100755 (executable)
 // TODO: Extended SequenceNodeI to hold parsed NHX strings
 package jalview.io;
 
-import java.util.Locale;
-
-import jalview.datamodel.BinaryNode;
-import jalview.datamodel.SequenceNode;
-import jalview.util.MessageManager;
-
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.util.Locale;
 import java.util.StringTokenizer;
 
 import com.stevesoft.pat.Regex;
 
+import jalview.bin.Jalview;
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.SequenceNode;
+import jalview.util.MessageManager;
+
 /**
  * Parse a new hanpshire style tree Caveats: NHX files are NOT supported and the
  * tree distances and topology are unreliable when they are parsed. TODO: on
@@ -488,7 +488,8 @@ public class NewickFile extends FileParse
         {
           try
           {
-            distance = (Double.valueOf(ndist.stringMatched(1))).floatValue();
+            distance = (Double.valueOf(ndist.stringMatched(1)))
+                    .floatValue();
             HasDistances = true;
             nodehasdistance = true;
           } catch (Exception e)
@@ -877,20 +878,20 @@ public class NewickFile extends FileParse
       {
         if (root.isDummy())
         {
-          _print(tf,  root.right());
-          _print(tf,  root.left());
+          _print(tf, root.right());
+          _print(tf, root.left());
         }
         else
         {
           tf.append("(");
-          _print(tf,  root.right());
+          _print(tf, root.right());
 
           if (root.left() != null)
           {
             tf.append(",");
           }
 
-          _print(tf,  root.left());
+          _print(tf, root.left());
           tf.append(")" + printRootField(root));
         }
       }
@@ -910,24 +911,24 @@ public class NewickFile extends FileParse
       {
         if (c.isDummy())
         {
-          _print(tf,  c.left());
+          _print(tf, c.left());
           if (c.left() != null)
           {
             tf.append(",");
           }
-          _print(tf,  c.right());
+          _print(tf, c.right());
         }
         else
         {
           tf.append("(");
-          _print(tf,  c.right());
+          _print(tf, c.right());
 
           if (c.left() != null)
           {
             tf.append(",");
           }
 
-          _print(tf,  c.left());
+          _print(tf, c.left());
           tf.append(")" + printNodeField(c));
         }
       }
@@ -945,9 +946,8 @@ public class NewickFile extends FileParse
     {
       if (args == null || args.length != 1)
       {
-        System.err.println(
-                "Takes one argument - file name of a newick tree file.");
-        System.exit(0);
+        Jalview.exit(
+                "Takes one argument - file name of a newick tree file.", 0);
       }
 
       File fn = new File(args[0]);
index f498c1e..61b3d1d 100644 (file)
@@ -23,6 +23,7 @@ package jalview.io;
 import java.awt.Color;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
+import java.net.MalformedURLException;
 import java.util.List;
 import java.util.Vector;
 
@@ -36,6 +37,7 @@ import jalview.datamodel.DBRefSource;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.PDBEntry.Type;
 import jalview.datamodel.SequenceI;
+import jalview.ext.jmol.JmolParser;
 import jalview.structure.StructureImportSettings;
 import jalview.structure.StructureImportSettings.TFType;
 import mc_view.PDBChain;
@@ -68,7 +70,7 @@ public abstract class StructureFile extends AlignFile
 
   private boolean pdbIdAvailable;
 
-  private StructureImportSettings.TFType temperatureFactorType = TFType.DEFAULT;
+  private TFType temperatureFactorType = TFType.DEFAULT;
 
   private String paeMatrix = null;
 
@@ -89,12 +91,12 @@ public abstract class StructureFile extends AlignFile
     return paeMatrix != null;
   }
 
-  public void setTemperatureFactorType(StructureImportSettings.TFType t)
+  public void setTemperatureFactorType(TFType t)
   {
     this.temperatureFactorType = t;
   }
 
-  public StructureImportSettings.TFType getTemperatureFactorType()
+  public TFType getTemperatureFactorType()
   {
     return temperatureFactorType;
   }
@@ -116,7 +118,7 @@ public abstract class StructureFile extends AlignFile
   }
 
   public StructureFile(Object inFile, DataSourceType sourceType,
-          StructureImportSettings.TFType tempfacType) throws IOException
+          TFType tempfacType) throws IOException
   {
     super(false, inFile, sourceType);
     this.setTemperatureFactorType(tempfacType);
@@ -125,7 +127,13 @@ public abstract class StructureFile extends AlignFile
 
   public StructureFile(FileParse fp) throws IOException
   {
-    super(fp);
+    this(fp, true);
+  }
+
+  public StructureFile(FileParse fp, boolean doXferSettings)
+          throws IOException
+  {
+    super(fp, doXferSettings);
   }
 
   public void addSettings(boolean addAlignmentAnnotations,
@@ -138,14 +146,17 @@ public abstract class StructureFile extends AlignFile
 
   public void xferSettings()
   {
-    this.visibleChainAnnotation = StructureImportSettings
-            .isVisibleChainAnnotation();
-    this.predictSecondaryStructure = StructureImportSettings
-            .isProcessSecondaryStructure();
-    this.externalSecondaryStructure = StructureImportSettings
-            .isExternalSecondaryStructure();
-    this.temperatureFactorType = StructureImportSettings
-            .getTemperatureFactorType();
+    if (this.getDoXferSettings())
+    {
+      this.visibleChainAnnotation = StructureImportSettings
+              .isVisibleChainAnnotation();
+      this.predictSecondaryStructure = StructureImportSettings
+              .isProcessSecondaryStructure();
+      this.externalSecondaryStructure = StructureImportSettings
+              .isExternalSecondaryStructure();
+      this.temperatureFactorType = StructureImportSettings
+              .getTemperatureFactorType();
+    }
   }
 
   public StructureFile(boolean parseImmediately, Object dataObject,
@@ -338,7 +349,7 @@ public abstract class StructureFile extends AlignFile
     {
       try
       {
-        processWithJmolParser(proteinSequences);
+        processWithJmolParser(proteinSequences, true);
       } catch (Exception x)
       {
         System.err.println(
@@ -349,7 +360,8 @@ public abstract class StructureFile extends AlignFile
   }
 
   @SuppressWarnings({ "unchecked", "rawtypes" })
-  private void processWithJmolParser(List<SequenceI> prot) throws Exception
+  private void NOTprocessWithJmolParser(List<SequenceI> prot)
+          throws Exception
   {
     try
     {
@@ -395,6 +407,36 @@ public abstract class StructureFile extends AlignFile
     StructureImportSettings.setShowSeqFeatures(true);
   }
 
+  private void processWithJmolParser(List<SequenceI> prot,
+          boolean doXferSettings) throws MalformedURLException, IOException
+  {
+    FileParse fp = new FileParse(getDataName(), dataSourceType);
+
+    StructureImportSettings.setShowSeqFeatures(false);
+    StructureImportSettings.setVisibleChainAnnotation(false);
+    StructureImportSettings
+            .setProcessSecondaryStructure(predictSecondaryStructure);
+    StructureImportSettings
+            .setExternalSecondaryStructure(externalSecondaryStructure);
+    StructureImportSettings.setTemperatureFactorType(temperatureFactorType);
+    JmolParser jmf = new JmolParser(fp, doXferSettings);
+    AlignmentI al = new Alignment((SequenceI[]) jmf.getSeqsAsArray());
+    jmf.addAnnotations(al);
+    for (SequenceI sq : al.getSequences())
+    {
+      if (sq.getDatasetSequence() != null)
+      {
+        sq.getDatasetSequence().getAllPDBEntries().clear();
+      }
+      else
+      {
+        sq.getAllPDBEntries().clear();
+      }
+    }
+    replaceAndUpdateChains(prot, al, AlignSeq.PEP, false);
+    StructureImportSettings.setShowSeqFeatures(true);
+  }
+
   /**
    * Answers the first PDBChain found matching the given id, or null if none is
    * found
index 8ae97b0..5b886b6 100755 (executable)
@@ -2157,7 +2157,7 @@ public class GAlignFrame extends JInternalFrame
    * @param forAlignment
    *          update non-sequence-related annotations
    */
-  protected void setAnnotationsVisibility(boolean visible,
+  public void setAnnotationsVisibility(boolean visible,
           boolean forSequences, boolean forAlignment)
   {
 
index 69c0f4d..03538ef 100755 (executable)
@@ -39,6 +39,7 @@ import java.awt.event.KeyListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.Arrays;
+import java.util.EnumSet;
 import java.util.List;
 
 import javax.swing.AbstractCellEditor;
@@ -1679,10 +1680,16 @@ public class GPreferences extends JPanel
      */
     structViewer.setFont(LABEL_FONT);
     structViewer.setBounds(new Rectangle(190, ypos, 120, height));
+    for (ViewerType v : EnumSet.allOf(ViewerType.class))
+    {
+      structViewer.addItem(v.name());
+    }
+    /*
     structViewer.addItem(ViewerType.JMOL.name());
     structViewer.addItem(ViewerType.CHIMERA.name());
     structViewer.addItem(ViewerType.CHIMERAX.name());
     structViewer.addItem(ViewerType.PYMOL.name());
+    */
     structViewer.addActionListener(new ActionListener()
     {
       @Override
index 714b0de..cfd41d0 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.log;
 
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -48,7 +49,7 @@ public abstract class JLogger implements JLoggerI
 
   public static boolean isLevel(String levelString)
   {
-    for (LogLevel l : LogLevel.values())
+    for (LogLevel l : EnumSet.allOf(LogLevel.class))
     {
       if (l.name().equals(levelString))
         return true;
index eb83f31..ad1fa4a 100644 (file)
  */
 package jalview.renderer;
 
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.geom.AffineTransform;
+import java.awt.image.ImageObserver;
+import java.util.BitSet;
+import java.util.Hashtable;
+
 import jalview.analysis.AAFrequency;
 import jalview.analysis.CodingUtils;
 import jalview.analysis.Rna;
@@ -38,18 +50,6 @@ import jalview.schemes.ResidueProperties;
 import jalview.schemes.ZappoColourScheme;
 import jalview.util.Platform;
 
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Image;
-import java.awt.geom.AffineTransform;
-import java.awt.image.ImageObserver;
-import java.util.BitSet;
-import java.util.Hashtable;
-
 public class AnnotationRenderer
 {
   private static final int UPPER_TO_LOWER = 'a' - 'A'; // 32
@@ -1077,22 +1077,22 @@ public class AnnotationRenderer
                     .getRendererFor(row);
             if (renderer != null)
             {
-              renderer.renderRow(g, charWidth, charHeight,
-                      hasHiddenColumns, av, hiddenColumns, columnSelection,
-                      row, row_annotations, startRes, endRes, row.graphMin,
+              renderer.renderRow(g, charWidth, charHeight, hasHiddenColumns,
+                      av, hiddenColumns, columnSelection, row,
+                      row_annotations, startRes, endRes, row.graphMin,
                       row.graphMax, y);
             }
             if (debugRedraw)
             {
               if (renderer == null)
               {
-                System.err.println("No renderer found for "
-                        + row.toString());
+                System.err
+                        .println("No renderer found for " + row.toString());
               }
               else
               {
-                System.err.println("rendered with "
-                        + renderer.getClass().toString());
+                System.err.println(
+                        "rendered with " + renderer.getClass().toString());
               }
             }
 
@@ -1300,8 +1300,7 @@ public class AnnotationRenderer
         break;
       }
 
-      if (aa_annotations[column] == null
-              || aa_annotations[column - 1] == null)
+      if (aa_annotations[column] == null)
       {
         x++;
         continue;
@@ -1316,6 +1315,25 @@ public class AnnotationRenderer
         g.setColor(aa_annotations[column].colour);
       }
 
+      if (aa_annotations[column - 1] == null
+              && aa_annotations.length > column + 1
+              && aa_annotations[column + 1] == null)
+      {
+        // standalone value
+        y1 = y - (int) (((aa_annotations[column].value - min) / range)
+                * graphHeight);
+        g.drawLine(x * charWidth + charWidth / 4, y1,
+                x * charWidth + 3 * charWidth / 4, y1);
+        x++;
+        continue;
+      }
+
+      if (aa_annotations[column - 1] == null)
+      {
+        x++;
+        continue;
+      }
+
       y1 = y - (int) (((aa_annotations[column - 1].value - min) / range)
               * graphHeight);
       y2 = y - (int) (((aa_annotations[column].value - min) / range)
index 14daed6..698769c 100644 (file)
  */
 package jalview.schemes;
 
+import java.util.LinkedHashMap;
 import java.util.Locale;
+import java.util.Map;
 
 import jalview.api.AlignViewportI;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
 
-import java.util.LinkedHashMap;
-import java.util.Map;
-
 public class ColourSchemes
 {
   /*
@@ -106,7 +105,7 @@ public class ColourSchemes
      * name is lower-case for non-case-sensitive lookup
      * (name in the colour keeps its true case)
      */
-    String lower = name.toLowerCase(Locale.ROOT);
+    String lower = getColourSchemeShortName(cs);
     if (schemes.containsKey(lower))
     {
       System.err
@@ -115,6 +114,19 @@ public class ColourSchemes
     schemes.put(lower, cs);
   }
 
+  private String getColourSchemeShortName(ColourSchemeI cs)
+  {
+    return getColourSchemeShortName(cs.getSchemeName());
+  }
+
+  private String getColourSchemeShortName(String name)
+  {
+    if (name == null)
+      return null;
+    return name.toLowerCase(Locale.ROOT).replaceAll("%", "pc")
+            .replaceAll("[^a-z0-9]", "-").replaceAll("--+", "-");
+  }
+
   /**
    * Removes a colour scheme by name
    * 
@@ -124,7 +136,7 @@ public class ColourSchemes
   {
     if (name != null)
     {
-      schemes.remove(name.toLowerCase(Locale.ROOT));
+      schemes.remove(getColourSchemeShortName(name));
     }
   }
 
@@ -150,7 +162,7 @@ public class ColourSchemes
     {
       return null;
     }
-    ColourSchemeI cs = schemes.get(name.toLowerCase(Locale.ROOT));
+    ColourSchemeI cs = schemes.get(getColourSchemeShortName(name));
     return cs == null ? null : cs.getInstance(viewport, forData);
   }
 
@@ -194,6 +206,6 @@ public class ColourSchemes
     {
       return false;
     }
-    return schemes.containsKey(name.toLowerCase(Locale.ROOT));
+    return schemes.containsKey(getColourSchemeShortName(name));
   }
 }
index accdc8a..da49fac 100644 (file)
@@ -34,10 +34,10 @@ public enum JalviewColourScheme
   PID("% Identity", PIDColourScheme.class),
   Zappo("Zappo", ZappoColourScheme.class),
   Taylor("Taylor", TaylorColourScheme.class),
-  Flower("gecos:flower", FlowerColourScheme.class),
-  Blossom("gecos:blossom", BlossomColourScheme.class),
-  Sunset("gecos:sunset", SunsetColourScheme.class),
-  Ocean("gecos:ocean", OceanColourScheme.class),
+  Flower("gecos-flower", FlowerColourScheme.class),
+  Blossom("gecos-blossom", BlossomColourScheme.class),
+  Sunset("gecos-sunset", SunsetColourScheme.class),
+  Ocean("gecos-ocean", OceanColourScheme.class),
   Hydrophobic("Hydrophobic", HydrophobicColourScheme.class),
   Helix("Helix Propensity", HelixColourScheme.class),
   Strand("Strand Propensity", StrandColourScheme.class),
index 3194cce..563f0e7 100644 (file)
@@ -327,7 +327,7 @@ public class StructureSelectionManager
           IProgressIndicator progress)
   {
     return computeMapping(true, sequence, targetChains, pdbFile, protocol,
-            progress, null, null);
+            progress, null, null, true);
   }
 
   /**
@@ -353,8 +353,17 @@ public class StructureSelectionManager
           String pdbFile, DataSourceType sourceType, TFType tft,
           String paeFilename)
   {
+    return setMapping(forStructureView, sequenceArray, targetChainIds,
+            pdbFile, sourceType, tft, paeFilename, true);
+  }
+
+  synchronized public StructureFile setMapping(boolean forStructureView,
+          SequenceI[] sequenceArray, String[] targetChainIds,
+          String pdbFile, DataSourceType sourceType, TFType tft,
+          String paeFilename, boolean doXferSettings)
+  {
     return computeMapping(forStructureView, sequenceArray, targetChainIds,
-            pdbFile, sourceType, null, tft, paeFilename);
+            pdbFile, sourceType, null, tft, paeFilename, doXferSettings);
   }
 
   /**
@@ -384,7 +393,8 @@ public class StructureSelectionManager
   synchronized public StructureFile computeMapping(boolean forStructureView,
           SequenceI[] sequenceArray, String[] targetChainIds,
           String pdbFile, DataSourceType sourceType,
-          IProgressIndicator progress, TFType tft, String paeFilename)
+          IProgressIndicator progress, TFType tft, String paeFilename,
+          boolean doXferSettings)
   {
     long progressSessionId = System.currentTimeMillis() * 3;
 
@@ -394,8 +404,7 @@ public class StructureSelectionManager
     // FIXME: possibly should just delete
 
     boolean parseSecStr = processSecondaryStructure
-            ? isStructureFileProcessed(pdbFile, sequenceArray)
-            : false;
+            && !isStructureFileProcessed(pdbFile, sequenceArray);
 
     StructureFile pdb = null;
     boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
@@ -412,7 +421,11 @@ public class StructureSelectionManager
       pdb.addSettings(parseSecStr && processSecondaryStructure,
               parseSecStr && addTempFacAnnot,
               parseSecStr && secStructServices);
+      // save doXferSettings and reset after doParse()
+      boolean temp = pdb.getDoXferSettings();
+      pdb.setDoXferSettings(doXferSettings);
       pdb.doParse();
+      pdb.setDoXferSettings(temp);
       if (pdb.getId() != null && pdb.getId().trim().length() > 0
               && DataSourceType.FILE == sourceType)
       {
@@ -667,7 +680,7 @@ public class StructureSelectionManager
   private boolean isStructureFileProcessed(String pdbFile,
           SequenceI[] sequenceArray)
   {
-    boolean parseSecStr = true;
+    boolean processed = false;
     if (isPDBFileRegistered(pdbFile))
     {
       for (SequenceI sq : sequenceArray)
@@ -687,13 +700,13 @@ public class StructureSelectionManager
             // passed, not the structure data ID -
             if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
             {
-              parseSecStr = false;
+              processed = true;
             }
           }
         }
       }
     }
-    return parseSecStr;
+    return processed;
   }
 
   public void addStructureMapping(StructureMapping sm)
index 9286794..cf44dcb 100644 (file)
@@ -48,6 +48,8 @@ import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.io.PrintStream;
 
+import jalview.bin.Jalview;
+
 public class AWTConsole extends WindowAdapter
         implements WindowListener, ActionListener, Runnable
 {
@@ -174,7 +176,7 @@ public class AWTConsole extends WindowAdapter
     } catch (Exception e)
     {
     }
-    System.exit(0);
+    Jalview.exit("Window closing. Bye!", 0);
   }
 
   @Override
index c05dac5..8142f8a 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.util;
 
+import java.util.Arrays;
+
 public class ArrayUtils
 {
   /**
@@ -44,4 +46,27 @@ public class ArrayUtils
       }
     }
   }
+
+  public static <T> T[] concatArrays(T[]... arrays)
+  {
+    if (arrays == null)
+      return null;
+    if (arrays.length == 1)
+      return arrays[0];
+
+    T[] result = arrays[0];
+    for (int i = 1; i < arrays.length; i++)
+    {
+      result = concatTwoArrays(result, arrays[i]);
+    }
+    return result;
+  }
+
+  private static <T> T[] concatTwoArrays(T[] array1, T[] array2)
+  {
+    T[] result = Arrays.copyOf(array1, array1.length + array2.length);
+    System.arraycopy(array2, 0, result, array1.length, array2.length);
+    return result;
+  }
+
 }
diff --git a/src/jalview/util/FileUtils.java b/src/jalview/util/FileUtils.java
new file mode 100644 (file)
index 0000000..e62a7d6
--- /dev/null
@@ -0,0 +1,192 @@
+package jalview.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import jalview.bin.Console;
+
+public class FileUtils
+{
+  /*
+   * Given string glob pattern (see
+   * https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String)
+   * ) return a List of Files that match the pattern.
+   * Note this is a java style glob, not necessarily a bash-style glob, though there are sufficient similarities. 
+   */
+  public static List<File> getFilesFromGlob(String pattern)
+  {
+    return getFilesFromGlob(pattern, true);
+  }
+
+  public static List<File> getFilesFromGlob(String pattern,
+          boolean allowSingleFilenameThatDoesNotExist)
+  {
+    pattern = substituteHomeDir(pattern);
+    List<File> files = new ArrayList<>();
+    /*
+     * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing.
+     * We look for the first glob character * { ? and then look for the last File.separator before that.
+     * Then we can reset the path to look at and shorten the globbing pattern.
+     * Relative paths can be used in pattern, which work from the pwd (though these are converted into
+     * full paths in the match). 
+     */
+    int firstGlobChar = -1;
+    boolean foundGlobChar = false;
+    for (char c : new char[] { '*', '{', '?' })
+    {
+      if (pattern.indexOf(c) > -1
+              && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar))
+      {
+        firstGlobChar = pattern.indexOf(c);
+        foundGlobChar = true;
+      }
+    }
+    int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar);
+    if (foundGlobChar)
+    {
+      String pS = pattern.substring(0, lastFS + 1);
+      String rest = pattern.substring(lastFS + 1);
+      Path parentDir = Paths.get(pS).toAbsolutePath();
+      if (parentDir.toFile().exists())
+      {
+        try
+        {
+          String glob = "glob:" + parentDir.toString() + File.separator
+                  + rest;
+          PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob);
+          int maxDepth = rest.contains("**") ? 1028
+                  : (int) (rest.chars()
+                          .filter(ch -> ch == File.separatorChar).count())
+                          + 1;
+
+          Files.walkFileTree(parentDir,
+                  EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth,
+                  new SimpleFileVisitor<Path>()
+                  {
+                    @Override
+                    public FileVisitResult visitFile(Path path,
+                            BasicFileAttributes attrs) throws IOException
+                    {
+                      if (pm.matches(path))
+                      {
+                        files.add(path.toFile());
+                      }
+                      return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult visitFileFailed(Path file,
+                            IOException exc) throws IOException
+                    {
+                      return FileVisitResult.CONTINUE;
+                    }
+                  });
+        } catch (IOException e)
+        {
+          e.printStackTrace();
+        }
+      }
+    }
+    else
+    {
+      // no wildcards
+      File f = new File(pattern);
+      if (allowSingleFilenameThatDoesNotExist || f.exists())
+      {
+        files.add(f);
+      }
+    }
+    Collections.sort(files);
+
+    return files;
+  }
+
+  public static List<String> getFilenamesFromGlob(String pattern)
+  {
+    // convert list of Files to list of File.getPath() Strings
+    return getFilesFromGlob(pattern).stream().map(f -> f.getPath())
+            .collect(Collectors.toList());
+  }
+
+  public static String substituteHomeDir(String path)
+  {
+    return path.startsWith("~" + File.separator)
+            ? System.getProperty("user.home") + path.substring(1)
+            : path;
+  }
+
+  /*
+   * This method returns the basename of File file
+   */
+  public static String getBasename(File file)
+  {
+    return getBasenameOrExtension(file, false);
+  }
+
+  /*
+   * This method returns the extension of File file.
+   */
+  public static String getExtension(File file)
+  {
+    return getBasenameOrExtension(file, true);
+  }
+
+  public static String getBasenameOrExtension(File file, boolean extension)
+  {
+    if (file == null)
+      return null;
+
+    String value = null;
+    String filename = file.getName();
+    int lastDot = filename.lastIndexOf('.');
+    if (lastDot > 0) // don't truncate if starts with '.'
+    {
+      value = extension ? filename.substring(lastDot + 1)
+              : filename.substring(0, lastDot);
+    }
+    else
+    {
+      value = extension ? "" : filename;
+    }
+    return value;
+  }
+
+  /*
+   * This method returns the dirname of the first --append or --open value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public static String getDirname(File file)
+  {
+    if (file == null)
+      return null;
+
+    String dirname = null;
+    try
+    {
+      File p = file.getParentFile();
+      File d = new File(substituteHomeDir(p.getPath()));
+      dirname = d.getCanonicalPath();
+    } catch (IOException e)
+    {
+      Console.debug(
+              "Exception when getting dirname of '" + file.getPath() + "'",
+              e);
+      dirname = "";
+    }
+    return dirname;
+  }
+}
index 0cd017f..016feee 100755 (executable)
@@ -20,8 +20,6 @@
  */
 package jalview.util;
 
-import jalview.io.JalviewFileChooser;
-
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
@@ -36,6 +34,8 @@ import org.jfree.graphics2d.svg.SVGGraphics2D;
 import org.jfree.graphics2d.svg.SVGHints;
 import org.jibble.epsgraphics.EpsGraphics2D;
 
+import jalview.io.JalviewFileChooser;
+
 public class ImageMaker
 {
   public static final String SVG_DESCRIPTION = "Scalable Vector Graphics";
@@ -110,10 +110,12 @@ public class ImageMaker
    * @param file
    * @param fileTitle
    * @param useLineart
+   * @param bitmapscale
    * @throws IOException
    */
   public ImageMaker(TYPE imageType, int width, int height, File file,
-          String fileTitle, boolean useLineart) throws IOException
+          String fileTitle, boolean useLineart, float bitmapscale,
+          int bitmapwidth, int bitmapheight) throws IOException
   {
     this.type = imageType;
 
@@ -127,7 +129,7 @@ public class ImageMaker
       setupEPS(width, height, fileTitle, useLineart);
       break;
     case PNG:
-      setupPNG(width, height);
+      setupPNG(width, height, bitmapscale, bitmapwidth, bitmapheight);
       break;
     default:
     }
@@ -176,14 +178,52 @@ public class ImageMaker
    * 
    * @param width
    * @param height
+   * @param scale
    */
-  protected void setupPNG(int width, int height)
+  protected void setupPNG(int width, int height, float scale,
+          int bitmapwidth, int bitmapheight)
   {
-    bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+    if (width == 0 || height == 0)
+      return;
+
+    float usescale = 0.0f;
+    int usewidth = width;
+    int useheight = height;
+
+    // use the smallest positive scale (i.e. fit in the box)
+    if (scale > 0.0f)
+    {
+      usescale = scale;
+      usewidth = Math.round(scale * width);
+      useheight = Math.round(scale * height);
+    }
+    if (bitmapwidth > 0)
+    {
+      float wscale = (float) bitmapwidth / width;
+      if (wscale > 0.0f && (usescale == 0.0f || wscale < usescale))
+      {
+        usescale = wscale;
+        usewidth = bitmapwidth;
+        useheight = Math.round(usescale * height);
+      }
+    }
+    if (bitmapheight > 0)
+    {
+      float hscale = (float) bitmapheight / height;
+      if (hscale > 0.0f && (usescale == 0.0f || hscale < usescale))
+      {
+        usescale = hscale;
+        usewidth = Math.round(usescale * width);
+        useheight = bitmapheight;
+      }
+    }
+    bi = new BufferedImage(usewidth, useheight, BufferedImage.TYPE_INT_RGB);
     graphics = bi.getGraphics();
     Graphics2D ig2 = (Graphics2D) graphics;
     ig2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
             RenderingHints.VALUE_ANTIALIAS_ON);
+    if (usescale > 0.0f)
+      ig2.scale(usescale, usescale);
   }
 
   /**
index 7e1b8ad..efaf3e2 100644 (file)
@@ -600,6 +600,7 @@ public class StringUtils
 
   /*
    * implementation of String.replaceLast.
+   * Replaces only the last occurrence of toReplace in string with replacement.
    */
   public static String replaceLast(String string, String toReplace,
           String replacement)
@@ -607,8 +608,10 @@ public class StringUtils
     int pos = string.lastIndexOf(toReplace);
     if (pos > -1)
     {
-      return string.substring(0, pos) + replacement
-              + string.substring(pos + toReplace.length());
+      return new StringBuilder().append(string.substring(0, pos))
+              .append(replacement)
+              .append(string.substring(pos + toReplace.length()))
+              .toString();
     }
     else
     {
index 41e677a..1ec856b 100644 (file)
@@ -224,17 +224,18 @@ public class PAEContactMatrix implements ContactMatrixI
   @Override
   public String getAnnotDescr()
   {
-    return "Predicted Alignment Error"+((refSeq==null) ? "" : (" for " + refSeq.getName()));
+    return "Predicted Alignment Error"
+            + ((refSeq == null) ? "" : (" for " + refSeq.getName()));
   }
 
   @Override
   public String getAnnotLabel()
   {
     StringBuilder label = new StringBuilder("PAE Matrix");
-    //if (this.getReferenceSeq() != null)
-    //{
-    //  label.append(":").append(this.getReferenceSeq().getDisplayId(false));
-    //}
+    // if (this.getReferenceSeq() != null)
+    // {
+    // label.append(":").append(this.getReferenceSeq().getDisplayId(false));
+    // }
     return label.toString();
   }
 
@@ -257,39 +258,50 @@ public class PAEContactMatrix implements ContactMatrixI
   {
     return length;
   }
-  List<BitSet> groups=null;
+
+  List<BitSet> groups = null;
+
   @Override
   public boolean hasGroups()
   {
-    return groups!=null;
+    return groups != null;
   }
-  String newick=null;
+
+  String newick = null;
+
   @Override
   public String getNewick()
   {
     return newick;
   }
+
   @Override
   public boolean hasTree()
   {
-    return newick!=null && newick.length()>0;
+    return newick != null && newick.length() > 0;
   }
+
   boolean abs;
+
   double thresh;
-  String treeType=null;
-  public void makeGroups(float thresh,boolean abs)
+
+  String treeType = null;
+
+  public void makeGroups(float thresh, boolean abs)
   {
-    AverageDistanceEngine clusterer = new AverageDistanceEngine(null, null, this);
+    AverageDistanceEngine clusterer = new AverageDistanceEngine(null, null,
+            this);
     double height = clusterer.findHeight(clusterer.getTopNode());
-    newick = new jalview.io.NewickFile(clusterer.getTopNode(),false,true).print();
+    newick = new jalview.io.NewickFile(clusterer.getTopNode(), false, true)
+            .print();
     treeType = "UPGMA";
-    Console.trace("Newick string\n"+newick);
+    Console.trace("Newick string\n" + newick);
 
     List<BinaryNode> nodegroups;
     if (abs ? height > thresh : 0 < thresh && thresh < 1)
     {
       float cut = abs ? (float) (thresh / height) : thresh;
-      Console.debug("Threshold "+cut+" for height="+height);
+      Console.debug("Threshold " + cut + " for height=" + height);
 
       nodegroups = clusterer.groupNodes(cut);
     }
@@ -298,31 +310,34 @@ public class PAEContactMatrix implements ContactMatrixI
       nodegroups = new ArrayList<BinaryNode>();
       nodegroups.add(clusterer.getTopNode());
     }
-    this.abs=abs;
-    this.thresh=thresh;
+    this.abs = abs;
+    this.thresh = thresh;
     groups = new ArrayList<>();
-    for (BinaryNode root:nodegroups)
+    for (BinaryNode root : nodegroups)
     {
-      BitSet gpset=new BitSet();
-      for (BinaryNode leaf:clusterer.findLeaves(root))
+      BitSet gpset = new BitSet();
+      for (BinaryNode leaf : clusterer.findLeaves(root))
       {
-        gpset.set((Integer)leaf.element());
+        gpset.set((Integer) leaf.element());
       }
       groups.add(gpset);
     }
   }
+
   @Override
   public void updateGroups(List<BitSet> colGroups)
   {
-    if (colGroups!=null)
+    if (colGroups != null)
     {
-      groups=colGroups;
-    }    
+      groups = colGroups;
+    }
   }
+
   @Override
   public BitSet getGroupsFor(int column)
   {
-    for (BitSet gp:groups) {
+    for (BitSet gp : groups)
+    {
       if (gp.get(column))
       {
         return gp;
@@ -331,43 +346,51 @@ public class PAEContactMatrix implements ContactMatrixI
     return ContactMatrixI.super.getGroupsFor(column);
   }
 
-  HashMap<BitSet,Color> colorMap = new HashMap<>();
-  @Override 
+  HashMap<BitSet, Color> colorMap = new HashMap<>();
+
+  @Override
   public Color getColourForGroup(BitSet bs)
   {
-    if (bs==null) {
+    if (bs == null)
+    {
       return Color.white;
     }
-    Color groupCol=colorMap.get(bs);
-    if (groupCol==null)
+    Color groupCol = colorMap.get(bs);
+    if (groupCol == null)
     {
       return Color.white;
     }
     return groupCol;
   }
-  @Override 
-  public void setColorForGroup(BitSet bs,Color color)
+
+  @Override
+  public void setColorForGroup(BitSet bs, Color color)
   {
-    colorMap.put(bs,color);
+    colorMap.put(bs, color);
   }
+
   public void restoreGroups(List<BitSet> newgroups, String treeMethod,
           String tree, double thresh2)
   {
-    treeType=treeMethod;
+    treeType = treeMethod;
     groups = newgroups;
-    thresh=thresh2;
-    newick =tree;
-    
+    thresh = thresh2;
+    newick = tree;
+
   }
+
   @Override
-  public boolean hasCutHeight() {
-    return groups!=null && thresh!=0;
+  public boolean hasCutHeight()
+  {
+    return groups != null && thresh != 0;
   }
+
   @Override
   public double getCutHeight()
   {
     return thresh;
   }
+
   @Override
   public String getTreeMethod()
   {
index d9cbbd9..a734f52 100644 (file)
@@ -294,11 +294,12 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
           AlignmentI pdbAlignment, String retrievalUrl) throws IOException
   {
     File pae = fetchAlphaFoldPAE(id, retrievalUrl);
-    addAlphaFoldPAE(pdbAlignment, pae, 0, null, false, false);
+    addAlphaFoldPAE(pdbAlignment, pae, 0, null, false, false, null);
   }
 
   public static void addAlphaFoldPAE(AlignmentI pdbAlignment, File pae,
-          int index, String id, boolean isStruct, boolean isStructId)
+          int index, String id, boolean isStruct, boolean isStructId,
+          String label)
   {
     FileInputStream paeInput = null;
     try
@@ -313,12 +314,14 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
 
     if (isStruct)
     {
+      // ###### WRITE A TEST for this bit of the logic addAlphaFoldPAE with
+      // different params.
       StructureSelectionManager ssm = StructureSelectionManager
               .getStructureSelectionManager(Desktop.instance);
       if (ssm != null)
       {
         String structFilename = isStructId ? ssm.findFileForPDBId(id) : id;
-        addPAEToStructure(ssm, structFilename, pae);
+        addPAEToStructure(ssm, structFilename, pae, label);
       }
 
     }
@@ -328,7 +331,7 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
       try
       {
         if (!importPaeJSONAsContactMatrixToSequence(pdbAlignment, paeInput,
-                index, id))
+                index, id, label))
         {
           Console.warn("Could not import contact matrix from '"
                   + pae.getAbsolutePath() + "' to sequence.");
@@ -347,7 +350,7 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
   }
 
   public static void addPAEToStructure(StructureSelectionManager ssm,
-          String structFilename, File pae)
+          String structFilename, File pae, String label)
   {
     FileInputStream paeInput = null;
     try
@@ -370,7 +373,8 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
 
       try
       {
-        if (!importPaeJSONAsContactMatrixToStructure(smArray, paeInput))
+        if (!importPaeJSONAsContactMatrixToStructure(smArray, paeInput,
+                label))
         {
           Console.warn("Could not import contact matrix from '"
                   + pae.getAbsolutePath() + "' to structure.");
@@ -400,7 +404,7 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
    */
   public static boolean importPaeJSONAsContactMatrixToSequence(
           AlignmentI pdbAlignment, InputStream pae_input, int index,
-          String seqId) throws IOException, ParseException
+          String seqId, String label) throws IOException, ParseException
   {
     SequenceI sequence = null;
     if (seqId == null)
@@ -427,12 +431,13 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
       return false;
     }
     return importPaeJSONAsContactMatrixToSequence(pdbAlignment, pae_input,
-            sequence);
+            sequence, label);
   }
 
   public static boolean importPaeJSONAsContactMatrixToSequence(
           AlignmentI pdbAlignment, InputStream pae_input,
-          SequenceI sequence) throws IOException, ParseException
+          SequenceI sequence, String label)
+          throws IOException, ParseException
   {
     JSONObject paeDict = parseJSONtoPAEContactMatrix(pae_input);
     if (paeDict == null)
@@ -445,6 +450,8 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
     ((PAEContactMatrix) matrix).makeGroups(5f, true);
 
     AlignmentAnnotation cmannot = sequence.addContactList(matrix);
+    if (label != null)
+      cmannot.label = label;
     pdbAlignment.addAnnotation(cmannot);
 
     return true;
@@ -469,22 +476,23 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
     return paeDict;
   }
 
+  // ###### TEST THIS
   public static boolean importPaeJSONAsContactMatrixToStructure(
-          StructureMapping[] smArray, InputStream paeInput)
+          StructureMapping[] smArray, InputStream paeInput, String label)
           throws IOException, ParseException
   {
     boolean someDone = false;
     for (StructureMapping sm : smArray)
     {
       boolean thisDone = importPaeJSONAsContactMatrixToStructure(sm,
-              paeInput);
+              paeInput, label);
       someDone |= thisDone;
     }
     return someDone;
   }
 
   public static boolean importPaeJSONAsContactMatrixToStructure(
-          StructureMapping sm, InputStream paeInput)
+          StructureMapping sm, InputStream paeInput, String label)
           throws IOException, ParseException
   {
     JSONObject pae_obj = parseJSONtoPAEContactMatrix(paeInput);
@@ -494,12 +502,14 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
       return false;
     }
 
-    ContactMatrixI matrix = new PAEContactMatrix(sm.getSequence(),
+    SequenceI seq = sm.getSequence();
+    ContactMatrixI matrix = new PAEContactMatrix(seq,
             (Map<String, Object>) pae_obj);
     ((PAEContactMatrix) matrix).makeGroups(5f, true);
-    AlignmentAnnotation cmannot = sm.getSequence().addContactList(matrix);
-    sm.getSequence().addAlignmentAnnotation(cmannot);
-
+    AlignmentAnnotation cmannot = seq.addContactList(matrix);
+    /* this already happens in Sequence.addContactList()
+     seq.addAlignmentAnnotation(cmannot);
+     */
     return true;
   }
 
index 2a89e61..21d3dcb 100755 (executable)
@@ -26,7 +26,6 @@ import java.util.Hashtable;
 import java.util.List;
 import java.util.Vector;
 
-import jalview.bin.Console;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.DBRefSource;
 import jalview.datamodel.SequenceI;
@@ -52,9 +51,7 @@ public class PDBfile extends StructureFile
           DataSourceType sourceType) throws IOException
   {
     super(false, dataObject, sourceType);
-    Console.debug("***** PDBfile constructor");
     addSettings(addAlignmentAnnotations, predictSecStr, externalSecStr);
-    Console.debug("***** About to doParse() 1");
     doParse();
   }
 
@@ -63,7 +60,6 @@ public class PDBfile extends StructureFile
   {
     super(false, source);
     addSettings(addAlignmentAnnotations, predictSecStr, externalSecStr);
-    Console.debug("***** About to doParse() 2");
     doParse();
   }
 
index 23db36f..f36b45a 100644 (file)
@@ -25,6 +25,7 @@ import static org.testng.AssertJUnit.assertFalse;
 import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertTrue;
 
+import jalview.bin.ArgsParser;
 import jalview.gui.JvOptionPane;
 
 import org.testng.annotations.BeforeClass;
index 7509ec9..0ab18d9 100644 (file)
@@ -34,7 +34,6 @@ import java.util.ArrayList;
 import org.testng.Assert;
 import org.testng.FileAssert;
 import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeTest;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -54,7 +53,7 @@ public class CommandLineOperations
   }
 
   // Note longer timeout needed on full test run than on individual tests
-  private static final int TEST_TIMEOUT = 13000;
+  private static final int TEST_TIMEOUT = 23000;
 
   private static final int SETUP_TIMEOUT = 9500;
 
@@ -213,17 +212,17 @@ public class CommandLineOperations
     }
   }
 
-  @BeforeTest(alwaysRun = true)
+  @BeforeClass(alwaysRun = true)
   public void initialize()
   {
     new CommandLineOperations();
   }
 
-  @BeforeTest(alwaysRun = true)
+  @BeforeClass(alwaysRun = true)
   public void setUpForHeadlessCommandLineInputOperations()
           throws IOException
   {
-    String cmds = "nodisplay -open examples/uniref50.fa -sortbytree --props=test/jalview/bin/testProps.jvprops -colour zappo "
+    String cmds = "nodisplay -open examples/uniref50.fa -sortbytree -props test/jalview/bin/testProps.jvprops -colour zappo "
             + "-jabaws http://www.compbio.dundee.ac.uk/jabaws -nosortbytree "
             + "-features examples/testdata/plantfdx.features -annotations examples/testdata/plantfdx.annotations -tree examples/testdata/uniref50_test_tree";
     Worker worker = getJalviewDesktopRunner(true, cmds, SETUP_TIMEOUT);
@@ -239,7 +238,7 @@ public class CommandLineOperations
     }
   }
 
-  @BeforeTest(alwaysRun = true)
+  @BeforeClass(alwaysRun = true)
   public void setUpForCommandLineInputOperations() throws IOException
   {
     String cmds = "-open examples/uniref50.fa -noquestionnaire -nousagestats";
@@ -261,7 +260,7 @@ public class CommandLineOperations
         int count = 0;
         try
         {
-          while ((ln = worker.getErrorReader().readLine()) != null)
+          while ((ln = worker.getOutputReader().readLine()) != null)
           {
             System.out.println(ln);
             successfulCMDs.add(ln);
@@ -341,9 +340,9 @@ public class CommandLineOperations
     return new Object[][] {
         // headless mode input operations
         { "CMD [-colour zappo] executed successfully!",
-            "Failed command : -color zappo" },
+            "Failed command : -colour zappo" },
         { "CMD [-props test/jalview/bin/testProps.jvprops] executed successfully!",
-            "Failed command : --props=File" },
+            "Failed command : -props File" },
         { "CMD [-sortbytree] executed successfully!",
             "Failed command : -sortbytree" },
         { "CMD [-jabaws http://www.compbio.dundee.ac.uk/jabaws] executed successfully!",
@@ -373,9 +372,11 @@ public class CommandLineOperations
     // since it works.
     // https://issues.jalview.org/browse/JAL-1889?focusedCommentId=21609&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-21609
     String workingDir = "test/jalview/bin/";
-    return new Object[][] { { "nodisplay -open examples/uniref50.fa",
-        " -eps", workingDir + "test_uniref50_out.eps", true,
-        MINFILESIZE_BIG, TEST_TIMEOUT },
+    return new Object[][] {
+        //
+        { "nodisplay -open examples/uniref50.fa", " -eps",
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
         { "nodisplay -open examples/uniref50.fa", " -eps",
             workingDir + "test_uniref50_out.eps", false, MINFILESIZE_BIG,
             TEST_TIMEOUT },
@@ -420,6 +421,8 @@ public class CommandLineOperations
             TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -jalview",
             workingDir + "test_uniref50_out.jvp", true, MINFILESIZE_SMALL,
-            TEST_TIMEOUT }, };
+            TEST_TIMEOUT },
+        //
+    };
   }
 }
diff --git a/test/jalview/bin/CommandLineOperationsNG.java b/test/jalview/bin/CommandLineOperationsNG.java
new file mode 100644 (file)
index 0000000..049e9a8
--- /dev/null
@@ -0,0 +1,462 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.bin;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.FileAssert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ModuleRef;
+import io.github.classgraph.ScanResult;
+import jalview.gui.JvOptionPane;
+
+public class CommandLineOperationsNG
+{
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
+  // Note longer timeout needed on full test run than on individual tests
+  private static final int TEST_TIMEOUT = 13000;
+
+  private static final int SETUP_TIMEOUT = 9500;
+
+  private static final int MINFILESIZE_SMALL = 2096;
+
+  private static final int MINFILESIZE_BIG = 4096;
+
+  private List<String> successfulCMDs = new ArrayList<>();
+
+  /***
+   * from
+   * http://stackoverflow.com/questions/808276/how-to-add-a-timeout-value-when
+   * -using-javas-runtime-exec
+   * 
+   * @author jimp
+   * 
+   */
+  private static class Worker extends Thread
+  {
+    private final Process process;
+
+    private BufferedReader outputReader;
+
+    private BufferedReader errorReader;
+
+    private Integer exit;
+
+    private Worker(Process process)
+    {
+      this.process = process;
+    }
+
+    @Override
+    public void run()
+    {
+      try
+      {
+        exit = process.waitFor();
+      } catch (InterruptedException ignore)
+      {
+        return;
+      }
+    }
+
+    public BufferedReader getOutputReader()
+    {
+      return outputReader;
+    }
+
+    public void setOutputReader(BufferedReader outputReader)
+    {
+      this.outputReader = outputReader;
+    }
+
+    public BufferedReader getErrorReader()
+    {
+      return errorReader;
+    }
+
+    public void setErrorReader(BufferedReader errorReader)
+    {
+      this.errorReader = errorReader;
+    }
+  }
+
+  private static ClassGraph scanner = null;
+
+  private static String classpath = null;
+
+  private static String modules = null;
+
+  private static String java_exe = null;
+
+  public synchronized static String getClassPath()
+  {
+    if (scanner == null)
+    {
+      scanner = new ClassGraph();
+      ScanResult scan = scanner.scan();
+      classpath = scan.getClasspath();
+      modules = "";
+      for (ModuleRef mr : scan.getModules())
+      {
+        modules.concat(mr.getName());
+      }
+      java_exe = System.getProperty("java.home") + File.separator + "bin"
+              + File.separator + "java";
+
+    }
+
+    while (classpath == null)
+    {
+      try
+      {
+        Thread.sleep(10);
+      } catch (InterruptedException x)
+      {
+
+      }
+    }
+    return classpath;
+  }
+
+  private Worker getJalviewDesktopRunner(boolean withAwt, String cmd,
+          int timeout)
+  {
+    /*
+    boolean win = System.getProperty("os.name").indexOf("Win") >= 0;
+    String pwd = "";
+    try
+    {
+      Path currentRelativePath = Paths.get("");
+      pwd = currentRelativePath.toAbsolutePath().toString();
+    } catch (Exception q)
+    {
+      q.printStackTrace();
+    }
+    if (pwd == null || pwd.length() == 0)
+      pwd = ".";
+    String[] classpaths = new String[] { pwd + "/bin/main",
+        pwd + "/j11lib/*", pwd + "/resources", pwd + "/help" };
+    String classpath = String.join(win ? ";" : ":", classpaths);
+    getClassPath();
+    */
+    // Note: JAL-3065 - don't include quotes for lib/* because the arguments are
+    // not expanded by the shell
+    String classpath = getClassPath();
+    String _cmd = java_exe + " "
+            + (withAwt ? "-Djava.awt.headless=true" : "") + " -classpath "
+            + classpath
+            + ((modules != null && modules.length() > 2)
+                    ? "--add-modules=\"" + modules + "\""
+                    : "")
+            + " jalview.bin.Jalview ";
+    Process ls2_proc = null;
+    Worker worker = null;
+    try
+    {
+      cmd = " --testoutput " + cmd;
+      System.out.println("Running '" + _cmd + cmd + "'");
+      ls2_proc = Runtime.getRuntime().exec(_cmd + cmd);
+    } catch (Throwable e1)
+    {
+      e1.printStackTrace();
+    }
+    if (ls2_proc != null)
+    {
+      BufferedReader outputReader = new BufferedReader(
+              new InputStreamReader(ls2_proc.getInputStream()));
+      BufferedReader errorReader = new BufferedReader(
+              new InputStreamReader(ls2_proc.getErrorStream()));
+      worker = new Worker(ls2_proc);
+      worker.start();
+      try
+      {
+        worker.join(timeout);
+      } catch (InterruptedException e)
+      {
+        System.err.println("Thread interrupted");
+      }
+      worker.setOutputReader(outputReader);
+      worker.setErrorReader(errorReader);
+    }
+    return worker;
+  }
+
+  @Test(groups = { "Functional", "testTask1" })
+  public void reportCurrentWorkingDirectory()
+  {
+    try
+    {
+      Path currentRelativePath = Paths.get("");
+      String s = currentRelativePath.toAbsolutePath().toString();
+      System.out.println("Test CWD is " + s);
+    } catch (Exception q)
+    {
+      q.printStackTrace();
+    }
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void initialize()
+  {
+    new CommandLineOperationsNG();
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpForHeadlessCommandLineInputOperations()
+          throws IOException
+  {
+    String cmds = "--headless " + "--open examples/uniref50.fa "
+            + "--sortbytree "
+            + "--props test/jalview/bin/testProps.jvprops "
+            + "--colour zappo "
+            + "--jabaws http://www.compbio.dundee.ac.uk/jabaws "
+            + "--features examples/testdata/plantfdx.features "
+            + "--annotations examples/testdata/plantfdx.annotations "
+            + "--tree examples/testdata/uniref50_test_tree "
+            + "--nousagestats ";
+    Worker worker = getJalviewDesktopRunner(true, cmds, SETUP_TIMEOUT);
+    String ln = null;
+    while ((ln = worker.getOutputReader().readLine()) != null)
+    {
+      System.out.println(ln);
+      successfulCMDs.add(ln);
+    }
+    while ((ln = worker.getErrorReader().readLine()) != null)
+    {
+      System.err.println(ln);
+    }
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpForCommandLineInputOperations() throws IOException
+  {
+    String cmds = "--open examples/uniref50.fa --noquestionnaire --nousagestats";
+    final Worker worker = getJalviewDesktopRunner(false, cmds,
+            SETUP_TIMEOUT);
+
+    // number of lines expected on STDERR when Jalview starts up normally
+    // may need to adjust this if Jalview is excessively noisy ?
+    final int STDERR_SETUPLINES = 50;
+
+    // thread monitors stderr - bails after SETUP_TIMEOUT or when
+    // STDERR_SETUPLINES have been read
+    Thread runner = new Thread(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        String ln = null;
+        int count = 0;
+        try
+        {
+          while ((ln = worker.getOutputReader().readLine()) != null)
+          {
+            System.out.println(ln);
+            successfulCMDs.add(ln);
+            if (++count > STDERR_SETUPLINES)
+            {
+              break;
+            }
+          }
+        } catch (Exception e)
+        {
+          System.err.println(
+                  "Unexpected Exception reading stderr from the Jalview process");
+          e.printStackTrace();
+        }
+      }
+    });
+    long t = System.currentTimeMillis() + SETUP_TIMEOUT;
+    runner.start();
+    while (!runner.isInterrupted() && System.currentTimeMillis() < t)
+    {
+      try
+      {
+        Thread.sleep(500);
+      } catch (InterruptedException e)
+      {
+      }
+    }
+    runner.interrupt();
+    if (worker != null && worker.exit == null)
+    {
+      worker.interrupt();
+      Thread.currentThread().interrupt();
+      worker.process.destroy();
+    }
+  }
+
+  @Test(
+    groups =
+    { "Functional", "testTask1" },
+    dataProvider = "allInputOperationsData")
+  public void testAllInputOperations(String expectedString,
+          String failureMsg)
+  {
+    if ("[TESTOUTPUT] arg --nousagestats was set".equals(expectedString))
+      Assert.assertTrue(successfulCMDs.contains(expectedString),
+              failureMsg);
+  }
+
+  @Test(
+    groups =
+    { "Functional", "testTask1" },
+    dataProvider = "headlessModeOutputOperationsData")
+  public void testHeadlessModeOutputOperations(String harg, String type,
+          String fileName, boolean withAWT, int expectedMinFileSize,
+          int timeout)
+  {
+    String cmd = harg + type + " " + fileName;
+    // System.out.println(">>>>>>>>>>>>>>>> Command : " + cmd);
+    File file = new File(fileName);
+    file.deleteOnExit();
+    Worker worker = getJalviewDesktopRunner(withAWT, cmd, timeout);
+    assertNotNull(worker, "worker is null");
+    String msg = "Didn't create an output" + type + " file.[" + cmd + "]";
+    assertTrue(file.exists(), msg);
+    FileAssert.assertFile(file, msg);
+    FileAssert.assertMinLength(file, expectedMinFileSize);
+    if (worker != null && worker.exit == null)
+    {
+      worker.interrupt();
+      Thread.currentThread().interrupt();
+      worker.process.destroy();
+      Assert.fail("Jalview did not exit after " + type
+              + " generation (try running test again to verify - timeout at "
+              + timeout + "ms). [" + harg + "]");
+    }
+    file.delete();
+  }
+
+  @DataProvider(name = "allInputOperationsData")
+  public Object[][] getHeadlessModeInputParams()
+  {
+    return new Object[][] {
+        // headless mode input operations
+        { "[TESTOUTPUT] arg --colour='zappo' was set",
+            "Failed setting arg --colour" },
+        { "[TESTOUTPUT] arg --props='test/jalview/bin/testProps.jvprops' was set",
+            "Failed setting arg --props" },
+        { "[TESTOUTPUT] arg --sortbytree was set",
+            "Failed setting arg --sortbytree" },
+        { "[TESTOUTPUT] arg --jabaws='http://www.compbio.dundee.ac.uk/jabaws' was set",
+            "Failed setting arg --jabaws" },
+        { "[TESTOUTPUT] arg --open='examples/uniref50.fa' was set",
+            "Failed setting arg --open" },
+        { "[TESTOUTPUT] arg --features='examples/testdata/plantfdx.features' was set",
+            "Failed setting arg --features" },
+        { "[TESTOUTPUT] arg --annotations='examples/testdata/plantfdx.annotations' was set",
+            "Failed setting arg --annotations" },
+        { "[TESTOUTPUT] arg --tree='examples/testdata/uniref50_test_tree' was set",
+            "Failed setting arg --tree" },
+        // non headless mode input operations
+        { "[TESTOUTPUT] arg --nousagestats was set",
+            "Failed setting arg --nousagestats" },
+        { "[TESTOUTPUT] arg --noquestionnaire was set",
+            "Failed setting arg --noquestionnaire" }
+        //
+    };
+  }
+
+  @DataProvider(name = "headlessModeOutputOperationsData")
+  public static Object[][] getHeadlessModeOutputParams()
+  {
+    // JBPNote: I'm not clear why need to specify full path for output file
+    // when running tests on build server, but we will keep this patch for now
+    // since it works.
+    // https://issues.jalview.org/browse/JAL-1889?focusedCommentId=21609&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-21609
+    String workingDir = "test/jalview/bin/";
+    return new Object[][] {
+        //
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", false, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", false, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.svg", false, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.png", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --image",
+            workingDir + "test_uniref50_out.html", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.mfa", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.aln", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.msf", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.aln", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.pir", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.pfam", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.blc", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        { "--headless --open examples/uniref50.fa", " --output",
+            workingDir + "test_uniref50_out.jvp", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
+        //
+    };
+  }
+}
diff --git a/test/jalview/bin/CommandsTest.java b/test/jalview/bin/CommandsTest.java
new file mode 100644 (file)
index 0000000..20ccd11
--- /dev/null
@@ -0,0 +1,390 @@
+package jalview.bin;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.util.ArrayUtils;
+
+@Test
+public class CommandsTest
+{
+  private static final String testfiles = "test/jalview/bin/argparser/testfiles";
+
+  @BeforeClass(alwaysRun = true)
+  public static void setUpBeforeClass() throws Exception
+  {
+    Cache.loadProperties("test/jalview/gui/quitProps.jvprops");
+    Date oneHourFromNow = new Date(
+            System.currentTimeMillis() + 3600 * 1000);
+    Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", oneHourFromNow);
+  }
+
+  @AfterClass(alwaysRun = true)
+  public static void resetProps()
+  {
+    Cache.loadProperties("test/jalview/testProps.jvprops");
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
+  @AfterMethod(alwaysRun = true)
+  public void tearDown()
+  {
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  /* --setprops is currently disabled so this test won't work
+  @Test(groups = "Functional")
+  public void setpropsTest()
+  {
+    final String MOSTLY_HARMLESS = "MOSTLY_HARMLESS";
+    String cmdLine = "--setprop=" + MOSTLY_HARMLESS + "=Earth";
+    String[] args = cmdLine.split("\\s+");
+    Jalview.main(args);
+    Assert.assertEquals(Cache.getDefault(MOSTLY_HARMLESS, "Magrathea"),
+            "Earth");
+  }
+  */
+
+  @Test(groups = "Functional", dataProvider = "cmdLines")
+  public void commandsOpenTest(String cmdLine, boolean cmdArgs,
+          int numFrames, String[] sequences)
+  {
+    String[] args = cmdLine.split("\\s+");
+    Jalview.main(args);
+    Commands cmds = Jalview.getInstance().getCommands();
+    Assert.assertNotNull(cmds);
+    Assert.assertEquals(cmds.commandArgsProvided(), cmdArgs,
+            "Commands were not provided in the args");
+    Assert.assertEquals(cmds.argsWereParsed(), cmdArgs,
+            "Overall command parse and operation is false");
+
+    Assert.assertEquals(Desktop.getAlignFrames().length, numFrames,
+            "Wrong number of AlignFrames");
+
+    if (sequences != null)
+    {
+      Set<String> openedSequenceNames = new HashSet<>();
+      AlignFrame[] afs = Desktop.getAlignFrames();
+      for (AlignFrame af : afs)
+      {
+        openedSequenceNames
+                .addAll(af.getViewport().getAlignment().getSequenceNames());
+      }
+      for (String sequence : sequences)
+      {
+        Assert.assertTrue(openedSequenceNames.contains(sequence),
+                "Sequence '" + sequence
+                        + "' was not found in opened alignment files: "
+                        + cmdLine + ".\nOpened sequence names are:\n"
+                        + String.join("\n", openedSequenceNames));
+      }
+    }
+
+    Assert.assertFalse(
+            lookForSequenceName("THIS_SEQUENCE_ID_DOESN'T_EXIST"));
+  }
+
+  @Test(groups = "Functional", dataProvider = "argfileOutputFiles")
+  public void argFilesGlobAndSubstitutionsTest(String cmdLine,
+          String[] filenames) throws IOException
+  {
+    cleanupFiles(filenames);
+    String[] args = cmdLine.split("\\s+");
+    Jalview.main(args);
+    Commands cmds = Jalview.getInstance().getCommands();
+    Assert.assertNotNull(cmds);
+    File lastFile = null;
+    for (String filename : filenames)
+    {
+      File file = new File(filename);
+      Assert.assertTrue(file.exists(), "File '" + filename
+              + "' was not created by '" + cmdLine + "'");
+      Assert.assertTrue(file.isFile(), "File '" + filename
+              + "' is not a file from '" + cmdLine + "'");
+      Assert.assertTrue(Files.size(file.toPath()) > 0, "File '" + filename
+              + "' has no content from '" + cmdLine + "'");
+      // make sure the successive output files get bigger!
+      if (lastFile != null)
+        Assert.assertTrue(
+                Files.size(file.toPath()) > Files.size(lastFile.toPath()));
+    }
+    cleanupFiles(filenames);
+    tearDown();
+  }
+
+  @DataProvider(name = "argfileOutputFiles")
+  public Object[][] argfileOutputFiles()
+  {
+    return new Object[][] {
+        //
+        { "--argfile=" + testfiles + "/**/*.txt", new String[]
+        { testfiles + "/dir1/test1.png", testfiles + "/dir2/test1.png",
+            testfiles + "/dir3/subdir/test0.png" } },
+        { "--argfile=" + testfiles + "/**/argfile.txt", new String[]
+        { testfiles + "/dir1/test1.png", testfiles + "/dir2/test1.png" } },
+        { "--argfile=" + testfiles + "/dir*/argfile.txt", new String[]
+        { testfiles + "/dir1/test1.png", testfiles + "/dir2/test1.png" } },
+        { "--initsubstitutions --append examples/uniref50.fa --image "
+                + testfiles + "/{basename}.png",
+            new String[]
+            { testfiles + "/uniref50.png" } },
+        { "--append examples/uniref50.fa --nosubstitutions --image "
+                + testfiles + "/{basename}.png",
+            new String[]
+            { testfiles + "/{basename}.png" } }
+        //
+    };
+
+  }
+
+  @DataProvider(name = "cmdLines")
+  public Object[][] cmdLines()
+  {
+    String[] someUniref50Seqs = new String[] { "FER_CAPAA", "FER_CAPAN",
+        "FER1_MAIZE", "FER1_SPIOL", "O80429_MAIZE" };
+    String[] t1 = new String[] { "TEST1" };
+    String[] t2 = new String[] { "TEST2" };
+    String[] t3 = new String[] { "TEST3" };
+    return new Object[][] {
+        /*
+        */
+        { "--append=examples/uniref50.fa", true, 1, someUniref50Seqs },
+        { "--append examples/uniref50.fa", true, 1, someUniref50Seqs },
+        { "--append=examples/uniref50*.fa", true, 1, someUniref50Seqs },
+        // NOTE we cannot use shell expansion in tests, so list all files!
+        { "--append examples/uniref50.fa examples/uniref50_mz.fa", true, 1,
+            someUniref50Seqs },
+        { "--append=[new]examples/uniref50*.fa", true, 2,
+            someUniref50Seqs },
+        { "--open=examples/uniref50*.fa", true, 2, someUniref50Seqs },
+        { "examples/uniref50.fa", true, 1, someUniref50Seqs },
+        { "examples/uniref50.fa " + testfiles + "/test1.fa", true, 2,
+            ArrayUtils.concatArrays(someUniref50Seqs, t1) },
+        { "examples/uniref50.fa " + testfiles + "/test1.fa", true, 2, t1 },
+        { "--argfile=" + testfiles + "/argfile0.txt", true, 1,
+            ArrayUtils.concatArrays(t1, t3) },
+        { "--argfile=" + testfiles + "/argfile*.txt", true, 5,
+            ArrayUtils.concatArrays(t1, t2, t3) },
+        { "--argfile=" + testfiles + "/argfile.autocounter", true, 3,
+            ArrayUtils.concatArrays(t1, t2) } };
+
+  }
+
+  public static boolean lookForSequenceName(String sequenceName)
+  {
+    AlignFrame[] afs = Desktop.getAlignFrames();
+    for (AlignFrame af : afs)
+    {
+      for (String name : af.getViewport().getAlignment().getSequenceNames())
+      {
+        if (sequenceName.equals(name))
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  public static void cleanupFiles(String[] filenames)
+  {
+    for (String filename : filenames)
+    {
+      File file = new File(filename);
+      if (file.exists())
+      {
+        file.delete();
+      }
+    }
+  }
+
+  @Test(
+    groups = "Functional",
+    dataProvider = "allLinkedIdsData",
+    singleThreaded = true)
+  public void allLinkedIdsTest(String cmdLine, String[] filenames,
+          String[] nonfilenames)
+  {
+    String[] args = cmdLine.split("\\s+");
+    Jalview.main(args);
+    Commands cmds = Jalview.getInstance().getCommands();
+    Assert.assertNotNull(cmds);
+    for (String filename : filenames)
+    {
+      Assert.assertTrue(new File(filename).exists(),
+              "File '" + filename + "' was not created");
+    }
+    cleanupFiles(filenames);
+    if (nonfilenames != null)
+    {
+      for (String nonfilename : nonfilenames)
+      {
+        File nonfile = new File(nonfilename);
+        Assert.assertFalse(nonfile.exists(),
+                "File " + nonfilename + " exists when it shouldn't!");
+      }
+    }
+  }
+
+  @DataProvider(name = "allLinkedIdsData")
+  public Object[][] allLinkedIdsData()
+  {
+    return new Object[][] {
+        //
+        /*
+         */
+        { "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --output={dirname}/{basename}.stk --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk", },
+            null },
+        { "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --image={dirname}/{basename}.png --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.png",
+                "test/jalview/bin/argparser/testfiles/test2.png",
+                "test/jalview/bin/argparser/testfiles/test3.png", },
+            null },
+        { "--open=test/jalview/bin/argparser/testfiles/*.fa --all --output={dirname}/{basename}.stk --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk", },
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", }, },
+        { "--open=test/jalview/bin/argparser/**/*.fa --all --output={dirname}/{basename}.stk --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", },
+            null },
+        { "--open=test/jalview/bin/argparser/**/*.fa --output=*.stk --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", },
+            null },
+        { "--open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output=*.stk --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk", },
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", }, },
+        { "--open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output=open*.stk --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk", },
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", }, },
+        { "--open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --opened --output={dirname}/{basename}.stk --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk", },
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", }, },
+        { "--open=test/jalview/bin/argparser/testfiles/dir1/*.fa --output open*.stk --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output=open*.aln --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test1.aln",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.aln",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.aln", },
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk",
+                "test/jalview/bin/argparser/testfiles/test1.aln",
+                "test/jalview/bin/argparser/testfiles/test2.aln",
+                "test/jalview/bin/argparser/testfiles/test3.aln",
+                "test/jalview/bin/argparser/testfiles/dir1/test1.aln",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.aln",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.aln",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.aln",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.aln",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.aln", }, },
+        //
+    };
+  }
+
+}
diff --git a/test/jalview/bin/CommandsTest2.java b/test/jalview/bin/CommandsTest2.java
new file mode 100644 (file)
index 0000000..f056da2
--- /dev/null
@@ -0,0 +1,190 @@
+package jalview.bin;
+
+import java.util.Date;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import jalview.api.AlignViewportI;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.StructureViewerBase;
+
+@Test
+public class CommandsTest2
+{
+  @BeforeClass(alwaysRun = true)
+  public static void setUpBeforeClass() throws Exception
+  {
+    Cache.loadProperties("test/jalview/bin/commandsTest2.jvprops");
+    Date oneHourFromNow = new Date(
+            System.currentTimeMillis() + 3600 * 1000);
+    Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", oneHourFromNow);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
+
+  }
+
+  @AfterClass(alwaysRun = true)
+  public static void resetProps()
+  {
+    Cache.loadProperties("test/jalview/testProps.jvprops");
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
+  @AfterMethod(alwaysRun = true)
+  public void tearDown()
+  {
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(
+    groups =
+    { "Functional", "testTask1" },
+    dataProvider = "structureOpeningArgsParams",
+    singleThreaded = true)
+  public void structureOpeningArgsTest(String cmdLine, int seqNum,
+          int annNum, int viewerNum)
+  {
+    String[] args = cmdLine.split("\\s+");
+
+    Jalview.main(args);
+    try
+    {
+      // sleep for slow build server to open annotations and viewer windows
+      Thread.sleep(seqNum * 50 + annNum * 50 + viewerNum * 500);
+    } catch (InterruptedException e)
+    {
+      e.printStackTrace();
+    }
+
+    AlignFrame[] afs = Desktop.getAlignFrames();
+    Assert.assertNotNull(afs);
+    Assert.assertTrue(afs.length > 0);
+
+    AlignFrame af = afs[0];
+    Assert.assertNotNull(af);
+
+    AlignmentPanel ap = af.alignPanel;
+    Assert.assertNotNull(ap);
+
+    AlignmentI al = ap.getAlignment();
+    Assert.assertNotNull(al);
+
+    List<SequenceI> seqs = al.getSequences();
+    Assert.assertNotNull(seqs);
+
+    Assert.assertEquals(seqs.size(), seqNum, "Wrong number of sequences");
+
+    AlignViewportI av = ap.getAlignViewport();
+    Assert.assertNotNull(av);
+
+    AlignmentAnnotation[] aas = al.getAlignmentAnnotation();
+    int visibleAnn = 0;
+    int dcount = 0;
+    for (AlignmentAnnotation aa : aas)
+    {
+      if (aa.visible)
+        visibleAnn++;
+    }
+
+    Assert.assertEquals(visibleAnn, annNum,
+            "Wrong number of visible annotations");
+
+    if (viewerNum > -1)
+    {
+      List<StructureViewerBase> openViewers = Desktop.instance
+              .getStructureViewers(ap, null);
+      Assert.assertNotNull(openViewers);
+      int count = 0;
+      for (StructureViewerBase svb : openViewers)
+      {
+        if (svb.isVisible())
+          count++;
+      }
+      Assert.assertEquals(count, viewerNum,
+              "Wrong number of structure viewers opened");
+    }
+  }
+
+  @DataProvider(name = "structureOpeningArgsParams")
+  public Object[][] structureOpeningArgsParams()
+  {
+    /*
+      String cmdLine,
+      int seqNum,
+      int annNum,
+      int viewerNum,
+      String propsFile
+     */
+    return new Object[][] {
+        //
+        /*
+         */
+        { "--nonews --nosplash --debug " + "--append=examples/uniref50.fa "
+                + "--colour=gecos-flower "
+                + "--structure=[seqid=FER1_SPIOL]examples/AlphaFold/AF-P00221-F1-model_v4.cif "
+                + "--paematrix=examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--props=test/jalview/bin/commandsTest2.jvprops1 ",
+            15, 7, 1 },
+        { "--nonews --nosplash --debug " + "--append=examples/uniref50.fa "
+                + "--colour=gecos-flower "
+                + "--structure=[seqid=FER1_SPIOL]examples/AlphaFold/AF-P00221-F1-model_v4.cif "
+                + "--paematrix=examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--props=test/jalview/bin/commandsTest2.jvprops2 ",
+            15, 4, 1 },
+        { "--nonews --nosplash --debug " + "--append=examples/uniref50.fa "
+                + "--colour=gecos-flower "
+                + "--structure=[seqid=FER1_SPIOL]examples/AlphaFold/AF-P00221-F1-model_v4.cif "
+                + "--paematrix=examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--noshowssannotations "
+                + "--props=test/jalview/bin/commandsTest2.jvprops1 ",
+            15, 4, 1 },
+        { "--nonews --nosplash --debug " + "--append=examples/uniref50.fa "
+                + "--colour=gecos-flower "
+                + "--structure=[seqid=FER1_SPIOL]examples/AlphaFold/AF-P00221-F1-model_v4.cif "
+                + "--paematrix=examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--noshowannotations "
+                + "--props=test/jalview/bin/commandsTest2.jvprops1 ",
+            15, 3, 1 },
+        { "--nonews --nosplash --debug " + "--append=examples/uniref50.fa "
+                + "--colour=gecos-flower "
+                + "--structure=[seqid=FER1_SPIOL]examples/AlphaFold/AF-P00221-F1-model_v4.cif "
+                + "--paematrix=examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--noshowannotations " + "--noshowssannotations "
+                + "--props=test/jalview/bin/commandsTest2.jvprops1 ",
+            15, 0, 1 },
+        { "--nonews --nosplash --debug " + "--append=examples/uniref50.fa "
+                + "--colour=gecos-flower "
+                + "--structure=[seqid=FER1_SPIOL]examples/AlphaFold/AF-P00221-F1-model_v4.cif "
+                + "--paematrix=examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json "
+                + "--noshowannotations " + "--noshowssannotations "
+                + "--props=test/jalview/bin/commandsTest2.jvprops1 ",
+            15, 0, 1 },
+        { "--nonews --nosplash --debug --nowebservicediscovery --props=test/jalview/bin/commandsTest.jvprops --argfile=test/jalview/bin/commandsTest2.argfile1 ",
+            16, 19, 3 },
+        { "--nonews --nosplash --debug --nowebservicediscovery --props=test/jalview/bin/commandsTest.jvprops --argfile=test/jalview/bin/commandsTest2.argfile2 ",
+            16, 0, 2 },
+        /*
+         */
+        //
+    };
+  }
+}
index 137d3e1..49a721f 100644 (file)
@@ -32,6 +32,7 @@ import jalview.gui.Desktop;
 import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
 import jalview.io.FileLoader;
+import jalview.util.Platform;
 
 /*
  * Testing a HiDPI display is difficult without running in a HiDPI display.
@@ -84,7 +85,8 @@ public class HiDPISettingTest1
   @AfterClass(alwaysRun = true)
   public void tearDown()
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" })
@@ -107,20 +109,22 @@ public class HiDPISettingTest1
       setMockScreen(1920, 1080, 96);
       assertEquals(HiDPISetting.getScalePropertyArg(), null);
 
+      // currently HiDPISetting only operates for Linux
+
       // 4K screen -- scale by 2
       setMockScreen(3180, 2160, 80);
       assertEquals(HiDPISetting.getScalePropertyArg(),
-              "-D" + scalePropertyName + "=2");
+              Platform.isLinux() ? "-D" + scalePropertyName + "=2" : null);
 
       // 4K screen with high dpi -- scale by 3
       setMockScreen(3180, 2160, 450);
       assertEquals(HiDPISetting.getScalePropertyArg(),
-              "-D" + scalePropertyName + "=3");
+              Platform.isLinux() ? "-D" + scalePropertyName + "=3" : null);
 
       // stupidly big screen -- scale by 8
       setMockScreen(19200, 10800, 72);
       assertEquals(HiDPISetting.getScalePropertyArg(),
-              "-D" + scalePropertyName + "=8");
+              Platform.isLinux() ? "-D" + scalePropertyName + "=8" : null);
     }
   }
 
diff --git a/test/jalview/bin/argparser/ArgParserTest.java b/test/jalview/bin/argparser/ArgParserTest.java
new file mode 100644 (file)
index 0000000..259acac
--- /dev/null
@@ -0,0 +1,336 @@
+package jalview.bin.argparser;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import jalview.bin.Cache;
+import jalview.gui.Desktop;
+
+@Test(singleThreaded = true)
+public class ArgParserTest
+{
+  @AfterClass(alwaysRun = true)
+  public static void resetProps()
+  {
+    Cache.loadProperties("test/jalview/testProps.jvprops");
+  }
+
+  @AfterMethod(alwaysRun = true)
+  public void tearDown()
+  {
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
+  }
+
+  @Test(groups = "Functional", dataProvider = "argLines")
+  public void parseArgsTest(String commandLineArgs, Arg a, String other)
+  {
+    String[] args = commandLineArgs.split("\\s+");
+    ArgParser argparser = new ArgParser(args);
+  }
+
+  @Test(groups = "Functional", dataProvider = "argSubValsAndLinkedIds")
+  public void parseSubValsAndLinkedIdsTest(String commandLineArgs,
+          String linkedId, Arg a, String subvalKey, String value,
+          boolean trueOrFalse)
+  {
+    String[] args = commandLineArgs.split("\\s+");
+    ArgParser argparser = new ArgParser(args);
+    ArgValuesMap avm = argparser.getLinkedArgs(linkedId);
+    ArgValue av = avm.getArgValue(a);
+    SubVals sv = av.getSubVals();
+    String testString = null;
+    if (subvalKey.equals("GETINDEX"))
+    {
+      testString = String.valueOf(sv.getIndex());
+    }
+    else
+    {
+      testString = sv.get(subvalKey);
+    }
+    if (trueOrFalse)
+    {
+      Assert.assertEquals(testString, value);
+    }
+    else
+    {
+      Assert.assertNotEquals(testString, value);
+    }
+  }
+
+  @Test(
+    groups = "Functional",
+    dataProvider = "argAutoIndexAndSubstitutions")
+  public void parseAutoIndexAndSubstitutionsTest(String commandLineArgs,
+          String linkedId, Arg a, String filename)
+  {
+    // { "--append=filename0 --new --append=filename1", "JALVIEW:1",
+    // Arg.OPEN, "filename1" },
+    String[] args = commandLineArgs.split("\\s+");
+    ArgParser argparser = new ArgParser(args);
+    ArgValuesMap avm = argparser.getLinkedArgs(linkedId);
+    ArgValue av = avm.getArgValue(a);
+    Assert.assertEquals(av.getValue(), filename);
+  }
+
+  @Test(groups = "Functional", dataProvider = "argLines")
+  public void bootstrapArgsTest(String commandLineArgs, Arg a, String other)
+  {
+    String[] args = commandLineArgs.split("\\s+");
+    BootstrapArgs b = BootstrapArgs.getBootstrapArgs(args);
+
+    Assert.assertTrue(b.contains(a));
+    if (a == Arg.PROPS)
+    {
+      Properties bP = Cache.bootstrapProperties(b.get(Arg.PROPS));
+      Assert.assertNotNull(bP);
+      Assert.assertTrue(other.equals(bP.get(Cache.BOOTSTRAP_TEST)));
+      Assert.assertFalse(bP.contains("NOT" + Cache.BOOTSTRAP_TEST));
+    }
+    else if (a == Arg.ARGFILE)
+    {
+      List<String> filenames = b.getList(a);
+      boolean found = false;
+      for (String s : filenames)
+      {
+        File f = new File(s);
+        File fo = new File(other);
+        try
+        {
+          if (fo.getCanonicalPath().equals(f.getCanonicalPath()))
+          {
+            found = true;
+            break;
+          }
+        } catch (IOException e)
+        {
+        }
+      }
+      Assert.assertTrue(found,
+              "File '" + other + "' not found in shell expanded glob '"
+                      + commandLineArgs + "'");
+    }
+  }
+
+  @Test(groups = "Functional", dataProvider = "argFiles")
+  public void argFilesTest(String commandLineArgs, Arg a, String other)
+  {
+    String[] args = commandLineArgs.split("\\s+");
+    BootstrapArgs b = BootstrapArgs.getBootstrapArgs(args);
+
+    Assert.assertTrue(b.contains(a));
+    Assert.assertFalse(b.contains(Arg.APPEND));
+    if (a == Arg.PROPS)
+    {
+      Properties bP = Cache.bootstrapProperties(b.get(Arg.PROPS));
+      Assert.assertTrue("true".equals(bP.get(Cache.BOOTSTRAP_TEST)));
+    }
+  }
+
+  @DataProvider(name = "argLinesNotworking")
+  public Object[][] argLinesTest()
+  {
+    return new Object[][] {
+        // can't use this one yet as it doesn't get shell glob expanded by the
+        // test
+        { "--argfile test/jalview/bin/argparser/testfiles/argfile*.txt",
+            Arg.ARGFILE,
+            "test/jalview/bin/argparser/testfiles/argfile0.txt" }, };
+  }
+
+  @DataProvider(name = "argLines")
+  public Object[][] argLines()
+  {
+    return new Object[][] { {
+        "--append=test/jalview/bin/argparser/testfiles/test1.fa --props=test/jalview/bin/argparser/testfiles/testProps.jvprops",
+        Arg.PROPS, "true" },
+        { "--debug --append=test/jalview/bin/argparser/testfiles/test1.fa",
+            Arg.DEBUG, null },
+        { "--append=test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            Arg.HEADLESS, null },
+
+        { "--argfile test/jalview/bin/argparser/testfiles/argfile0.txt",
+            Arg.ARGFILE,
+            "test/jalview/bin/argparser/testfiles/argfile0.txt" },
+        // these next three are what a shell glob expansion would look like
+        { "--argfile test/jalview/bin/argparser/testfiles/argfile0.txt test/jalview/bin/argparser/testfiles/argfile1.txt test/jalview/bin/argparser/testfiles/argfile2.txt",
+            Arg.ARGFILE,
+            "test/jalview/bin/argparser/testfiles/argfile0.txt" },
+        { "--argfile test/jalview/bin/argparser/testfiles/argfile0.txt test/jalview/bin/argparser/testfiles/argfile1.txt test/jalview/bin/argparser/testfiles/argfile2.txt",
+            Arg.ARGFILE,
+            "test/jalview/bin/argparser/testfiles/argfile1.txt" },
+        { "--argfile test/jalview/bin/argparser/testfiles/argfile0.txt test/jalview/bin/argparser/testfiles/argfile1.txt test/jalview/bin/argparser/testfiles/argfile2.txt",
+            Arg.ARGFILE,
+            "test/jalview/bin/argparser/testfiles/argfile2.txt" },
+        { "--argfile=test/jalview/bin/argparser/testfiles/argfile*.txt",
+            Arg.ARGFILE,
+            "test/jalview/bin/argparser/testfiles/argfile0.txt" },
+        { "--argfile=test/jalview/bin/argparser/testfiles/argfile*.txt",
+            Arg.ARGFILE,
+            "test/jalview/bin/argparser/testfiles/argfile1.txt" },
+        { "--argfile=test/jalview/bin/argparser/testfiles/argfile*.txt",
+            Arg.ARGFILE,
+            "test/jalview/bin/argparser/testfiles/argfile2.txt" } };
+  }
+
+  @DataProvider(name = "argSubValsAndLinkedIds")
+  public Object[][] argSubValsAndLinkedIds()
+  {
+    return new Object[][] { {
+        "--debug --append=[hi]test/jalview/bin/argparser/testfiles/test1.fa",
+        "JALVIEW:0", Arg.APPEND, "hi", "true", true },
+        { "--append[linkedId1]=[new,hello=world,1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId1", Arg.APPEND, "new", "true", true },
+        { "--append[linkedId2]=[new,hello=world,1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId2", Arg.APPEND, "hello", "world", true },
+        { "--append[linkedId3]=[new,hello=world,1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId3", Arg.APPEND, "GETINDEX", "1", true },
+        { "--append[linkedId4]=[new,hello=world,1]test/jalview/bin/argparser/testfiles/test1.fa --append[linkedId5]=[notnew;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId5", Arg.APPEND, "new", "true", false },
+        { "--append[linkedId5]=[new,hello=worlddomination,1]test/jalview/bin/argparser/testfiles/test1.fa --append[linkedId2]=[new;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId5", Arg.APPEND, "hello", "world", false },
+        { "--append[linkedId6]=[new,hello=world,0]test/jalview/bin/argparser/testfiles/test1.fa --append[linkedId7]=[new;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId7", Arg.APPEND, "GETINDEX", "0", false }, };
+  }
+
+  @DataProvider(name = "argAutoIndexAndSubstitutions")
+  public Object[][] argAutoIndexAndSubstitutions()
+  {
+    return new Object[][] {
+        //
+        /*
+         */
+        { "--append=filename0 --append=filename1", "JALVIEW:0", Arg.APPEND,
+            "filename0" },
+        { "--append=filename0 --new --append=filename1", "JALVIEW:1",
+            Arg.APPEND, "filename1" },
+        { "--append=filename0 --new --new --append=filename2", "JALVIEW:0",
+            Arg.APPEND, "filename0" },
+        { "--append=filename0 --new --new --append=filename2", "JALVIEW:2",
+            Arg.APPEND, "filename2" },
+        { "--append[linkA-{n}]=filenameA0 --append[linkA-{++n}]=filenameA1",
+            "linkA-0", Arg.APPEND, "filenameA0" },
+        { "--append[linkB-{n}]=filenameB0 --append[linkB-{++n}]=filenameB1",
+            "linkB-1", Arg.APPEND, "filenameB1" },
+        { "--append[linkC-{n}]=filenameC0 --image[linkC-{n}]=outputC{n}.txt",
+            "linkC-0", Arg.IMAGE, "outputC{n}.txt" },
+        { "--append[linkD-{n}]=filenameD0 --substitutions --image[linkD-{n}]=outputD{n}.txt",
+            "linkD-0", Arg.IMAGE, "outputD0.txt" },
+        { "--append[linkE-{n}]=filenameE0 --substitutions --image[linkE-{n}]=output-E{n}.txt --nil[{++n}] --image[linkE-{n}]=outputE{n}.txt",
+            "linkE-0", Arg.IMAGE, "output-E0.txt" },
+        { "--append[linkF-{n}]=filenameF0 --substitutions --image[linkF-{n}]=output-F{n}.txt --nil[{++n}] --image[linkF-{n}]=outputF{n}.txt",
+            "linkF-1", Arg.IMAGE, "outputF1.txt" },
+        { "--append[linkG-{n}]=filenameG0 --substitutions --image[linkG-{n}]=output-G{n}.txt --nil[{++n}] --nosubstitutions --image[linkG-{n}]=outputG{n}.txt",
+            "linkG-1", Arg.IMAGE, "outputG{n}.txt" },
+        { "--append[linkH-{n}]=filenameH0 --substitutions --image[linkH-{n}]=output-H{n}.txt --nil[{++n}] --nosubstitutions --image[linkH-{n}]=outputH{n}.txt",
+            "linkH-0", Arg.IMAGE, "output-H0.txt" },
+        { "--open=filename0 --append=filename1", "JALVIEW:0", Arg.OPEN,
+            "filename0" },
+        { "--open=filename0 --new --append=filename1", "JALVIEW:1",
+            Arg.APPEND, "filename1" },
+        { "--open=filename0 --new --new --append=filename2", "JALVIEW:0",
+            Arg.OPEN, "filename0" },
+        { "--open=filename0 --new --new --append=filename2", "JALVIEW:2",
+            Arg.APPEND, "filename2" },
+        { "--open[linkA-{n}]=filenameA0 --append[linkA-{++n}]=filenameA1",
+            "linkA-0", Arg.OPEN, "filenameA0" },
+        { "--open[linkB-{n}]=filenameB0 --append[linkB-{++n}]=filenameB1",
+            "linkB-1", Arg.APPEND, "filenameB1" },
+        { "--open[linkC-{n}]=filenameC0 --image[linkC-{n}]=outputC{n}.txt",
+            "linkC-0", Arg.IMAGE, "outputC{n}.txt" },
+        { "--open[linkD-{n}]=filenameD0 --substitutions --image[linkD-{n}]=outputD{n}.txt",
+            "linkD-0", Arg.IMAGE, "outputD0.txt" },
+        { "--open[linkE-{n}]=filenameE0 --substitutions --image[linkE-{n}]=output-E{n}.txt --nil[{++n}] --image[linkE-{n}]=outputE{n}.txt",
+            "linkE-0", Arg.IMAGE, "output-E0.txt" },
+        { "--open[linkF-{n}]=filenameF0 --substitutions --image[linkF-{n}]=output-F{n}.txt --nil[{++n}] --image[linkF-{n}]=outputF{n}.txt",
+            "linkF-1", Arg.IMAGE, "outputF1.txt" },
+        { "--open[linkG-{n}]=filenameG0 --substitutions --image[linkG-{n}]=output-G{n}.txt --nil[{++n}] --nosubstitutions --image[linkG-{n}]=outputG{n}.txt",
+            "linkG-1", Arg.IMAGE, "outputG{n}.txt" },
+        { "--open[linkH-{n}]=filenameH0 --substitutions --image[linkH-{n}]=output-H{n}.txt --nil[{++n}] --nosubstitutions --image[linkH-{n}]=outputH{n}.txt",
+            "linkH-0", Arg.IMAGE, "output-H0.txt" },
+        /*
+         */
+
+        //
+    };
+  }
+
+  @DataProvider(name = "argFiles")
+  public Object[][] argFiles()
+  {
+    return new Object[][] { {
+        "--argfile=test/jalview/bin/argparser/testfiles/argfile0.txt --open=shouldntbeabootstrap",
+        Arg.ARGFILE, "test/jalview/bin/argfiles/testfiles/test1.fa" } };
+  }
+
+  @Test(groups = "Functional", dataProvider = "allLinkedIdsData")
+  public void allLinkedIdsTest(String commandLineArgs, Arg a,
+          String[] values, String[] nonvalues)
+  {
+    String[] args = commandLineArgs.split("\\s+");
+    ArgParser argparser = new ArgParser(args);
+
+    int num = values.length;
+    List<String> linkedIds = argparser.getLinkedIds();
+    Assert.assertEquals(linkedIds.size(), num,
+            "Wrong number of linkedIds: " + linkedIds.toString());
+    for (int i = 0; i < num; i++)
+    {
+      String value = values[i];
+      String linkedId = linkedIds.get(i);
+      ArgValuesMap avm = argparser.getLinkedArgs(linkedId);
+      if (value == null)
+      {
+        Assert.assertTrue(avm.containsArg(a),
+                "Arg value for " + a.argString()
+                        + " not applied correctly to linkedId '" + linkedId
+                        + "'");
+      }
+      else
+      {
+        ArgValues avs = avm.getArgValues(a);
+        ArgValue av = avs.getArgValue();
+        String v = av.getValue();
+        value = new File(value).getAbsolutePath();
+        Assert.assertEquals(v, value, "Arg value for " + a.argString()
+                + " not applied correctly to linkedId '" + linkedId + "'");
+      }
+    }
+
+  }
+
+  @DataProvider(name = "allLinkedIdsData")
+  public Object[][] allLinkedIdsData()
+  {
+    return new Object[][] {
+        //
+        /*
+        */
+        { "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --image={dirname}/{basename}.png --close",
+            Arg.CLOSE, new String[]
+            { null, null, null },
+            null },
+        { "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --output={dirname}/{basename}.stk --close",
+            Arg.OUTPUT, new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk", },
+            null },
+        { "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --image={dirname}/{basename}.png --close",
+            Arg.IMAGE, new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.png",
+                "test/jalview/bin/argparser/testfiles/test2.png",
+                "test/jalview/bin/argparser/testfiles/test3.png", },
+            null },
+        //
+    };
+  }
+}
diff --git a/test/jalview/bin/argparser/testfiles/argfile.autocounter b/test/jalview/bin/argparser/testfiles/argfile.autocounter
new file mode 100644 (file)
index 0000000..58445a5
--- /dev/null
@@ -0,0 +1,7 @@
+--open[{++n}]=test/jalview/bin/argparser/testfiles/test1.fa
+--colour[{n}]=gecos-flower
+--open[{++n}]=test/jalview/bin/argparser/testfiles/test2.fa
+--colour[{n}]=zappo
+--open[{++n}]=test/jalview/bin/argparser/testfiles/test1.fa
+--append[{n}]=test/jalview/bin/argparser/testfiles/test2.fa
+--colour[{n}]=taylor
diff --git a/test/jalview/bin/argparser/testfiles/argfile0.txt b/test/jalview/bin/argparser/testfiles/argfile0.txt
new file mode 100644 (file)
index 0000000..e8a8cdc
--- /dev/null
@@ -0,0 +1,2 @@
+--open=test/jalview/bin/argparser/testfiles/test1.fa
+--append=test/jalview/bin/argparser/testfiles/test3.fa
diff --git a/test/jalview/bin/argparser/testfiles/argfile1.txt b/test/jalview/bin/argparser/testfiles/argfile1.txt
new file mode 100644 (file)
index 0000000..e2d0fa4
--- /dev/null
@@ -0,0 +1,4 @@
+--open[all]=test/jalview/bin/argparser/testfiles/test1.fa
+--append[all]=test/jalview/bin/argparser/testfiles/test2.fa
+--open[1]=test/jalview/bin/argparser/testfiles/test1.fa
+--open[2]=test/jalview/bin/argparser/testfiles/test2.fa
diff --git a/test/jalview/bin/argparser/testfiles/argfile2.txt b/test/jalview/bin/argparser/testfiles/argfile2.txt
new file mode 100644 (file)
index 0000000..85ce3c2
--- /dev/null
@@ -0,0 +1 @@
+--open=test/jalview/bin/argparser/testfiles/test2.fa
diff --git a/test/jalview/bin/argparser/testfiles/dir1/argfile.txt b/test/jalview/bin/argparser/testfiles/dir1/argfile.txt
new file mode 100644 (file)
index 0000000..0e9ae50
--- /dev/null
@@ -0,0 +1,6 @@
+--substitutions
+--new
+--append={argfiledirname}/*.fa
+--colour=gecos-flower
+--image={argfiledirname}/{basename}.png
+--close
diff --git a/test/jalview/bin/argparser/testfiles/dir1/test1.fa b/test/jalview/bin/argparser/testfiles/dir1/test1.fa
new file mode 100644 (file)
index 0000000..c9e687f
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST1
+AAAA
diff --git a/test/jalview/bin/argparser/testfiles/dir1/test2.fa b/test/jalview/bin/argparser/testfiles/dir1/test2.fa
new file mode 100644 (file)
index 0000000..fbd15c3
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST2
+LLLL
diff --git a/test/jalview/bin/argparser/testfiles/dir2/argfile.txt b/test/jalview/bin/argparser/testfiles/dir2/argfile.txt
new file mode 100644 (file)
index 0000000..0e9ae50
--- /dev/null
@@ -0,0 +1,6 @@
+--substitutions
+--new
+--append={argfiledirname}/*.fa
+--colour=gecos-flower
+--image={argfiledirname}/{basename}.png
+--close
diff --git a/test/jalview/bin/argparser/testfiles/dir2/test1.fa b/test/jalview/bin/argparser/testfiles/dir2/test1.fa
new file mode 100644 (file)
index 0000000..c9e687f
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST1
+AAAA
diff --git a/test/jalview/bin/argparser/testfiles/dir2/test2.fa b/test/jalview/bin/argparser/testfiles/dir2/test2.fa
new file mode 100644 (file)
index 0000000..fbd15c3
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST2
+LLLL
diff --git a/test/jalview/bin/argparser/testfiles/dir2/test3.fa b/test/jalview/bin/argparser/testfiles/dir2/test3.fa
new file mode 100644 (file)
index 0000000..f9503d4
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST3
+AAARG
diff --git a/test/jalview/bin/argparser/testfiles/dir3/subdir/subdirfile.txt b/test/jalview/bin/argparser/testfiles/dir3/subdir/subdirfile.txt
new file mode 100644 (file)
index 0000000..0e9ae50
--- /dev/null
@@ -0,0 +1,6 @@
+--substitutions
+--new
+--append={argfiledirname}/*.fa
+--colour=gecos-flower
+--image={argfiledirname}/{basename}.png
+--close
diff --git a/test/jalview/bin/argparser/testfiles/dir3/subdir/test0.fa b/test/jalview/bin/argparser/testfiles/dir3/subdir/test0.fa
new file mode 100644 (file)
index 0000000..c9fae78
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST0
+AAAA
diff --git a/test/jalview/bin/argparser/testfiles/dir3/subdir/test1.fa b/test/jalview/bin/argparser/testfiles/dir3/subdir/test1.fa
new file mode 100644 (file)
index 0000000..c9e687f
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST1
+AAAA
diff --git a/test/jalview/bin/argparser/testfiles/dir3/subdir/test2.fa b/test/jalview/bin/argparser/testfiles/dir3/subdir/test2.fa
new file mode 100644 (file)
index 0000000..fbd15c3
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST2
+LLLL
diff --git a/test/jalview/bin/argparser/testfiles/dir3/subdir/test3.fa b/test/jalview/bin/argparser/testfiles/dir3/subdir/test3.fa
new file mode 100644 (file)
index 0000000..f9503d4
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST3
+AAARG
diff --git a/test/jalview/bin/argparser/testfiles/test1.fa b/test/jalview/bin/argparser/testfiles/test1.fa
new file mode 100644 (file)
index 0000000..c9e687f
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST1
+AAAA
diff --git a/test/jalview/bin/argparser/testfiles/test2.fa b/test/jalview/bin/argparser/testfiles/test2.fa
new file mode 100644 (file)
index 0000000..fbd15c3
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST2
+LLLL
diff --git a/test/jalview/bin/argparser/testfiles/test3.fa b/test/jalview/bin/argparser/testfiles/test3.fa
new file mode 100644 (file)
index 0000000..f9503d4
--- /dev/null
@@ -0,0 +1,2 @@
+>TEST3
+AAARG
diff --git a/test/jalview/bin/argparser/testfiles/testProps.jvprops b/test/jalview/bin/argparser/testfiles/testProps.jvprops
new file mode 100644 (file)
index 0000000..8008953
--- /dev/null
@@ -0,0 +1,87 @@
+#---JalviewX Properties File---
+#Fri Apr 25 09:54:25 BST 2014
+SCREEN_Y=768
+SCREEN_X=936
+SHOW_WSDISCOVERY_ERRORS=true
+LATEST_VERSION=2.8.0b1
+SHOW_CONSERVATION=true
+JALVIEW_RSS_WINDOW_SCREEN_WIDTH=550
+JAVA_CONSOLE_SCREEN_WIDTH=450
+LAST_DIRECTORY=/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples
+ID_ITALICS=true
+SORT_ALIGNMENT=No sort
+SHOW_IDENTITY=true
+WSMENU_BYHOST=false
+SEQUENCE_LINKS=EMBL-EBI Search|http\://www.ebi.ac.uk/ebisearch/search.ebi?db\=allebi&query\=$SEQUENCE_ID$
+SHOW_FULLSCREEN=false
+RECENT_URL=http\://www.jalview.org/examples/exampleFile_2_7.jar
+FONT_NAME=SansSerif
+BLC_JVSUFFIX=true
+VERSION_CHECK=false
+YEAR=2011
+SHOW_DBREFS_TOOLTIP=true
+MSF_JVSUFFIX=true
+SCREENGEOMETRY_HEIGHT=1600
+JAVA_CONSOLE_SCREEN_Y=475
+JAVA_CONSOLE_SCREEN_X=830
+PFAM_JVSUFFIX=true
+PIR_JVSUFFIX=true
+STARTUP_FILE=http\://www.jalview.org/examples/exampleFile_2_3.jar
+JAVA_CONSOLE_SCREEN_HEIGHT=162
+PIR_MODELLER=false
+GAP_SYMBOL=-
+SHOW_QUALITY=true
+SHOW_GROUP_CONSERVATION=false
+SHOW_JWS2_SERVICES=true
+SHOW_NPFEATS_TOOLTIP=true
+FONT_STYLE=plain
+ANTI_ALIAS=false
+SORT_BY_TREE=false
+RSBS_SERVICES=|Multi-Harmony|Analysis|Sequence Harmony and Multi-Relief (Brandt et al. 2010)|hseparable,gapCharacter\='-',returns\='ANNOTATION'|?tool\=jalview|http\://zeus.few.vu.nl/programs/shmrwww/index.php?tool\=jalview&groups\=$PARTITION\:min\='2',minsize\='2',sep\=' '$&ali_file\=$ALIGNMENT\:format\='FASTA',writeasfile$
+AUTHORFNAMES=Jim Procter, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton
+JALVIEW_RSS_WINDOW_SCREEN_HEIGHT=328
+SHOW_GROUP_CONSENSUS=false
+SHOW_CONSENSUS_HISTOGRAM=true
+SHOW_OVERVIEW=false
+AUTHORS=J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
+FIGURE_AUTOIDWIDTH=false
+SCREEN_WIDTH=900
+ANNOTATIONCOLOUR_MIN=ffc800
+SHOW_STARTUP_FILE=false
+RECENT_FILE=examples/uniref50.fa\t/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples/RF00031_folded.stk\t/Volumes/Data/Users/jimp/bs_ig_mult.out
+DEFAULT_FILE_FORMAT=FASTA
+SHOW_JAVA_CONSOLE=false
+VERSION=2.8b1
+FIGURE_USERIDWIDTH=
+WSMENU_BYTYPE=false
+DEFAULT_COLOUR=None
+NOQUESTIONNAIRES=true
+JALVIEW_NEWS_RSS_LASTMODIFIED=Apr 23, 2014 2\:53\:26 PM
+BUILD_DATE=01 November 2013
+PILEUP_JVSUFFIX=true
+SHOW_CONSENSUS_LOGO=false
+SCREENGEOMETRY_WIDTH=2560
+SHOW_ANNOTATIONS=true
+JALVIEW_RSS_WINDOW_SCREEN_Y=0
+USAGESTATS=false
+JALVIEW_RSS_WINDOW_SCREEN_X=0
+SHOW_UNCONSERVED=false
+SHOW_JVSUFFIX=true
+DAS_LOCAL_SOURCE=
+SCREEN_HEIGHT=650
+ANNOTATIONCOLOUR_MAX=ff0000
+AUTO_CALC_CONSENSUS=true
+FASTA_JVSUFFIX=true
+DAS_ACTIVE_SOURCE=uniprot\t
+JWS2HOSTURLS=http\://www.compbio.dundee.ac.uk/jabaws
+PAD_GAPS=false
+CLUSTAL_JVSUFFIX=true
+SHOW_ENFIN_SERVICES=true
+FONT_SIZE=10
+RIGHT_ALIGN_IDS=false
+USE_PROXY=false
+WRAP_ALIGNMENT=false
+#DAS_REGISTRY_URL=http\://www.dasregistry.org/das/ # retired 01/05/2015
+DAS_REGISTRY_URL=http\://www.ebi.ac.uk/das-srv/registry/das/
+BOOTSTRAP_TEST=true
+NOTBOOTSTRAP_TEST=true
diff --git a/test/jalview/bin/commandsTest.jvprops b/test/jalview/bin/commandsTest.jvprops
new file mode 100644 (file)
index 0000000..66e2780
--- /dev/null
@@ -0,0 +1,90 @@
+#---JalviewX Properties File---
+#Fri Apr 25 09:54:25 BST 2014
+SCREEN_Y=768
+SCREEN_X=936
+SHOW_WSDISCOVERY_ERRORS=true
+LATEST_VERSION=2.8.0b1
+JALVIEW_RSS_WINDOW_SCREEN_WIDTH=550
+JAVA_CONSOLE_SCREEN_WIDTH=450
+LAST_DIRECTORY=/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples
+ID_ITALICS=true
+SORT_ALIGNMENT=No sort
+WSMENU_BYHOST=false
+SEQUENCE_LINKS=EMBL-EBI Search|http\://www.ebi.ac.uk/ebisearch/search.ebi?db\=allebi&query\=$SEQUENCE_ID$
+SHOW_FULLSCREEN=false
+RECENT_URL=http\://www.jalview.org/examples/exampleFile_2_7.jar
+FONT_NAME=SansSerif
+BLC_JVSUFFIX=true
+VERSION_CHECK=false
+YEAR=2011
+SHOW_DBREFS_TOOLTIP=true
+MSF_JVSUFFIX=true
+SCREENGEOMETRY_HEIGHT=1600
+JAVA_CONSOLE_SCREEN_Y=475
+JAVA_CONSOLE_SCREEN_X=830
+PFAM_JVSUFFIX=true
+PIR_JVSUFFIX=true
+STARTUP_FILE=http\://www.jalview.org/examples/exampleFile_2_3.jar
+JAVA_CONSOLE_SCREEN_HEIGHT=162
+PIR_MODELLER=false
+GAP_SYMBOL=-
+SHOW_QUALITY=true
+SHOW_GROUP_CONSERVATION=false
+SHOW_JWS2_SERVICES=true
+SHOW_NPFEATS_TOOLTIP=true
+FONT_STYLE=plain
+ANTI_ALIAS=false
+SORT_BY_TREE=false
+RSBS_SERVICES=|Multi-Harmony|Analysis|Sequence Harmony and Multi-Relief (Brandt et al. 2010)|hseparable,gapCharacter\='-',returns\='ANNOTATION'|?tool\=jalview|http\://zeus.few.vu.nl/programs/shmrwww/index.php?tool\=jalview&groups\=$PARTITION\:min\='2',minsize\='2',sep\=' '$&ali_file\=$ALIGNMENT\:format\='FASTA',writeasfile$
+AUTHORFNAMES=Jim Procter, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton
+JALVIEW_RSS_WINDOW_SCREEN_HEIGHT=328
+SHOW_GROUP_CONSENSUS=false
+SHOW_CONSENSUS_HISTOGRAM=true
+SHOW_OVERVIEW=false
+AUTHORS=J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
+FIGURE_AUTOIDWIDTH=false
+SCREEN_WIDTH=900
+ANNOTATIONCOLOUR_MIN=ffc800
+SHOW_STARTUP_FILE=false
+RECENT_FILE=examples/uniref50.fa\t/Volumes/Data/Users/jimp/Documents/testing/Jalview/examples/RF00031_folded.stk\t/Volumes/Data/Users/jimp/bs_ig_mult.out
+DEFAULT_FILE_FORMAT=FASTA
+SHOW_JAVA_CONSOLE=false
+VERSION=2.8b1
+FIGURE_USERIDWIDTH=
+WSMENU_BYTYPE=false
+DEFAULT_COLOUR=None
+NOQUESTIONNAIRES=true
+JALVIEW_NEWS_RSS_LASTMODIFIED=Apr 23, 2014 2\:53\:26 PM
+BUILD_DATE=01 November 2013
+PILEUP_JVSUFFIX=true
+SHOW_CONSENSUS_LOGO=false
+SCREENGEOMETRY_WIDTH=2560
+JALVIEW_RSS_WINDOW_SCREEN_Y=0
+USAGESTATS=false
+JALVIEW_RSS_WINDOW_SCREEN_X=0
+SHOW_UNCONSERVED=false
+SHOW_JVSUFFIX=true
+DAS_LOCAL_SOURCE=
+SCREEN_HEIGHT=650
+ANNOTATIONCOLOUR_MAX=ff0000
+AUTO_CALC_CONSENSUS=true
+FASTA_JVSUFFIX=true
+DAS_ACTIVE_SOURCE=uniprot\t
+JWS2HOSTURLS=http\://www.compbio.dundee.ac.uk/jabaws
+PAD_GAPS=false
+CLUSTAL_JVSUFFIX=true
+SHOW_ENFIN_SERVICES=true
+FONT_SIZE=10
+RIGHT_ALIGN_IDS=false
+USE_PROXY=false
+WRAP_ALIGNMENT=false
+#DAS_REGISTRY_URL=http\://www.dasregistry.org/das/ # retired 01/05/2015
+DAS_REGISTRY_URL=http\://www.ebi.ac.uk/das-srv/registry/das/
+SPLASH=false
+ADD_SS_ANN=true
+ADD_TEMPFACT_ANN=true
+SHOW_ANNOTATIONS=true
+SHOW_CONSERVATION=true
+SHOW_IDENTITY=true
+SHOW_OCCUPANCY=true
+STRUCT_FROM_PDB=true
diff --git a/test/jalview/bin/commandsTest2.argfile1 b/test/jalview/bin/commandsTest2.argfile1
new file mode 100644 (file)
index 0000000..ea3a1be
--- /dev/null
@@ -0,0 +1,22 @@
+--substitutions
+--append=examples/test_fab41.result/sample.a2m
+--showannotations
+--showssannotations
+--colour=gecos-flower
+--structure=[viewer=jmol,tempfac=plddt,paematrix={dirname}/test_fab41_unrelaxed_rank_1_model_3_scores.json]{dirname}/test_fab41_unrelaxed_rank_1_model_3.pdb
+--structure={dirname}/test_fab41_unrelaxed_rank_2_model_4.pdb
+--structureviewer=jmol
+--paematrix={dirname}/test_fab41_unrelaxed_rank_2_model_4_scores.json
+--tempfac=plddt
+--structure={dirname}/test_fab41_unrelaxed_rank_3_model_2.pdb
+--structureviewer=jmol
+--paematrix={dirname}/test_fab41_unrelaxed_rank_3_model_2_scores.json
+--tempfac=plddt
+--structure=[viewer=none]{dirname}/test_fab41_unrelaxed_rank_4_model_5.pdb
+--paematrix={dirname}/test_fab41_unrelaxed_rank_4_model_5_scores.json
+--tempfac=plddt
+--structure={dirname}/test_fab41_unrelaxed_rank_5_model_1.pdb
+--structureviewer=none
+--tempfac=plddt
+--paematrix={dirname}/test_fab41_unrelaxed_rank_5_model_1_scores.json
+#--headless
diff --git a/test/jalview/bin/commandsTest2.argfile2 b/test/jalview/bin/commandsTest2.argfile2
new file mode 100644 (file)
index 0000000..1afc69c
--- /dev/null
@@ -0,0 +1,26 @@
+--debug
+--nonews
+--nosplash
+--substitutions
+--append=examples/test_fab41.result/sample.a2m
+--noshowannotations
+--noshowssannotations
+--colour=gecos-flower
+--structure=[tempfac=plddt,paematrix={dirname}/test_fab41_unrelaxed_rank_1_model_3_scores.json]{dirname}/test_fab41_unrelaxed_rank_1_model_3.pdb
+--structureviewer=none
+--structure={dirname}/test_fab41_unrelaxed_rank_2_model_4.pdb
+--structureviewer=jmol
+--paematrix={dirname}/test_fab41_unrelaxed_rank_2_model_4_scores.json
+--tempfac=plddt
+--structure={dirname}/test_fab41_unrelaxed_rank_3_model_2.pdb
+--structureviewer=jmol
+--paematrix={dirname}/test_fab41_unrelaxed_rank_3_model_2_scores.json
+--tempfac=plddt
+--structure=[structureviewer=none]{dirname}/test_fab41_unrelaxed_rank_4_model_5.pdb
+--paematrix={dirname}/test_fab41_unrelaxed_rank_4_model_5_scores.json
+--tempfac=plddt
+--structure={dirname}/test_fab41_unrelaxed_rank_5_model_1.pdb
+--structureviewer=none
+--tempfac=plddt
+--paematrix={dirname}/test_fab41_unrelaxed_rank_5_model_1_scores.json
+#--headless
diff --git a/test/jalview/bin/commandsTest2.jvprops1 b/test/jalview/bin/commandsTest2.jvprops1
new file mode 100644 (file)
index 0000000..bd510e7
--- /dev/null
@@ -0,0 +1,4 @@
+SHOW_STARTUP_FILE=false
+SPLASH=false
+STRUCT_FROM_PDB=true
+ADD_TEMPFACT_ANN=true
diff --git a/test/jalview/bin/commandsTest2.jvprops2 b/test/jalview/bin/commandsTest2.jvprops2
new file mode 100644 (file)
index 0000000..576269a
--- /dev/null
@@ -0,0 +1,4 @@
+SHOW_STARTUP_FILE=false
+SPLASH=false
+STRUCT_FROM_PDB=false
+ADD_TEMPFACT_ANN=true
index e451ed2..9dec19b 100644 (file)
@@ -24,6 +24,12 @@ import static org.junit.Assert.assertNotNull;
 import static org.testng.Assert.assertEquals;
 import static org.testng.AssertJUnit.assertTrue;
 
+import java.lang.reflect.InvocationTargetException;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
@@ -31,6 +37,7 @@ import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
 import jalview.gui.JvOptionPane;
 import jalview.gui.Preferences;
 import jalview.gui.StructureViewer;
@@ -39,12 +46,6 @@ import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
 import jalview.io.FileLoader;
 
-import java.lang.reflect.InvocationTargetException;
-
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
 @Test(singleThreaded = true)
 public class JmolViewerTest
 {
@@ -74,7 +75,8 @@ public class JmolViewerTest
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" })
index 7f9aa9b..c8e30a7 100644 (file)
@@ -100,7 +100,8 @@ public class JalviewChimeraView
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @AfterMethod(alwaysRun = true)
index 2a768ff..ab2a1d0 100644 (file)
@@ -75,7 +75,8 @@ public class AlignFrameTest
   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
   }
 
   /**
index f0b3aef..1b988ec 100644 (file)
@@ -29,9 +29,13 @@ import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
 
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import jalview.api.FeatureColourI;
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.FeatureMatcher;
@@ -46,6 +50,23 @@ import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 
 public class FeatureSettingsTest
 {
+  @BeforeClass(alwaysRun = true)
+  public static void setUpBeforeClass() throws Exception
+  {
+    /*
+     * use read-only test properties file
+     */
+    Cache.loadProperties("test/jalview/io/testProps.jvprops");
+    Jalview.main(new String[] { "-nonews" });
+  }
+
+  @AfterMethod(alwaysRun = true)
+  public void tearDown()
+  {
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
+  }
+
   /**
    * Test a roundtrip of save and reload of feature colours and filters as XML
    * 
index 81e37d8..a9eca49 100644 (file)
@@ -137,7 +137,8 @@ public class FreeUpMemoryTest
 
     doStuffInJalview(f);
 
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
 
     checkUsedMemory(MAX_RESIDUAL_HEAP);
   }
index b257088..42df90e 100644 (file)
@@ -66,9 +66,12 @@ public class QuitHandlerTest
     // reset mock response
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
     // close desktop windows/frames
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
     // reset debug delay
     Jalview2XML.setDebugDelaySave(20);
+    // load normal testprops
+    Cache.loadProperties("test/jalview/testProps.jvprops");
   }
 
   @BeforeMethod(alwaysRun = true)
@@ -79,7 +82,8 @@ public class QuitHandlerTest
     // reset mock response
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
     // close desktop windows/frames
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
     // reset debug delay
     Cache.setProperty("DEBUG_DELAY_SAVE", "false");
     Jalview2XML.setDebugDelaySave(3);
@@ -126,7 +130,6 @@ public class QuitHandlerTest
     Assert.assertTrue(end - start < 500,
             "Quit-with-no-save-needed took too long (" + (end - start)
                     + "ms)");
-    Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" }, singleThreaded = true, priority = 10)
@@ -154,8 +157,6 @@ public class QuitHandlerTest
     Assert.assertEquals(response, QResponse.QUIT);
     Assert.assertTrue(end - start > 2900,
             "Quit-whilst-saving was too short (" + (end - start) + "ms)");
-
-    Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" }, singleThreaded = true, priority = 9)
@@ -182,7 +183,6 @@ public class QuitHandlerTest
 
     // if not saved this would be CANCEL_QUIT
     Assert.assertEquals(response, QResponse.QUIT);
-    Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" }, singleThreaded = true, priority = 9)
@@ -207,7 +207,6 @@ public class QuitHandlerTest
 
     // if not saved this would be CANCEL_QUIT
     Assert.assertEquals(response, QResponse.QUIT);
-    Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" }, singleThreaded = true, priority = 1)
@@ -227,7 +226,6 @@ public class QuitHandlerTest
     QResponse response = QuitHandler.getQuitResponse(true);
 
     Assert.assertEquals(response, QResponse.CANCEL_QUIT);
-    Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" }, singleThreaded = true, priority = 1)
@@ -255,7 +253,6 @@ public class QuitHandlerTest
     QResponse response = QuitHandler.getQuitResponse(false);
 
     Assert.assertEquals(response, QResponse.QUIT);
-    Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" }, singleThreaded = true, priority = 11)
@@ -298,7 +295,6 @@ public class QuitHandlerTest
             "Force-Quit-whilst-saving was too long (" + (end - start)
                     + "ms)");
 
-    Desktop.instance.closeAll_actionPerformed(null);
   }
 
 }
index f905ef2..701431b 100644 (file)
@@ -250,7 +250,8 @@ public class SeqPanelTest
   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = "Functional")
@@ -920,7 +921,7 @@ public class SeqPanelTest
     }
     assertEquals(dna.length(), 51200);
     AlignFrame alignFrame = new FileLoader()
-            .LoadFileWaitTillLoaded("dna "+dna, DataSourceType.PASTE);
+            .LoadFileWaitTillLoaded("dna " + dna, DataSourceType.PASTE);
     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
     AlignViewport av = alignFrame.getViewport();
     av.setScaleAboveWrapped(true);
index b74baf1..dd5cd4c 100644 (file)
@@ -24,15 +24,23 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.AssertJUnit.assertNotNull;
 import static org.testng.AssertJUnit.assertTrue;
 
+import java.io.File;
 import java.util.Collection;
+import java.util.List;
 import java.util.Vector;
 
 import org.junit.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import jalview.api.AlignViewportI;
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Sequence;
@@ -43,8 +51,15 @@ import jalview.fts.service.pdb.PDBFTSRestClient;
 import jalview.fts.service.pdb.PDBFTSRestClientTest;
 import jalview.fts.service.threedbeacons.TDBeaconsFTSRestClient;
 import jalview.fts.threedbeacons.TDBeaconsFTSRestClientTest;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.gui.structurechooser.PDBStructureChooserQuerySource;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormatException;
+import jalview.io.FileFormatI;
+import jalview.io.FileLoader;
+import jalview.io.IdentifyFile;
 import jalview.jbgui.FilterOption;
+import jalview.structure.StructureImportSettings.TFType;
 import junit.extensions.PA;
 
 @Test(singleThreaded = true)
@@ -259,4 +274,147 @@ public class StructureChooserTest
     assertEquals("abcde12345afg",
             PDBStructureChooserQuerySource.sanitizeSeqName(name));
   }
+
+  @Test(groups = { "Functional" }, dataProvider = "openStructureFileParams")
+  public void openStructureFileForSequenceTest(String alfile, String seqid,
+          String sFilename, TFType tft, String paeFilename,
+          boolean showRefAnnotations, boolean doXferSettings,
+          ViewerType viewerType, int seqNum, int annNum, int viewerNum,
+          String propsFile)
+  {
+    Cache.loadProperties(
+            propsFile == null ? "test/jalview/io/testProps.jvprops"
+                    : propsFile);
+
+    Jalview.main(
+            propsFile == null ? null : new String[]
+            { "--props", propsFile });
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.OK_OPTION);
+
+    FileLoader fileLoader = new FileLoader(true);
+    FileFormatI format = null;
+    File alFile = new File(alfile);
+    try
+    {
+      format = new IdentifyFile().identify(alFile, DataSourceType.FILE);
+    } catch (FileFormatException e1)
+    {
+      Assert.fail(
+              "Unknown file format for '" + alFile.getAbsolutePath() + "'");
+    }
+
+    AlignFrame af = fileLoader.LoadFileWaitTillLoaded(alFile,
+            DataSourceType.FILE, format);
+    AlignmentPanel ap = af.alignPanel;
+    Assert.assertNotNull("No alignPanel", ap);
+
+    AlignmentI al = ap.getAlignment();
+    Assert.assertNotNull(al);
+
+    SequenceI seq = al.findName(seqid);
+    Assert.assertNotNull("Sequence '" + seqid + "' not found in alignment",
+            seq);
+
+    StructureChooser.openStructureFileForSequence(null, null, ap, seq,
+            false, sFilename, tft, paeFilename, false, showRefAnnotations,
+            doXferSettings, viewerType);
+
+    List<SequenceI> seqs = al.getSequences();
+    Assert.assertNotNull(seqs);
+
+    Assert.assertEquals("Wrong number of sequences", seqNum, seqs.size());
+
+    AlignViewportI av = ap.getAlignViewport();
+    Assert.assertNotNull(av);
+
+    AlignmentAnnotation[] aas = al.getAlignmentAnnotation();
+    int visibleAnn = 0;
+    for (AlignmentAnnotation aa : aas)
+    {
+      if (aa.visible)
+        visibleAnn++;
+    }
+    Assert.assertEquals("Wrong number of viewed annotations", annNum,
+            visibleAnn);
+
+    if (viewerNum > -1)
+    {
+      try
+      {
+        Thread.sleep(100);
+      } catch (InterruptedException e)
+      {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+      List<StructureViewerBase> openViewers = Desktop.instance
+              .getStructureViewers(ap, null);
+      Assert.assertNotNull(openViewers);
+      int count = 0;
+      for (StructureViewerBase svb : openViewers)
+      {
+        if (svb.isVisible())
+          count++;
+      }
+      Assert.assertEquals("Wrong number of structure viewers opened",
+              viewerNum, count);
+
+    }
+
+    if (af != null)
+    {
+      af.setVisible(false);
+      af.dispose();
+    }
+  }
+
+  @DataProvider(name = "openStructureFileParams")
+  public Object[][] openStructureFileParams()
+  {
+    /*
+        String alFile,
+        String seqid,
+        String structureFilename,
+        TFType tft,
+        String paeFilename,
+        boolean showRefAnnotations,
+        boolean doXferSettings, // false for Commands
+        ViewerType viewerType,
+        int seqNum,
+        int annNum,
+        int viewerNum,
+        String propsFile
+     */
+    return new Object[][] {
+        /*
+        */
+        { "examples/uniref50.fa", "FER1_SPIOL",
+            "examples/AlphaFold/AF-P00221-F1-model_v4.cif", TFType.DEFAULT,
+            "examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json",
+            true, false, null, 15, 7, 0, null },
+        { "examples/uniref50.fa", "FER1_SPIOL",
+            "examples/AlphaFold/AF-P00221-F1-model_v4.cif", TFType.PLDDT,
+            "examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json",
+            true, false, null, 15, 7, 0, null },
+        { "examples/uniref50.fa", "FER1_SPIOL",
+            "examples/AlphaFold/AF-P00221-F1-model_v4.cif", TFType.PLDDT,
+            "examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json",
+            false, false, null, 15, 4, 0, null },
+        { "examples/uniref50.fa", "FER1_SPIOL",
+            "examples/AlphaFold/AF-P00221-F1-model_v4.cif", TFType.DEFAULT,
+            "examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json",
+            true, false, ViewerType.JMOL, 15, 7, 1, null },
+        { "examples/uniref50.fa", "FER1_SPIOL",
+            "examples/AlphaFold/AF-P00221-F1-model_v4.cif", TFType.PLDDT,
+            "examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json",
+            true, false, ViewerType.JMOL, 15, 7, 1, null },
+        { "examples/uniref50.fa", "FER1_SPIOL",
+            "examples/AlphaFold/AF-P00221-F1-model_v4.cif", TFType.PLDDT,
+            "examples/AlphaFold/AF-P00221-F1-predicted_aligned_error_v4.json",
+            false, false, ViewerType.JMOL, 15, 4, 1, null }, };
+  }
+
 }
index 689bd59..1b62ce3 100644 (file)
@@ -24,6 +24,15 @@ import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertNotNull;
 import static org.testng.AssertJUnit.assertTrue;
 
+import java.io.File;
+import java.util.List;
+
+import org.junit.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
@@ -32,19 +41,11 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.SequenceFeatures;
 import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
 import jalview.gui.JvOptionPane;
 import jalview.structure.StructureImportSettings;
 import jalview.structure.StructureImportSettings.StructureParser;
 
-import java.io.File;
-import java.util.List;
-
-import org.junit.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
 public class AnnotatedPDBFileInputTest
 {
 
@@ -207,8 +208,8 @@ public class AnnotatedPDBFileInputTest
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
-
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" })
index a356621..01a9ab0 100644 (file)
@@ -34,6 +34,7 @@ import java.util.TreeMap;
 
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -44,6 +45,7 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 
+@Test(singleThreaded = true)
 public class BackupFilesTest
 {
   @BeforeClass(alwaysRun = true)
@@ -104,7 +106,8 @@ public class BackupFilesTest
 
     // check no backup files
     File[] backupFiles = getBackupFiles();
-    Assert.assertTrue(backupFiles.length == 0);
+    Assert.assertEquals(backupFiles.length, 0, "Number of backup files is "
+            + backupFiles.length + ", not " + 0);
   }
 
   // save with no numbers in the backup file names
@@ -326,6 +329,7 @@ public class BackupFilesTest
   }
 
   // this deletes the newFile (if it exists) and any saved backup file for it
+  @AfterTest(alwaysRun = true)
   @AfterClass(alwaysRun = true)
   private void cleanupTmpFiles()
   {
@@ -341,6 +345,7 @@ public class BackupFilesTest
       newfile.delete();
     }
     File[] tmpFiles = getBackupFiles(file, mysuffix, mydigits);
+    boolean a = true;
     for (int i = 0; i < tmpFiles.length; i++)
     {
       if (actuallyDeleteTmpFiles)
index 3ca6ed8..0189ef2 100644 (file)
@@ -24,20 +24,6 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
-import jalview.analysis.CrossRef;
-import jalview.api.AlignmentViewPanel;
-import jalview.datamodel.AlignedCodonFrame;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.AlignmentTest;
-import jalview.datamodel.SequenceI;
-import jalview.gui.AlignFrame;
-import jalview.gui.CrossRefAction;
-import jalview.gui.Desktop;
-import jalview.gui.JvOptionPane;
-import jalview.gui.SequenceFetcher;
-import jalview.project.Jalview2XML;
-import jalview.util.DBRefUtils;
-
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -51,6 +37,19 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import jalview.analysis.CrossRef;
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.AlignedCodonFrame;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentTest;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.CrossRefAction;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.SequenceFetcher;
+import jalview.project.Jalview2XML;
+import jalview.util.DBRefUtils;
 import junit.extensions.PA;
 
 @Test(singleThreaded = true)
@@ -211,7 +210,8 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
       }
       else
       {
-        Desktop.instance.closeAll_actionPerformed(null);
+        if (Desktop.instance != null)
+          Desktop.instance.closeAll_actionPerformed(null);
         // recover stored project
         af = new FileLoader(false).LoadFileWaitTillLoaded(
                 savedProjects.get(first).toString(), DataSourceType.FILE);
@@ -278,7 +278,8 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
           }
           else
           {
-            Desktop.instance.closeAll_actionPerformed(null);
+            if (Desktop.instance != null)
+              Desktop.instance.closeAll_actionPerformed(null);
             pass3 = 0;
             // recover stored project
             File storedProject = savedProjects.get(nextxref);
@@ -389,7 +390,8 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
                 }
                 else
                 {
-                  Desktop.instance.closeAll_actionPerformed(null);
+                  if (Desktop.instance != null)
+                    Desktop.instance.closeAll_actionPerformed(null);
                   // recover stored project
                   File storedProject = savedProjects.get(nextnextxref);
                   if (storedProject == null)
@@ -416,7 +418,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
                     cra_views2.add(af2.getViewport().getAlignPanel());
                     cra_views2.add(((jalview.gui.AlignViewport) af2
                             .getViewport().getCodingComplement())
-                                    .getAlignPanel());
+                            .getAlignPanel());
 
                   }
                   else
@@ -424,7 +426,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
                     // bottom view, then top
                     cra_views2.add(((jalview.gui.AlignViewport) af2
                             .getViewport().getCodingComplement())
-                                    .getAlignPanel());
+                            .getAlignPanel());
                     cra_views2.add(af2.getViewport().getAlignPanel());
                   }
                   Assert.assertEquals(cra_views2.size(), 2);
index dde23e6..a197340 100644 (file)
  */
 package jalview.io;
 
+import java.util.Date;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeTest;
+
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.datamodel.AlignmentAnnotation;
@@ -27,12 +33,6 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.Desktop;
 import jalview.gui.JvOptionPane;
 
-import java.util.Date;
-
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeTest;
-
 public class Jalview2xmlBase
 {
 
@@ -71,7 +71,8 @@ public class Jalview2xmlBase
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @BeforeTest(alwaysRun = true)
index 1d138c0..59c5986 100644 (file)
@@ -22,14 +22,15 @@ package jalview.io;
 
 import static org.testng.AssertJUnit.assertTrue;
 
-import jalview.datamodel.SequenceGroup;
-import jalview.gui.AlignFrame;
-import jalview.gui.JvOptionPane;
-
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+import jalview.datamodel.SequenceGroup;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+
 /**
  * tests which verify that properties and preferences are correctly interpreted
  * when exporting/importing data
@@ -64,7 +65,8 @@ public class JalviewExportPropertiesTests
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
 
   }
 
diff --git a/test/jalview/io/uniref50-relaxed.phy b/test/jalview/io/uniref50-relaxed.phy
new file mode 100644 (file)
index 0000000..d444d13
--- /dev/null
@@ -0,0 +1,48 @@
+15 157
+FER_CAPAA    -----------------------------------------------------------A
+FER_CAPAN    MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA
+FER1_SOLLC   MA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA
+Q93XJ9_SOLTU MA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA
+FER1_PEA     MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA
+Q7XA98_TRIPR MATT---PALYGTAVSTSFMRRQPVPMSV-ATTTTTKAFPSGFGLKSVSTKRGDLAVAMA
+FER1_MESCR   MAAT--TAALSGATMSTAFAPK--TPPMTAALPTNVGR--ALFGLKS-SASR-GRVTAMA
+FER1_SPIOL   MAAT--TTTMMG--MATTFVPKPQAPPMMAALPSNTGR--SLFGLKT-GSR--GGRMTMA
+FER3_RAPSA   -----------------------------------------------------------A
+FER2_ARATH   MAST----ALSSAIVGTSFIRRSPAPISLRSLPSANTQ--SLFGLKS-GTARGGRVTAMA
+FER_BRANA    -----------------------------------------------------------A
+FER1_ARATH   MAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA
+Q93Z60_ARATH MAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA
+FER1_MAIZE   MATVLGSPRAPAFFFSSSSLRAAPAPTAV--ALPAAKV--GIMGRSA-SSRR--RLRAQA
+O80429_MAIZE MAAT---------ALSMSILR---APPPCFSSPLRLRV--AVAKPLA-APMRRQLLRAQA
+
+SYKVKLITPDGPIEFDCPDDVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG
+SYKVKLITPDGPIEFDCPDNVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG
+SYKVKLITPEGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGSVDQSDG
+SYKVKLITPDGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGTVDQSDG
+SYKVKLVTPDGTQEFECPSDVYILDHAEEVGIDLPYSCRAGSCSSCAGKVVGGEVDQSDG
+TYKVKLITPEGPQEFDCPDDVYILDHAEEVGIELPYSCRAGSCSSCAGKVVNGNVNQEDG
+AYKVTLVTPEGKQELECPDDVYILDAAEEAGIDLPYSCRAGSCSSCAGKVTSGSVNQDDG
+AYKVTLVTPTGNVEFQCPDDVYILDAAEEEGIDLPYSCRAGSCSSCAGKLKTGSLNQDDQ
+TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYKVKFITPEGELEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGFVDQSDE
+TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ
+TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ
+TYNVKLITPEGEVELQVPDDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYNVKLITPEGEVELQVPDDVYILDFAEEEGIDLPFSCRAGSCSSCAGKVVSGSVDQSDQ
+
+NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-
+NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-
+NFLDEDQEAAGFVLTCVAYPKGDVTIETHKEEELTA-
+KFLDDDQEAAGFVLTCVAYPKCDVTIETHKEEELTA-
+SFLDDEQIEAGFVLTCVAYPTSDVVIETHKEEDLTA-
+SFLDDEQIEGGWVLTCVAFPTSDVTIETHKEEELTA-
+SFLDDDQIKEGWVLTCVAYPTGDVTIETHKEEELTA-
+SFLDDDQIDEGWVLTCAAYPVSDVTIETHKEEELTA-
+SFLDDDQIAEGFVLTCAAYPTSDVTIETHREEDMV--
+SFLDDEQIGEGFVLTCAAYPTSDVTIETHKEEDIV--
+SFLDDDQIAEGFVLTCAAYPTSDVTIETHKEEELV--
+SFLDDEQMSEGYVLTCVAYPTSDVVIETHKEEAIM--
+SFLDD--------------------------------
+SYLDDGQIADGWVLTCHAYPTSDVVIETHKEEELTGA
+SFLNDNQVADGWVLTCAAYPTSDVVIETHKEDDLL--
diff --git a/test/jalview/io/uniref50-strict.phy b/test/jalview/io/uniref50-strict.phy
new file mode 100644 (file)
index 0000000..ae38cec
--- /dev/null
@@ -0,0 +1,48 @@
+15 157
+FER_CAPAA -----------------------------------------------------------A
+FER_CAPAN MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA
+FER1_SOLLCMA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA
+Q93XJ9_SOLMA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA
+FER1_PEA  MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA
+Q7XA98_TRIMATT---PALYGTAVSTSFMRRQPVPMSV-ATTTTTKAFPSGFGLKSVSTKRGDLAVAMA
+FER1_MESCRMAAT--TAALSGATMSTAFAPK--TPPMTAALPTNVGR--ALFGLKS-SASR-GRVTAMA
+FER1_SPIOLMAAT--TTTMMG--MATTFVPKPQAPPMMAALPSNTGR--SLFGLKT-GSR--GGRMTMA
+FER3_RAPSA-----------------------------------------------------------A
+FER2_ARATHMAST----ALSSAIVGTSFIRRSPAPISLRSLPSANTQ--SLFGLKS-GTARGGRVTAMA
+FER_BRANA -----------------------------------------------------------A
+FER1_ARATHMAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA
+Q93Z60_ARAMAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA
+FER1_MAIZEMATVLGSPRAPAFFFSSSSLRAAPAPTAV--ALPAAKV--GIMGRSA-SSRR--RLRAQA
+O80429_MAIMAAT---------ALSMSILR---APPPCFSSPLRLRV--AVAKPLA-APMRRQLLRAQA
+
+SYKVKLITPDGPIEFDCPDDVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG
+SYKVKLITPDGPIEFDCPDNVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG
+SYKVKLITPEGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGSVDQSDG
+SYKVKLITPDGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGTVDQSDG
+SYKVKLVTPDGTQEFECPSDVYILDHAEEVGIDLPYSCRAGSCSSCAGKVVGGEVDQSDG
+TYKVKLITPEGPQEFDCPDDVYILDHAEEVGIELPYSCRAGSCSSCAGKVVNGNVNQEDG
+AYKVTLVTPEGKQELECPDDVYILDAAEEAGIDLPYSCRAGSCSSCAGKVTSGSVNQDDG
+AYKVTLVTPTGNVEFQCPDDVYILDAAEEEGIDLPYSCRAGSCSSCAGKLKTGSLNQDDQ
+TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYKVKFITPEGELEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGFVDQSDE
+TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ
+TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ
+TYNVKLITPEGEVELQVPDDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYNVKLITPEGEVELQVPDDVYILDFAEEEGIDLPFSCRAGSCSSCAGKVVSGSVDQSDQ
+
+NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-
+NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-
+NFLDEDQEAAGFVLTCVAYPKGDVTIETHKEEELTA-
+KFLDDDQEAAGFVLTCVAYPKCDVTIETHKEEELTA-
+SFLDDEQIEAGFVLTCVAYPTSDVVIETHKEEDLTA-
+SFLDDEQIEGGWVLTCVAFPTSDVTIETHKEEELTA-
+SFLDDDQIKEGWVLTCVAYPTGDVTIETHKEEELTA-
+SFLDDDQIDEGWVLTCAAYPVSDVTIETHKEEELTA-
+SFLDDDQIAEGFVLTCAAYPTSDVTIETHREEDMV--
+SFLDDEQIGEGFVLTCAAYPTSDVTIETHKEEDIV--
+SFLDDDQIAEGFVLTCAAYPTSDVTIETHKEEELV--
+SFLDDEQMSEGYVLTCVAYPTSDVVIETHKEEAIM--
+SFLDD--------------------------------
+SYLDDGQIADGWVLTCHAYPTSDVVIETHKEEELTGA
+SFLNDNQVADGWVLTCAAYPTSDVVIETHKEDDLL--
diff --git a/test/jalview/io/uniref50.phy b/test/jalview/io/uniref50.phy
new file mode 100644 (file)
index 0000000..ae38cec
--- /dev/null
@@ -0,0 +1,48 @@
+15 157
+FER_CAPAA -----------------------------------------------------------A
+FER_CAPAN MA------SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALFGLKS-A--NGGKVTCMA
+FER1_SOLLCMA------SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA
+Q93XJ9_SOLMA------SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALFGLKS-G--RNGRITCMA
+FER1_PEA  MATT---PALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFLGLKT-SLKRGDLAVAMA
+Q7XA98_TRIMATT---PALYGTAVSTSFMRRQPVPMSV-ATTTTTKAFPSGFGLKSVSTKRGDLAVAMA
+FER1_MESCRMAAT--TAALSGATMSTAFAPK--TPPMTAALPTNVGR--ALFGLKS-SASR-GRVTAMA
+FER1_SPIOLMAAT--TTTMMG--MATTFVPKPQAPPMMAALPSNTGR--SLFGLKT-GSR--GGRMTMA
+FER3_RAPSA-----------------------------------------------------------A
+FER2_ARATHMAST----ALSSAIVGTSFIRRSPAPISLRSLPSANTQ--SLFGLKS-GTARGGRVTAMA
+FER_BRANA -----------------------------------------------------------A
+FER1_ARATHMAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA
+Q93Z60_ARAMAST----ALSSAIVSTSFLRRQQTPISLRSLPFANTQ--SLFGLKS-STARGGRVTAMA
+FER1_MAIZEMATVLGSPRAPAFFFSSSSLRAAPAPTAV--ALPAAKV--GIMGRSA-SSRR--RLRAQA
+O80429_MAIMAAT---------ALSMSILR---APPPCFSSPLRLRV--AVAKPLA-APMRRQLLRAQA
+
+SYKVKLITPDGPIEFDCPDDVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG
+SYKVKLITPDGPIEFDCPDNVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDG
+SYKVKLITPEGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGSVDQSDG
+SYKVKLITPDGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGTVDQSDG
+SYKVKLVTPDGTQEFECPSDVYILDHAEEVGIDLPYSCRAGSCSSCAGKVVGGEVDQSDG
+TYKVKLITPEGPQEFDCPDDVYILDHAEEVGIELPYSCRAGSCSSCAGKVVNGNVNQEDG
+AYKVTLVTPEGKQELECPDDVYILDAAEEAGIDLPYSCRAGSCSSCAGKVTSGSVNQDDG
+AYKVTLVTPTGNVEFQCPDDVYILDAAEEEGIDLPYSCRAGSCSSCAGKLKTGSLNQDDQ
+TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYKVKFITPEGELEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGFVDQSDE
+TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ
+TYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQ
+TYNVKLITPEGEVELQVPDDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQ
+TYNVKLITPEGEVELQVPDDVYILDFAEEEGIDLPFSCRAGSCSSCAGKVVSGSVDQSDQ
+
+NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-
+NFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-
+NFLDEDQEAAGFVLTCVAYPKGDVTIETHKEEELTA-
+KFLDDDQEAAGFVLTCVAYPKCDVTIETHKEEELTA-
+SFLDDEQIEAGFVLTCVAYPTSDVVIETHKEEDLTA-
+SFLDDEQIEGGWVLTCVAFPTSDVTIETHKEEELTA-
+SFLDDDQIKEGWVLTCVAYPTGDVTIETHKEEELTA-
+SFLDDDQIDEGWVLTCAAYPVSDVTIETHKEEELTA-
+SFLDDDQIAEGFVLTCAAYPTSDVTIETHREEDMV--
+SFLDDEQIGEGFVLTCAAYPTSDVTIETHKEEDIV--
+SFLDDDQIAEGFVLTCAAYPTSDVTIETHKEEELV--
+SFLDDEQMSEGYVLTCVAYPTSDVVIETHKEEAIM--
+SFLDD--------------------------------
+SYLDDGQIADGWVLTCHAYPTSDVVIETHKEEELTGA
+SFLNDNQVADGWVLTCAAYPTSDVVIETHKEDDLL--
index 81756e0..af52eda 100644 (file)
@@ -110,6 +110,8 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @BeforeClass(alwaysRun = true)
   public void setUpJvOptionPane()
   {
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
     JvOptionPane.setInteractiveMode(false);
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
@@ -1564,9 +1566,11 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     }
     PAEContactMatrix dummyMat = new PAEContactMatrix(sq, paevals);
     String content = ContactMatrix.contactToFloatString(dummyMat);
-    Assert.assertTrue(content.contains("\t1.")); // at least one element must be 1
-    float[][] vals = ContactMatrix.fromFloatStringToContacts(content, sq.getLength(), sq.getLength());
-    assertEquals(vals[3][4],paevals[3][4]);
+    Assert.assertTrue(content.contains("\t1.")); // at least one element must be
+                                                 // 1
+    float[][] vals = ContactMatrix.fromFloatStringToContacts(content,
+            sq.getLength(), sq.getLength());
+    assertEquals(vals[3][4], paevals[3][4]);
     dummyMat.makeGroups(0.5f, false);
     Assert.assertNotSame(dummyMat.getNewick(), "");
     AlignmentAnnotation paeCm = sq.addContactList(dummyMat);
@@ -1602,9 +1606,8 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     Assert.assertEquals(restoredMat.hasGroups(), dummyMat.hasGroups());
     Assert.assertEquals(restoredMat.getGroups(), dummyMat.getGroups());
     Assert.assertEquals(restoredMat.hasTree(), dummyMat.hasTree());
-    Assert.assertEquals( restoredMat.getNewick(),dummyMat.getNewick());
-    
-    
+    Assert.assertEquals(restoredMat.getNewick(), dummyMat.getNewick());
+
   }
 
 }
index a3d0a7c..f7d8be5 100644 (file)
@@ -27,6 +27,16 @@ import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
 import jalview.analysis.GeneticCodes;
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureColourI;
@@ -46,17 +56,14 @@ import jalview.schemes.FeatureColour;
 import jalview.util.matcher.Condition;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
 
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.testng.annotations.Test;
-
 public class FeatureRendererTest
 {
+  @BeforeMethod(alwaysRun = true)
+  public void closeAll()
+  {
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
+  }
 
   @Test(groups = "Functional")
   public void testFindAllFeatures()
@@ -618,7 +625,7 @@ public class FeatureRendererTest
   {
     Jalview.main(
             new String[]
-            { "-nonews", "-props", "test/jalview/testProps.jvprops" });
+            { "-nonews", "--props", "test/jalview/testProps.jvprops" });
 
     // codons for MCWHSE
     String cdsSeq = ">cds\nATGtgtTGGcacTCAgaa";
index 58e272d..0184f12 100644 (file)
@@ -25,6 +25,14 @@ import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 
+import java.awt.Color;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
 import jalview.api.AlignViewportI;
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
@@ -38,14 +46,6 @@ import jalview.io.DataSourceType;
 import jalview.io.FileLoader;
 import jalview.schemes.ClustalxColourScheme.ClustalColour;
 
-import java.awt.Color;
-import java.util.Iterator;
-import java.util.Map;
-
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
 public class ColourSchemesTest
 {
   /*
@@ -192,7 +192,8 @@ public class ColourSchemesTest
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
   }
 
   @Test(groups = "Functional")
index ac8235d..624548f 100644 (file)
@@ -204,7 +204,8 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
   {
     // for some reason 'BeforeMethod' (which should be inherited from
     // Jalview2XmlBase isn't always called)...
-    Desktop.instance.closeAll_actionPerformed(null);
+    if (Desktop.instance != null)
+      Desktop.instance.closeAll_actionPerformed(null);
     try
     {
       Thread.sleep(200);
index 95da22e..fc58822 100644 (file)
@@ -83,3 +83,4 @@ USE_PROXY=false
 WRAP_ALIGNMENT=false
 #DAS_REGISTRY_URL=http\://www.dasregistry.org/das/ # retired 01/05/2015
 DAS_REGISTRY_URL=http\://www.ebi.ac.uk/das-srv/registry/das/
+SPLASH=false
diff --git a/test/jalview/util/FileUtilsTest.java b/test/jalview/util/FileUtilsTest.java
new file mode 100644 (file)
index 0000000..35853b0
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test
+public class FileUtilsTest
+{
+  @Test(groups = "Functional", dataProvider = "patternsAndMinNumFiles")
+  public void testJavaFileGlob(String pattern, int atLeast, int atMost)
+  {
+    List<File> files = FileUtils.getFilesFromGlob(pattern);
+    if (atLeast != -1)
+    {
+      Assert.assertTrue(files.size() > atLeast,
+              "Did not find more than " + atLeast + " files with " + pattern
+                      + " (found " + files.size() + ")");
+    }
+    if (atLeast != -1)
+    {
+      Assert.assertTrue(files.size() > atLeast,
+              "Did not find more than " + atLeast + " files with " + pattern
+                      + " (found " + files.size() + ")");
+    }
+    if (atMost != -1)
+    {
+      Assert.assertTrue(files.size() < atMost,
+              "Did not find fewer than " + atMost + " files with " + pattern
+                      + " (found " + files.size() + ")");
+    }
+  }
+
+  @Test(groups = "Functional", dataProvider = "dirnamesAndBasenames")
+  public void testDirnamesAndBasenames(String filename, int where,
+          String dirname, String basename, String notInDirname)
+  {
+    File file = new File(filename);
+    String d = FileUtils.getDirname(file);
+    String b = FileUtils.getBasename(file);
+    Assert.assertEquals(b, basename);
+    if (where == 0)
+      Assert.assertEquals(d, dirname);
+    else if (where < 0)
+      Assert.assertTrue(d.startsWith(dirname),
+              "getDirname(" + file.getPath() + ")=" + d
+                      + " didn't start with '" + dirname + "'");
+    else if (where > 0)
+      Assert.assertTrue(d.endsWith(dirname), "getDirname(" + file.getPath()
+              + ")=" + d + " didn't end with '" + d + "'");
+
+    // ensure dirname doesn't end with basename (which means you can't use same
+    // filename as dir in tests!)
+    Assert.assertFalse(d.endsWith(b));
+
+    if (notInDirname != null)
+      Assert.assertFalse(d.contains(notInDirname));
+  }
+
+  @DataProvider(name = "patternsAndMinNumFiles")
+  public Object[][] patternsAndMinNumFiles()
+  {
+    return new Object[][] { { "src/**/*.java", 900, 100000 },
+        { "src/**.java", 900, 100000 },
+        { "test/**/*.java", 250, 2500 },
+        { "test/**.java", 250, 2500 },
+        { "help/**/*.html", 100, 1000 },
+        { "test/**/F*.java", 15, 150 },
+        { "test/jalview/*/F*.java", 10, 15 }, // 12 at time of writing
+        { "test/jalview/**/F*.java", 18, 30 }, // 20 at time of writing
+        { "test/jalview/util/F**.java", 1, 5 }, // 2 at time of writing
+        { "src/jalview/b*/*.java", 14, 19 }, // 15 at time of writing
+        { "src/jalview/b**/*.java", 20, 25 }, // 22 at time of writing
+    };
+  }
+
+  @DataProvider(name = "dirnamesAndBasenames")
+  public Object[][] dirnamesAndBasenames()
+  {
+    String homeDir = null;
+    try
+    {
+      homeDir = new File(System.getProperty("user.home"))
+              .getCanonicalPath();
+    } catch (IOException e)
+    {
+      System.err.println("Problem getting canonical home dir");
+      e.printStackTrace();
+    }
+    return new Object[][] { // -1=startsWith, 0=equals, 1=endsWith
+        { "~/hello/sailor", -1, homeDir, "sailor", "~" }, //
+        { "~/hello/sailor", 1, "/hello", "sailor", "~" }, //
+        { "./examples/uniref50.fa", -1, "/", "uniref50", "." }, //
+        { "./examples/uniref50.fa", 1, "/examples", "uniref50", "." }, //
+        { "examples/uniref50.fa", 1, "/examples", "uniref50", ".fa" }, //
+    };
+  }
+}
index e619b8e..076ee7e 100644 (file)
@@ -1,14 +1,22 @@
 package jalview.ws.dbsources;
 
+import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 
+import org.json.simple.parser.ParseException;
 import org.junit.Assert;
+import org.testng.FileAssert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import jalview.datamodel.Sequence;
+import jalview.gui.Desktop;
 import jalview.gui.JvOptionPane;
+import jalview.structure.StructureMapping;
+import jalview.structure.StructureSelectionManager;
 import jalview.ws.datamodel.alphafold.PAEContactMatrix;
 
 public class EBIAlphaFoldTest
@@ -40,4 +48,36 @@ public class EBIAlphaFoldTest
                     new FileInputStream(paeFile)));
     Assert.assertNotEquals("No data from " + paeFile, cm.getMax(), 0);
   }
+
+  @Test(groups = { "Functional" }, dataProvider = "getPDBandPAEfiles")
+  public void checkImportPAEToStructure(String pdbFile, String paeFile)
+  {
+    FileInputStream paeInput = null;
+    try
+    {
+      paeInput = new FileInputStream(paeFile);
+    } catch (FileNotFoundException e)
+    {
+      e.printStackTrace();
+      FileAssert.assertFile(new File(paeFile),
+              "Test file '" + paeFile + "' doesn't seem to exist");
+    }
+    StructureSelectionManager ssm = StructureSelectionManager
+            .getStructureSelectionManager(Desktop.instance);
+
+    StructureMapping[] smArray = ssm.getMapping(pdbFile);
+
+    try
+    {
+      boolean done = EBIAlfaFold.importPaeJSONAsContactMatrixToStructure(
+              smArray, paeInput, "label");
+      Assert.assertTrue(
+              "Import of '" + paeFile + "' didn't complete successfully",
+              done);
+    } catch (IOException | ParseException e)
+    {
+      e.printStackTrace();
+    }
+
+  }
 }
index 14033a6..ccb1d75 100755 (executable)
@@ -1,3 +1,3 @@
 #!/usr/bin/env sh
 
-java -cp "./bin/main:./j11lib/*:./resources:./help" jalview.bin.Jalview "${@}"
+java -cp "./bin/main:./j11lib/*:./resources:./help" jalview.bin.Launcher "${@}"
diff --git a/utils/eclipse/launcher b/utils/eclipse/launcher
new file mode 100755 (executable)
index 0000000..ccb1d75
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+
+java -cp "./bin/main:./j11lib/*:./resources:./help" jalview.bin.Launcher "${@}"