JAL-4187 JAL-629 JAL-3830 Fix ArgParser to check for empty string argument. Fixed...
[jalview.git] / src / jalview / bin / Jalview.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import java.awt.Color;
24 import java.io.BufferedReader;
25 import java.io.File;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.io.OutputStream;
30 import java.io.OutputStreamWriter;
31 import java.io.PrintStream;
32 import java.io.PrintWriter;
33 import java.net.MalformedURLException;
34 import java.net.URI;
35 import java.net.URISyntaxException;
36 import java.net.URL;
37 import java.security.AllPermission;
38 import java.security.CodeSource;
39 import java.security.PermissionCollection;
40 import java.security.Permissions;
41 import java.security.Policy;
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.Properties;
48 import java.util.Vector;
49 import java.util.logging.ConsoleHandler;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52 import java.util.stream.Collectors;
53
54 import javax.swing.JDialog;
55 import javax.swing.JFrame;
56 import javax.swing.JOptionPane;
57 import javax.swing.SwingUtilities;
58 import javax.swing.UIManager;
59 import javax.swing.UIManager.LookAndFeelInfo;
60 import javax.swing.UnsupportedLookAndFeelException;
61
62 import com.formdev.flatlaf.FlatLightLaf;
63 import com.formdev.flatlaf.themes.FlatMacLightLaf;
64 import com.formdev.flatlaf.util.SystemInfo;
65 import com.threerings.getdown.util.LaunchUtil;
66
67 //import edu.stanford.ejalbert.launching.IBrowserLaunching;
68 import groovy.lang.Binding;
69 import groovy.util.GroovyScriptEngine;
70 import jalview.bin.argparser.Arg;
71 import jalview.bin.argparser.Arg.Opt;
72 import jalview.bin.argparser.Arg.Type;
73 import jalview.bin.argparser.ArgParser;
74 import jalview.bin.argparser.BootstrapArgs;
75 import jalview.ext.so.SequenceOntology;
76 import jalview.gui.AlignFrame;
77 import jalview.gui.Desktop;
78 import jalview.gui.PromptUserConfig;
79 import jalview.gui.QuitHandler;
80 import jalview.gui.QuitHandler.QResponse;
81 import jalview.io.AppletFormatAdapter;
82 import jalview.io.BioJsHTMLOutput;
83 import jalview.io.DataSourceType;
84 import jalview.io.FileFormat;
85 import jalview.io.FileFormatException;
86 import jalview.io.FileFormatI;
87 import jalview.io.FileFormats;
88 import jalview.io.FileLoader;
89 import jalview.io.HtmlSvgOutput;
90 import jalview.io.IdentifyFile;
91 import jalview.io.NewickFile;
92 import jalview.io.gff.SequenceOntologyFactory;
93 import jalview.schemes.ColourSchemeI;
94 import jalview.schemes.ColourSchemeProperty;
95 import jalview.util.ChannelProperties;
96 import jalview.util.HttpUtils;
97 import jalview.util.LaunchUtils;
98 import jalview.util.MessageManager;
99 import jalview.util.Platform;
100 import jalview.ws.jws2.Jws2Discoverer;
101
102 /**
103  * Main class for Jalview Application <br>
104  * <br>
105  * start with: java -classpath "$PATH_TO_LIB$/*:$PATH_TO_CLASSES$" \
106  * jalview.bin.Jalview
107  * 
108  * or on Windows: java -classpath "$PATH_TO_LIB$/*;$PATH_TO_CLASSES$" \
109  * jalview.bin.Jalview jalview.bin.Jalview
110  * 
111  * (ensure -classpath arg is quoted to avoid shell expansion of '*' and do not
112  * embellish '*' to e.g. '*.jar')
113  * 
114  * @author $author$
115  * @version $Revision$
116  */
117 public class Jalview
118 {
119   static
120   {
121     Platform.getURLCommandArguments();
122     Platform.addJ2SDirectDatabaseCall("https://www.jalview.org");
123     Platform.addJ2SDirectDatabaseCall("http://www.jalview.org");
124     Platform.addJ2SDirectDatabaseCall("http://www.compbio.dundee.ac.uk");
125     Platform.addJ2SDirectDatabaseCall("https://www.compbio.dundee.ac.uk");
126   }
127
128   /*
129    * singleton instance of this class
130    */
131   private static Jalview instance;
132
133   private Desktop desktop;
134
135   protected Commands cmds;
136
137   public static AlignFrame currentAlignFrame;
138
139   public ArgParser argparser = null;
140
141   public BootstrapArgs bootstrapArgs = null;
142
143   private boolean QUIET = false;
144
145   public static boolean quiet()
146   {
147     return Jalview.getInstance() != null && Jalview.getInstance().QUIET;
148   }
149
150   static
151   {
152     if (!Platform.isJS())
153     /**
154      * Java only
155      * 
156      * @j2sIgnore
157      */
158     {
159       // grab all the rights we can for the JVM
160       Policy.setPolicy(new Policy()
161       {
162         @Override
163         public PermissionCollection getPermissions(CodeSource codesource)
164         {
165           Permissions perms = new Permissions();
166           perms.add(new AllPermission());
167           return (perms);
168         }
169
170         @Override
171         public void refresh()
172         {
173         }
174       });
175     }
176   }
177
178   /**
179    * keep track of feature fetching tasks.
180    * 
181    * @author JimP
182    * 
183    */
184   class FeatureFetcher
185   {
186     /*
187      * TODO: generalise to track all jalview events to orchestrate batch processing
188      * events.
189      */
190
191     private int queued = 0;
192
193     private int running = 0;
194
195     public FeatureFetcher()
196     {
197
198     }
199
200     public void addFetcher(final AlignFrame af,
201             final Vector<String> dasSources)
202     {
203       final long id = System.currentTimeMillis();
204       queued++;
205       final FeatureFetcher us = this;
206       new Thread(new Runnable()
207       {
208
209         @Override
210         public void run()
211         {
212           synchronized (us)
213           {
214             queued--;
215             running++;
216           }
217
218           af.setProgressBar(MessageManager
219                   .getString("status.das_features_being_retrived"), id);
220           af.featureSettings_actionPerformed(null);
221           af.setProgressBar(null, id);
222           synchronized (us)
223           {
224             running--;
225           }
226         }
227       }).start();
228     }
229
230     public synchronized boolean allFinished()
231     {
232       return queued == 0 && running == 0;
233     }
234
235   }
236
237   public static Jalview getInstance()
238   {
239     return instance;
240   }
241
242   /**
243    * main class for Jalview application
244    * 
245    * @param args
246    *          open <em>filename</em>
247    */
248   public static void main(String[] args)
249   {
250     // setLogging(); // BH - for event debugging in JavaScript
251     instance = new Jalview();
252     instance.doMain(args);
253   }
254
255   private static void logClass(String name)
256   {
257     // BH - for event debugging in JavaScript
258     ConsoleHandler consoleHandler = new ConsoleHandler();
259     consoleHandler.setLevel(Level.ALL);
260     Logger logger = Logger.getLogger(name);
261     logger.setLevel(Level.ALL);
262     logger.addHandler(consoleHandler);
263   }
264
265   @SuppressWarnings("unused")
266   private static void setLogging()
267   {
268
269     /**
270      * @j2sIgnore
271      * 
272      */
273     {
274       System.out.println("not in js");
275     }
276
277     // BH - for event debugging in JavaScript (Java mode only)
278     if (!Platform.isJS())
279     /**
280      * Java only
281      * 
282      * @j2sIgnore
283      */
284     {
285       Logger.getLogger("").setLevel(Level.ALL);
286       logClass("java.awt.EventDispatchThread");
287       logClass("java.awt.EventQueue");
288       logClass("java.awt.Component");
289       logClass("java.awt.focus.Component");
290       logClass("java.awt.focus.DefaultKeyboardFocusManager");
291     }
292
293   }
294
295   /**
296    * @param args
297    */
298   void doMain(String[] args)
299   {
300     if (!Platform.isJS())
301     {
302       System.setSecurityManager(null);
303     }
304
305     if (args == null || args.length == 0 || (args.length == 1
306             && (args[0] == null || args[0].length() == 0)))
307     {
308       args = new String[] {};
309     }
310
311     // get args needed before proper ArgParser
312     bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
313
314     if (!Platform.isJS())
315     {
316       // are we being --quiet ?
317       if (bootstrapArgs.contains(Arg.QUIET))
318       {
319         QUIET = true;
320         OutputStream devNull = new OutputStream()
321         {
322
323           @Override
324           public void write(int b)
325           {
326             // DO NOTHING
327           }
328         };
329         System.setOut(new PrintStream(devNull));
330         // redirecting stderr not working
331         if (bootstrapArgs.getList(Arg.QUIET).size() > 1)
332         {
333           System.setErr(new PrintStream(devNull));
334         }
335       }
336
337       if (bootstrapArgs.contains(Arg.HELP)
338               || bootstrapArgs.contains(Arg.VERSION))
339       {
340         QUIET = true;
341       }
342     }
343
344     // Move any new getdown-launcher-new.jar into place over old
345     // getdown-launcher.jar
346     String appdirString = System.getProperty("getdownappdir");
347     if (appdirString != null && appdirString.length() > 0)
348     {
349       final File appdir = new File(appdirString);
350       new Thread()
351       {
352
353         @Override
354         public void run()
355         {
356           LaunchUtil.upgradeGetdown(
357                   new File(appdir, "getdown-launcher-old.jar"),
358                   new File(appdir, "getdown-launcher.jar"),
359                   new File(appdir, "getdown-launcher-new.jar"));
360         }
361       }.start();
362     }
363
364     if (!quiet() || bootstrapArgs.contains(Arg.VERSION))
365     {
366       System.out.println(
367               "Java version: " + System.getProperty("java.version"));
368       System.out.println("Java home: " + System.getProperty("java.home"));
369       System.out.println("Java arch: " + System.getProperty("os.arch") + " "
370               + System.getProperty("os.name") + " "
371               + System.getProperty("os.version"));
372
373       String val = System.getProperty("sys.install4jVersion");
374       if (val != null)
375       {
376         System.out.println("Install4j version: " + val);
377       }
378       val = System.getProperty("installer_template_version");
379       if (val != null)
380       {
381         System.out.println("Install4j template version: " + val);
382       }
383       val = System.getProperty("launcher_version");
384       if (val != null)
385       {
386         System.out.println("Launcher version: " + val);
387       }
388     }
389
390     if (Platform.isLinux() && LaunchUtils.getJavaVersion() < 11)
391     {
392       System.setProperty("flatlaf.uiScale", "1");
393     }
394
395     // get bootstrap properties (mainly for the logger level)
396     Properties bootstrapProperties = Cache
397             .bootstrapProperties(bootstrapArgs.get(Arg.PROPS));
398
399     // report Jalview version
400     Cache.loadBuildProperties(
401             !quiet() || bootstrapArgs.contains(Arg.VERSION));
402
403     // stop now if only after --version
404     if (bootstrapArgs.contains(Arg.VERSION))
405     {
406       Jalview.exit(null, 0);
407     }
408
409     // old ArgsParser
410     ArgsParser aparser = new ArgsParser(args);
411
412     // old
413     boolean headless = false;
414     // new
415     boolean headlessArg = false;
416
417     try
418     {
419       String logLevel = null;
420       if (bootstrapArgs.contains(Arg.TRACE))
421       {
422         logLevel = "TRACE";
423       }
424       else if (bootstrapArgs.contains(Arg.DEBUG))
425       {
426         logLevel = "DEBUG";
427       }
428       if (logLevel == null && !(bootstrapProperties == null))
429       {
430         logLevel = bootstrapProperties.getProperty(Cache.JALVIEWLOGLEVEL);
431       }
432       Console.initLogger(logLevel);
433     } catch (NoClassDefFoundError error)
434     {
435       error.printStackTrace();
436       String message = "\nEssential logging libraries not found."
437               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
438       Jalview.exit(message, 0);
439     }
440
441     // register SIGTERM listener
442     Runtime.getRuntime().addShutdownHook(new Thread()
443     {
444       public void run()
445       {
446         Console.debug("Running shutdown hook");
447         if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
448         {
449           // Got to here by a SIGTERM signal.
450           // Note we will not actually cancel the quit from here -- it's too
451           // late -- but we can wait for saving files.
452           Console.debug("Checking for saving files");
453           QuitHandler.getQuitResponse(false);
454         }
455         else
456         {
457           Console.debug("Nothing more to do");
458         }
459         Console.debug("Exiting, bye!");
460         // shutdownHook cannot be cancelled, JVM will now halt
461       }
462     });
463
464     String usrPropsFile = bootstrapArgs.contains(Arg.PROPS)
465             ? bootstrapArgs.get(Arg.PROPS)
466             : aparser.getValue("props");
467     // if usrPropsFile == null, loadProperties will use the Channel
468     // preferences.file
469     Cache.loadProperties(usrPropsFile);
470     if (usrPropsFile != null)
471     {
472       System.out.println(
473               "CMD [-props " + usrPropsFile + "] executed successfully!");
474       testoutput(bootstrapArgs, Arg.PROPS,
475               "test/jalview/bin/testProps.jvprops", usrPropsFile);
476     }
477
478     // --argfile=... -- OVERRIDES ALL NON-BOOTSTRAP ARGS
479     if (bootstrapArgs.contains(Arg.ARGFILE))
480     {
481       argparser = ArgParser.parseArgFiles(
482               bootstrapArgs.getValueList(Arg.ARGFILE),
483               bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
484               bootstrapArgs);
485     }
486     else
487     {
488       argparser = new ArgParser(args,
489               bootstrapArgs.getBoolean(Arg.INITSUBSTITUTIONS),
490               bootstrapArgs);
491     }
492
493     if (!Platform.isJS())
494     /**
495      * Java only
496      * 
497      * @j2sIgnore
498      */
499     {
500       if (bootstrapArgs.contains(Arg.HELP))
501       {
502         List<Map.Entry<Type, String>> helpArgs = bootstrapArgs
503                 .getList(Arg.HELP);
504         System.out.println(Arg.usage(helpArgs.stream().map(e -> e.getKey())
505                 .collect(Collectors.toList())));
506         Jalview.exit(null, 0);
507       }
508       if (aparser.contains("help") || aparser.contains("h"))
509       {
510         /*
511          * Now using new usage statement.
512         showUsage();
513         */
514         System.out.println(Arg.usage());
515         Jalview.exit(null, 0);
516       }
517
518       if (bootstrapArgs.contains(Arg.HEADLESS))
519       {
520         System.setProperty("java.awt.headless", "true");
521         // new
522         headlessArg = bootstrapArgs.getBoolean(Arg.HEADLESS);
523       }
524       if (aparser.contains("nodisplay") || aparser.contains("nogui")
525               || aparser.contains("headless"))
526       {
527         System.setProperty("java.awt.headless", "true");
528         // old
529         headless = true;
530       }
531       // anything else!
532
533       // allow https handshakes to download intermediate certs if necessary
534       System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
535
536       String jabawsUrl = bootstrapArgs.get(Arg.JABAWS);
537       if (jabawsUrl == null)
538         jabawsUrl = aparser.getValue("jabaws");
539       if (jabawsUrl != null)
540       {
541         try
542         {
543           Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
544           System.out.println(
545                   "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
546           testoutput(bootstrapArgs, Arg.JABAWS,
547                   "http://www.compbio.dundee.ac.uk/jabaws", jabawsUrl);
548         } catch (MalformedURLException e)
549         {
550           System.err.println(
551                   "Invalid jabaws parameter: " + jabawsUrl + " ignored");
552         }
553       }
554     }
555
556     List<String> setprops = new ArrayList<>();
557     if (bootstrapArgs.contains(Arg.SETPROP))
558     {
559       setprops = bootstrapArgs.getValueList(Arg.SETPROP);
560     }
561     else
562     {
563       String sp = aparser.getValue("setprop");
564       while (sp != null)
565       {
566         setprops.add(sp);
567         sp = aparser.getValue("setprop");
568       }
569     }
570     for (String setprop : setprops)
571     {
572       int p = setprop.indexOf('=');
573       if (p == -1)
574       {
575         System.err
576                 .println("Ignoring invalid setprop argument : " + setprop);
577       }
578       else
579       {
580         System.out.println("Executing setprop argument: " + setprop);
581         if (Platform.isJS())
582         {
583           Cache.setProperty(setprop.substring(0, p),
584                   setprop.substring(p + 1));
585         }
586         // DISABLED FOR SECURITY REASONS
587         // TODO: add a property to allow properties to be overriden by cli args
588         // Cache.setProperty(setprop.substring(0,p), setprop.substring(p+1));
589       }
590     }
591     if (System.getProperty("java.awt.headless") != null
592             && System.getProperty("java.awt.headless").equals("true"))
593     {
594       headless = true;
595     }
596     System.setProperty("http.agent",
597             "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
598
599     try
600     {
601       Console.initLogger();
602     } catch (
603
604     NoClassDefFoundError error)
605     {
606       error.printStackTrace();
607       String message = "\nEssential logging libraries not found."
608               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
609       Jalview.exit(message, 0);
610     }
611     desktop = null;
612
613     if (!(headless || headlessArg))
614       setLookAndFeel();
615
616     /*
617      * configure 'full' SO model if preferences say to, else use the default (full SO)
618      * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
619      */
620     boolean soDefault = !Platform.isJS();
621     if (Cache.getDefault("USE_FULL_SO", soDefault))
622     {
623       SequenceOntologyFactory.setInstance(new SequenceOntology());
624     }
625
626     if (!(headless || headlessArg))
627     {
628       Desktop.nosplash = "false".equals(bootstrapArgs.get(Arg.SPLASH))
629               || aparser.contains("nosplash")
630               || Cache.getDefault("SPLASH", "true").equals("false");
631       desktop = new Desktop();
632       desktop.setInBatchMode(true); // indicate we are starting up
633
634       try
635       {
636         JalviewTaskbar.setTaskbar(this);
637       } catch (Exception e)
638       {
639         Console.info("Cannot set Taskbar");
640         Console.error(e.getMessage());
641         // e.printStackTrace();
642       } catch (Throwable t)
643       {
644         Console.info("Cannot set Taskbar");
645         Console.error(t.getMessage());
646         // t.printStackTrace();
647       }
648
649       // set Proxy settings before all the internet calls
650       Cache.setProxyPropertiesFromPreferences();
651
652       desktop.setVisible(true);
653
654       if (!Platform.isJS())
655       /**
656        * Java only
657        * 
658        * @j2sIgnore
659        */
660       {
661
662         /**
663          * Check to see that the JVM version being run is suitable for the Java
664          * version this Jalview was compiled for. Popup a warning if not.
665          */
666         if (!LaunchUtils.checkJavaVersion())
667         {
668           Console.warn("The Java version being used (Java "
669                   + LaunchUtils.getJavaVersion()
670                   + ") may lead to problems. This installation of Jalview should be used with Java "
671                   + LaunchUtils.getJavaCompileVersion() + ".");
672
673           if (!LaunchUtils
674                   .getBooleanUserPreference("IGNORE_JVM_WARNING_POPUP"))
675           {
676             Object[] options = {
677                 MessageManager.getString("label.continue") };
678             JOptionPane.showOptionDialog(null,
679                     MessageManager.formatMessage(
680                             "warning.wrong_jvm_version_message",
681                             LaunchUtils.getJavaVersion(),
682                             LaunchUtils.getJavaCompileVersion()),
683                     MessageManager
684                             .getString("warning.wrong_jvm_version_title"),
685                     JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
686                     null, options, options[0]);
687           }
688         }
689
690         boolean webservicediscovery = bootstrapArgs
691                 .getBoolean(Arg.WEBSERVICEDISCOVERY);
692         if (aparser.contains("nowebservicediscovery"))
693           webservicediscovery = false;
694         if (webservicediscovery)
695         {
696           desktop.startServiceDiscovery();
697         }
698         else
699         {
700           testoutput(argparser, Arg.WEBSERVICEDISCOVERY);
701         }
702
703         boolean usagestats = bootstrapArgs.getBoolean(Arg.USAGESTATS);
704         if (aparser.contains("nousagestats"))
705           usagestats = false;
706         if (usagestats)
707         {
708           startUsageStats(desktop);
709           testoutput(argparser, Arg.USAGESTATS);
710         }
711         else
712         {
713           System.out.println("CMD [-nousagestats] executed successfully!");
714           testoutput(argparser, Arg.USAGESTATS);
715         }
716
717         boolean questionnaire = bootstrapArgs.getBoolean(Arg.QUESTIONNAIRE);
718         if (aparser.contains("noquestionnaire"))
719           questionnaire = false;
720         if (questionnaire)
721         {
722           String url = aparser.getValue("questionnaire");
723           if (url != null)
724           {
725             // Start the desktop questionnaire prompter with the specified
726             // questionnaire
727             Console.debug("Starting questionnaire url at " + url);
728             desktop.checkForQuestionnaire(url);
729             System.out.println("CMD questionnaire[-" + url
730                     + "] executed successfully!");
731           }
732           else
733           {
734             if (Cache.getProperty("NOQUESTIONNAIRES") == null)
735             {
736               // Start the desktop questionnaire prompter with the specified
737               // questionnaire
738               // String defurl =
739               // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
740               // //
741               String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
742               Console.debug(
743                       "Starting questionnaire with default url: " + defurl);
744               desktop.checkForQuestionnaire(defurl);
745             }
746           }
747         }
748         else
749         {
750           System.out
751                   .println("CMD [-noquestionnaire] executed successfully!");
752           testoutput(argparser, Arg.QUESTIONNAIRE);
753         }
754
755         if ((!aparser.contains("nonews")
756                 && Cache.getProperty("NONEWS") == null
757                 && !"false".equals(bootstrapArgs.get(Arg.NEWS)))
758                 || "true".equals(bootstrapArgs.get(Arg.NEWS)))
759         {
760           desktop.checkForNews();
761         }
762
763         if (!aparser.contains("nohtmltemplates")
764                 && Cache.getProperty("NOHTMLTEMPLATES") == null)
765         {
766           BioJsHTMLOutput.updateBioJS();
767         }
768       }
769     }
770     // Run Commands from cli
771     cmds = new Commands(argparser, headlessArg);
772     boolean commandsSuccess = cmds.argsWereParsed();
773     if (commandsSuccess)
774     {
775       if (headlessArg)
776       {
777         Jalview.exit("Successfully completed commands in headless mode", 0);
778       }
779       Console.info("Successfully completed commands");
780     }
781     else
782     {
783       if (headlessArg)
784       {
785         Jalview.exit("Error when running Commands in headless mode", 1);
786       }
787       Console.warn("Error when running commands");
788     }
789
790     // Check if JVM and compile version might cause problems and log if it
791     // might.
792     if (headless && !Platform.isJS() && !LaunchUtils.checkJavaVersion())
793     {
794       Console.warn("The Java version being used (Java "
795               + LaunchUtils.getJavaVersion()
796               + ") may lead to problems. This installation of Jalview should be used with Java "
797               + LaunchUtils.getJavaCompileVersion() + ".");
798     }
799
800     String file = null, data = null;
801
802     FileFormatI format = null;
803
804     DataSourceType protocol = null;
805
806     FileLoader fileLoader = new FileLoader(!headless);
807
808     String groovyscript = null; // script to execute after all loading is
809     // completed one way or another
810     // extract groovy argument and execute if necessary
811     groovyscript = aparser.getValue("groovy", true);
812     file = aparser.getValue("open", true);
813
814     if (file == null && desktop == null && !commandsSuccess)
815     {
816       Jalview.exit("No files to open!", 1);
817     }
818
819     long progress = -1;
820     // Finally, deal with the remaining input data.
821     if (file != null)
822     {
823       if (!headless)
824       {
825         desktop.setProgressBar(
826                 MessageManager
827                         .getString("status.processing_commandline_args"),
828                 progress = System.currentTimeMillis());
829       }
830       System.out.println("CMD [-open " + file + "] executed successfully!");
831
832       if (!Platform.isJS())
833       /**
834        * ignore in JavaScript -- can't just file existence - could load it?
835        * 
836        * @j2sIgnore
837        */
838       {
839         if (!HttpUtils.startsWithHttpOrHttps(file))
840         {
841           if (!(new File(file)).exists())
842           {
843             if (headless)
844             {
845               Jalview.exit(
846                       "Can't find file '" + file + "' in headless mode", 1);
847             }
848             Console.warn("Can't find file'" + file + "'");
849           }
850         }
851       }
852
853       protocol = AppletFormatAdapter.checkProtocol(file);
854
855       try
856       {
857         format = new IdentifyFile().identify(file, protocol);
858       } catch (FileFormatException e1)
859       {
860         // TODO ?
861       }
862
863       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
864               format);
865       if (af == null)
866       {
867         System.out.println("error");
868       }
869       else
870       {
871         setCurrentAlignFrame(af);
872         data = aparser.getValue("colour", true);
873         if (data != null)
874         {
875           data.replaceAll("%20", " ");
876
877           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
878                   af.getViewport(), af.getViewport().getAlignment(), data);
879
880           if (cs != null)
881           {
882             System.out.println(
883                     "CMD [-colour " + data + "] executed successfully!");
884           }
885           af.changeColour(cs);
886         }
887
888         // Must maintain ability to use the groups flag
889         data = aparser.getValue("groups", true);
890         if (data != null)
891         {
892           af.parseFeaturesFile(data,
893                   AppletFormatAdapter.checkProtocol(data));
894           // System.out.println("Added " + data);
895           System.out.println(
896                   "CMD groups[-" + data + "]  executed successfully!");
897         }
898         data = aparser.getValue("features", true);
899         if (data != null)
900         {
901           af.parseFeaturesFile(data,
902                   AppletFormatAdapter.checkProtocol(data));
903           // System.out.println("Added " + data);
904           System.out.println(
905                   "CMD [-features " + data + "]  executed successfully!");
906         }
907
908         data = aparser.getValue("annotations", true);
909         if (data != null)
910         {
911           af.loadJalviewDataFile(data, null, null, null);
912           // System.out.println("Added " + data);
913           System.out.println(
914                   "CMD [-annotations " + data + "] executed successfully!");
915         }
916         // set or clear the sortbytree flag.
917         if (aparser.contains("sortbytree"))
918         {
919           af.getViewport().setSortByTree(true);
920           if (af.getViewport().getSortByTree())
921           {
922             System.out.println("CMD [-sortbytree] executed successfully!");
923           }
924         }
925         if (aparser.contains("no-annotation"))
926         {
927           af.getViewport().setShowAnnotation(false);
928           if (!af.getViewport().isShowAnnotation())
929           {
930             System.out.println("CMD no-annotation executed successfully!");
931           }
932         }
933         if (aparser.contains("nosortbytree"))
934         {
935           af.getViewport().setSortByTree(false);
936           if (!af.getViewport().getSortByTree())
937           {
938             System.out
939                     .println("CMD [-nosortbytree] executed successfully!");
940           }
941         }
942         data = aparser.getValue("tree", true);
943         if (data != null)
944         {
945           try
946           {
947             System.out.println(
948                     "CMD [-tree " + data + "] executed successfully!");
949             NewickFile nf = new NewickFile(data,
950                     AppletFormatAdapter.checkProtocol(data));
951             af.getViewport()
952                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
953           } catch (IOException ex)
954           {
955             System.err.println("Couldn't add tree " + data);
956             ex.printStackTrace(System.err);
957           }
958         }
959         // TODO - load PDB structure(s) to alignment JAL-629
960         // (associate with identical sequence in alignment, or a specified
961         // sequence)
962         if (groovyscript != null)
963         {
964           // Execute the groovy script after we've done all the rendering stuff
965           // and before any images or figures are generated.
966           System.out.println("Executing script " + groovyscript);
967           executeGroovyScript(groovyscript, af);
968           System.out.println("CMD groovy[" + groovyscript
969                   + "] executed successfully!");
970           groovyscript = null;
971         }
972         String imageName = "unnamed.png";
973         while (aparser.getSize() > 1)
974         {
975           String outputFormat = aparser.nextValue();
976           file = aparser.nextValue();
977
978           if (outputFormat.equalsIgnoreCase("png"))
979           {
980             af.createPNG(new File(file));
981             imageName = (new File(file)).getName();
982             System.out.println("Creating PNG image: " + file);
983             continue;
984           }
985           else if (outputFormat.equalsIgnoreCase("svg"))
986           {
987             File imageFile = new File(file);
988             imageName = imageFile.getName();
989             af.createSVG(imageFile);
990             System.out.println("Creating SVG image: " + file);
991             continue;
992           }
993           else if (outputFormat.equalsIgnoreCase("html"))
994           {
995             File imageFile = new File(file);
996             imageName = imageFile.getName();
997             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
998             htmlSVG.exportHTML(file);
999
1000             System.out.println("Creating HTML image: " + file);
1001             continue;
1002           }
1003           else if (outputFormat.equalsIgnoreCase("biojsmsa"))
1004           {
1005             if (file == null)
1006             {
1007               System.err.println("The output html file must not be null");
1008               return;
1009             }
1010             try
1011             {
1012               BioJsHTMLOutput.refreshVersionInfo(
1013                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
1014             } catch (URISyntaxException e)
1015             {
1016               e.printStackTrace();
1017             }
1018             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
1019             bjs.exportHTML(file);
1020             System.out
1021                     .println("Creating BioJS MSA Viwer HTML file: " + file);
1022             continue;
1023           }
1024           else if (outputFormat.equalsIgnoreCase("imgMap"))
1025           {
1026             af.createImageMap(new File(file), imageName);
1027             System.out.println("Creating image map: " + file);
1028             continue;
1029           }
1030           else if (outputFormat.equalsIgnoreCase("eps"))
1031           {
1032             File outputFile = new File(file);
1033             System.out.println(
1034                     "Creating EPS file: " + outputFile.getAbsolutePath());
1035             af.createEPS(outputFile);
1036             continue;
1037           }
1038           FileFormatI outFormat = null;
1039           try
1040           {
1041             outFormat = FileFormats.getInstance().forName(outputFormat);
1042           } catch (Exception formatP)
1043           {
1044             System.out.println("Couldn't parse " + outFormat
1045                     + " as a valid Jalview format string.");
1046           }
1047           if (outFormat != null)
1048           {
1049             if (!outFormat.isWritable())
1050             {
1051               System.out.println(
1052                       "This version of Jalview does not support alignment export as "
1053                               + outputFormat);
1054             }
1055             else
1056             {
1057               af.saveAlignment(file, outFormat);
1058               if (af.isSaveAlignmentSuccessful())
1059               {
1060                 System.out.println("Written alignment in "
1061                         + outFormat.getName() + " format to " + file);
1062               }
1063               else
1064               {
1065                 System.out.println("Error writing file " + file + " in "
1066                         + outFormat.getName() + " format!!");
1067               }
1068             }
1069           }
1070
1071         }
1072
1073         while (aparser.getSize() > 0)
1074         {
1075           System.out.println("Unknown arg: " + aparser.nextValue());
1076         }
1077       }
1078     }
1079
1080     AlignFrame startUpAlframe = null;
1081     // We'll only open the default file if the desktop is visible.
1082     // And the user
1083     // ////////////////////
1084
1085     if (!Platform.isJS() && !headless && file == null
1086             && Cache.getDefault("SHOW_STARTUP_FILE", true)
1087             && !cmds.commandArgsProvided())
1088     // don't open the startup file if command line args have been processed
1089     // (&& !Commands.commandArgsProvided())
1090     /**
1091      * Java only
1092      * 
1093      * @j2sIgnore
1094      */
1095     {
1096       file = Cache.getDefault("STARTUP_FILE",
1097               Cache.getDefault("www.jalview.org", "https://www.jalview.org")
1098                       + "/examples/exampleFile_2_7.jvp");
1099       if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
1100               || file.equals(
1101                       "http://www.jalview.org/examples/exampleFile_2_7.jar"))
1102       {
1103         file.replace("http:", "https:");
1104         // hardwire upgrade of the startup file
1105         file.replace("_2_3", "_2_7");
1106         file.replace("2_7.jar", "2_7.jvp");
1107         // and remove the stale setting
1108         Cache.removeProperty("STARTUP_FILE");
1109       }
1110
1111       protocol = AppletFormatAdapter.checkProtocol(file);
1112
1113       if (file.endsWith(".jar"))
1114       {
1115         format = FileFormat.Jalview;
1116       }
1117       else
1118       {
1119         try
1120         {
1121           format = new IdentifyFile().identify(file, protocol);
1122         } catch (FileFormatException e)
1123         {
1124           // TODO what?
1125         }
1126       }
1127
1128       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
1129               format);
1130       // don't ask to save when quitting if only the startup file has been
1131       // opened
1132       Console.debug("Resetting up-to-date flag for startup file");
1133       startUpAlframe.getViewport().setSavedUpToDate(true);
1134       // extract groovy arguments before anything else.
1135     }
1136
1137     // Once all other stuff is done, execute any groovy scripts (in order)
1138     if (groovyscript != null)
1139     {
1140       if (Cache.groovyJarsPresent())
1141       {
1142         System.out.println("Executing script " + groovyscript);
1143         executeGroovyScript(groovyscript, startUpAlframe);
1144       }
1145       else
1146       {
1147         System.err.println(
1148                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
1149                         + groovyscript);
1150       }
1151     }
1152     // and finally, turn off batch mode indicator - if the desktop still exists
1153     if (desktop != null)
1154     {
1155       if (progress != -1)
1156       {
1157         desktop.setProgressBar(null, progress);
1158       }
1159       desktop.setInBatchMode(false);
1160     }
1161   }
1162
1163   private static void setLookAndFeel()
1164   {
1165     // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
1166     // "mac" or "flat"
1167     // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
1168     // try Quaqua/Vaqua.
1169     String lafProp = System.getProperty("laf");
1170     String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
1171     String laf = "none";
1172     if (lafProp != null)
1173     {
1174       laf = lafProp;
1175     }
1176     else if (lafSetting != null)
1177     {
1178       laf = lafSetting;
1179     }
1180     boolean lafSet = false;
1181     switch (laf)
1182     {
1183     case "crossplatform":
1184       lafSet = setCrossPlatformLookAndFeel();
1185       if (!lafSet)
1186       {
1187         Console.error("Could not set requested laf=" + laf);
1188       }
1189       break;
1190     case "system":
1191       lafSet = setSystemLookAndFeel();
1192       if (!lafSet)
1193       {
1194         Console.error("Could not set requested laf=" + laf);
1195       }
1196       break;
1197     case "gtk":
1198       lafSet = setGtkLookAndFeel();
1199       if (!lafSet)
1200       {
1201         Console.error("Could not set requested laf=" + laf);
1202       }
1203       break;
1204     case "metal":
1205       lafSet = setMetalLookAndFeel();
1206       if (!lafSet)
1207       {
1208         Console.error("Could not set requested laf=" + laf);
1209       }
1210       break;
1211     case "nimbus":
1212       lafSet = setNimbusLookAndFeel();
1213       if (!lafSet)
1214       {
1215         Console.error("Could not set requested laf=" + laf);
1216       }
1217       break;
1218     case "flat":
1219       lafSet = setFlatLookAndFeel();
1220       if (!lafSet)
1221       {
1222         Console.error("Could not set requested laf=" + laf);
1223       }
1224       break;
1225     case "mac":
1226       lafSet = setMacLookAndFeel();
1227       if (!lafSet)
1228       {
1229         Console.error("Could not set requested laf=" + laf);
1230       }
1231       break;
1232     case "none":
1233       break;
1234     default:
1235       Console.error("Requested laf=" + laf + " not implemented");
1236     }
1237     if (!lafSet)
1238     {
1239       setSystemLookAndFeel();
1240       if (Platform.isLinux())
1241       {
1242         setLinuxLookAndFeel();
1243       }
1244       if (Platform.isMac())
1245       {
1246         setMacLookAndFeel();
1247       }
1248     }
1249   }
1250
1251   private static boolean setCrossPlatformLookAndFeel()
1252   {
1253     boolean set = false;
1254     try
1255     {
1256       UIManager.setLookAndFeel(
1257               UIManager.getCrossPlatformLookAndFeelClassName());
1258       set = true;
1259     } catch (Exception ex)
1260     {
1261       Console.error("Unexpected Look and Feel Exception");
1262       Console.error(ex.getMessage());
1263       Console.debug(Cache.getStackTraceString(ex));
1264     }
1265     return set;
1266   }
1267
1268   private static boolean setSystemLookAndFeel()
1269   {
1270     boolean set = false;
1271     try
1272     {
1273       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1274       set = true;
1275     } catch (Exception ex)
1276     {
1277       Console.error("Unexpected Look and Feel Exception");
1278       Console.error(ex.getMessage());
1279       Console.debug(Cache.getStackTraceString(ex));
1280     }
1281     return set;
1282   }
1283
1284   private static boolean setSpecificLookAndFeel(String name,
1285           String className, boolean nameStartsWith)
1286   {
1287     boolean set = false;
1288     try
1289     {
1290       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
1291       {
1292         if (info.getName() != null && nameStartsWith
1293                 ? info.getName().toLowerCase(Locale.ROOT)
1294                         .startsWith(name.toLowerCase(Locale.ROOT))
1295                 : info.getName().toLowerCase(Locale.ROOT)
1296                         .equals(name.toLowerCase(Locale.ROOT)))
1297         {
1298           className = info.getClassName();
1299           break;
1300         }
1301       }
1302       UIManager.setLookAndFeel(className);
1303       set = true;
1304     } catch (Exception ex)
1305     {
1306       Console.error("Unexpected Look and Feel Exception");
1307       Console.error(ex.getMessage());
1308       Console.debug(Cache.getStackTraceString(ex));
1309     }
1310     return set;
1311   }
1312
1313   private static boolean setGtkLookAndFeel()
1314   {
1315     return setSpecificLookAndFeel("gtk",
1316             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
1317   }
1318
1319   private static boolean setMetalLookAndFeel()
1320   {
1321     return setSpecificLookAndFeel("metal",
1322             "javax.swing.plaf.metal.MetalLookAndFeel", false);
1323   }
1324
1325   private static boolean setNimbusLookAndFeel()
1326   {
1327     return setSpecificLookAndFeel("nimbus",
1328             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
1329   }
1330
1331   private static boolean setFlatLookAndFeel()
1332   {
1333     boolean set = false;
1334     if (SystemInfo.isMacOS)
1335     {
1336       try
1337       {
1338         UIManager.setLookAndFeel(
1339                 "com.formdev.flatlaf.themes.FlatMacLightLaf");
1340         set = true;
1341         Console.debug("Using FlatMacLightLaf");
1342       } catch (ClassNotFoundException | InstantiationException
1343               | IllegalAccessException | UnsupportedLookAndFeelException e)
1344       {
1345         Console.debug("Exception loading FlatLightLaf", e);
1346       }
1347       System.setProperty("apple.laf.useScreenMenuBar", "true");
1348       System.setProperty("apple.awt.application.name",
1349               ChannelProperties.getProperty("app_name"));
1350       System.setProperty("apple.awt.application.appearance", "system");
1351       if (SystemInfo.isMacFullWindowContentSupported
1352               && Desktop.desktop != null)
1353       {
1354         Console.debug("Setting transparent title bar");
1355         Desktop.desktop.getRootPane()
1356                 .putClientProperty("apple.awt.fullWindowContent", true);
1357         Desktop.desktop.getRootPane()
1358                 .putClientProperty("apple.awt.transparentTitleBar", true);
1359         Desktop.desktop.getRootPane()
1360                 .putClientProperty("apple.awt.fullscreenable", true);
1361       }
1362       SwingUtilities.invokeLater(() -> {
1363         FlatMacLightLaf.setup();
1364       });
1365       Console.debug("Using FlatMacLightLaf");
1366       set = true;
1367     }
1368     if (!set)
1369     {
1370       try
1371       {
1372         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1373         set = true;
1374         Console.debug("Using FlatLightLaf");
1375       } catch (ClassNotFoundException | InstantiationException
1376               | IllegalAccessException | UnsupportedLookAndFeelException e)
1377       {
1378         Console.debug("Exception loading FlatLightLaf", e);
1379       }
1380       // Windows specific properties here
1381       SwingUtilities.invokeLater(() -> {
1382         FlatLightLaf.setup();
1383       });
1384       Console.debug("Using FlatLightLaf");
1385       set = true;
1386     }
1387     else if (SystemInfo.isLinux)
1388     {
1389       try
1390       {
1391         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1392         set = true;
1393         Console.debug("Using FlatLightLaf");
1394       } catch (ClassNotFoundException | InstantiationException
1395               | IllegalAccessException | UnsupportedLookAndFeelException e)
1396       {
1397         Console.debug("Exception loading FlatLightLaf", e);
1398       }
1399       // enable custom window decorations
1400       JFrame.setDefaultLookAndFeelDecorated(true);
1401       JDialog.setDefaultLookAndFeelDecorated(true);
1402       SwingUtilities.invokeLater(() -> {
1403         FlatLightLaf.setup();
1404       });
1405       Console.debug("Using FlatLightLaf");
1406       set = true;
1407     }
1408
1409     if (!set)
1410     {
1411       try
1412       {
1413         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1414         set = true;
1415         Console.debug("Using FlatLightLaf");
1416       } catch (ClassNotFoundException | InstantiationException
1417               | IllegalAccessException | UnsupportedLookAndFeelException e)
1418       {
1419         Console.debug("Exception loading FlatLightLaf", e);
1420       }
1421     }
1422
1423     if (set)
1424     {
1425       UIManager.put("TabbedPane.tabType", "card");
1426       UIManager.put("TabbedPane.showTabSeparators", true);
1427       UIManager.put("TabbedPane.showContentSeparator", true);
1428       UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
1429       UIManager.put("TabbedPane.tabsOverlapBorder", true);
1430       UIManager.put("TabbedPane.hasFullBorder", true);
1431       UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
1432       UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
1433       UIManager.put("TabbedPane.smoothScrolling", true);
1434       UIManager.put("TabbedPane.tabWidthMode", "compact");
1435       UIManager.put("TabbedPane.selectedBackground", Color.white);
1436     }
1437
1438     Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
1439     return set;
1440   }
1441
1442   private static boolean setMacLookAndFeel()
1443   {
1444     boolean set = false;
1445     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
1446             ChannelProperties.getProperty("app_name"));
1447     System.setProperty("apple.laf.useScreenMenuBar", "true");
1448     /*
1449      * broken native LAFs on (ARM?) macbooks
1450     set = setQuaquaLookAndFeel();
1451     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
1452             .toLowerCase(Locale.ROOT).contains("quaqua"))
1453     {
1454       set = setVaquaLookAndFeel();
1455     }
1456      */
1457     set = setFlatLookAndFeel();
1458     return set;
1459   }
1460
1461   private static boolean setLinuxLookAndFeel()
1462   {
1463     boolean set = false;
1464     set = setFlatLookAndFeel();
1465     if (!set)
1466       set = setMetalLookAndFeel();
1467     // avoid GtkLookAndFeel -- not good results especially on HiDPI
1468     if (!set)
1469       set = setNimbusLookAndFeel();
1470     return set;
1471   }
1472
1473   private static void showUsage()
1474   {
1475     System.out.println(
1476             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
1477                     + "-nodisplay\tRun Jalview without User Interface.\n"
1478                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
1479                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
1480                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
1481                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
1482                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
1483                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
1484                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
1485                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
1486                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
1487                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
1488                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
1489                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
1490                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
1491                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
1492                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
1493                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
1494                     + "-html FILE\tCreate HTML file from alignment.\n"
1495                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
1496                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
1497                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
1498                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
1499                     + "-noquestionnaire\tTurn off questionnaire check.\n"
1500                     + "-nonews\tTurn off check for Jalview news.\n"
1501                     + "-nousagestats\tTurn off google analytics tracking for this session.\n"
1502                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
1503                     // +
1504                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
1505                     // after all other properties files have been read\n\t
1506                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
1507                     // passed in correctly)"
1508                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
1509                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
1510                     + "-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"
1511                     + "-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"
1512                     + "-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"
1513                     + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
1514   }
1515
1516   private static void startUsageStats(final Desktop desktop)
1517   {
1518     /**
1519      * start a User Config prompt asking if we can log usage statistics.
1520      */
1521     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
1522             "USAGESTATS", "Jalview Usage Statistics",
1523             "Do you want to help make Jalview better by enabling "
1524                     + "the collection of usage statistics with Google Analytics ?"
1525                     + "\n\n(you can enable or disable usage tracking in the preferences)",
1526             new Runnable()
1527             {
1528               @Override
1529               public void run()
1530               {
1531                 Console.debug(
1532                         "Initialising googletracker for usage stats.");
1533                 Cache.initGoogleTracker();
1534                 Console.debug("Tracking enabled.");
1535               }
1536             }, new Runnable()
1537             {
1538               @Override
1539               public void run()
1540               {
1541                 Console.debug("Not enabling Google Tracking.");
1542               }
1543             }, null, true);
1544     desktop.addDialogThread(prompter);
1545   }
1546
1547   /**
1548    * Locate the given string as a file and pass it to the groovy interpreter.
1549    * 
1550    * @param groovyscript
1551    *          the script to execute
1552    * @param jalviewContext
1553    *          the Jalview Desktop object passed in to the groovy binding as the
1554    *          'Jalview' object.
1555    */
1556   protected void executeGroovyScript(String groovyscript, AlignFrame af)
1557   {
1558     /**
1559      * for scripts contained in files
1560      */
1561     File tfile = null;
1562     /**
1563      * script's URI
1564      */
1565     URL sfile = null;
1566     if (groovyscript.trim().equals("STDIN"))
1567     {
1568       // read from stdin into a tempfile and execute it
1569       try
1570       {
1571         tfile = File.createTempFile("jalview", "groovy");
1572         PrintWriter outfile = new PrintWriter(
1573                 new OutputStreamWriter(new FileOutputStream(tfile)));
1574         BufferedReader br = new BufferedReader(
1575                 new InputStreamReader(System.in));
1576         String line = null;
1577         while ((line = br.readLine()) != null)
1578         {
1579           outfile.write(line + "\n");
1580         }
1581         br.close();
1582         outfile.flush();
1583         outfile.close();
1584
1585       } catch (Exception ex)
1586       {
1587         System.err.println("Failed to read from STDIN into tempfile "
1588                 + ((tfile == null) ? "(tempfile wasn't created)"
1589                         : tfile.toString()));
1590         ex.printStackTrace();
1591         return;
1592       }
1593       try
1594       {
1595         sfile = tfile.toURI().toURL();
1596       } catch (Exception x)
1597       {
1598         System.err.println(
1599                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1600                         + tfile.toURI());
1601         x.printStackTrace();
1602         return;
1603       }
1604     }
1605     else
1606     {
1607       try
1608       {
1609         sfile = new URI(groovyscript).toURL();
1610       } catch (Exception x)
1611       {
1612         tfile = new File(groovyscript);
1613         if (!tfile.exists())
1614         {
1615           System.err.println("File '" + groovyscript + "' does not exist.");
1616           return;
1617         }
1618         if (!tfile.canRead())
1619         {
1620           System.err.println("File '" + groovyscript + "' cannot be read.");
1621           return;
1622         }
1623         if (tfile.length() < 1)
1624         {
1625           System.err.println("File '" + groovyscript + "' is empty.");
1626           return;
1627         }
1628         try
1629         {
1630           sfile = tfile.getAbsoluteFile().toURI().toURL();
1631         } catch (Exception ex)
1632         {
1633           System.err.println("Failed to create a file URL for "
1634                   + tfile.getAbsoluteFile());
1635           return;
1636         }
1637       }
1638     }
1639     try
1640     {
1641       Map<String, java.lang.Object> vbinding = new HashMap<>();
1642       vbinding.put("Jalview", this);
1643       if (af != null)
1644       {
1645         vbinding.put("currentAlFrame", af);
1646       }
1647       Binding gbinding = new Binding(vbinding);
1648       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1649       gse.run(sfile.toString(), gbinding);
1650       if ("STDIN".equals(groovyscript))
1651       {
1652         // delete temp file that we made -
1653         // only if it was successfully executed
1654         tfile.delete();
1655       }
1656     } catch (Exception e)
1657     {
1658       System.err.println("Exception Whilst trying to execute file " + sfile
1659               + " as a groovy script.");
1660       e.printStackTrace(System.err);
1661
1662     }
1663   }
1664
1665   public static boolean isHeadlessMode()
1666   {
1667     String isheadless = System.getProperty("java.awt.headless");
1668     if (isheadless != null && isheadless.equalsIgnoreCase("true"))
1669     {
1670       return true;
1671     }
1672     return false;
1673   }
1674
1675   public AlignFrame[] getAlignFrames()
1676   {
1677     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1678             : Desktop.getAlignFrames();
1679
1680   }
1681
1682   /**
1683    * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
1684    */
1685   public void quit()
1686   {
1687     // System.exit will run the shutdownHook first
1688     Jalview.exit("Quitting now. Bye!", 0);
1689   }
1690
1691   public static AlignFrame getCurrentAlignFrame()
1692   {
1693     return Jalview.currentAlignFrame;
1694   }
1695
1696   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
1697   {
1698     Jalview.currentAlignFrame = currentAlignFrame;
1699   }
1700
1701   protected Commands getCommands()
1702   {
1703     return cmds;
1704   }
1705
1706   public static void exit(String message, int exitcode)
1707   {
1708     if (Console.log == null)
1709     {
1710       // Don't start the logger just to exit!
1711       if (message != null)
1712       {
1713         if (exitcode == 0)
1714         {
1715           System.out.println(message);
1716         }
1717         else
1718         {
1719           System.err.println(message);
1720         }
1721       }
1722     }
1723     else
1724     {
1725       Console.debug("Using Jalview.exit");
1726       if (message != null)
1727       {
1728         if (exitcode == 0)
1729         {
1730           Console.info(message);
1731         }
1732         else
1733         {
1734           Console.error(message);
1735         }
1736       }
1737     }
1738     if (exitcode > -1)
1739     {
1740       System.exit(exitcode);
1741     }
1742   }
1743
1744   /*
1745    * testoutput for string values
1746    */
1747   protected static void testoutput(ArgParser ap, Arg a, String s1,
1748           String s2)
1749   {
1750     BootstrapArgs bsa = ap.getBootstrapArgs();
1751     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1752       return;
1753     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1754     {
1755       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1756               + s2 + "' for arg " + a.argString());
1757       return;
1758     }
1759     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1760             : ap.isSet(a);
1761     if (!isset)
1762     {
1763       Console.warn("Arg '" + a.getName() + "' not set at all");
1764       return;
1765     }
1766     testoutput(true, a, s1, s2);
1767   }
1768
1769   protected static void testoutput(BootstrapArgs bsa, Arg a, String s1,
1770           String s2)
1771   {
1772     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1773       return;
1774     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1775     {
1776       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1777               + s2 + "' for arg " + a.argString());
1778       return;
1779     }
1780     if (!a.hasOption(Opt.BOOTSTRAP))
1781     {
1782       Console.error("Non-bootstrap Arg '" + a.getName()
1783               + "' given to testoutput(BootstrapArgs bsa, Arg a, String s1, String s2) with only BootstrapArgs");
1784     }
1785     if (!bsa.contains(a))
1786     {
1787       Console.warn("Arg '" + a.getName() + "' not set at all");
1788       return;
1789     }
1790     testoutput(true, a, s1, s2);
1791   }
1792
1793   private static void testoutput(boolean yes, Arg a, String s1, String s2)
1794   {
1795     if (yes && ((s1 == null && s2 == null)
1796             || (s1 != null && s1.equals(s2))))
1797     {
1798       System.out.println("[TESTOUTPUT] arg " + a.argString() + "='" + s1
1799               + "' was set");
1800     }
1801   }
1802
1803   /*
1804    * testoutput for boolean values
1805    */
1806   protected static void testoutput(ArgParser ap, Arg a)
1807   {
1808     if (ap == null)
1809       return;
1810     BootstrapArgs bsa = ap.getBootstrapArgs();
1811     if (bsa == null)
1812       return;
1813     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1814       return;
1815     boolean val = a.hasOption(Opt.BOOTSTRAP) ? bsa.getBoolean(a)
1816             : ap.getBoolean(a);
1817     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1818             : ap.isSet(a);
1819     if (!isset)
1820     {
1821       Console.warn("Arg '" + a.getName() + "' not set at all");
1822       return;
1823     }
1824     testoutput(val, a);
1825   }
1826
1827   protected static void testoutput(BootstrapArgs bsa, Arg a)
1828   {
1829     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1830       return;
1831     if (!a.hasOption(Opt.BOOTSTRAP))
1832     {
1833       Console.warn("Non-bootstrap Arg '" + a.getName()
1834               + "' given to testoutput(BootstrapArgs bsa, Arg a) with only BootstrapArgs");
1835
1836     }
1837     if (!bsa.contains(a))
1838     {
1839       Console.warn("Arg '" + a.getName() + "' not set at all");
1840       return;
1841     }
1842     testoutput(bsa.getBoolean(a), a);
1843   }
1844
1845   private static void testoutput(boolean yes, Arg a)
1846   {
1847     System.out.println("[TESTOUTPUT] arg "
1848             + (yes ? a.argString() : a.negateArgString()) + " was set");
1849   }
1850 }