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