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