JAL-629 correct a timeout. Correct passing html flag.
[jalview.git] / src / jalview / bin / Jalview.java
index 763b10b..7518fce 100755 (executable)
  */
 package jalview.bin;
 
-import groovy.lang.Binding;
-import groovy.util.GroovyScriptEngine;
-
-import jalview.ext.so.SequenceOntology;
-import jalview.gui.AlignFrame;
-import jalview.gui.Desktop;
-import jalview.gui.PromptUserConfig;
-import jalview.io.AppletFormatAdapter;
-import jalview.io.BioJsHTMLOutput;
-import jalview.io.FileLoader;
-import jalview.io.FormatAdapter;
-import jalview.io.HtmlSvgOutput;
-import jalview.io.IdentifyFile;
-import jalview.io.NewickFile;
-import jalview.io.gff.SequenceOntologyFactory;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ColourSchemeProperty;
-import jalview.schemes.UserColourScheme;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-import jalview.ws.jws2.Jws2Discoverer;
-
+import java.awt.Color;
 import java.io.BufferedReader;
 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;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.security.AllPermission;
 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;
 import java.util.Vector;
-
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+import javax.swing.UnsupportedLookAndFeelException;
+
+import com.formdev.flatlaf.FlatLightLaf;
+import com.formdev.flatlaf.themes.FlatMacLightLaf;
+import com.formdev.flatlaf.util.SystemInfo;
+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.Opt;
+import jalview.bin.argparser.Arg.Type;
+import jalview.bin.argparser.ArgParser;
+import jalview.bin.argparser.BootstrapArgs;
+import jalview.ext.so.SequenceOntology;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.PromptUserConfig;
+import jalview.gui.QuitHandler;
+import jalview.gui.QuitHandler.QResponse;
+import jalview.gui.StructureViewerBase;
+import jalview.io.AppletFormatAdapter;
+import jalview.io.BioJsHTMLOutput;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
+import jalview.io.FileFormatException;
+import jalview.io.FileFormatI;
+import jalview.io.FileFormats;
+import jalview.io.FileLoader;
+import jalview.io.HtmlSvgOutput;
+import jalview.io.IdentifyFile;
+import jalview.io.NewickFile;
+import jalview.io.exceptions.ImageOutputException;
+import jalview.io.gff.SequenceOntologyFactory;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeProperty;
+import jalview.util.ChannelProperties;
+import jalview.util.HttpUtils;
+import jalview.util.LaunchUtils;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.ws.jws2.Jws2Discoverer;
 
 /**
  * Main class for Jalview Application <br>
  * <br>
- * start with java -Djava.ext.dirs=$PATH_TO_LIB$ jalview.bin.Jalview
+ * start with: java -classpath "$PATH_TO_LIB$/*:$PATH_TO_CLASSES$" \
+ * jalview.bin.Jalview
+ * 
+ * or on Windows: java -classpath "$PATH_TO_LIB$/*;$PATH_TO_CLASSES$" \
+ * jalview.bin.Jalview jalview.bin.Jalview
+ * 
+ * (ensure -classpath arg is quoted to avoid shell expansion of '*' and do not
+ * embellish '*' to e.g. '*.jar')
  * 
  * @author $author$
  * @version $Revision$
  */
 public class Jalview
 {
+  static
+  {
+    Platform.getURLCommandArguments();
+    Platform.addJ2SDirectDatabaseCall("https://www.jalview.org");
+    Platform.addJ2SDirectDatabaseCall("http://www.jalview.org");
+    Platform.addJ2SDirectDatabaseCall("http://www.compbio.dundee.ac.uk");
+    Platform.addJ2SDirectDatabaseCall("https://www.compbio.dundee.ac.uk");
+  }
+
   /*
    * singleton instance of this class
    */
@@ -80,26 +136,47 @@ public class Jalview
 
   private Desktop desktop;
 
+  protected Commands cmds;
+
   public static AlignFrame currentAlignFrame;
 
+  private ArgParser argparser = null;
+
+  private BootstrapArgs bootstrapArgs = null;
+
+  private boolean QUIET = false;
+
+  public static boolean quiet()
+  {
+    return Jalview.getInstance() != null && Jalview.getInstance().QUIET;
+  }
+
   static
   {
-    // grab all the rights we can the JVM
-    Policy.setPolicy(new Policy()
+    if (!Platform.isJS())
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
     {
-      @Override
-      public PermissionCollection getPermissions(CodeSource codesource)
+      // grab all the rights we can for the JVM
+      Policy.setPolicy(new Policy()
       {
-        Permissions perms = new Permissions();
-        perms.add(new AllPermission());
-        return (perms);
-      }
+        @Override
+        public PermissionCollection getPermissions(CodeSource codesource)
+        {
+          Permissions perms = new Permissions();
+          perms.add(new AllPermission());
+          return (perms);
+        }
 
-      @Override
-      public void refresh()
-      {
-      }
-    });
+        @Override
+        public void refresh()
+        {
+        }
+      });
+    }
   }
 
   /**
@@ -111,8 +188,8 @@ public class Jalview
   class FeatureFetcher
   {
     /*
-     * TODO: generalise to track all jalview events to orchestrate batch
-     * processing events.
+     * TODO: generalise to track all jalview events to orchestrate batch processing
+     * events.
      */
 
     private int queued = 0;
@@ -145,7 +222,6 @@ public class Jalview
           af.setProgressBar(MessageManager
                   .getString("status.das_features_being_retrived"), id);
           af.featureSettings_actionPerformed(null);
-          af.featureSettings.fetchDasFeatures(dasSources, true);
           af.setProgressBar(null, id);
           synchronized (us)
           {
@@ -175,321 +251,701 @@ public class Jalview
    */
   public static void main(String[] args)
   {
+    // setLogging(); // BH - for event debugging in JavaScript
     instance = new Jalview();
     instance.doMain(args);
   }
 
-  /**
-   * @param args
-   */
-  void doMain(String[] args)
+  private static void logClass(String name)
   {
-    System.setSecurityManager(null);
-    System.out.println("Java version: "
-            + System.getProperty("java.version"));
-    System.out.println(System.getProperty("os.arch") + " "
-            + System.getProperty("os.name") + " "
-            + System.getProperty("os.version"));
+    // BH - for event debugging in JavaScript
+    ConsoleHandler consoleHandler = new ConsoleHandler();
+    consoleHandler.setLevel(Level.ALL);
+    Logger logger = Logger.getLogger(name);
+    logger.setLevel(Level.ALL);
+    logger.addHandler(consoleHandler);
+  }
 
-    ArgsParser aparser = new ArgsParser(args);
-    boolean headless = false;
+  @SuppressWarnings("unused")
+  private static void setLogging()
+  {
 
-    if (aparser.contains("help") || aparser.contains("h"))
+    /**
+     * @j2sIgnore
+     * 
+     */
     {
-      showUsage();
-      System.exit(0);
+      Console.outPrintln("not in js");
     }
-    if (aparser.contains("nodisplay") || aparser.contains("nogui")
-            || aparser.contains("headless"))
+
+    // BH - for event debugging in JavaScript (Java mode only)
+    if (!Platform.isJS())
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
     {
-      System.setProperty("java.awt.headless", "true");
-      headless = true;
+      Logger.getLogger("").setLevel(Level.ALL);
+      logClass("java.awt.EventDispatchThread");
+      logClass("java.awt.EventQueue");
+      logClass("java.awt.Component");
+      logClass("java.awt.focus.Component");
+      logClass("java.awt.focus.DefaultKeyboardFocusManager");
     }
-    String usrPropsFile = aparser.getValue("props");
-    Cache.loadProperties(usrPropsFile); // must do this before
-    if (usrPropsFile != null)
+
+  }
+
+  /**
+   * @param args
+   */
+  void doMain(String[] args)
+  {
+    if (!Platform.isJS())
+    {
+      System.setSecurityManager(null);
+    }
+
+    if (args == null || args.length == 0 || (args.length == 1
+            && (args[0] == null || args[0].length() == 0)))
     {
-      System.out.println("CMD [-props " + usrPropsFile
-              + "] executed successfully!");
+      args = new String[] {};
     }
 
-    // anything else!
+    // get args needed before proper ArgParser
+    bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
 
-    final String jabawsUrl = aparser.getValue("jabaws");
-    if (jabawsUrl != null)
+    if (!Platform.isJS())
     {
-      try
+      // are we being --quiet ?
+      if (bootstrapArgs.contains(Arg.QUIET))
       {
-        Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
-        System.out.println("CMD [-jabaws " + jabawsUrl
-                + "] executed successfully!");
-      } catch (MalformedURLException e)
+        QUIET = true;
+        OutputStream devNull = new OutputStream()
+        {
+
+          @Override
+          public void write(int b)
+          {
+            // DO NOTHING
+          }
+        };
+        System.setOut(new PrintStream(devNull));
+        // redirecting stderr not working
+        if (bootstrapArgs.getList(Arg.QUIET).size() > 1)
+        {
+          System.setErr(new PrintStream(devNull));
+        }
+      }
+
+      if (bootstrapArgs.contains(Arg.HELP)
+              || bootstrapArgs.contains(Arg.VERSION))
       {
-        System.err.println("Invalid jabaws parameter: " + jabawsUrl
-                + " ignored");
+        QUIET = true;
       }
     }
 
-    String defs = aparser.getValue("setprop");
-    while (defs != null)
+    // set individual session preferences
+    if (bootstrapArgs.contains(Arg.P))
     {
-      int p = defs.indexOf('=');
-      if (p == -1)
+      for (String kev : bootstrapArgs.getValueList(Arg.P))
       {
-        System.err.println("Ignoring invalid setprop argument : " + defs);
+        if (kev == null)
+        {
+          continue;
+        }
+        int equalsIndex = kev.indexOf(ArgParser.EQUALS);
+        if (equalsIndex > -1)
+        {
+          String key = kev.substring(0, equalsIndex);
+          String val = kev.substring(equalsIndex + 1);
+          Cache.setSessionProperty(key, val);
+        }
       }
-      else
+    }
+
+    // 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()
       {
-        System.out.println("Executing setprop argument: " + defs);
-        // 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));
-      }
-      defs = aparser.getValue("setprop");
+
+        @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();
     }
-    if (System.getProperty("java.awt.headless") != null
-            && System.getProperty("java.awt.headless").equals("true"))
+
+    if (!quiet() || !bootstrapArgs.outputToStdout()
+            || bootstrapArgs.contains(Arg.VERSION))
     {
-      headless = true;
+      Console.outPrintln(
+              "Java version: " + System.getProperty("java.version"));
+      Console.outPrintln("Java home: " + System.getProperty("java.home"));
+      Console.outPrintln("Java arch: " + System.getProperty("os.arch") + " "
+              + System.getProperty("os.name") + " "
+              + System.getProperty("os.version"));
+
+      String val = System.getProperty("sys.install4jVersion");
+      if (val != null)
+      {
+        Console.outPrintln("Install4j version: " + val);
+      }
+      val = System.getProperty("installer_template_version");
+      if (val != null)
+      {
+        Console.outPrintln("Install4j template version: " + val);
+      }
+      val = System.getProperty("launcher_version");
+      if (val != null)
+      {
+        Console.outPrintln("Launcher version: " + val);
+      }
     }
-    System.setProperty("http.agent",
-            "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
-    try
+
+    if (Platform.isLinux() && LaunchUtils.getJavaVersion() < 11)
     {
-      Cache.initLogger();
-    } catch (NoClassDefFoundError error)
+      System.setProperty("flatlaf.uiScale", "1");
+    }
+
+    // get bootstrap properties (mainly for the logger level)
+    Properties bootstrapProperties = Cache
+            .bootstrapProperties(bootstrapArgs.getValue(Arg.PROPS));
+
+    // report Jalview version
+    Cache.loadBuildProperties(
+            !quiet() || bootstrapArgs.contains(Arg.VERSION));
+
+    // stop now if only after --version
+    if (bootstrapArgs.contains(Arg.VERSION))
     {
-      error.printStackTrace();
-      System.out
-              .println("\nEssential logging libraries not found."
-                      + "\nUse: java -Djava.ext.dirs=$PATH_TO_LIB$ jalview.bin.Jalview");
-      System.exit(0);
+      Jalview.exit(null, ExitCode.OK);
     }
 
-    desktop = null;
+    // old ArgsParser
+    ArgsParser aparser = new ArgsParser(args);
+
+    // old
+    boolean headless = false;
+    // new
+    boolean headlessArg = false;
 
     try
     {
-      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-    } catch (Exception ex)
+      String logLevel = null;
+      if (bootstrapArgs.contains(Arg.TRACE))
+      {
+        logLevel = "TRACE";
+      }
+      else if (bootstrapArgs.contains(Arg.DEBUG))
+      {
+        logLevel = "DEBUG";
+      }
+      if (logLevel == null && !(bootstrapProperties == null))
+      {
+        logLevel = bootstrapProperties.getProperty(Cache.JALVIEWLOGLEVEL);
+      }
+      Console.initLogger(logLevel);
+    } catch (NoClassDefFoundError error)
     {
+      error.printStackTrace();
+      String message = "\nEssential logging libraries not found."
+              + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
+      Jalview.exit(message, ExitCode.OK);
     }
-    if (Platform.isAMac())
+
+    // register SIGTERM listener
+    Runtime.getRuntime().addShutdownHook(new Thread()
     {
-      System.setProperty("com.apple.mrj.application.apple.menu.about.name",
-              "Jalview");
-      System.setProperty("apple.laf.useScreenMenuBar", "true");
-      try
-      {
-        UIManager.setLookAndFeel(ch.randelshofer.quaqua.QuaquaManager
-                .getLookAndFeel());
-      } catch (Throwable e)
+      @Override
+      public void run()
       {
-        System.err.println("Failed to set QuaQua look and feel: "
-                + e.toString());
+        Console.debug("Running shutdown hook");
+        QuitHandler.startForceQuit();
+        boolean closeExternal = Cache
+                .getDefault("DEFAULT_CLOSE_EXTERNAL_VIEWERS", false)
+                || Cache.getDefault("ALWAYS_CLOSE_EXTERNAL_VIEWERS", false);
+        StructureViewerBase.setQuitClose(closeExternal);
+        if (desktop != null)
+        {
+          for (JInternalFrame frame : Desktop.desktop.getAllFrames())
+          {
+            if (frame instanceof StructureViewerBase)
+            {
+              ((StructureViewerBase) frame).closeViewer(closeExternal);
+            }
+          }
+        }
+
+        if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
+        {
+          // Got to here by a SIGTERM signal.
+          // Note we will not actually cancel the quit from here -- it's too
+          // late -- but we can wait for saving files and close external viewers
+          // if configured.
+          // Close viewers/Leave viewers open
+          Console.debug("Checking for saving files");
+          QuitHandler.getQuitResponse(false);
+        }
+        else
+        {
+          Console.debug("Nothing more to do");
+        }
+        Console.debug("Exiting, bye!");
+        // shutdownHook cannot be cancelled, JVM will now halt
       }
+    });
+
+    String usrPropsFile = bootstrapArgs.contains(Arg.PROPS)
+            ? bootstrapArgs.getValue(Arg.PROPS)
+            : aparser.getValue("props");
+    // if usrPropsFile == null, loadProperties will use the Channel
+    // preferences.file
+    Cache.loadProperties(usrPropsFile);
+    if (usrPropsFile != null)
+    {
+      Console.outPrintln(
+              "CMD [-props " + usrPropsFile + "] executed successfully!");
+      testoutput(bootstrapArgs, Arg.PROPS,
+              "test/jalview/bin/testProps.jvprops", usrPropsFile);
     }
 
-    /*
-     * configure 'full' SO model if preferences say to, 
-     * else use the default (SO Lite)
-     */
-    if (Cache.getDefault("USE_FULL_SO", false))
+    // --argfile=... -- OVERRIDES ALL NON-BOOTSTRAP ARGS
+    if (bootstrapArgs.contains(Arg.ARGFILE))
     {
-      SequenceOntologyFactory.setInstance(new SequenceOntology());
+      argparser = ArgParser.parseArgFiles(
+              bootstrapArgs.getValueList(Arg.ARGFILE),
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
+              bootstrapArgs);
+    }
+    else
+    {
+      argparser = new ArgParser(args,
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
+              bootstrapArgs);
     }
 
-    if (!headless)
+    if (!Platform.isJS())
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
     {
-      desktop = new Desktop();
-      desktop.setInBatchMode(true); // indicate we are starting up
-      desktop.setVisible(true);
-      desktop.startServiceDiscovery();
-      if (!aparser.contains("nousagestats"))
+      if (bootstrapArgs.contains(Arg.HELP))
       {
-        startUsageStats(desktop);
+        List<Map.Entry<Type, String>> helpArgs = bootstrapArgs
+                .getList(Arg.HELP);
+        Console.outPrintln(Arg.usage(helpArgs.stream().map(e -> e.getKey())
+                .collect(Collectors.toList())));
+        Jalview.exit(null, ExitCode.OK);
       }
-      else
+      if (aparser.contains("help") || aparser.contains("h"))
       {
-        System.err.println("CMD [-nousagestats] executed successfully!");
+        /*
+         * Now using new usage statement.
+        showUsage();
+        */
+        Console.outPrintln(Arg.usage());
+        Jalview.exit(null, ExitCode.OK);
       }
 
-      if (!aparser.contains("noquestionnaire"))
+      // new CLI
+      headlessArg = bootstrapArgs.isHeadless();
+      if (headlessArg)
       {
-        String url = aparser.getValue("questionnaire");
-        if (url != null)
+        System.setProperty("java.awt.headless", "true");
+      }
+      // old CLI
+      if (aparser.contains("nodisplay") || aparser.contains("nogui")
+              || aparser.contains("headless"))
+      {
+        System.setProperty("java.awt.headless", "true");
+        headless = true;
+      }
+      // anything else!
+
+      // allow https handshakes to download intermediate certs if necessary
+      System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
+
+      String jabawsUrl = bootstrapArgs.getValue(Arg.JABAWS);
+      if (jabawsUrl == null)
+        jabawsUrl = aparser.getValue("jabaws");
+      if (jabawsUrl != null)
+      {
+        try
         {
-          // Start the desktop questionnaire prompter with the specified
-          // questionnaire
-          Cache.log.debug("Starting questionnaire url at " + url);
-          desktop.checkForQuestionnaire(url);
-          System.out.println("CMD questionnaire[-" + url
-                  + "] executed successfully!");
-        }
-        else
+          Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
+          Console.outPrintln(
+                  "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
+          testoutput(bootstrapArgs, Arg.JABAWS,
+                  "http://www.compbio.dundee.ac.uk/jabaws", jabawsUrl);
+        } catch (MalformedURLException e)
         {
-          if (Cache.getProperty("NOQUESTIONNAIRES") == null)
-          {
-            // Start the desktop questionnaire prompter with the specified
-            // questionnaire
-            // String defurl =
-            // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
-            // //
-            String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
-            Cache.log.debug("Starting questionnaire with default url: "
-                    + defurl);
-            desktop.checkForQuestionnaire(defurl);
-          }
+          jalview.bin.Console.errPrintln(
+                  "Invalid jabaws parameter: " + jabawsUrl + " ignored");
         }
       }
-      else
+    }
+
+    List<String> setprops = new ArrayList<>();
+    if (bootstrapArgs.contains(Arg.SETPROP))
+    {
+      setprops = bootstrapArgs.getValueList(Arg.SETPROP);
+    }
+    else
+    {
+      String sp = aparser.getValue("setprop");
+      while (sp != null)
       {
-        System.err.println("CMD [-noquestionnaire] executed successfully!");
+        setprops.add(sp);
+        sp = aparser.getValue("setprop");
       }
-
-      if (!aparser.contains("nonews"))
+    }
+    for (String setprop : setprops)
+    {
+      int p = setprop.indexOf('=');
+      if (p == -1)
       {
-        desktop.checkForNews();
+        System.err
+                .println("Ignoring invalid setprop argument : " + setprop);
       }
+      else
+      {
+        jalview.bin.Console
+                .errPrintln("Executing setprop argument: " + setprop);
+        if (Platform.isJS())
+        {
+          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(setprop.substring(0,p), setprop.substring(p+1));
+      }
+    }
+    if (System.getProperty("java.awt.headless") != null
+            && System.getProperty("java.awt.headless").equals("true"))
+    {
+      headless = true;
+    }
+    System.setProperty("http.agent", HttpUtils.getUserAgent());
 
-      BioJsHTMLOutput.updateBioJS();
+    try
+    {
+      Console.initLogger();
+    } catch (NoClassDefFoundError error)
+    {
+      error.printStackTrace();
+      String message = "\nEssential logging libraries not found."
+              + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
+      Jalview.exit(message, ExitCode.NO_LOGGING);
     }
+    desktop = null;
 
-    String file = null, protocol = null, format = null, data = null;
-    FileLoader fileLoader = new FileLoader(!headless);
-    Vector<String> getFeatures = null; // vector of das source nicknames to
-                                       // fetch
-    // features from
-    // loading is done.
-    String groovyscript = null; // script to execute after all loading is
-    // completed one way or another
-    // extract groovy argument and execute if necessary
-    groovyscript = aparser.getValue("groovy", true);
-    file = aparser.getValue("open", true);
+    if (!(headless || headlessArg))
+      setLookAndFeel();
 
-    if (file == null && desktop == null)
+    /*
+     * configure 'full' SO model if preferences say to, else use the default (full SO)
+     * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
+     */
+    boolean soDefault = !Platform.isJS();
+    if (Cache.getDefault("USE_FULL_SO", soDefault))
     {
-      System.out.println("No files to open!");
-      System.exit(1);
+      SequenceOntologyFactory.setInstance(new SequenceOntology());
     }
-    String vamsasImport = aparser.getValue("vdoc");
-    String vamsasSession = aparser.getValue("vsess");
-    if (vamsasImport != null || vamsasSession != null)
+
+    if (!(headless || headlessArg))
     {
-      if (desktop == null || headless)
+      Desktop.nosplash = "false".equals(bootstrapArgs.getValue(Arg.SPLASH))
+              || aparser.contains("nosplash")
+              || Cache.getDefault("SPLASH", "true").equals("false");
+      desktop = new Desktop();
+      desktop.setInBatchMode(true); // indicate we are starting up
+
+      mixedCliWarning();
+
+      try
+      {
+        JalviewTaskbar.setTaskbar(this);
+      } catch (Exception e)
       {
-        System.out
-                .println("Headless vamsas sessions not yet supported. Sorry.");
-        System.exit(1);
+        Console.info("Cannot set Taskbar");
+        Console.error(e.getMessage());
+        // e.printStackTrace();
+      } catch (Throwable t)
+      {
+        Console.info("Cannot set Taskbar");
+        Console.error(t.getMessage());
+        // t.printStackTrace();
       }
-      // if we have a file, start a new session and import it.
-      boolean inSession = false;
-      if (vamsasImport != null)
+
+      // set Proxy settings before all the internet calls
+      Cache.setProxyPropertiesFromPreferences();
+
+      desktop.setVisible(true);
+
+      if (!Platform.isJS())
+      /**
+       * Java only
+       * 
+       * @j2sIgnore
+       */
       {
-        try
+
+        /**
+         * Check to see that the JVM version being run is suitable for the Java
+         * version this Jalview was compiled for. Popup a warning if not.
+         */
+        if (!LaunchUtils.checkJavaVersion())
         {
-          String viprotocol = AppletFormatAdapter
-                  .checkProtocol(vamsasImport);
-          if (viprotocol == jalview.io.FormatAdapter.FILE)
-          {
-            inSession = desktop.vamsasImport(new File(vamsasImport));
-          }
-          else if (viprotocol == FormatAdapter.URL)
+          Console.warn("The Java version being used (Java "
+                  + LaunchUtils.getJavaVersion()
+                  + ") may lead to problems. This installation of Jalview should be used with Java "
+                  + LaunchUtils.getJavaCompileVersion() + ".");
+
+          if (!LaunchUtils
+                  .getBooleanUserPreference("IGNORE_JVM_WARNING_POPUP"))
           {
-            inSession = desktop.vamsasImport(new URL(vamsasImport));
+            Object[] options = {
+                MessageManager.getString("label.continue") };
+            JOptionPane.showOptionDialog(null,
+                    MessageManager.formatMessage(
+                            "warning.wrong_jvm_version_message",
+                            LaunchUtils.getJavaVersion(),
+                            LaunchUtils.getJavaCompileVersion()),
+                    MessageManager
+                            .getString("warning.wrong_jvm_version_title"),
+                    JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
+                    null, options, options[0]);
           }
+        }
 
-        } catch (Exception e)
+        boolean webservicediscovery = bootstrapArgs
+                .getBoolean(Arg.WEBSERVICEDISCOVERY);
+        if (aparser.contains("nowebservicediscovery"))
+          webservicediscovery = false;
+        if (webservicediscovery)
         {
-          System.err.println("Exeption when importing " + vamsasImport
-                  + " as a vamsas document.");
-          e.printStackTrace();
+          desktop.startServiceDiscovery();
         }
-        if (!inSession)
+        else
         {
-          System.err.println("Failed to import " + vamsasImport
-                  + " as a vamsas document.");
+          testoutput(argparser, Arg.WEBSERVICEDISCOVERY);
         }
-        else
+
+        boolean usagestats = !bootstrapArgs.getBoolean(Arg.NOUSAGESTATS);
+        if (aparser.contains("nousagestats"))
+          usagestats = false;
+        if (usagestats)
         {
-          System.out.println("Imported Successfully into new session "
-                  + desktop.getVamsasApplication().getCurrentSession());
+          startUsageStats(desktop);
+          testoutput(argparser, Arg.NOUSAGESTATS);
         }
-      }
-      if (vamsasSession != null)
-      {
-        if (vamsasImport != null)
+        else
         {
-          // close the newly imported session and import the Jalview specific
-          // remnants into the new session later on.
-          desktop.vamsasStop_actionPerformed(null);
+          Console.outPrintln("CMD [-nousagestats] executed successfully!");
+          testoutput(argparser, Arg.NOUSAGESTATS);
         }
-        // now join the new session
-        try
+
+        boolean questionnaire = bootstrapArgs.getBoolean(Arg.QUESTIONNAIRE);
+        if (aparser.contains("noquestionnaire"))
+          questionnaire = false;
+        if (questionnaire)
         {
-          if (desktop.joinVamsasSession(vamsasSession))
+          String url = aparser.getValue("questionnaire");
+          if (url != null)
           {
-            System.out.println("Successfully joined vamsas session "
-                    + vamsasSession);
+            // Start the desktop questionnaire prompter with the specified
+            // questionnaire
+            Console.debug("Starting questionnaire url at " + url);
+            desktop.checkForQuestionnaire(url);
+            Console.outPrintln("CMD questionnaire[-" + url
+                    + "] executed successfully!");
           }
           else
           {
-            System.err.println("WARNING: Failed to join vamsas session "
-                    + vamsasSession);
+            if (Cache.getProperty("NOQUESTIONNAIRES") == null)
+            {
+              // Start the desktop questionnaire prompter with the specified
+              // questionnaire
+              // String defurl =
+              // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
+              // //
+              String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
+              Console.debug(
+                      "Starting questionnaire with default url: " + defurl);
+              desktop.checkForQuestionnaire(defurl);
+            }
           }
-        } catch (Exception e)
+        }
+        else
+        {
+          Console.outPrintln(
+                  "CMD [-noquestionnaire] executed successfully!");
+          testoutput(argparser, Arg.QUESTIONNAIRE);
+        }
+
+        if ((!aparser.contains("nonews")
+                && Cache.getProperty("NONEWS") == null
+                && !"false".equals(bootstrapArgs.getValue(Arg.NEWS)))
+                || "true".equals(bootstrapArgs.getValue(Arg.NEWS)))
+        {
+          desktop.checkForNews();
+        }
+
+        if (!aparser.contains("nohtmltemplates")
+                && Cache.getProperty("NOHTMLTEMPLATES") == null)
+        {
+          BioJsHTMLOutput.updateBioJS();
+        }
+      }
+    }
+    else
+    {
+
+      if (getArgParser().isMixedStyle())
+      {
+        String warning = MessageManager.formatMessage(
+                "warning.using_mixed_command_line_arguments",
+                getArgParser().getMixedExamples());
+        Console.warn(warning);
+        Jalview.exit(
+                "Exiting due to mixed old and new command line arguments",
+                ExitCode.INVALID_ARGUMENT);
+      }
+      if (getArgParser().isOldStyle())
+      {
+        String warning = MessageManager
+                .getString("warning.using_old_command_line_arguments")
+                .replace("\n", " ")
+                + "https://www.jalview.org/help/html/features/commandline.html";
+        Console.warn(warning);
+      }
+
+    }
+
+    // Run Commands from cli
+    cmds = new Commands(argparser, headlessArg);
+    cmds.processArgs();
+    boolean commandsSuccess = cmds.argsWereParsed();
+
+    if (commandsSuccess)
+    {
+      if (headlessArg)
+      {
+        if (argparser.getBoolean(Arg.NOQUIT))
         {
-          System.err.println("ERROR: Failed to join vamsas session "
-                  + vamsasSession);
-          e.printStackTrace();
+          Console.warn(
+                  "Completed " + Arg.HEADLESS.getName() + " commands, but "
+                          + Arg.NOQUIT + " is set so not quitting!");
         }
-        if (vamsasImport != null)
+        else
         {
-          // the Jalview specific remnants can now be imported into the new
-          // session at the user's leisure.
-          Cache.log
-                  .info("Skipping Push for import of data into existing vamsas session."); // TODO:
-          // enable
-          // this
-          // when
-          // debugged
-          // desktop.getVamsasApplication().push_update();
+          Jalview.exit("Successfully completed commands in headless mode",
+                  ExitCode.OK);
         }
       }
+      Console.info("Successfully completed commands");
+    }
+    else
+    {
+      if (headlessArg)
+      {
+        Jalview.exit("Error when running Commands in headless mode",
+                ExitCode.ERROR_RUNNING_COMMANDS);
+      }
+      Console.warn("Error when running commands");
+    }
+
+    // Check if JVM and compile version might cause problems and log if it
+    // might.
+    if (headless && !Platform.isJS() && !LaunchUtils.checkJavaVersion())
+    {
+      Console.warn("The Java version being used (Java "
+              + LaunchUtils.getJavaVersion()
+              + ") may lead to problems. This installation of Jalview should be used with Java "
+              + LaunchUtils.getJavaCompileVersion() + ".");
+    }
+
+    String file = null, data = null;
+
+    FileFormatI format = null;
+
+    DataSourceType protocol = null;
+
+    FileLoader fileLoader = new FileLoader(!headless);
+
+    String groovyscript = null; // script to execute after all loading is
+    // completed one way or another
+    // extract groovy argument and execute if necessary
+    groovyscript = aparser.getValue("groovy", true);
+    file = aparser.getValue("open", true);
+
+    if (file == null && desktop == null && !commandsSuccess)
+    {
+      Jalview.exit("No files to open!", ExitCode.NO_FILES);
     }
+
     long progress = -1;
     // Finally, deal with the remaining input data.
     if (file != null)
     {
       if (!headless)
       {
-        desktop.setProgressBar(MessageManager
-                .getString("status.processing_commandline_args"),
+        desktop.setProgressBar(
+                MessageManager
+                        .getString("status.processing_commandline_args"),
                 progress = System.currentTimeMillis());
       }
-      System.out.println("CMD [-open " + file + "] executed successfully!");
-
-      if (!file.startsWith("http://"))
+      Console.outPrintln("CMD [-open " + file + "] executed successfully!");
+
+      if (!Platform.isJS())
+      /**
+       * ignore in JavaScript -- can't just file existence - could load it?
+       * 
+       * @j2sIgnore
+       */
       {
-        if (!(new File(file)).exists())
+        if (!HttpUtils.startsWithHttpOrHttps(file))
         {
-          System.out.println("Can't find " + file);
-          if (headless)
+          if (!(new File(file)).exists())
           {
-            System.exit(1);
+            if (headless)
+            {
+              Jalview.exit(
+                      "Can't find file '" + file + "' in headless mode",
+                      ExitCode.FILE_NOT_FOUND);
+            }
+            Console.warn("Can't find file'" + file + "'");
           }
         }
       }
 
       protocol = AppletFormatAdapter.checkProtocol(file);
 
-      format = new IdentifyFile().identify(file, protocol);
+      try
+      {
+        format = new IdentifyFile().identify(file, protocol);
+      } catch (FileFormatException e1)
+      {
+        // TODO ?
+      }
 
       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
               format);
       if (af == null)
       {
-        System.out.println("error");
+        Console.outPrintln("error");
       }
       else
       {
@@ -499,19 +955,13 @@ public class Jalview
         {
           data.replaceAll("%20", " ");
 
-          ColourSchemeI cs = ColourSchemeProperty.getColour(af
-                  .getViewport().getAlignment(), data);
+          ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
+                  af.getViewport(), af.getViewport().getAlignment(), data);
 
-          if (cs == null)
+          if (cs != null)
           {
-            UserColourScheme ucs = new UserColourScheme("white");
-            ucs.parseAppletParameter(data);
-            cs = ucs;
-          }
-          else
-          {
-            System.out.println("CMD [-color " + data
-                    + "] executed successfully!");
+            Console.outPrintln(
+                    "CMD [-colour " + data + "] executed successfully!");
           }
           af.changeColour(cs);
         }
@@ -522,27 +972,27 @@ public class Jalview
         {
           af.parseFeaturesFile(data,
                   AppletFormatAdapter.checkProtocol(data));
-          // System.out.println("Added " + data);
-          System.out.println("CMD groups[-" + data
-                  + "]  executed successfully!");
+          // Console.outPrintln("Added " + data);
+          Console.outPrintln(
+                  "CMD groups[-" + data + "]  executed successfully!");
         }
         data = aparser.getValue("features", true);
         if (data != null)
         {
           af.parseFeaturesFile(data,
                   AppletFormatAdapter.checkProtocol(data));
-          // System.out.println("Added " + data);
-          System.out.println("CMD [-features " + data
-                  + "]  executed successfully!");
+          // Console.outPrintln("Added " + data);
+          Console.outPrintln(
+                  "CMD [-features " + data + "]  executed successfully!");
         }
 
         data = aparser.getValue("annotations", true);
         if (data != null)
         {
           af.loadJalviewDataFile(data, null, null, null);
-          // System.out.println("Added " + data);
-          System.out.println("CMD [-annotations " + data
-                  + "] executed successfully!");
+          // Console.outPrintln("Added " + data);
+          Console.outPrintln(
+                  "CMD [-annotations " + data + "] executed successfully!");
         }
         // set or clear the sortbytree flag.
         if (aparser.contains("sortbytree"))
@@ -550,7 +1000,7 @@ public class Jalview
           af.getViewport().setSortByTree(true);
           if (af.getViewport().getSortByTree())
           {
-            System.out.println("CMD [-sortbytree] executed successfully!");
+            Console.outPrintln("CMD [-sortbytree] executed successfully!");
           }
         }
         if (aparser.contains("no-annotation"))
@@ -558,7 +1008,7 @@ public class Jalview
           af.getViewport().setShowAnnotation(false);
           if (!af.getViewport().isShowAnnotation())
           {
-            System.out.println("CMD no-annotation executed successfully!");
+            Console.outPrintln("CMD no-annotation executed successfully!");
           }
         }
         if (aparser.contains("nosortbytree"))
@@ -566,211 +1016,573 @@ public class Jalview
           af.getViewport().setSortByTree(false);
           if (!af.getViewport().getSortByTree())
           {
-            System.out
-                    .println("CMD [-nosortbytree] executed successfully!");
+            Console.outPrintln(
+                    "CMD [-nosortbytree] executed successfully!");
           }
         }
         data = aparser.getValue("tree", true);
         if (data != null)
         {
-          jalview.io.NewickFile fin = null;
           try
           {
-            System.out.println("CMD [-tree " + data
-                    + "] executed successfully!");
-            fin = new NewickFile(data,
+            Console.outPrintln(
+                    "CMD [-tree " + data + "] executed successfully!");
+            NewickFile nf = new NewickFile(data,
                     AppletFormatAdapter.checkProtocol(data));
-            if (fin != null)
-            {
-              af.getViewport().setCurrentTree(
-                      af.ShowNewickTree(fin, data).getTree());
-            }
+            af.getViewport()
+                    .setCurrentTree(af.showNewickTree(nf, data).getTree());
           } catch (IOException ex)
           {
-            System.err.println("Couldn't add tree " + data);
+            jalview.bin.Console.errPrintln("Couldn't add tree " + data);
             ex.printStackTrace(System.err);
           }
         }
-        // TODO - load PDB structure(s) to alignment JAL-629
-        // (associate with identical sequence in alignment, or a specified
-        // sequence)
-
-        getFeatures = checkDasArguments(aparser);
-        if (af != null && getFeatures != null)
+
+        if (groovyscript != null)
+        {
+          // Execute the groovy script after we've done all the rendering stuff
+          // and before any images or figures are generated.
+          Console.outPrintln("Executing script " + groovyscript);
+          executeGroovyScript(groovyscript, af);
+          Console.outPrintln("CMD groovy[" + groovyscript
+                  + "] executed successfully!");
+          groovyscript = null;
+        }
+        String imageName = "unnamed.png";
+        while (aparser.getSize() > 1)
+        {
+          try
+          {
+            String outputFormat = aparser.nextValue();
+            file = aparser.nextValue();
+
+            if (outputFormat.equalsIgnoreCase("png"))
+            {
+              Console.outPrintln("Creating PNG image: " + file);
+              af.createPNG(new File(file));
+              imageName = (new File(file)).getName();
+              continue;
+            }
+            else if (outputFormat.equalsIgnoreCase("svg"))
+            {
+              Console.outPrintln("Creating SVG image: " + file);
+              File imageFile = new File(file);
+              imageName = imageFile.getName();
+              af.createSVG(imageFile);
+              continue;
+            }
+            else if (outputFormat.equalsIgnoreCase("html"))
+            {
+              File imageFile = new File(file);
+              imageName = imageFile.getName();
+              HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
+
+              Console.outPrintln("Creating HTML image: " + file);
+              htmlSVG.exportHTML(file);
+              continue;
+            }
+            else if (outputFormat.equalsIgnoreCase("biojsmsa"))
+            {
+              if (file == null)
+              {
+                jalview.bin.Console.errPrintln(
+                        "The output html file must not be null");
+                return;
+              }
+              try
+              {
+                BioJsHTMLOutput.refreshVersionInfo(
+                        BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
+              } catch (URISyntaxException e)
+              {
+                e.printStackTrace();
+              }
+              BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
+              Console.outPrintln(
+                      "Creating BioJS MSA Viwer HTML file: " + file);
+              bjs.exportHTML(file);
+              continue;
+            }
+            else if (outputFormat.equalsIgnoreCase("imgMap"))
+            {
+              Console.outPrintln("Creating image map: " + file);
+              af.createImageMap(new File(file), imageName);
+              continue;
+            }
+            else if (outputFormat.equalsIgnoreCase("eps"))
+            {
+              File outputFile = new File(file);
+              Console.outPrintln(
+                      "Creating EPS file: " + outputFile.getAbsolutePath());
+              af.createEPS(outputFile);
+              continue;
+            }
+
+            FileFormatI outFormat = null;
+            try
+            {
+              outFormat = FileFormats.getInstance().forName(outputFormat);
+            } catch (Exception formatP)
+            {
+              Console.outPrintln("Couldn't parse " + outFormat
+                      + " as a valid Jalview format string.");
+            }
+            if (outFormat != null)
+            {
+              if (!outFormat.isWritable())
+              {
+                Console.outPrintln(
+                        "This version of Jalview does not support alignment export as "
+                                + outputFormat);
+              }
+              else
+              {
+                af.saveAlignment(file, outFormat);
+                if (af.isSaveAlignmentSuccessful())
+                {
+                  Console.outPrintln("Written alignment in "
+                          + outFormat.getName() + " format to " + file);
+                }
+                else
+                {
+                  Console.outPrintln("Error writing file " + file + " in "
+                          + outFormat.getName() + " format!!");
+                }
+              }
+            }
+          } catch (ImageOutputException ioexc)
+          {
+            Console.outPrintln(
+                    "Unexpected error whilst exporting image to " + file);
+            ioexc.printStackTrace();
+          }
+
+        }
+
+        while (aparser.getSize() > 0)
+        {
+          Console.outPrintln("Unknown arg: " + aparser.nextValue());
+        }
+      }
+    }
+
+    AlignFrame startUpAlframe = null;
+    // We'll only open the default file if the desktop is visible.
+    // And the user
+    // ////////////////////
+
+    if (!Platform.isJS() && !headless && file == null
+            && Cache.getDefault("SHOW_STARTUP_FILE", true)
+            && !cmds.commandArgsProvided()
+            && !bootstrapArgs.getBoolean(Arg.NOSTARTUPFILE))
+    // don't open the startup file if command line args have been processed
+    // (&& !Commands.commandArgsProvided())
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
+    {
+      file = Cache.getDefault("STARTUP_FILE",
+              Cache.getDefault("www.jalview.org", "https://www.jalview.org")
+                      + "/examples/exampleFile_2_7.jvp");
+      if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
+              || file.equals(
+                      "http://www.jalview.org/examples/exampleFile_2_7.jar"))
+      {
+        file.replace("http:", "https:");
+        // hardwire upgrade of the startup file
+        file.replace("_2_3", "_2_7");
+        file.replace("2_7.jar", "2_7.jvp");
+        // and remove the stale setting
+        Cache.removeProperty("STARTUP_FILE");
+      }
+
+      protocol = AppletFormatAdapter.checkProtocol(file);
+
+      if (file.endsWith(".jar"))
+      {
+        format = FileFormat.Jalview;
+      }
+      else
+      {
+        try
+        {
+          format = new IdentifyFile().identify(file, protocol);
+        } catch (FileFormatException e)
+        {
+          // TODO what?
+        }
+      }
+
+      startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
+              format);
+      // don't ask to save when quitting if only the startup file has been
+      // opened
+      Console.debug("Resetting up-to-date flag for startup file");
+      startUpAlframe.getViewport().setSavedUpToDate(true);
+      // extract groovy arguments before anything else.
+    }
+
+    // Once all other stuff is done, execute any groovy scripts (in order)
+    if (groovyscript != null)
+    {
+      if (Cache.groovyJarsPresent())
+      {
+        Console.outPrintln("Executing script " + groovyscript);
+        executeGroovyScript(groovyscript, startUpAlframe);
+      }
+      else
+      {
+        jalview.bin.Console.errPrintln(
+                "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
+                        + groovyscript);
+      }
+    }
+    // and finally, turn off batch mode indicator - if the desktop still exists
+    if (desktop != null)
+    {
+      if (progress != -1)
+      {
+        desktop.setProgressBar(null, progress);
+      }
+      desktop.setInBatchMode(false);
+    }
+
+    cliWarning();
+  }
+
+  private static void setLookAndFeel()
+  {
+    if (!Platform.isJS())
+    /**
+     * Java only
+     * 
+     * @j2sIgnore
+     */
+    {
+      // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
+      // "mac" or "flat"
+      // If not set (or chosen laf fails), use the normal SystemLaF and if on
+      // Mac,
+      // try Quaqua/Vaqua.
+      String lafProp = System.getProperty("laf");
+      String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
+      String laf = "none";
+      if (lafProp != null)
+      {
+        laf = lafProp;
+      }
+      else if (lafSetting != null)
+      {
+        laf = lafSetting;
+      }
+      boolean lafSet = false;
+      switch (laf)
+      {
+      case "crossplatform":
+        lafSet = setCrossPlatformLookAndFeel();
+        if (!lafSet)
+        {
+          Console.error("Could not set requested laf=" + laf);
+        }
+        break;
+      case "system":
+        lafSet = setSystemLookAndFeel();
+        if (!lafSet)
+        {
+          Console.error("Could not set requested laf=" + laf);
+        }
+        break;
+      case "gtk":
+        lafSet = setGtkLookAndFeel();
+        if (!lafSet)
+        {
+          Console.error("Could not set requested laf=" + laf);
+        }
+        break;
+      case "metal":
+        lafSet = setMetalLookAndFeel();
+        if (!lafSet)
+        {
+          Console.error("Could not set requested laf=" + laf);
+        }
+        break;
+      case "nimbus":
+        lafSet = setNimbusLookAndFeel();
+        if (!lafSet)
         {
-          FeatureFetcher ff = startFeatureFetching(getFeatures);
-          if (ff != null)
-          {
-            while (!ff.allFinished() || af.operationInProgress())
-            {
-              // wait around until fetching is finished.
-              try
-              {
-                Thread.sleep(100);
-              } catch (Exception e)
-              {
-
-              }
-            }
-          }
-          getFeatures = null; // have retrieved features - forget them now.
+          Console.error("Could not set requested laf=" + laf);
         }
-        if (groovyscript != null)
+        break;
+      case "flat":
+        lafSet = setFlatLookAndFeel();
+        if (!lafSet)
         {
-          // Execute the groovy script after we've done all the rendering stuff
-          // and before any images or figures are generated.
-          System.out.println("Executing script " + groovyscript);
-          executeGroovyScript(groovyscript, af);
-          System.out.println("CMD groovy[" + groovyscript
-                  + "] executed successfully!");
-          groovyscript = null;
+          Console.error("Could not set requested laf=" + laf);
         }
-        String imageName = "unnamed.png";
-        while (aparser.getSize() > 1)
+        break;
+      case "mac":
+        lafSet = setMacLookAndFeel();
+        if (!lafSet)
         {
-          format = aparser.nextValue();
-          file = aparser.nextValue();
-
-          if (format.equalsIgnoreCase("png"))
-          {
-            af.createPNG(new File(file));
-            imageName = (new File(file)).getName();
-            System.out.println("Creating PNG image: " + file);
-            continue;
-          }
-          else if (format.equalsIgnoreCase("svg"))
-          {
-            File imageFile = new File(file);
-            imageName = imageFile.getName();
-            af.createSVG(imageFile);
-            System.out.println("Creating SVG image: " + file);
-            continue;
-          }
-          else if (format.equalsIgnoreCase("html"))
-          {
-            File imageFile = new File(file);
-            imageName = imageFile.getName();
-            new HtmlSvgOutput(new File(file), af.alignPanel);
-            System.out.println("Creating HTML image: " + file);
-            continue;
-          }
-          else if (format.equalsIgnoreCase("imgMap"))
-          {
-            af.createImageMap(new File(file), imageName);
-            System.out.println("Creating image map: " + file);
-            continue;
-          }
-          else if (format.equalsIgnoreCase("eps"))
-          {
-            File outputFile = new File(file);
-            System.out.println("Creating EPS file: "
-                    + outputFile.getAbsolutePath());
-            af.createEPS(outputFile);
-            continue;
-          }
-
-          if (af.saveAlignment(file, format))
-          {
-            System.out.println("Written alignment in " + format
-                    + " format to " + file);
-          }
-          else
-          {
-            System.out.println("Error writing file " + file + " in "
-                    + format + " format!!");
-          }
-
+          Console.error("Could not set requested laf=" + laf);
         }
-
-        while (aparser.getSize() > 0)
+        break;
+      case "none":
+        break;
+      default:
+        Console.error("Requested laf=" + laf + " not implemented");
+      }
+      if (!lafSet)
+      {
+        // Flatlaf default for everyone!
+        lafSet = setFlatLookAndFeel();
+        if (!lafSet)
         {
-          System.out.println("Unknown arg: " + aparser.nextValue());
+          setSystemLookAndFeel();
+        }
+        if (Platform.isLinux())
+        {
+          setLinuxLookAndFeel();
+        }
+        if (Platform.isMac())
+        {
+          setMacLookAndFeel();
         }
       }
     }
-    AlignFrame startUpAlframe = null;
-    // We'll only open the default file if the desktop is visible.
-    // And the user
-    // ////////////////////
+  }
 
-    if (!headless && file == null && vamsasImport == null
-            && jalview.bin.Cache.getDefault("SHOW_STARTUP_FILE", true))
+  private static boolean setCrossPlatformLookAndFeel()
+  {
+    boolean set = false;
+    try
     {
-      file = jalview.bin.Cache.getDefault(
-              "STARTUP_FILE",
-              jalview.bin.Cache.getDefault("www.jalview.org",
-                      "http://www.jalview.org")
-                      + "/examples/exampleFile_2_7.jar");
-      if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar"))
-      {
-        // hardwire upgrade of the startup file
-        file.replace("_2_3.jar", "_2_7.jar");
-        // and remove the stale setting
-        jalview.bin.Cache.removeProperty("STARTUP_FILE");
-      }
+      UIManager.setLookAndFeel(
+              UIManager.getCrossPlatformLookAndFeelClassName());
+      set = true;
+    } catch (Exception ex)
+    {
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
+    }
+    return set;
+  }
 
-      protocol = "File";
+  private static boolean setSystemLookAndFeel()
+  {
+    boolean set = false;
+    try
+    {
+      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+      set = true;
+    } catch (Exception ex)
+    {
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
+    }
+    return set;
+  }
 
-      if (file.indexOf("http:") > -1)
+  private static boolean setSpecificLookAndFeel(String name,
+          String className, boolean nameStartsWith)
+  {
+    boolean set = false;
+    try
+    {
+      for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
       {
-        protocol = "URL";
+        if (info.getName() != null && nameStartsWith
+                ? info.getName().toLowerCase(Locale.ROOT)
+                        .startsWith(name.toLowerCase(Locale.ROOT))
+                : info.getName().toLowerCase(Locale.ROOT)
+                        .equals(name.toLowerCase(Locale.ROOT)))
+        {
+          className = info.getClassName();
+          break;
+        }
       }
+      UIManager.setLookAndFeel(className);
+      set = true;
+    } catch (Exception ex)
+    {
+      Console.error("Unexpected Look and Feel Exception");
+      Console.error(ex.getMessage());
+      Console.debug(Cache.getStackTraceString(ex));
+    }
+    return set;
+  }
 
-      if (file.endsWith(".jar"))
+  private static boolean setGtkLookAndFeel()
+  {
+    return setSpecificLookAndFeel("gtk",
+            "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
+  }
+
+  private static boolean setMetalLookAndFeel()
+  {
+    return setSpecificLookAndFeel("metal",
+            "javax.swing.plaf.metal.MetalLookAndFeel", false);
+  }
+
+  private static boolean setNimbusLookAndFeel()
+  {
+    return setSpecificLookAndFeel("nimbus",
+            "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
+  }
+
+  private static boolean setFlatLookAndFeel()
+  {
+    boolean set = false;
+    if (SystemInfo.isMacOS)
+    {
+      try
+      {
+        UIManager.setLookAndFeel(
+                "com.formdev.flatlaf.themes.FlatMacLightLaf");
+        set = true;
+        Console.debug("Using FlatMacLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
       {
-        format = "Jalview";
+        Console.debug("Exception loading FlatLightLaf", e);
       }
-      else
+      System.setProperty("apple.laf.useScreenMenuBar", "true");
+      System.setProperty("apple.awt.application.name",
+              ChannelProperties.getProperty("app_name"));
+      System.setProperty("apple.awt.application.appearance", "system");
+      if (SystemInfo.isMacFullWindowContentSupported
+              && Desktop.desktop != null)
       {
-        format = new IdentifyFile().identify(file, protocol);
+        Console.debug("Setting transparent title bar");
+        Desktop.desktop.getRootPane()
+                .putClientProperty("apple.awt.fullWindowContent", true);
+        Desktop.desktop.getRootPane()
+                .putClientProperty("apple.awt.transparentTitleBar", true);
+        Desktop.desktop.getRootPane()
+                .putClientProperty("apple.awt.fullscreenable", true);
       }
-
-      startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
-              format);
-      getFeatures = checkDasArguments(aparser);
-      // extract groovy arguments before anything else.
+      SwingUtilities.invokeLater(() -> {
+        FlatMacLightLaf.setup();
+      });
+      Console.debug("Using FlatMacLightLaf");
+      set = true;
     }
-    // If the user has specified features to be retrieved,
-    // or a groovy script to be executed, do them if they
-    // haven't been done already
-    // fetch features for the default alignment
-    if (getFeatures != null)
+    if (!set)
     {
-      if (startUpAlframe != null)
+      try
       {
-        startFeatureFetching(getFeatures);
+        UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
+        set = true;
+        Console.debug("Using FlatLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        Console.debug("Exception loading FlatLightLaf", e);
       }
+      // Windows specific properties here
+      SwingUtilities.invokeLater(() -> {
+        FlatLightLaf.setup();
+      });
+      Console.debug("Using FlatLightLaf");
+      set = true;
     }
-    // Once all other stuff is done, execute any groovy scripts (in order)
-    if (groovyscript != null)
+    else if (SystemInfo.isLinux)
     {
-      if (Cache.groovyJarsPresent())
+      try
       {
-        System.out.println("Executing script " + groovyscript);
-        executeGroovyScript(groovyscript, startUpAlframe);
-      }
-      else
+        UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
+        set = true;
+        Console.debug("Using FlatLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
       {
-        System.err
-                .println("Sorry. Groovy Support is not available, so ignoring the provided groovy script "
-                        + groovyscript);
+        Console.debug("Exception loading FlatLightLaf", e);
       }
+      // enable custom window decorations
+      JFrame.setDefaultLookAndFeelDecorated(true);
+      JDialog.setDefaultLookAndFeelDecorated(true);
+      SwingUtilities.invokeLater(() -> {
+        FlatLightLaf.setup();
+      });
+      Console.debug("Using FlatLightLaf");
+      set = true;
     }
-    // and finally, turn off batch mode indicator - if the desktop still exists
-    if (desktop != null)
+
+    if (!set)
     {
-      if (progress != -1)
+      try
       {
-        desktop.setProgressBar(null, progress);
+        UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
+        set = true;
+        Console.debug("Using FlatLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        Console.debug("Exception loading FlatLightLaf", e);
       }
-      desktop.setInBatchMode(false);
     }
+
+    if (set)
+    {
+      UIManager.put("TabbedPane.tabType", "card");
+      UIManager.put("TabbedPane.showTabSeparators", true);
+      UIManager.put("TabbedPane.showContentSeparator", true);
+      // UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
+      UIManager.put("TabbedPane.tabsOverlapBorder", true);
+      UIManager.put("TabbedPane.hasFullBorder", true);
+      UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
+      UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
+      UIManager.put("TabbedPane.smoothScrolling", true);
+      UIManager.put("TabbedPane.tabWidthMode", "compact");
+      UIManager.put("TabbedPane.selectedBackground", Color.white);
+      UIManager.put("TabbedPane.background", new Color(236, 236, 236));
+      UIManager.put("TabbedPane.hoverColor", Color.lightGray);
+    }
+
+    Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
+    return set;
+  }
+
+  private static boolean setMacLookAndFeel()
+  {
+    boolean set = false;
+    System.setProperty("com.apple.mrj.application.apple.menu.about.name",
+            ChannelProperties.getProperty("app_name"));
+    System.setProperty("apple.laf.useScreenMenuBar", "true");
+    /*
+     * broken native LAFs on (ARM?) macbooks
+    set = setQuaquaLookAndFeel();
+    if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
+            .toLowerCase(Locale.ROOT).contains("quaqua"))
+    {
+      set = setVaquaLookAndFeel();
+    }
+     */
+    set = setFlatLookAndFeel();
+    return set;
+  }
+
+  private static boolean setLinuxLookAndFeel()
+  {
+    boolean set = false;
+    set = setFlatLookAndFeel();
+    if (!set)
+      set = setMetalLookAndFeel();
+    // avoid GtkLookAndFeel -- not good results especially on HiDPI
+    if (!set)
+      set = setNimbusLookAndFeel();
+    return set;
   }
 
+  /*
   private static void showUsage()
   {
-    System.out
-            .println("Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
+    jalview.bin.Console.outPrintln(
+            "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
                     + "-nodisplay\tRun Jalview without User Interface.\n"
                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
@@ -789,55 +1601,51 @@ public class Jalview
                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
                     + "-html FILE\tCreate HTML file from alignment.\n"
+                    + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
                     + "-noquestionnaire\tTurn off questionnaire check.\n"
                     + "-nonews\tTurn off check for Jalview news.\n"
-                    + "-nousagestats\tTurn off google analytics tracking for this session.\n"
+                    + "-nousagestats\tTurn off analytics tracking for this session.\n"
                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
                     // +
-                    // "-setprop PROPERTY=VALUE\tSet the given Jalview property, after all other properties files have been read\n\t (quote the 'PROPERTY=VALUE' pair to ensure spaces are passed in correctly)"
+                    // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
+                    // after all other properties files have been read\n\t
+                    // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
+                    // passed in correctly)"
                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
-                    + "-dasserver nickname=URL\tAdd and enable a das server with given nickname\n\t\t\t(alphanumeric or underscores only) for retrieval of features for all alignments.\n"
-                    + "\t\t\tSources that also support the sequence command may be specified by prepending the URL with sequence:\n"
-                    + "\t\t\t e.g. sequence:http://localdas.somewhere.org/das/source)\n"
                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
-                    // +
-                    // "-vdoc vamsas-document\tImport vamsas document into new session or join existing session with same URN\n"
-                    // + "-vses vamsas-session\tJoin session with given URN\n"
                     + "-groovy FILE\tExecute groovy script in FILE, after all other arguments have been processed (if FILE is the text 'STDIN' then the file will be read from STDIN)\n"
-                    + "\n~Read documentation in Application or visit http://www.jalview.org for description of Features and Annotations file~\n\n");
+                    + "-jvmmempc=PERCENT\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
+                    + "-jvmmemmax=MAXMEMORY\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m), gigabytes(g) or if you're lucky enough, terabytes(t). This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
+                    + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
   }
+  */
 
   private static void startUsageStats(final Desktop desktop)
   {
     /**
      * start a User Config prompt asking if we can log usage statistics.
      */
-    PromptUserConfig prompter = new PromptUserConfig(
-            Desktop.desktop,
+    PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
             "USAGESTATS",
-            "Jalview Usage Statistics",
-            "Do you want to help make Jalview better by enabling "
-                    + "the collection of usage statistics with Google Analytics ?"
-                    + "\n\n(you can enable or disable usage tracking in the preferences)",
-            new Runnable()
+            MessageManager.getString("prompt.analytics_title"),
+            MessageManager.getString("prompt.analytics"), new Runnable()
             {
               @Override
               public void run()
               {
-                Cache.log
-                        .debug("Initialising googletracker for usage stats.");
-                Cache.initGoogleTracker();
-                Cache.log.debug("Tracking enabled.");
+                Console.debug("Initialising analytics for usage stats.");
+                Cache.initAnalytics();
+                Console.debug("Tracking enabled.");
               }
             }, new Runnable()
             {
               @Override
               public void run()
               {
-                Cache.log.debug("Not enabling Google Tracking.");
+                Console.debug("Not enabling analytics.");
               }
             }, null, true);
     desktop.addDialogThread(prompter);
@@ -852,7 +1660,7 @@ public class Jalview
    *          the Jalview Desktop object passed in to the groovy binding as the
    *          'Jalview' object.
    */
-  private void executeGroovyScript(String groovyscript, AlignFrame af)
+  protected void executeGroovyScript(String groovyscript, AlignFrame af)
   {
     /**
      * for scripts contained in files
@@ -868,10 +1676,10 @@ public class Jalview
       try
       {
         tfile = File.createTempFile("jalview", "groovy");
-        PrintWriter outfile = new PrintWriter(new OutputStreamWriter(
-                new FileOutputStream(tfile)));
-        BufferedReader br = new BufferedReader(new InputStreamReader(
-                System.in));
+        PrintWriter outfile = new PrintWriter(
+                new OutputStreamWriter(new FileOutputStream(tfile)));
+        BufferedReader br = new BufferedReader(
+                new InputStreamReader(System.in));
         String line = null;
         while ((line = br.readLine()) != null)
         {
@@ -883,9 +1691,10 @@ public class Jalview
 
       } catch (Exception ex)
       {
-        System.err.println("Failed to read from STDIN into tempfile "
-                + ((tfile == null) ? "(tempfile wasn't created)" : tfile
-                        .toString()));
+        jalview.bin.Console
+                .errPrintln("Failed to read from STDIN into tempfile "
+                        + ((tfile == null) ? "(tempfile wasn't created)"
+                                : tfile.toString()));
         ex.printStackTrace();
         return;
       }
@@ -894,8 +1703,8 @@ public class Jalview
         sfile = tfile.toURI().toURL();
       } catch (Exception x)
       {
-        System.err
-                .println("Unexpected Malformed URL Exception for temporary file created from STDIN: "
+        jalview.bin.Console.errPrintln(
+                "Unexpected Malformed URL Exception for temporary file created from STDIN: "
                         + tfile.toURI());
         x.printStackTrace();
         return;
@@ -911,17 +1720,20 @@ public class Jalview
         tfile = new File(groovyscript);
         if (!tfile.exists())
         {
-          System.err.println("File '" + groovyscript + "' does not exist.");
+          jalview.bin.Console.errPrintln(
+                  "File '" + groovyscript + "' does not exist.");
           return;
         }
         if (!tfile.canRead())
         {
-          System.err.println("File '" + groovyscript + "' cannot be read.");
+          jalview.bin.Console.errPrintln(
+                  "File '" + groovyscript + "' cannot be read.");
           return;
         }
         if (tfile.length() < 1)
         {
-          System.err.println("File '" + groovyscript + "' is empty.");
+          jalview.bin.Console
+                  .errPrintln("File '" + groovyscript + "' is empty.");
           return;
         }
         try
@@ -929,7 +1741,7 @@ public class Jalview
           sfile = tfile.getAbsoluteFile().toURI().toURL();
         } catch (Exception ex)
         {
-          System.err.println("Failed to create a file URL for "
+          jalview.bin.Console.errPrintln("Failed to create a file URL for "
                   + tfile.getAbsoluteFile());
           return;
         }
@@ -937,7 +1749,7 @@ public class Jalview
     }
     try
     {
-      Map<String, Object> vbinding = new HashMap<String, Object>();
+      Map<String, java.lang.Object> vbinding = new HashMap<>();
       vbinding.put("Jalview", this);
       if (af != null)
       {
@@ -954,142 +1766,327 @@ public class Jalview
       }
     } catch (Exception e)
     {
-      System.err.println("Exception Whilst trying to execute file " + sfile
-              + " as a groovy script.");
+      jalview.bin.Console
+              .errPrintln("Exception Whilst trying to execute file " + sfile
+                      + " as a groovy script.");
       e.printStackTrace(System.err);
 
     }
   }
 
+  public static boolean isHeadlessMode()
+  {
+    String isheadless = System.getProperty("java.awt.headless");
+    if (isheadless != null && isheadless.equalsIgnoreCase("true"))
+    {
+      return true;
+    }
+    return false;
+  }
+
+  public AlignFrame[] getAlignFrames()
+  {
+    return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
+            : Desktop.getAlignFrames();
+
+  }
+
   /**
-   * Check commandline for any das server definitions or any fetchfrom switches
-   * 
-   * @return vector of DAS source nicknames to retrieve from
+   * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
    */
-  private static Vector<String> checkDasArguments(ArgsParser aparser)
+  public void quit()
   {
-    Vector<String> source = null;
-    String data;
-    String locsources = Cache.getProperty(Cache.DAS_LOCAL_SOURCE);
-    while ((data = aparser.getValue("dasserver", true)) != null)
-    {
-      String nickname = null;
-      String url = null;
-      int pos = data.indexOf('=');
-      // determine capabilities
-      if (pos > 0)
-      {
-        nickname = data.substring(0, pos);
-      }
-      url = data.substring(pos + 1);
-      if (url != null
-              && (url.startsWith("http:") || url
-                      .startsWith("sequence:http:")))
+    // System.exit will run the shutdownHook first
+    Jalview.exit("Quitting now. Bye!", ExitCode.OK);
+  }
+
+  public static AlignFrame getCurrentAlignFrame()
+  {
+    return Jalview.currentAlignFrame;
+  }
+
+  public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
+  {
+    Jalview.currentAlignFrame = currentAlignFrame;
+  }
+
+  public Commands getCommands()
+  {
+    return cmds;
+  }
+
+  public static void exit(String message, ExitCode ec)
+  {
+    int exitcode = ec == ExitCode.OK ? 0 : ec.ordinal() + 1;
+    if (Console.log == null)
+    {
+      // Don't start the logger just to exit!
+      if (message != null)
       {
-        if (nickname == null)
+        if (exitcode == 0)
         {
-          nickname = url;
+          Console.outPrintln(message);
         }
-        if (locsources == null)
+        else
         {
-          locsources = "";
+          jalview.bin.Console.errPrintln(message);
         }
-        else
+      }
+    }
+    else
+    {
+      Console.debug("Using Jalview.exit");
+      if (message != null)
+      {
+        if (exitcode == 0)
         {
-          locsources += "\t";
+          Console.info(message);
         }
-        locsources = locsources + nickname + "|" + url;
-        System.err
-                .println("NOTE! dasserver parameter not yet really supported (got args of "
-                        + nickname + "|" + url);
-        if (source == null)
+        else
         {
-          source = new Vector<String>();
+          Console.error(message);
         }
-        source.addElement(nickname);
       }
-      System.out.println("CMD [-dasserver " + data
-              + "] executed successfully!");
-    } // loop until no more server entries are found.
-    if (locsources != null && locsources.indexOf('|') > -1)
+    }
+    if (exitcode > -1)
+    {
+      System.exit(exitcode);
+    }
+  }
+
+  public enum ExitCode
+  {
+    // only add new ones to the end of the list (to preserve ordinal values)
+    OK, FILE_NOT_FOUND, FILE_NOT_READABLE, NO_FILES, INVALID_FORMAT,
+    INVALID_ARGUMENT, INVALID_VALUE, MIXED_CLI_ARGUMENTS,
+    ERROR_RUNNING_COMMANDS, NO_LOGGING;
+  }
+
+  /******************************
+   * 
+   * TEST OUTPUT METHODS
+   * 
+   * these operate only when Arg.TESTOUTPUT has been passed, and variously check
+   * if an expected value / arg was set and report it to the test framework.
+   * 
+   ******************************/
+  /**
+   * report string values parsed/processed during tests When the Bootstrap
+   * argument Arg.TESTOUTPUT is present - reports on debug if given s1 is not
+   * null and not equals s2, warns if given argument is not set, and calls
+   * testoutput(true,a,s1,s2) to report processing progress.
+   * 
+   * @param ap
+   *          - ArgParser handling parsing
+   * @param a
+   *          - Arg currently being processed
+   * @param s1
+   *          - expected
+   * @param s2
+   */
+  protected static void testoutput(ArgParser ap, Arg a, String s1,
+          String s2)
+  {
+    BootstrapArgs bsa = ap.getBootstrapArgs();
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
     {
-      Cache.log.debug("Setting local source list in properties file to:\n"
-              + locsources);
-      Cache.setProperty(Cache.DAS_LOCAL_SOURCE, locsources);
+      Console.debug("testoutput with unmatching values '" + s1 + "' and '"
+              + s2 + "' for arg " + a.argString());
+      return;
     }
-    while ((data = aparser.getValue("fetchfrom", true)) != null)
+    boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
+            : ap.isSet(a);
+    if (!isset)
     {
-      System.out.println("adding source '" + data + "'");
-      if (source == null)
-      {
-        source = new Vector<String>();
-      }
-      source.addElement(data);
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
     }
-    return source;
+    testoutput(true, a, s1, s2);
   }
 
   /**
-   * start a feature fetcher for every alignment frame
+   * report values passed via bootstrap arguments
    * 
-   * @param dasSources
+   * TODO: significant code duplication with testouput(Argparser...) - move it
    */
-  private FeatureFetcher startFeatureFetching(
-          final Vector<String> dasSources)
+
+  protected static void testoutput(BootstrapArgs bsa, Arg a, String s1,
+          String s2)
   {
-    FeatureFetcher ff = new FeatureFetcher();
-    AlignFrame afs[] = Desktop.getAlignFrames();
-    if (afs == null || afs.length == 0)
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
+    {
+      Console.debug("testoutput with unmatching values '" + s1 + "' and '"
+              + s2 + "' for arg " + a.argString());
+      return;
+    }
+    if (!a.hasOption(Opt.BOOTSTRAP))
     {
-      return null;
+      Console.error("Non-bootstrap Arg '" + a.getName()
+              + "' given to testoutput(BootstrapArgs bsa, Arg a, String s1, String s2) with only BootstrapArgs");
     }
-    for (int i = 0; i < afs.length; i++)
+    if (!bsa.contains(a))
     {
-      ff.addFetcher(afs[i], dasSources);
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
     }
-    return ff;
+    testoutput(true, a, s1, s2);
   }
 
-  public static boolean isHeadlessMode()
+  /**
+   * conditionally (on @param yes) report that expected value s1 was set during
+   * CommandsTest tests
+   */
+  private static void testoutput(boolean yes, Arg a, String s1, String s2)
   {
-    String isheadless = System.getProperty("java.awt.headless");
-    if (isheadless != null && isheadless.equalsIgnoreCase("true"))
+    if (yes && ((s1 == null && s2 == null)
+            || (s1 != null && s1.equals(s2))))
     {
-      return true;
+      Console.outPrintln("[TESTOUTPUT] arg " + a.argString() + "='" + s1
+              + "' was set");
     }
-    return false;
   }
 
-  public AlignFrame[] getAlignFrames()
+  /*
+   * testoutput for boolean and unary values
+   */
+  protected static void testoutput(ArgParser ap, Arg a)
   {
-    return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
-            : Desktop.getAlignFrames();
+    if (ap == null)
+      return;
+    BootstrapArgs bsa = ap.getBootstrapArgs();
+    if (bsa == null)
+      return;
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    boolean val = a.hasOption(Opt.BOOTSTRAP) ? bsa.getBoolean(a)
+            : ap.getBoolean(a);
+    boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
+            : ap.isSet(a);
+    if (!isset)
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(val, a);
+  }
+
+  protected static void testoutput(BootstrapArgs bsa, Arg a)
+  {
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!a.hasOption(Opt.BOOTSTRAP))
+    {
+      Console.warn("Non-bootstrap Arg '" + a.getName()
+              + "' given to testoutput(BootstrapArgs bsa, Arg a) with only BootstrapArgs");
 
+    }
+    if (!bsa.contains(a))
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(bsa.getBoolean(a), a);
   }
 
-  /**
-   * Quit method delegates to Desktop.quit - unless running in headless mode
-   * when it just ends the JVM
-   */
-  public void quit()
+  private static void testoutput(boolean yes, Arg a)
   {
-    if (desktop != null)
+    String message = null;
+    if (a.hasOption(Opt.BOOLEAN))
     {
-      desktop.quit();
+      message = (yes ? a.argString() : a.negateArgString()) + " was set";
     }
-    else
+    else if (a.hasOption(Opt.UNARY))
     {
-      System.exit(0);
+      message = a.argString() + (yes ? " was set" : " was not set");
     }
+    Console.outPrintln("[TESTOUTPUT] arg " + message);
   }
 
-  public static AlignFrame getCurrentAlignFrame()
+  public ArgParser getArgParser()
   {
-    return Jalview.currentAlignFrame;
+    return argparser;
   }
 
-  public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
+  public BootstrapArgs getBootstrapArgs()
   {
-    Jalview.currentAlignFrame = currentAlignFrame;
+    return bootstrapArgs;
+  }
+
+  public static boolean isBatchMode()
+  {
+    return getInstance() != null && (getInstance().desktop == null
+            || getInstance().desktop.isInBatchMode());
+  }
+
+  /**
+   * Warning about old or mixed command line arguments
+   */
+  private void mixedCliWarning()
+  {
+    Jalview j = Jalview.getInstance();
+    boolean mixedStyle = j.getArgParser() != null
+            && j.getArgParser().isMixedStyle();
+    String title = MessageManager.getString("label.command_line_arguments");
+    if (mixedStyle)
+    {
+      String warning = MessageManager.formatMessage(
+              "warning.using_mixed_command_line_arguments",
+              j.getArgParser().getMixedExamples());
+      String quit = MessageManager.getString("action.quit");
+
+      Desktop.instance.nonBlockingDialog(title, warning, null, quit,
+              JvOptionPane.WARNING_MESSAGE, false, false, true, 30000);
+
+      Jalview.exit(
+              "Exiting due to mixed old and new command line arguments.",
+              ExitCode.MIXED_CLI_ARGUMENTS);
+    }
+  }
+
+  private void cliWarning()
+  {
+    Jalview j = Jalview.getInstance();
+    Commands c = j.getCommands();
+    boolean oldStyle = j.getArgParser() != null
+            && j.getArgParser().isOldStyle();
+    String title = MessageManager.getString("label.command_line_arguments");
+    if (oldStyle)
+    {
+      String warning = MessageManager
+              .getString("warning.using_old_command_line_arguments");
+      String url = "<a href=\"https://www.jalview.org/help/html/features/commandline.html\">https://www.jalview.org/help/html/features/commandline.html</a>";
+      if (Desktop.instance != null)
+      {
+        String cont = MessageManager.getString("label.continue");
+
+        Desktop.instance.nonBlockingDialog(title, warning, url, cont,
+                JvOptionPane.WARNING_MESSAGE, false, true, true, 30000);
+      }
+    }
+    if (j.getCommands() != null && j.getCommands().getErrors().size() > 0)
+    {
+      if (Desktop.instance != null)
+      {
+        String message = MessageManager
+                .getString("warning.the_following_errors");
+        String ok = MessageManager.getString("action.ok");
+        int shortest = 60;
+        List<String> errors = j.getCommands().getErrors();
+        for (int i = 0; i < errors.size(); i++)
+        {
+          shortest = Math.min(shortest, errors.get(i).length());
+        }
+        Desktop.instance.nonBlockingDialog(
+                Math.max(message.length(), Math.min(60, shortest)),
+                Math.min(errors.size(), 20), title, message,
+                j.getCommands().errorsToString(), ok,
+                JvOptionPane.WARNING_MESSAGE, true, false, true, -1);
+      }
+    }
   }
+
 }