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