Merge branch 'develop' into features/r2_11_2_alphafold/JAL-629
authorBen Soares <b.soares@dundee.ac.uk>
Wed, 12 Apr 2023 14:49:00 +0000 (15:49 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Wed, 12 Apr 2023 14:49:00 +0000 (15:49 +0100)
75 files changed:
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]
src/jalview/bin/ArgParser.java [deleted file]
src/jalview/bin/Cache.java
src/jalview/bin/Commands.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/ext/jmol/JmolParser.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/Desktop.java
src/jalview/io/FileLoader.java
src/jalview/io/NewickFile.java
src/jalview/util/AWTConsole.java
src/jalview/util/ArrayUtils.java
src/jalview/util/FileUtils.java [new file with mode: 0644]
src/jalview/util/StringUtils.java
src/jalview/ws/datamodel/alphafold/PAEContactMatrix.java
src/jalview/ws/dbsources/EBIAlfaFold.java
test/jalview/bin/ArgsParserTest.java
test/jalview/bin/CommandLineOperations.java
test/jalview/bin/CommandsTest.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/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/io/AnnotatedPDBFileInputTest.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/util/FileUtilsTest.java [new file with mode: 0644]

index 87ca397..7c08c2b 100644 (file)
@@ -3986,3 +3986,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..50e898e
--- /dev/null
@@ -0,0 +1,18 @@
+--debug
+--nonews
+--nosplash
+--substitutions
+--open={argfiledirname}/sample.a2m
+--colour=gecos:flower
+--structure={dirname}/test_fab41_unrelaxed_rank_1_model_3.pdb
+--paematrix=[seqid=101;label=PAE R1-M3]{dirname}/test_fab41_unrelaxed_rank_1_model_3_scores.json
+--structure={dirname}/test_fab41_unrelaxed_rank_2_model_4.pdb
+--paematrix=[label=PAE R2-M4]{dirname}/test_fab41_unrelaxed_rank_2_model_4_scores.json
+--structure={dirname}/test_fab41_unrelaxed_rank_3_model_2.pdb
+--paematrix=[label=PAE R3-M2]{dirname}/test_fab41_unrelaxed_rank_3_model_2_scores.json
+--structure={dirname}/test_fab41_unrelaxed_rank_4_model_5.pdb
+--paematrix=[label=PAE R4-M5]{dirname}/test_fab41_unrelaxed_rank_4_model_5_scores.json
+--structure={dirname}/test_fab41_unrelaxed_rank_5_model_1.pdb
+--paematrix=[label=PAE R5-M1]{dirname}/test_fab41_unrelaxed_rank_5_model_1_scores.json
+--image={dirname}/{basename}.html
+#--headless
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..da6b1ea 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
    */
@@ -1622,7 +1625,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)
   {
index b42f08e..4a70bd9 100644 (file)
@@ -2,6 +2,7 @@ package jalview.bin;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -10,13 +11,16 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Map.Entry;
 
 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.bin.argparser.Arg;
+import jalview.bin.argparser.ArgParser;
+import jalview.bin.argparser.ArgValue;
+import jalview.bin.argparser.ArgValues;
+import jalview.bin.argparser.ArgValuesMap;
+import jalview.bin.argparser.SubVals;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.PDBEntry;
@@ -31,6 +35,7 @@ import jalview.gui.StructureChooser;
 import jalview.gui.StructureViewer;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
 import jalview.io.FileFormatException;
 import jalview.io.FileFormatI;
 import jalview.io.FileLoader;
@@ -50,46 +55,65 @@ 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);
     }
+  }
+
+  private boolean processArgs(ArgParser argparser, boolean h)
+  {
+    argParser = argparser;
+    headless = h;
+    boolean theseArgsWereParsed = false;
 
     if (argParser != null && argParser.linkedIds() != null)
     {
       for (String id : argParser.linkedIds())
       {
-        Commands cmds = new Commands();
+        ArgValuesMap avm = argParser.linkedArgs(id);
+        theseArgsWereParsed = true;
         if (id == null)
         {
-          cmds.processUnlinked(id);
-          argsWereParsed &= cmds.wereParsed();
+          theseArgsWereParsed &= processUnlinked(id);
         }
         else
         {
-          cmds.processLinked(id);
-          argsWereParsed &= cmds.wereParsed();
+          theseArgsWereParsed &= processLinked(id);
         }
-        cmds.processImages(id);
-        argsWereParsed &= cmds.wereParsed();
+        theseArgsWereParsed &= processImages(id);
+
+        // close ap
+        if (avm.getBoolean(Arg.CLOSE))
+        {
+          AlignFrame af = afMap.get(id);
+          if (af != null)
+          {
+            af.closeMenuItem_actionPerformed(true);
+          }
+        }
+
       }
 
     }
@@ -99,51 +123,40 @@ public class Commands
       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()
+  public boolean commandArgsProvided()
   {
-    return argsWereParsed;
-  }
-
-  public Commands()
-  {
-    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)
   {
+    boolean theseArgsWereParsed = false;
     ArgValuesMap avm = argParser.linkedArgs(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.OPEN) || avm.containsArg(Arg.OPENNEW))
     {
       commandArgsProvided = true;
       long progress = -1;
@@ -151,13 +164,26 @@ public class Commands
       boolean first = true;
       boolean progressBarSet = false;
       AlignFrame af;
-      for (ArgValue av : avm.getArgValueList(Arg.OPEN))
+      // Combine the OPEN and OPENNEW files into one list, along with whether it
+      // was OPEN or OPENNEW
+      List<Entry<Arg, ArgValue>> openAvList = new ArrayList<>();
+      avm.getArgValueList(Arg.OPEN).stream()
+              .forEachOrdered(av -> openAvList.add(
+                      new AbstractMap.SimpleEntry<Arg, ArgValue>(Arg.OPEN,
+                              av)));
+      avm.getArgValueList(Arg.OPENNEW).stream()
+              .forEachOrdered(av -> openAvList
+                      .add(new AbstractMap.SimpleEntry<Arg, ArgValue>(
+                              Arg.OPENNEW, av)));
+      for (Entry<Arg, ArgValue> aav : openAvList)
       {
+        Arg a = aav.getKey();
+        ArgValue av = aav.getValue();
         String openFile = av.getValue();
         if (openFile == null)
           continue;
 
-        argsWereParsed = true;
+        theseArgsWereParsed = true;
         if (first)
         {
           first = false;
@@ -200,36 +226,23 @@ public class Commands
         }
 
         af = afMap.get(id);
-        if (af == null)
+        if (af == null || "true".equals(av.getSubVal("new"))
+                || a == Arg.OPENNEW || 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);
-          }
+           * 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
@@ -247,8 +260,8 @@ public class Commands
             } catch (IllegalArgumentException e)
             {
               // Just an error message!
-              StringBuilder sb = new StringBuilder().append("Cannot set --")
-                      .append(Arg.TEMPFAC.getName()).append(" to '")
+              StringBuilder sb = new StringBuilder().append("Cannot set ")
+                      .append(Arg.TEMPFAC.argString()).append(" to '")
                       .append(tempfacType)
                       .append("', ignoring.  Valid values are: ");
               Iterator<StructureImportSettings.TFType> it = Arrays
@@ -312,9 +325,10 @@ public class Commands
                     false, false);
           }
           else
-          /* comment out hacky approach up to here and add this line:
-           if (showTemperatureFactor)
-             */
+          /*
+           * comment out hacky approach up to here and add this line: if
+           * (showTemperatureFactor)
+           */
           {
             if (avm.containsArg(Arg.TEMPFAC_LABEL))
             {
@@ -353,7 +367,7 @@ public class Commands
         {
           Console.debug(
                   "Opening '" + openFile + "' in existing alignment frame");
-          af.getCurrentView().addFile(new File(openFile), format);
+          af.getCurrentView().addFile(new File(openFile), format, false);
         }
 
         Console.debug("Command " + Arg.OPEN + " executed successfully!");
@@ -363,8 +377,7 @@ public class Commands
       {
         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,12 +399,20 @@ public class Commands
         for (ArgValue av : avm.getArgValueList(Arg.STRUCTURE))
         {
           String val = av.getValue();
-          SubVals subId = new SubVals(val);
+          SubVals subId = av.getSubVals();
           SequenceI seq = getSpecifiedSequence(af, subId);
           if (seq == null)
           {
-            Console.warn("Could not find sequence for argument --"
-                    + Arg.STRUCTURE + "=" + val);
+            // 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.argString() + "=" + val);
             // you probably want to continue here, not break
             // break;
             continue;
@@ -404,20 +425,16 @@ public class Commands
             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)
@@ -458,17 +475,17 @@ public class Commands
 
           // get tft, paeFilename, label?
           /*
-          ArgValue tftAv = avm.getArgValuesReferringTo("structid", structId,
-                  Arg.TEMPFAC);
+           * ArgValue tftAv = avm.getArgValuesReferringTo("structid", structId,
+           * Arg.TEMPFAC);
            */
           StructureChooser.openStructureFileForSequence(null, null, ap, seq,
                   false, structureFile.getAbsolutePath(), null, null); // tft,
-                                                                       // paeFilename);
+          // paeFilename);
         }
       }
     }
 
-    // load a pAE file if given
+    // load a PAE file if given
     if (avm.containsArg(Arg.PAEMATRIX))
     {
       AlignFrame af = afMap.get(id);
@@ -477,7 +494,8 @@ public class Commands
         for (ArgValue av : avm.getArgValueList(Arg.PAEMATRIX))
         {
           String val = av.getValue();
-          SubVals subVals = ArgParser.getSubVals(val);
+          SubVals subVals = av.getSubVals();
+          String paeLabel = subVals.get("label");
           File paeFile = new File(subVals.getContent());
           String paePath = null;
           try
@@ -489,38 +507,66 @@ public class Commands
             Console.warn(
                     "Problem with the PAE file path: '" + paePath + "'");
           }
-          String structId = subVals.get("structid");
-          if (subVals.notSet())
+          String structid = subVals.get("structid");
+          String structfile = subVals.get("structfile");
+          String seqid = subVals.get("seqid");
+          int seqindex = subVals.getIndex();
+
+          // let's find a structure
+          if (structfile == null && structid == null && seqid == null
+                  && seqindex == SubVals.NOTSET)
+          {
+            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();
+                Console.debug(
+                        "##### Using closest previous structure argument '"
+                                + structfile + "'");
+              }
+            }
+          }
+
+          if (structfile != null)
           {
-            // take structid from pdbfilename
+            Console.debug("##### Attaching paeFile '" + paePath + "' to "
+                    + "structfile=" + structfile);
+            EBIAlfaFold.addAlphaFoldPAE(af.getCurrentView().getAlignment(),
+                    paeFile, seqindex, structfile, true, false, paeLabel);
           }
-          if (subVals.has("structfile"))
+          else if (structid != null)
           {
-            Console.info("***** Attaching paeFile '" + paePath + "' to "
-                    + "structfile=" + subVals.get("structfile"));
+            Console.debug("##### Attaching paeFile '" + paePath + "' to "
+                    + "structid=" + structid);
             EBIAlfaFold.addAlphaFoldPAE(af.getCurrentView().getAlignment(),
-                    paeFile, subVals.getIndex(), subVals.get("structfile"),
-                    true, false);
+                    paeFile, seqindex, structid, true, true, paeLabel);
           }
-          else if (subVals.has("structid"))
+          else if (seqid != null)
           {
-            Console.info("***** Attaching paeFile '" + paePath + "' to "
-                    + "structid=" + subVals.get("structid"));
+            Console.debug("##### Attaching paeFile '" + paePath + "' to "
+                    + "seqid=" + seqid);
             EBIAlfaFold.addAlphaFoldPAE(af.getCurrentView().getAlignment(),
-                    paeFile, subVals.getIndex(), subVals.get("structid"),
-                    true, true);
+                    paeFile, seqindex, seqid, false, false, paeLabel);
           }
-          else
+          else if (seqindex >= 0)
           {
-            Console.debug("***** Attaching paeFile '" + paePath
-                    + "' to sequence index " + subVals.getIndex());
+            Console.debug("##### Attaching paeFile '" + paePath
+                    + "' to sequence index " + seqindex);
             EBIAlfaFold.addAlphaFoldPAE(af.getCurrentView().getAlignment(),
-                    paeFile, subVals.getIndex(), null, false, false);
-            // required to readjust the height and position of the pAE
-            // annotation
+                    paeFile, seqindex, null, false, false, paeLabel);
           }
           for (AlignmentViewPanel ap : af.getAlignPanels())
           {
+            // required to readjust the height and position of the PAE
+            // annotation
             ap.adjustAnnotationHeight();
           }
         }
@@ -541,9 +587,11 @@ public class Commands
         Console.info("Changed colour " + acg.toString());
       }
     }
+
+    return theseArgsWereParsed;
   }
 
-  protected void processImages(String id)
+  protected boolean processImages(String id)
   {
     ArgValuesMap avm = argParser.linkedArgs(id);
     AlignFrame af = afMap.get(id);
@@ -551,7 +599,7 @@ public class Commands
     if (af == null)
     {
       Console.warn("Did not have an alignment window for id=" + id);
-      return;
+      return false;
     }
 
     if (avm.containsArg(Arg.IMAGE))
@@ -559,7 +607,7 @@ public class Commands
       for (ArgValue av : avm.getArgValueList(Arg.IMAGE))
       {
         String val = av.getValue();
-        SubVals subVal = new SubVals(val);
+        SubVals subVal = av.getSubVals();
         String type = "png"; // default
         String fileName = subVal.getContent();
         File file = new File(fileName);
@@ -597,24 +645,28 @@ public class Commands
           htmlSVG.exportHTML(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)
   {
+    if (subId == null)
+      return null;
     AlignmentI al = af.getCurrentView().getAlignment();
-    if (-1 < subId.getIndex()
-            && subId.getIndex() < al.getSequences().size())
+    if (subId.has("seqid"))
     {
-      return al.getSequenceAt(subId.getIndex());
+      return al.findName(subId.get("seqid"));
     }
-    else if (subId.has("seqid"))
+    else if (-1 < subId.getIndex()
+            && subId.getIndex() < al.getSequences().size())
     {
-      return al.findName(subId.get("seqid"));
+      return al.getSequenceAt(subId.getIndex());
     }
     return null;
   }
index c792a96..44494e2 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,9 @@ 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.ArgParser;
+import jalview.bin.argparser.BootstrapArgs;
 import jalview.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
@@ -123,6 +129,8 @@ public class Jalview
 
   private Desktop desktop;
 
+  protected Commands cmds;
+
   public static AlignFrame currentAlignFrame;
 
   static
@@ -281,9 +289,49 @@ public class Jalview
       System.setSecurityManager(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)
+    {
+      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();
+    }
+
     // get args needed before proper ArgParser
-    Map<ArgParser.Arg, String> bootstrapArgs = ArgParser
-            .bootstrapArgs(args);
+    BootstrapArgs bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
+
+    if (!Platform.isJS())
+    {
+      // are we being --quiet ?
+      if (bootstrapArgs.contains(Arg.QUIET))
+      {
+        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));
+        }
+      }
+    }
 
     System.out
             .println("Java version: " + System.getProperty("java.version"));
@@ -330,8 +378,7 @@ public class Jalview
 
     try
     {
-      String logLevel = bootstrapArgs.containsKey(Arg.DEBUG) ? "DEBUG"
-              : null;
+      String logLevel = bootstrapArgs.contains(Arg.DEBUG) ? "DEBUG" : null;
       if (logLevel == null && !(bootstrapProperties == null))
       {
         logLevel = bootstrapProperties.getProperty(Cache.JALVIEWLOGLEVEL);
@@ -340,9 +387,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,9 +415,11 @@ 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)
     {
@@ -381,13 +430,16 @@ public class Jalview
     // 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));
     }
     else
     {
-      argparser = new ArgParser(args);
+      argparser = new ArgParser(args,
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS));
     }
 
     if (!Platform.isJS())
@@ -397,14 +449,18 @@ 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"))
       {
         showUsage();
-        System.exit(0);
+        Jalview.exit(null, 0);
       }
 
-      if (argparser.isSet(Arg.HEADLESS))
+      if (bootstrapArgs.contains(Arg.HEADLESS))
       {
         System.setProperty("java.awt.headless", "true");
         // new
@@ -438,26 +494,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 +545,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 +566,8 @@ public class Jalview
 
     if (!(headless || headlessArg))
     {
-      Desktop.nosplash = aparser.contains("nosplash");
+      Desktop.nosplash = "false".equals(bootstrapArgs.get(Arg.SPLASH))
+              || aparser.contains("nosplash");
       desktop = new Desktop();
       desktop.setInBatchMode(true); // indicate we are starting up
 
@@ -603,32 +674,39 @@ public class Jalview
                   .println("CMD [-noquestionnaire] executed successfully!");
         }
 
-        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 +719,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 +733,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 +762,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 +1006,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())
     /**
@@ -1548,7 +1607,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 +1619,21 @@ 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);
+  }
 }
index a55146d..cab2c00 100644 (file)
@@ -208,6 +208,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 +254,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..e22cfc6
--- /dev/null
@@ -0,0 +1,314 @@
+package jalview.bin.argparser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+public enum Arg
+{
+  HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
+  COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
+  ANNOTATION2, DISPLAY, GUI, NEWS, SORTBYTREE, USAGESTATS, OPEN, OPENNEW,
+  PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC, VSESS, OUTPUT, OUTPUTTYPE,
+  SSANNOTATION, NOTEMPFAC, TEMPFAC, TEMPFAC_LABEL, TEMPFAC_DESC,
+  TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP, NOSTRUCTURE, STRUCTURE, IMAGE,
+  QUIT, CLOSE, DEBUG("d"), QUIET("q"), ARGFILE, INCREMENT, NPP("n++"),
+  SUBSTITUTIONS, INITSUBSTITUTIONS, NIL, SPLASH, SETARGFILE, UNSETARGFILE;
+
+  protected static enum Opt
+  {
+    BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP,
+    GLOB, NOACTION, ALLOWSUBSTITUTIONS, PRIVATE
+  }
+
+  static
+  {
+    HELP.setOptions("Display this help message", Opt.UNARY, Opt.BOOTSTRAP);
+    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,
+            Opt.ALLOWSUBSTITUTIONS);
+    GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
+            Opt.ALLOWSUBSTITUTIONS);
+    GROUPS.setOptions(Opt.STRING, Opt.LINKED);
+    HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP);
+    JABAWS.setOptions(Opt.STRING);
+    ANNOTATION.setOptions(true, Opt.BOOLEAN, Opt.LINKED);
+    ANNOTATION2.setOptions(true, Opt.BOOLEAN, Opt.LINKED);
+    DISPLAY.setOptions(true, Opt.BOOLEAN);
+    GUI.setOptions(true, Opt.BOOLEAN);
+    NEWS.setOptions(true, Opt.BOOLEAN, Opt.BOOTSTRAP);
+    SPLASH.setOptions(true, Opt.BOOLEAN, Opt.BOOTSTRAP);
+    // expects a string value
+    SORTBYTREE.setOptions(true, Opt.BOOLEAN);
+    USAGESTATS.setOptions(true, Opt.BOOLEAN);
+    OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
+            Opt.ALLOWSUBSTITUTIONS);
+    OPENNEW.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
+            Opt.ALLOWSUBSTITUTIONS);
+    PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
+    QUESTIONNAIRE.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
+    SETPROP.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP);
+    TREE.setOptions(Opt.STRING);
+
+    VDOC.setOptions(Opt.UNARY);
+    VSESS.setOptions(Opt.UNARY);
+
+    OUTPUT.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS);
+    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,
+            Opt.ALLOWSUBSTITUTIONS);
+    NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
+    STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
+            Opt.ALLOWSUBSTITUTIONS);
+    WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
+    IMAGE.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS);
+    QUIT.setOptions(Opt.UNARY);
+    CLOSE.setOptions(Opt.UNARY, Opt.LINKED);
+    DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
+    QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
+    ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
+            Opt.ALLOWSUBSTITUTIONS);
+    INCREMENT.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
+    NPP.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
+    SUBSTITUTIONS.setOptions(Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION);
+    INITSUBSTITUTIONS.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION);
+    NIL.setOptions(Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION);
+    SETARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.PRIVATE, Opt.NOACTION);
+    UNSETARGFILE.setOptions(Opt.MULTI, Opt.PRIVATE, Opt.NOACTION);
+    // Opt.BOOTSTRAP args are parsed (not linked with no SubVals so using a
+    // simplified parser, see jalview.bin.argparser.BootstrapArgs)
+    // before a full parse of arguments and so can be accessible at an earlier
+    // stage to (e.g.) set debug log level, provide a props file (that might set
+    // log level), run headlessly, read an argfile instead of other args.
+  }
+
+  private final String[] argNames;
+
+  private Opt[] argOptions;
+
+  private boolean defaultBoolValue = false;
+
+  private String description = null;
+
+  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 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)
+  {
+    setOptions("", false, options);
+  }
+
+  protected void setOptions(String desc, Opt... options)
+  {
+    setOptions(desc, false, options);
+  }
+
+  protected void setOptions(boolean defaultBoolValue, Opt... options)
+  {
+    setOptions("", defaultBoolValue, options);
+  }
+
+  protected void setOptions(String desc, boolean defaultBoolValue,
+          Opt... options)
+  {
+    this.description = desc;
+    this.defaultBoolValue = defaultBoolValue;
+    this.argOptions = options;
+  }
+
+  protected boolean getDefaultBoolValue()
+  {
+    return defaultBoolValue;
+  }
+
+  private void setDescription(String d)
+  {
+    description = d;
+  }
+
+  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("Usage: jalview [args]");
+    sb.append(System.lineSeparator());
+
+    int maxArgLength = 0;
+    for (Arg a : EnumSet.allOf(Arg.class))
+    {
+      if (a.hasOption(Opt.PRIVATE))
+        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))
+        continue;
+      StringBuilder argSb = new StringBuilder();
+      argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
+              : a.argString());
+      if (a.hasOption(Opt.STRING))
+        argSb.append("=value");
+      sb.append(String.format("%-" + maxArgLength + "s  - %s",
+              argSb.toString(), a.getDescription()));
+
+      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 (options.size() > 0)
+      {
+        sb.append(" (");
+        sb.append(String.join("; ", options));
+        sb.append(')');
+      }
+      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..5f0cad6
--- /dev/null
@@ -0,0 +1,710 @@
+/*
+ * 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.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jalview.bin.Console;
+import jalview.bin.Jalview;
+import jalview.bin.argparser.Arg.Opt;
+import jalview.util.FileUtils;
+
+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 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
+  private int opennewLinkedIdCounter = 0;
+
+  // the linked id prefix used for --opennew files
+  protected static final String OPENNEWLINKEDIDPREFIX = "OPENNEW:";
+
+  // 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 base filename of --open
+  // or --opennew
+  private static final String LINKEDIDBASENAME = "{basename}";
+
+  // the linked id substitution string used to use the dir path of --open
+  // or --opennew
+  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}";
+
+  // flag to say whether {n} subtitutions in output filenames should be made.
+  // Turn on and off with --subs and --nosubs
+  private boolean substitutions = false;
+
+  protected static final Map<String, Arg> argMap;
+
+  protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
+
+  protected List<String> linkedOrder = null;
+
+  protected List<Arg> argList;
+
+  private static final char ARGFILECOMMENT = '#';
+
+  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);
+  }
+
+  public ArgParser(String[] args, boolean initsubstitutions)
+  {
+    // 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 --open/--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);
+  }
+
+  public ArgParser(List<String> args, boolean initsubstitutions)
+  {
+    this(args, initsubstitutions, false);
+  }
+
+  public ArgParser(List<String> args, boolean initsubstitutions,
+          boolean allowPrivate)
+  {
+    // do nothing if there are no "--" args and some "-" args
+    boolean d = false;
+    boolean dd = false;
+    for (String arg : args)
+    {
+      if (arg.startsWith(DOUBLEDASH))
+      {
+        dd = true;
+        break;
+      }
+      else if (arg.startsWith("-"))
+      {
+        d = true;
+      }
+    }
+    if (d && !dd)
+    {
+      // leave it to the old style -- parse an empty list
+      parse(new ArrayList<String>(), false, false);
+      return;
+    }
+    parse(args, initsubstitutions, allowPrivate);
+  }
+
+  private void parse(List<String> args, boolean initsubstitutions,
+          boolean allowPrivate)
+  {
+    this.substitutions = initsubstitutions;
+    int argIndex = 0;
+    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 "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("-") && new File(arg).exists())
+      {
+        arg = Arg.OPENNEW.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. Ignoring.");
+          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.INCREMENT)
+        {
+          defaultLinkedIdCounter++;
+        }
+        else 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;
+        }
+
+        String autoCounterString = null;
+        boolean usingAutoCounterLinkedId = false;
+        String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
+                .append(Integer.toString(defaultLinkedIdCounter))
+                .toString();
+        boolean usingDefaultLinkedId = false;
+        if (a.hasOption(Opt.LINKED))
+        {
+          if (linkedId == null)
+          {
+            if (a == Arg.OPENNEW)
+            {
+              // use the next default prefixed OPENNEWLINKEDID
+              linkedId = new StringBuilder(OPENNEWLINKEDIDPREFIX)
+                      .append(Integer.toString(opennewLinkedIdCounter))
+                      .toString();
+              opennewLinkedIdCounter++;
+            }
+            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);
+          }
+        }
+
+        if (!linkedArgs.containsKey(linkedId))
+          linkedArgs.put(linkedId, new ArgValuesMap());
+
+        // do not continue for NOACTION args
+        if (a.hasOption(Opt.NOACTION))
+          continue;
+
+        ArgValuesMap avm = linkedArgs.get(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;
+        }
+
+        boolean argIndexIncremented = false;
+        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)
+          {
+            for (String v : globVals)
+            {
+              v = makeSubstitutions(v, linkedId);
+              SubVals vsv = new SubVals(globSubVals, v);
+              avs.addValue(vsv, v, argIndex++);
+              argIndexIncremented = true;
+            }
+          }
+          else
+          {
+            avs.addValue(makeSubstitutions(val, linkedId), 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();
+        if (!argIndexIncremented)
+          argIndex++;
+
+        // store in appropriate place
+        if (a.hasOption(Opt.LINKED))
+        {
+          // 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);
+
+      }
+    }
+  }
+
+  private String makeSubstitutions(String val, String linkedId)
+  {
+    if (!this.substitutions)
+      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(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 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 ArgParser parseArgFiles(List<String> argFilenameGlobs,
+          boolean initsubstitutions)
+  {
+    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);
+  }
+
+  public static ArgParser parseArgFileList(List<File> argFiles,
+          boolean initsubstitutions)
+  {
+    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);
+  }
+
+  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;
+  }
+
+}
\ 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..c9e86b8
--- /dev/null
@@ -0,0 +1,66 @@
+package jalview.bin.argparser;
+
+/**
+ * A helper class to keep an index of argument position with argument values
+ */
+public class ArgValue
+{
+  private int argIndex;
+
+  private String value;
+
+  private String id;
+
+  private SubVals subVals;
+
+  protected ArgValue(SubVals sv, String content, int argIndex)
+  {
+    this.value = content;
+    this.argIndex = argIndex;
+    this.subVals = sv == null ? new SubVals("") : sv;
+  }
+
+  protected ArgValue(String value, int argIndex)
+  {
+    this.argIndex = argIndex;
+    this.subVals = new SubVals(value);
+    this.value = getSubVals().getContent();
+  }
+
+  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);
+  }
+}
\ 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..c166da0
--- /dev/null
@@ -0,0 +1,162 @@
+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 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:");
+      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()
+  {
+    addValue(null, -1);
+  }
+
+  protected void addValue(String val, int argIndex)
+  {
+    addArgValue(new ArgValue(val, argIndex));
+  }
+
+  protected void addValue(SubVals sv, String content, int argIndex)
+  {
+    addArgValue(new ArgValue(sv, content, 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 = new SubVals(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);
+  }
+}
\ 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..2117ee9
--- /dev/null
@@ -0,0 +1,230 @@
+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;
+
+  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);
+  }
+
+  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
+            : this.getBoolean(a);
+  }
+
+  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();
+    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 --open or --opennew value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public String getBasename()
+  {
+    return getDirOrBasename(false);
+  }
+
+  /*
+   * This method returns the dirname of the first --open or --opennew value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public String getDirname()
+  {
+    return getDirOrBasename(true);
+  }
+
+  public String getDirOrBasename(boolean dirname)
+  {
+    String filename = null;
+    String openVal = getValue(Arg.OPEN);
+    String opennewVal = getValue(Arg.OPENNEW);
+    if (openVal != null)
+      filename = openVal;
+    if (filename == null && opennewVal != null)
+      filename = opennewVal;
+    if (filename == null)
+      return null;
+
+    File file = new File(filename);
+    return dirname ? FileUtils.getDirname(file)
+            : FileUtils.getBasename(file);
+  }
+}
diff --git a/src/jalview/bin/argparser/BootstrapArgs.java b/src/jalview/bin/argparser/BootstrapArgs.java
new file mode 100644 (file)
index 0000000..4829ef8
--- /dev/null
@@ -0,0 +1,209 @@
+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);
+  }
+
+  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..6708cf9
--- /dev/null
@@ -0,0 +1,150 @@
+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 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 f0e477c..a276fb6 100644 (file)
@@ -305,7 +305,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 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 1521d0a..cd62c9f 100644 (file)
@@ -808,7 +808,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 +835,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);
 
-        new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
+          FileFormatI format = new IdentifyFile().identify(file,
+                  DataSourceType.PASTE);
 
+          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 +1478,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 449c685..971aba3 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();
+    }
   }
 
   /**
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 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..9b56c64
--- /dev/null
@@ -0,0 +1,179 @@
+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 the first --open or --opennew value. 
+   * Used primarily for substitutions in output filenames.
+   */
+  public static String getBasename(File file)
+  {
+    if (file == null)
+      return null;
+
+    String basename = null;
+    String filename = file.getName();
+    int lastDot = filename.lastIndexOf('.');
+    if (lastDot > 0) // don't truncate if starts with '.'
+    {
+      basename = filename.substring(0, lastDot);
+    }
+    else
+    {
+      basename = filename;
+    }
+    return basename;
+  }
+
+  /*
+   * This method returns the dirname of the first --open or --opennew 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 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..d0538c1 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
@@ -318,7 +319,7 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
       if (ssm != null)
       {
         String structFilename = isStructId ? ssm.findFileForPDBId(id) : id;
-        addPAEToStructure(ssm, structFilename, pae);
+        addPAEToStructure(ssm, structFilename, pae, label);
       }
 
     }
@@ -328,7 +329,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 +348,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 +371,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 +402,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 +429,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 +448,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;
@@ -470,21 +475,21 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
   }
 
   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,11 +499,14 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
       return false;
     }
 
-    ContactMatrixI matrix = new PAEContactMatrix(sm.getSequence(),
+    SequenceI seq = sm.getSequence();
+    Console.debug("##### SEQUENCE FOUND=" + seq.getName());
+    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);
+    seq.addAlignmentAnnotation(cmannot);
+    // seq.addAlignmentAnnotation(cmannot);
 
     return true;
   }
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..c6d3332 100644 (file)
@@ -223,7 +223,7 @@ public class CommandLineOperations
   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);
@@ -341,9 +341,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!",
diff --git a/test/jalview/bin/CommandsTest.java b/test/jalview/bin/CommandsTest.java
new file mode 100644 (file)
index 0000000..fda047c
--- /dev/null
@@ -0,0 +1,217 @@
+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";
+
+  private static final String png1 = testfiles + "/dir1/test1.png";
+
+  private static final String png2 = testfiles + "/dir2/test1.png";
+
+  @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);
+
+    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 --open examples/uniref50.fa --image "
+                + testfiles + "/{basename}.png",
+            new String[]
+            { testfiles + "/uniref50.png" } },
+        { "--open examples/uniref50.fa --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[][] {
+        { "--open=examples/uniref50.fa", true, 1, someUniref50Seqs },
+        { "--open examples/uniref50.fa", true, 1, someUniref50Seqs },
+        { "--open=examples/uniref50*.fa", true, 1, someUniref50Seqs },
+        // NOTE we cannot use shell expansion in tests, so list all files!
+        { "--open examples/uniref50.fa examples/uniref50_mz.fa", true, 1,
+            someUniref50Seqs },
+        { "--open=[new]examples/uniref50*.fa", true, 2, someUniref50Seqs },
+        { "--opennew=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, 4,
+            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();
+      }
+    }
+  }
+
+}
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..6d7f88f
--- /dev/null
@@ -0,0 +1,231 @@
+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.DataProvider;
+import org.testng.annotations.Test;
+
+import jalview.bin.Cache;
+
+@Test(singleThreaded = true)
+public class ArgParserTest
+{
+  @AfterClass(alwaysRun = true)
+  public static void resetProps()
+  {
+    Cache.loadProperties("test/jalview/testProps.jvprops");
+  }
+
+  @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.linkedArgs(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)
+  {
+    // { "--open=filename0 --increment --open=filename1", "JALVIEW:1", Arg.OPEN,
+    // "filename1" },
+    String[] args = commandLineArgs.split("\\s+");
+    ArgParser argparser = new ArgParser(args);
+    ArgValuesMap avm = argparser.linkedArgs(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.OPEN));
+    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[][] { {
+        "--open=test/jalview/bin/argparser/testfiles/test1.fa --props=test/jalview/bin/argparser/testfiles/testProps.jvprops",
+        Arg.PROPS, "true" },
+        { "--debug --open=test/jalview/bin/argparser/testfiles/test1.fa",
+            Arg.DEBUG, null },
+        { "--open=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 --open=[hi]test/jalview/bin/argparser/testfiles/test1.fa",
+        "JALVIEW:0", Arg.OPEN, "hi", "true", true },
+        { "--open[linkedId1]=[new;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId1", Arg.OPEN, "new", "true", true },
+        { "--open[linkedId2]=[new;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId2", Arg.OPEN, "hello", "world", true },
+        { "--open[linkedId3]=[new;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId3", Arg.OPEN, "GETINDEX", "1", true },
+        { "--open[linkedId4]=[new;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --open[linkedId5]=[notnew;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId5", Arg.OPEN, "new", "true", false },
+        { "--open[linkedId5]=[new;hello=worlddomination;1]test/jalview/bin/argparser/testfiles/test1.fa --open[linkedId2]=[new;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId5", Arg.OPEN, "hello", "world", false },
+        { "--open[linkedId6]=[new;hello=world;0]test/jalview/bin/argparser/testfiles/test1.fa --open[linkedId7]=[new;hello=world;1]test/jalview/bin/argparser/testfiles/test1.fa --headless",
+            "linkedId7", Arg.OPEN, "GETINDEX", "0", false }, };
+  }
+
+  @DataProvider(name = "argAutoIndexAndSubstitutions")
+  public Object[][] argAutoIndexAndSubstitutions()
+  {
+    return new Object[][] { { "--open=filename0 --open=filename1",
+        "JALVIEW:0", Arg.OPEN, "filename0" },
+        { "--open=filename0 --increment --open=filename1", "JALVIEW:1",
+            Arg.OPEN, "filename1" },
+        { "--open=filename0 --increment --increment --open=filename2",
+            "JALVIEW:0", Arg.OPEN, "filename0" },
+        { "--open=filename0 --increment --increment --open=filename2",
+            "JALVIEW:2", Arg.OPEN, "filename2" },
+        { "--open[linkA-{n}]=filenameA0 --open[linkA-{++n}]=filenameA1",
+            "linkA-0", Arg.OPEN, "filenameA0" },
+        { "--open[linkB-{n}]=filenameB0 --open[linkB-{++n}]=filenameB1",
+            "linkB-1", Arg.OPEN, "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" } };
+  }
+}
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..7eaf95e
--- /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
+--open[{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..6e5185e
--- /dev/null
@@ -0,0 +1,2 @@
+--open=test/jalview/bin/argparser/testfiles/test1.fa
+--open=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..bb33f21
--- /dev/null
@@ -0,0 +1,4 @@
+--open[all]=test/jalview/bin/argparser/testfiles/test1.fa
+--open[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..cd6b31b
--- /dev/null
@@ -0,0 +1,6 @@
+--substitutions
+--increment
+--open={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..cd6b31b
--- /dev/null
@@ -0,0 +1,6 @@
+--substitutions
+--increment
+--open={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..cd6b31b
--- /dev/null
@@ -0,0 +1,6 @@
+--substitutions
+--increment
+--open={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
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 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 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);
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" }, //
+    };
+  }
+}