JAL-629 Add --nowebdiscovery. Distinguish arg=="open" as old style. Increase timeout...
[jalview.git] / src / jalview / bin / Jalview.java
index 1428906..e384a0b 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,30 +39,42 @@ 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 javax.swing.JDialog;
+import javax.swing.JFrame;
 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.ArgParser;
+import jalview.bin.argparser.BootstrapArgs;
 import jalview.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
 import jalview.gui.PromptUserConfig;
+import jalview.gui.QuitHandler;
+import jalview.gui.QuitHandler.QResponse;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.BioJsHTMLOutput;
 import jalview.io.DataSourceType;
@@ -115,6 +129,8 @@ public class Jalview
 
   private Desktop desktop;
 
+  protected Commands cmds;
+
   public static AlignFrame currentAlignFrame;
 
   static
@@ -267,18 +283,65 @@ public class Jalview
    */
   void doMain(String[] args)
   {
-
     if (!Platform.isJS())
     {
       System.setSecurityManager(null);
     }
 
+    if (args == null)
+      args = new String[] {};
+
+    // Move any new getdown-launcher-new.jar into place over old
+    // getdown-launcher.jar
+    String appdirString = System.getProperty("getdownappdir");
+    if (appdirString != null && appdirString.length() > 0)
+    {
+      final File appdir = new File(appdirString);
+      new Thread()
+      {
+        @Override
+        public void run()
+        {
+          LaunchUtil.upgradeGetdown(
+                  new File(appdir, "getdown-launcher-old.jar"),
+                  new File(appdir, "getdown-launcher.jar"),
+                  new File(appdir, "getdown-launcher-new.jar"));
+        }
+      }.start();
+    }
+
+    // get args needed before proper ArgParser
+    BootstrapArgs bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
+
+    if (!Platform.isJS())
+    {
+      // are we being --quiet ?
+      if (bootstrapArgs.contains(Arg.QUIET))
+      {
+        OutputStream devNull = new OutputStream()
+        {
+          @Override
+          public void write(int b)
+          {
+            // DO NOTHING
+          }
+        };
+        System.setOut(new PrintStream(devNull));
+        // redirecting stderr not working
+        if (bootstrapArgs.getList(Arg.QUIET).size() > 1)
+        {
+          System.setErr(new PrintStream(devNull));
+        }
+      }
+    }
+
     System.out
             .println("Java version: " + System.getProperty("java.version"));
     System.out.println("Java Home: " + System.getProperty("java.home"));
     System.out.println(System.getProperty("os.arch") + " "
             + System.getProperty("os.name") + " "
             + System.getProperty("os.version"));
+
     String val = System.getProperty("sys.install4jVersion");
     if (val != null)
     {
@@ -295,20 +358,92 @@ public class Jalview
       System.out.println("Launcher version: " + val);
     }
 
+    if (Platform.isLinux() && LaunchUtils.getJavaVersion() < 11)
+    {
+      System.setProperty("flatlaf.uiScale", "1");
+    }
+
+    // get bootstrap properties (mainly for the logger level)
+    Properties bootstrapProperties = Cache
+            .bootstrapProperties(bootstrapArgs.get(Arg.PROPS));
+
     // report Jalview version
     Cache.loadBuildProperties(true);
 
+    // old ArgsParser
     ArgsParser aparser = new ArgsParser(args);
+
+    // old
     boolean headless = false;
+    // new
+    boolean headlessArg = false;
+
+    try
+    {
+      String logLevel = bootstrapArgs.contains(Arg.DEBUG) ? "DEBUG" : null;
+      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, 0);
+    }
 
-    String usrPropsFile = aparser.getValue("props");
-    Cache.loadProperties(usrPropsFile); // must do this before
+    // register SIGTERM listener
+    Runtime.getRuntime().addShutdownHook(new Thread()
+    {
+      public void run()
+      {
+        Console.debug("Running shutdown hook");
+        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.
+          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.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!");
     }
 
+    // new ArgParser
+    ArgParser argparser;
+    // --argfile=... -- OVERRIDES ALL NON-BOOTSTRAP ARGS
+    if (bootstrapArgs.contains(Arg.ARGFILE))
+    {
+      argparser = ArgParser.parseArgFiles(
+              bootstrapArgs.getList(Arg.ARGFILE),
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS));
+    }
+    else
+    {
+      argparser = new ArgParser(args,
+              bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS));
+    }
+
     if (!Platform.isJS())
     /**
      * Java only
@@ -316,15 +451,28 @@ public class Jalview
      * @j2sIgnore
      */
     {
+      if (bootstrapArgs.contains(Arg.HELP))
+      {
+        System.out.println(Arg.usage());
+        Jalview.exit(null, 0);
+      }
       if (aparser.contains("help") || aparser.contains("h"))
       {
         showUsage();
-        System.exit(0);
+        Jalview.exit(null, 0);
+      }
+
+      if (bootstrapArgs.contains(Arg.HEADLESS))
+      {
+        System.setProperty("java.awt.headless", "true");
+        // new
+        headlessArg = argparser.getBool(Arg.HEADLESS);
       }
       if (aparser.contains("nodisplay") || aparser.contains("nogui")
               || aparser.contains("headless"))
       {
         System.setProperty("java.awt.headless", "true");
+        // old
         headless = true;
       }
       // anything else!
@@ -348,26 +496,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"))
@@ -380,17 +542,19 @@ public class Jalview
     try
     {
       Console.initLogger();
-    } catch (NoClassDefFoundError error)
+    } 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);
     }
-
     desktop = null;
 
-    setLookAndFeel();
+    if (!(headless || headlessArg))
+      setLookAndFeel();
 
     /*
      * configure 'full' SO model if preferences say to, else use the default (full SO)
@@ -402,9 +566,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
 
@@ -464,7 +630,12 @@ public class Jalview
           }
         }
 
-        if (!aparser.contains("nowebservicediscovery"))
+        boolean doWebServiceDiscovery = !aparser
+                .contains("nowebservicediscovery");
+        if (bootstrapArgs.contains(Arg.WEBSERVICEDISCOVERY))
+          doWebServiceDiscovery = bootstrapArgs
+                  .getBoolean(Arg.WEBSERVICEDISCOVERY);
+        if (doWebServiceDiscovery)
         {
           desktop.startServiceDiscovery();
         }
@@ -511,19 +682,40 @@ public class Jalview
                   .println("CMD [-noquestionnaire] executed successfully!");
         }
 
-        if (!aparser.contains("nonews")
-                || Cache.getProperty("NONEWS") == null)
+        if ((!aparser.contains("nonews")
+                && Cache.getProperty("NONEWS") == null
+                && !"false".equals(bootstrapArgs.get(Arg.NEWS)))
+                || "true".equals(bootstrapArgs.get(Arg.NEWS)))
         {
           desktop.checkForNews();
         }
 
         if (!aparser.contains("nohtmltemplates")
-                || Cache.getProperty("NOHTMLTEMPLATES") == null)
+                && Cache.getProperty("NOHTMLTEMPLATES") == null)
         {
           BioJsHTMLOutput.updateBioJS();
         }
       }
     }
+    // Run Commands from cli
+    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");
+    }
+    else
+    {
+      if (headlessArg)
+      {
+        Jalview.exit("Error when running Commands in headless mode", 1);
+      }
+      Console.warn("Error when running commands");
+    }
 
     // Check if JVM and compile version might cause problems and log if it
     // might.
@@ -535,28 +727,12 @@ 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;
+
     DataSourceType protocol = null;
+
     FileLoader fileLoader = new FileLoader(!headless);
 
     String groovyscript = null; // script to execute after all loading is
@@ -565,11 +741,11 @@ 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;
     // Finally, deal with the remaining input data.
     if (file != null)
@@ -594,11 +770,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 + "'");
           }
         }
       }
@@ -633,7 +810,7 @@ public class Jalview
           if (cs != null)
           {
             System.out.println(
-                    "CMD [-color " + data + "] executed successfully!");
+                    "CMD [-colour " + data + "] executed successfully!");
           }
           af.changeColour(cs);
         }
@@ -829,13 +1006,17 @@ public class Jalview
         }
       }
     }
+
     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))
+            && 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
      * 
@@ -876,6 +1057,10 @@ public class Jalview
 
       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.
     }
 
@@ -967,20 +1152,6 @@ public class Jalview
         Console.error("Could not set requested laf=" + laf);
       }
       break;
-    case "quaqua":
-      lafSet = setQuaquaLookAndFeel();
-      if (!lafSet)
-      {
-        Console.error("Could not set requested laf=" + laf);
-      }
-      break;
-    case "vaqua":
-      lafSet = setVaquaLookAndFeel();
-      if (!lafSet)
-      {
-        Console.error("Could not set requested laf=" + laf);
-      }
-      break;
     case "mac":
       lafSet = setMacLookAndFeel();
       if (!lafSet)
@@ -998,7 +1169,7 @@ public class Jalview
       setSystemLookAndFeel();
       if (Platform.isLinux())
       {
-        setMetalLookAndFeel();
+        setLinuxLookAndFeel();
       }
       if (Platform.isMac())
       {
@@ -1089,55 +1260,113 @@ public class Jalview
 
   private static boolean setFlatLookAndFeel()
   {
-    boolean set = setSpecificLookAndFeel("flatlaf light",
-            "com.formdev.flatlaf.FlatLightLaf", false);
-    if (set)
+    boolean set = false;
+    if (SystemInfo.isMacOS)
     {
-      if (Platform.isMac())
+      try
       {
-        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)
-        {
-          Desktop.desktop.getRootPane()
-                  .putClientProperty("apple.awt.fullWindowContent", true);
-          Desktop.desktop.getRootPane()
-                  .putClientProperty("apple.awt.transparentTitleBar", true);
-        }
+        UIManager.setLookAndFeel(
+                "com.formdev.flatlaf.themes.FlatMacLightLaf");
+        set = true;
+        Console.debug("Using FlatMacLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        Console.debug("Exception loading FlatLightLaf", e);
+      }
+      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)
+      {
+        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);
+      }
+      SwingUtilities.invokeLater(() -> {
+        FlatMacLightLaf.setup();
+      });
+      Console.debug("Using FlatMacLightLaf");
+      set = true;
+    }
+    if (!set)
+    {
+      try
+      {
+        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;
+    }
+    else if (SystemInfo.isLinux)
+    {
+      try
+      {
+        UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
+        set = true;
+        Console.debug("Using FlatLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        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;
+    }
 
-        SwingUtilities.invokeLater(() -> {
-          FlatLightLaf.setup();
-        });
+    if (!set)
+    {
+      try
+      {
+        UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
+        set = true;
+        Console.debug("Using FlatLightLaf");
+      } catch (ClassNotFoundException | InstantiationException
+              | IllegalAccessException | UnsupportedLookAndFeelException e)
+      {
+        Console.debug("Exception loading FlatLightLaf", e);
       }
+    }
 
+    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.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);
     }
-    return set;
-  }
-
-  private static boolean setQuaquaLookAndFeel()
-  {
-    return setSpecificLookAndFeel("quaqua",
-            ch.randelshofer.quaqua.QuaquaManager.getLookAndFeel().getClass()
-                    .getName(),
-            false);
-  }
 
-  private static boolean setVaquaLookAndFeel()
-  {
-    return setSpecificLookAndFeel("vaqua",
-            "org.violetlib.aqua.AquaLookAndFeel", false);
+    Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
+    return set;
   }
 
   private static boolean setMacLookAndFeel()
@@ -1159,6 +1388,18 @@ public class Jalview
     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(
@@ -1369,19 +1610,12 @@ public class Jalview
   }
 
   /**
-   * Quit method delegates to Desktop.quit - unless running in headless mode
-   * when it just ends the JVM
+   * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
    */
   public void quit()
   {
-    if (desktop != null)
-    {
-      desktop.quit();
-    }
-    else
-    {
-      System.exit(0);
-    }
+    // System.exit will run the shutdownHook first
+    Jalview.exit("Quitting now. Bye!", 0);
   }
 
   public static AlignFrame getCurrentAlignFrame()
@@ -1393,4 +1627,21 @@ public class Jalview
   {
     Jalview.currentAlignFrame = currentAlignFrame;
   }
+
+  protected Commands getCommands()
+  {
+    return cmds;
+  }
+
+  public static void exit(String message, int exitcode)
+  {
+    Console.debug("Using Jalview.exit");
+    if (message != null)
+      if (exitcode == 0)
+        Console.info(message);
+      else
+        Console.error(message);
+    if (exitcode > -1)
+      System.exit(exitcode);
+  }
 }