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