JAL-629 Fix --tempfac. Hide non-working --notempfac. Add --scale, --width, --height...
[jalview.git] / src / jalview / bin / Jalview.java
index 3cc71af..cf73c81 100755 (executable)
@@ -26,7 +26,9 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URI;
@@ -37,7 +39,9 @@ import java.security.CodeSource;
 import java.security.PermissionCollection;
 import java.security.Permissions;
 import java.security.Policy;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
@@ -62,7 +66,10 @@ import com.threerings.getdown.util.LaunchUtil;
 //import edu.stanford.ejalbert.launching.IBrowserLaunching;
 import groovy.lang.Binding;
 import groovy.util.GroovyScriptEngine;
-import jalview.bin.ArgParser.Arg;
+import jalview.bin.argparser.Arg;
+import jalview.bin.argparser.Arg.Opt;
+import jalview.bin.argparser.ArgParser;
+import jalview.bin.argparser.BootstrapArgs;
 import jalview.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
@@ -123,8 +130,21 @@ public class Jalview
 
   private Desktop desktop;
 
+  protected Commands cmds;
+
   public static AlignFrame currentAlignFrame;
 
+  public ArgParser argparser = null;
+
+  public BootstrapArgs bootstrapArgs = null;
+
+  private boolean QUIET = false;
+
+  public static boolean quiet()
+  {
+    return Jalview.getInstance() != null && Jalview.getInstance().QUIET;
+  }
+
   static
   {
     if (!Platform.isJS())
@@ -275,36 +295,90 @@ public class Jalview
    */
   void doMain(String[] args)
   {
-
     if (!Platform.isJS())
     {
       System.setSecurityManager(null);
     }
 
-    // get args needed before proper ArgParser
-    Map<String, String> bootstrapArgs = ArgParser.bootstrapArgs(args);
+    if (args == null)
+      args = new String[] {};
 
-    System.out
-            .println("Java version: " + System.getProperty("java.version"));
-    System.out.println("Java Home: " + System.getProperty("java.home"));
-    System.out.println(System.getProperty("os.arch") + " "
-            + System.getProperty("os.name") + " "
-            + System.getProperty("os.version"));
+    // get args needed before proper ArgParser
+    bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
 
-    String val = System.getProperty("sys.install4jVersion");
-    if (val != null)
+    if (!Platform.isJS())
     {
-      System.out.println("Install4j version: " + val);
+      // are we being --quiet ?
+      if (bootstrapArgs.contains(Arg.QUIET))
+      {
+        QUIET = true;
+        OutputStream devNull = new OutputStream()
+        {
+
+          @Override
+          public void write(int b)
+          {
+            // DO NOTHING
+          }
+        };
+        System.setOut(new PrintStream(devNull));
+        // redirecting stderr not working
+        if (bootstrapArgs.getList(Arg.QUIET).size() > 1)
+        {
+          System.setErr(new PrintStream(devNull));
+        }
+      }
+
+      if (bootstrapArgs.contains(Arg.HELP)
+              || bootstrapArgs.contains(Arg.VERSION))
+      {
+        QUIET = true;
+      }
     }
-    val = System.getProperty("installer_template_version");
-    if (val != null)
+
+    // Move any new getdown-launcher-new.jar into place over old
+    // getdown-launcher.jar
+    String appdirString = System.getProperty("getdownappdir");
+    if (appdirString != null && appdirString.length() > 0)
     {
-      System.out.println("Install4j template version: " + val);
+      final File appdir = new File(appdirString);
+      new Thread()
+      {
+        @Override
+        public void run()
+        {
+          LaunchUtil.upgradeGetdown(
+                  new File(appdir, "getdown-launcher-old.jar"),
+                  new File(appdir, "getdown-launcher.jar"),
+                  new File(appdir, "getdown-launcher-new.jar"));
+        }
+      }.start();
     }
-    val = System.getProperty("launcher_version");
-    if (val != null)
+
+    if (!quiet() || bootstrapArgs.contains(Arg.VERSION))
     {
-      System.out.println("Launcher version: " + val);
+      System.out.println(
+              "Java version: " + System.getProperty("java.version"));
+      System.out.println("Java home: " + System.getProperty("java.home"));
+      System.out.println("Java arch: " + System.getProperty("os.arch") + " "
+              + System.getProperty("os.name") + " "
+              + System.getProperty("os.version"));
+
+      String val = System.getProperty("sys.install4jVersion");
+      if (val != null)
+      {
+        System.out.println("Install4j version: " + val);
+      }
+      val = System.getProperty("installer_template_version");
+      if (val != null)
+      {
+        System.out.println("Install4j template version: " + val);
+      }
+      val = System.getProperty("launcher_version");
+      if (val != null)
+      {
+        System.out.println("Launcher version: " + val);
+      }
     }
 
     if (Platform.isLinux() && LaunchUtils.getJavaVersion() < 11)
@@ -314,19 +388,37 @@ public class Jalview
 
     // get bootstrap properties (mainly for the logger level)
     Properties bootstrapProperties = Cache
-            .bootstrapProperties(bootstrapArgs.get("props"));
+            .bootstrapProperties(bootstrapArgs.get(Arg.PROPS));
 
     // report Jalview version
-    Cache.loadBuildProperties(true);
+    Cache.loadBuildProperties(
+            !quiet() || bootstrapArgs.contains(Arg.VERSION));
+
+    // stop now if only after --version
+    if (bootstrapArgs.contains(Arg.VERSION))
+    {
+      Jalview.exit(null, 0);
+    }
 
     // old ArgsParser
     ArgsParser aparser = new ArgsParser(args);
 
+    // old
     boolean headless = false;
+    // new
+    boolean headlessArg = false;
 
     try
     {
-      String logLevel = bootstrapArgs.containsKey("debug") ? "DEBUG" : null;
+      String logLevel = null;
+      if (bootstrapArgs.contains(Arg.TRACE))
+      {
+        logLevel = "TRACE";
+      }
+      else if (bootstrapArgs.contains(Arg.DEBUG))
+      {
+        logLevel = "DEBUG";
+      }
       if (logLevel == null && !(bootstrapProperties == null))
       {
         logLevel = bootstrapProperties.getProperty(Cache.JALVIEWLOGLEVEL);
@@ -335,9 +427,9 @@ public class Jalview
     } catch (NoClassDefFoundError error)
     {
       error.printStackTrace();
-      System.out.println("\nEssential logging libraries not found."
-              + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview");
-      System.exit(0);
+      String message = "\nEssential logging libraries not found."
+              + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
+      Jalview.exit(message, 0);
     }
 
     // register SIGTERM listener
@@ -363,21 +455,34 @@ public class Jalview
       }
     });
 
-    String usrPropsFile = bootstrapArgs.containsKey("props")
-            ? bootstrapArgs.get("props")
+    String usrPropsFile = bootstrapArgs.contains(Arg.PROPS)
+            ? bootstrapArgs.get(Arg.PROPS)
             : aparser.getValue("props");
+    // if usrPropsFile == null, loadProperties will use the Channel
+    // preferences.file
     Cache.loadProperties(usrPropsFile);
     if (usrPropsFile != null)
     {
       System.out.println(
               "CMD [-props " + usrPropsFile + "] executed successfully!");
+      testoutput(bootstrapArgs, Arg.PROPS,
+              "test/jalview/bin/testProps.jvprops", usrPropsFile);
     }
 
-    // new ArgParser
-    ArgParser argparser = new ArgParser(args);
-
-    if (argparser.isSet(Arg.HEADLESS))
-      headless = argparser.getBool(Arg.HEADLESS);
+    // --argfile=... -- OVERRIDES ALL NON-BOOTSTRAP ARGS
+    if (bootstrapArgs.contains(Arg.ARGFILE))
+    {
+      argparser = ArgParser.parseArgFiles(
+              bootstrapArgs.getList(Arg.ARGFILE),
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
+              bootstrapArgs);
+    }
+    else
+    {
+      argparser = new ArgParser(args,
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
+              bootstrapArgs);
+    }
 
     if (!Platform.isJS())
     /**
@@ -386,20 +491,32 @@ public class Jalview
      * @j2sIgnore
      */
     {
-      if (argparser.isSet(Arg.HEADLESS))
+      if (bootstrapArgs.contains(Arg.HELP))
       {
-        headless = argparser.getBool(Arg.HEADLESS);
+        System.out.println(Arg.usage());
+        Jalview.exit(null, 0);
       }
-
       if (aparser.contains("help") || aparser.contains("h"))
       {
+        /*
+         * Now using new usage statement.
         showUsage();
-        System.exit(0);
+        */
+        System.out.println(Arg.usage());
+        Jalview.exit(null, 0);
+      }
+
+      if (bootstrapArgs.contains(Arg.HEADLESS))
+      {
+        System.setProperty("java.awt.headless", "true");
+        // new
+        headlessArg = bootstrapArgs.getBoolean(Arg.HEADLESS);
       }
-      if (headless || aparser.contains("nodisplay")
-              || aparser.contains("nogui") || aparser.contains("headless"))
+      if (aparser.contains("nodisplay") || aparser.contains("nogui")
+              || aparser.contains("headless"))
       {
         System.setProperty("java.awt.headless", "true");
+        // old
         headless = true;
       }
       // anything else!
@@ -407,7 +524,9 @@ public class Jalview
       // allow https handshakes to download intermediate certs if necessary
       System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
 
-      final String jabawsUrl = aparser.getValue("jabaws");
+      String jabawsUrl = bootstrapArgs.get(Arg.JABAWS);
+      if (jabawsUrl == null)
+        jabawsUrl = aparser.getValue("jabaws");
       if (jabawsUrl != null)
       {
         try
@@ -415,6 +534,8 @@ public class Jalview
           Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
           System.out.println(
                   "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
+          testoutput(bootstrapArgs, Arg.JABAWS,
+                  "http://www.compbio.dundee.ac.uk/jabaws", jabawsUrl);
         } catch (MalformedURLException e)
         {
           System.err.println(
@@ -423,26 +544,40 @@ public class Jalview
       }
     }
 
-    String defs = aparser.getValue("setprop");
-    while (defs != null)
+    List<String> setprops = new ArrayList<>();
+    if (bootstrapArgs.contains(Arg.SETPROP))
     {
-      int p = defs.indexOf('=');
+      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 = 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"))
@@ -460,13 +595,14 @@ 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;
 
-    setLookAndFeel();
+    if (!(headless || headlessArg))
+      setLookAndFeel();
 
     /*
      * configure 'full' SO model if preferences say to, else use the default (full SO)
@@ -478,9 +614,11 @@ public class Jalview
       SequenceOntologyFactory.setInstance(new SequenceOntology());
     }
 
-    if (!headless)
+    if (!(headless || headlessArg))
     {
-      Desktop.nosplash = aparser.contains("nosplash");
+      Desktop.nosplash = "false".equals(bootstrapArgs.get(Arg.SPLASH))
+              || aparser.contains("nosplash")
+              || Cache.getDefault("SPLASH", "true").equals("false");
       desktop = new Desktop();
       desktop.setInBatchMode(true); // indicate we are starting up
 
@@ -540,20 +678,37 @@ public class Jalview
           }
         }
 
-        if (!aparser.contains("nowebservicediscovery"))
+        boolean webservicediscovery = bootstrapArgs
+                .getBoolean(Arg.WEBSERVICEDISCOVERY);
+        if (aparser.contains("nowebservicediscovery"))
+          webservicediscovery = false;
+        if (webservicediscovery)
         {
           desktop.startServiceDiscovery();
         }
-        if (!aparser.contains("nousagestats"))
+        else
+        {
+          testoutput(argparser, Arg.WEBSERVICEDISCOVERY);
+        }
+
+        boolean usagestats = bootstrapArgs.getBoolean(Arg.USAGESTATS);
+        if (aparser.contains("nousagestats"))
+          usagestats = false;
+        if (usagestats)
         {
           startUsageStats(desktop);
+          testoutput(argparser, Arg.USAGESTATS);
         }
         else
         {
-          System.err.println("CMD [-nousagestats] executed successfully!");
+          System.out.println("CMD [-nousagestats] executed successfully!");
+          testoutput(argparser, Arg.USAGESTATS);
         }
 
-        if (!aparser.contains("noquestionnaire"))
+        boolean questionnaire = bootstrapArgs.getBoolean(Arg.QUESTIONNAIRE);
+        if (aparser.contains("noquestionnaire"))
+          questionnaire = false;
+        if (questionnaire)
         {
           String url = aparser.getValue("questionnaire");
           if (url != null)
@@ -583,36 +738,44 @@ public class Jalview
         }
         else
         {
-          System.err
+          System.out
                   .println("CMD [-noquestionnaire] executed successfully!");
+          testoutput(argparser, Arg.QUESTIONNAIRE);
         }
 
-        if (!aparser.contains("nonews")
-                || Cache.getProperty("NONEWS") == null)
+        if ((!aparser.contains("nonews")
+                && Cache.getProperty("NONEWS") == null
+                && !"false".equals(bootstrapArgs.get(Arg.NEWS)))
+                || "true".equals(bootstrapArgs.get(Arg.NEWS)))
         {
           desktop.checkForNews();
         }
 
         if (!aparser.contains("nohtmltemplates")
-                || Cache.getProperty("NOHTMLTEMPLATES") == null)
+                && Cache.getProperty("NOHTMLTEMPLATES") == null)
         {
           BioJsHTMLOutput.updateBioJS();
         }
       }
     }
     // Run Commands from cli
-    boolean commandsSuccess = Commands.processArgs(argparser, headless);
+    cmds = new Commands(argparser, headlessArg);
+    boolean commandsSuccess = cmds.argsWereParsed();
     if (commandsSuccess)
     {
+      if (headlessArg)
+      {
+        Jalview.exit("Successfully completed commands in headless mode", 0);
+      }
       Console.info("Successfully completed commands");
-      if (headless)
-        System.exit(0);
     }
     else
     {
+      if (headlessArg)
+      {
+        Jalview.exit("Error when running Commands in headless mode", 1);
+      }
       Console.warn("Error when running commands");
-      if (headless)
-        System.exit(1);
     }
 
     // Check if JVM and compile version might cause problems and log if it
@@ -625,25 +788,6 @@ public class Jalview
               + LaunchUtils.getJavaCompileVersion() + ".");
     }
 
-    // Move any new getdown-launcher-new.jar into place over old
-    // getdown-launcher.jar
-    String appdirString = System.getProperty("getdownappdir");
-    if (appdirString != null && appdirString.length() > 0)
-    {
-      final File appdir = new File(appdirString);
-      new Thread()
-      {
-        @Override
-        public void run()
-        {
-          LaunchUtil.upgradeGetdown(
-                  new File(appdir, "getdown-launcher-old.jar"),
-                  new File(appdir, "getdown-launcher.jar"),
-                  new File(appdir, "getdown-launcher-new.jar"));
-        }
-      }.start();
-    }
-
     String file = null, data = null;
 
     FileFormatI format = null;
@@ -658,10 +802,9 @@ public class Jalview
     groovyscript = aparser.getValue("groovy", true);
     file = aparser.getValue("open", true);
 
-    if (file == null && desktop == null)
+    if (file == null && desktop == null && !commandsSuccess)
     {
-      System.out.println("No files to open!");
-      System.exit(1);
+      Jalview.exit("No files to open!", 1);
     }
 
     long progress = -1;
@@ -688,11 +831,12 @@ public class Jalview
         {
           if (!(new File(file)).exists())
           {
-            System.out.println("Can't find " + file);
             if (headless)
             {
-              System.exit(1);
+              Jalview.exit(
+                      "Can't find file '" + file + "' in headless mode", 1);
             }
+            Console.warn("Can't find file'" + file + "'");
           }
         }
       }
@@ -727,7 +871,7 @@ public class Jalview
           if (cs != null)
           {
             System.out.println(
-                    "CMD [-color " + data + "] executed successfully!");
+                    "CMD [-colour " + data + "] executed successfully!");
           }
           af.changeColour(cs);
         }
@@ -930,7 +1074,10 @@ public class Jalview
     // ////////////////////
 
     if (!Platform.isJS() && !headless && file == null
-            && Cache.getDefault("SHOW_STARTUP_FILE", true))
+            && Cache.getDefault("SHOW_STARTUP_FILE", true)
+            && !cmds.commandArgsProvided())
+    // don't open the startup file if command line args have been processed
+    // (&& !Commands.commandArgsProvided())
     /**
      * Java only
      * 
@@ -1397,7 +1544,7 @@ public class Jalview
    *          the Jalview Desktop object passed in to the groovy binding as the
    *          'Jalview' object.
    */
-  private void executeGroovyScript(String groovyscript, AlignFrame af)
+  protected void executeGroovyScript(String groovyscript, AlignFrame af)
   {
     /**
      * for scripts contained in files
@@ -1529,7 +1676,7 @@ public class Jalview
   public void quit()
   {
     // System.exit will run the shutdownHook first
-    System.exit(0);
+    Jalview.exit("Quitting now. Bye!", 0);
   }
 
   public static AlignFrame getCurrentAlignFrame()
@@ -1541,4 +1688,128 @@ public class Jalview
   {
     Jalview.currentAlignFrame = currentAlignFrame;
   }
+
+  protected Commands getCommands()
+  {
+    return cmds;
+  }
+
+  public static void exit(String message, int exitcode)
+  {
+    Console.debug("Using Jalview.exit");
+    if (message != null)
+      if (exitcode == 0)
+        Console.info(message);
+      else
+        Console.error(message);
+    if (exitcode > -1)
+      System.exit(exitcode);
+  }
+
+  /*
+   * testoutput for string values
+   */
+  protected static void testoutput(ArgParser ap, Arg a, String s1,
+          String s2)
+  {
+    BootstrapArgs bsa = ap.getBootstrapArgs();
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
+    {
+      Console.debug("testoutput with unmatching values '" + s1 + "' and '"
+              + s2 + "' for arg " + a.argString());
+      return;
+    }
+    boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
+            : ap.isSet(a);
+    if (!isset)
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(true, a, s1, s2);
+  }
+
+  protected static void testoutput(BootstrapArgs bsa, Arg a, String s1,
+          String s2)
+  {
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
+    {
+      Console.debug("testoutput with unmatching values '" + s1 + "' and '"
+              + s2 + "' for arg " + a.argString());
+      return;
+    }
+    if (!a.hasOption(Opt.BOOTSTRAP))
+    {
+      Console.error("Non-bootstrap Arg '" + a.getName()
+              + "' given to testoutput(BootstrapArgs bsa, Arg a, String s1, String s2) with only BootstrapArgs");
+    }
+    if (!bsa.contains(a))
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(true, a, s1, s2);
+  }
+
+  private static void testoutput(boolean yes, Arg a, String s1, String s2)
+  {
+    if (yes && ((s1 == null && s2 == null)
+            || (s1 != null && s1.equals(s2))))
+    {
+      System.out.println("[TESTOUTPUT] arg " + a.argString() + "='" + s1
+              + "' was set");
+    }
+  }
+
+  /*
+   * testoutput for boolean values
+   */
+  protected static void testoutput(ArgParser ap, Arg a)
+  {
+    if (ap == null)
+      return;
+    BootstrapArgs bsa = ap.getBootstrapArgs();
+    if (bsa == null)
+      return;
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    boolean val = a.hasOption(Opt.BOOTSTRAP) ? bsa.getBoolean(a)
+            : ap.getBoolean(a);
+    boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
+            : ap.isSet(a);
+    if (!isset)
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(val, a);
+  }
+
+  protected static void testoutput(BootstrapArgs bsa, Arg a)
+  {
+    if (!bsa.getBoolean(Arg.TESTOUTPUT))
+      return;
+    if (!a.hasOption(Opt.BOOTSTRAP))
+    {
+      Console.warn("Non-bootstrap Arg '" + a.getName()
+              + "' given to testoutput(BootstrapArgs bsa, Arg a) with only BootstrapArgs");
+
+    }
+    if (!bsa.contains(a))
+    {
+      Console.warn("Arg '" + a.getName() + "' not set at all");
+      return;
+    }
+    testoutput(bsa.getBoolean(a), a);
+  }
+
+  private static void testoutput(boolean yes, Arg a)
+  {
+    System.out.println("[TESTOUTPUT] arg "
+            + (yes ? a.argString() : a.negateArgString()) + " was set");
+  }
 }