Merge branch 'patch/JAL-4195_cli_eps_export_npe' into develop
[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         
1009         if (groovyscript != null)
1010         {
1011           // Execute the groovy script after we've done all the rendering stuff
1012           // and before any images or figures are generated.
1013           System.out.println("Executing script " + groovyscript);
1014           executeGroovyScript(groovyscript, af);
1015           System.out.println("CMD groovy[" + groovyscript
1016                   + "] executed successfully!");
1017           groovyscript = null;
1018         }
1019         String imageName = "unnamed.png";
1020         while (aparser.getSize() > 1)
1021         {
1022           String outputFormat = aparser.nextValue();
1023           file = aparser.nextValue();
1024
1025           if (outputFormat.equalsIgnoreCase("png"))
1026           {
1027             af.createPNG(new File(file));
1028             imageName = (new File(file)).getName();
1029             System.out.println("Creating PNG image: " + file);
1030             continue;
1031           }
1032           else if (outputFormat.equalsIgnoreCase("svg"))
1033           {
1034             File imageFile = new File(file);
1035             imageName = imageFile.getName();
1036             af.createSVG(imageFile);
1037             System.out.println("Creating SVG image: " + file);
1038             continue;
1039           }
1040           else if (outputFormat.equalsIgnoreCase("html"))
1041           {
1042             File imageFile = new File(file);
1043             imageName = imageFile.getName();
1044             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
1045             htmlSVG.exportHTML(file);
1046
1047             System.out.println("Creating HTML image: " + file);
1048             continue;
1049           }
1050           else if (outputFormat.equalsIgnoreCase("biojsmsa"))
1051           {
1052             if (file == null)
1053             {
1054               System.err.println("The output html file must not be null");
1055               return;
1056             }
1057             try
1058             {
1059               BioJsHTMLOutput.refreshVersionInfo(
1060                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
1061             } catch (URISyntaxException e)
1062             {
1063               e.printStackTrace();
1064             }
1065             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
1066             bjs.exportHTML(file);
1067             System.out
1068                     .println("Creating BioJS MSA Viwer HTML file: " + file);
1069             continue;
1070           }
1071           else if (outputFormat.equalsIgnoreCase("imgMap"))
1072           {
1073             af.createImageMap(new File(file), imageName);
1074             System.out.println("Creating image map: " + file);
1075             continue;
1076           }
1077           else if (outputFormat.equalsIgnoreCase("eps"))
1078           {
1079             File outputFile = new File(file);
1080             System.out.println(
1081                     "Creating EPS file: " + outputFile.getAbsolutePath());
1082             af.createEPS(outputFile);
1083             continue;
1084           }
1085           FileFormatI outFormat = null;
1086           try
1087           {
1088             outFormat = FileFormats.getInstance().forName(outputFormat);
1089           } catch (Exception formatP)
1090           {
1091             System.out.println("Couldn't parse " + outFormat
1092                     + " as a valid Jalview format string.");
1093           }
1094           if (outFormat != null)
1095           {
1096             if (!outFormat.isWritable())
1097             {
1098               System.out.println(
1099                       "This version of Jalview does not support alignment export as "
1100                               + outputFormat);
1101             }
1102             else
1103             {
1104               af.saveAlignment(file, outFormat);
1105               if (af.isSaveAlignmentSuccessful())
1106               {
1107                 System.out.println("Written alignment in "
1108                         + outFormat.getName() + " format to " + file);
1109               }
1110               else
1111               {
1112                 System.out.println("Error writing file " + file + " in "
1113                         + outFormat.getName() + " format!!");
1114               }
1115             }
1116           }
1117
1118         }
1119
1120         while (aparser.getSize() > 0)
1121         {
1122           System.out.println("Unknown arg: " + aparser.nextValue());
1123         }
1124       }
1125     }
1126
1127     AlignFrame startUpAlframe = null;
1128     // We'll only open the default file if the desktop is visible.
1129     // And the user
1130     // ////////////////////
1131
1132     if (!Platform.isJS() && !headless && file == null
1133             && Cache.getDefault("SHOW_STARTUP_FILE", true)
1134             && !cmds.commandArgsProvided()
1135             && !bootstrapArgs.getBoolean(Arg.NOSTARTUPFILE))
1136     // don't open the startup file if command line args have been processed
1137     // (&& !Commands.commandArgsProvided())
1138     /**
1139      * Java only
1140      * 
1141      * @j2sIgnore
1142      */
1143     {
1144       file = Cache.getDefault("STARTUP_FILE",
1145               Cache.getDefault("www.jalview.org", "https://www.jalview.org")
1146                       + "/examples/exampleFile_2_7.jvp");
1147       if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
1148               || file.equals(
1149                       "http://www.jalview.org/examples/exampleFile_2_7.jar"))
1150       {
1151         file.replace("http:", "https:");
1152         // hardwire upgrade of the startup file
1153         file.replace("_2_3", "_2_7");
1154         file.replace("2_7.jar", "2_7.jvp");
1155         // and remove the stale setting
1156         Cache.removeProperty("STARTUP_FILE");
1157       }
1158
1159       protocol = AppletFormatAdapter.checkProtocol(file);
1160
1161       if (file.endsWith(".jar"))
1162       {
1163         format = FileFormat.Jalview;
1164       }
1165       else
1166       {
1167         try
1168         {
1169           format = new IdentifyFile().identify(file, protocol);
1170         } catch (FileFormatException e)
1171         {
1172           // TODO what?
1173         }
1174       }
1175
1176       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
1177               format);
1178       // don't ask to save when quitting if only the startup file has been
1179       // opened
1180       Console.debug("Resetting up-to-date flag for startup file");
1181       startUpAlframe.getViewport().setSavedUpToDate(true);
1182       // extract groovy arguments before anything else.
1183     }
1184
1185     // Once all other stuff is done, execute any groovy scripts (in order)
1186     if (groovyscript != null)
1187     {
1188       if (Cache.groovyJarsPresent())
1189       {
1190         System.out.println("Executing script " + groovyscript);
1191         executeGroovyScript(groovyscript, startUpAlframe);
1192       }
1193       else
1194       {
1195         System.err.println(
1196                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
1197                         + groovyscript);
1198       }
1199     }
1200     // and finally, turn off batch mode indicator - if the desktop still exists
1201     if (desktop != null)
1202     {
1203       if (progress != -1)
1204       {
1205         desktop.setProgressBar(null, progress);
1206       }
1207       desktop.setInBatchMode(false);
1208     }
1209   }
1210
1211   private static void setLookAndFeel()
1212   {
1213     // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
1214     // "mac" or "flat"
1215     // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
1216     // try Quaqua/Vaqua.
1217     String lafProp = System.getProperty("laf");
1218     String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
1219     String laf = "none";
1220     if (lafProp != null)
1221     {
1222       laf = lafProp;
1223     }
1224     else if (lafSetting != null)
1225     {
1226       laf = lafSetting;
1227     }
1228     boolean lafSet = false;
1229     switch (laf)
1230     {
1231     case "crossplatform":
1232       lafSet = setCrossPlatformLookAndFeel();
1233       if (!lafSet)
1234       {
1235         Console.error("Could not set requested laf=" + laf);
1236       }
1237       break;
1238     case "system":
1239       lafSet = setSystemLookAndFeel();
1240       if (!lafSet)
1241       {
1242         Console.error("Could not set requested laf=" + laf);
1243       }
1244       break;
1245     case "gtk":
1246       lafSet = setGtkLookAndFeel();
1247       if (!lafSet)
1248       {
1249         Console.error("Could not set requested laf=" + laf);
1250       }
1251       break;
1252     case "metal":
1253       lafSet = setMetalLookAndFeel();
1254       if (!lafSet)
1255       {
1256         Console.error("Could not set requested laf=" + laf);
1257       }
1258       break;
1259     case "nimbus":
1260       lafSet = setNimbusLookAndFeel();
1261       if (!lafSet)
1262       {
1263         Console.error("Could not set requested laf=" + laf);
1264       }
1265       break;
1266     case "flat":
1267       lafSet = setFlatLookAndFeel();
1268       if (!lafSet)
1269       {
1270         Console.error("Could not set requested laf=" + laf);
1271       }
1272       break;
1273     case "mac":
1274       lafSet = setMacLookAndFeel();
1275       if (!lafSet)
1276       {
1277         Console.error("Could not set requested laf=" + laf);
1278       }
1279       break;
1280     case "none":
1281       break;
1282     default:
1283       Console.error("Requested laf=" + laf + " not implemented");
1284     }
1285     if (!lafSet)
1286     {
1287       // Flatlaf default for everyone!
1288       lafSet = setFlatLookAndFeel();
1289       if (!lafSet)
1290       {
1291         setSystemLookAndFeel();
1292       }
1293       if (Platform.isLinux())
1294       {
1295         setLinuxLookAndFeel();
1296       }
1297       if (Platform.isMac())
1298       {
1299         setMacLookAndFeel();
1300       }
1301     }
1302   }
1303
1304   private static boolean setCrossPlatformLookAndFeel()
1305   {
1306     boolean set = false;
1307     try
1308     {
1309       UIManager.setLookAndFeel(
1310               UIManager.getCrossPlatformLookAndFeelClassName());
1311       set = true;
1312     } catch (Exception ex)
1313     {
1314       Console.error("Unexpected Look and Feel Exception");
1315       Console.error(ex.getMessage());
1316       Console.debug(Cache.getStackTraceString(ex));
1317     }
1318     return set;
1319   }
1320
1321   private static boolean setSystemLookAndFeel()
1322   {
1323     boolean set = false;
1324     try
1325     {
1326       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1327       set = true;
1328     } catch (Exception ex)
1329     {
1330       Console.error("Unexpected Look and Feel Exception");
1331       Console.error(ex.getMessage());
1332       Console.debug(Cache.getStackTraceString(ex));
1333     }
1334     return set;
1335   }
1336
1337   private static boolean setSpecificLookAndFeel(String name,
1338           String className, boolean nameStartsWith)
1339   {
1340     boolean set = false;
1341     try
1342     {
1343       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
1344       {
1345         if (info.getName() != null && nameStartsWith
1346                 ? info.getName().toLowerCase(Locale.ROOT)
1347                         .startsWith(name.toLowerCase(Locale.ROOT))
1348                 : info.getName().toLowerCase(Locale.ROOT)
1349                         .equals(name.toLowerCase(Locale.ROOT)))
1350         {
1351           className = info.getClassName();
1352           break;
1353         }
1354       }
1355       UIManager.setLookAndFeel(className);
1356       set = true;
1357     } catch (Exception ex)
1358     {
1359       Console.error("Unexpected Look and Feel Exception");
1360       Console.error(ex.getMessage());
1361       Console.debug(Cache.getStackTraceString(ex));
1362     }
1363     return set;
1364   }
1365
1366   private static boolean setGtkLookAndFeel()
1367   {
1368     return setSpecificLookAndFeel("gtk",
1369             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
1370   }
1371
1372   private static boolean setMetalLookAndFeel()
1373   {
1374     return setSpecificLookAndFeel("metal",
1375             "javax.swing.plaf.metal.MetalLookAndFeel", false);
1376   }
1377
1378   private static boolean setNimbusLookAndFeel()
1379   {
1380     return setSpecificLookAndFeel("nimbus",
1381             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
1382   }
1383
1384   private static boolean setFlatLookAndFeel()
1385   {
1386     boolean set = false;
1387     if (SystemInfo.isMacOS)
1388     {
1389       try
1390       {
1391         UIManager.setLookAndFeel(
1392                 "com.formdev.flatlaf.themes.FlatMacLightLaf");
1393         set = true;
1394         Console.debug("Using FlatMacLightLaf");
1395       } catch (ClassNotFoundException | InstantiationException
1396               | IllegalAccessException | UnsupportedLookAndFeelException e)
1397       {
1398         Console.debug("Exception loading FlatLightLaf", e);
1399       }
1400       System.setProperty("apple.laf.useScreenMenuBar", "true");
1401       System.setProperty("apple.awt.application.name",
1402               ChannelProperties.getProperty("app_name"));
1403       System.setProperty("apple.awt.application.appearance", "system");
1404       if (SystemInfo.isMacFullWindowContentSupported
1405               && Desktop.desktop != null)
1406       {
1407         Console.debug("Setting transparent title bar");
1408         Desktop.desktop.getRootPane()
1409                 .putClientProperty("apple.awt.fullWindowContent", true);
1410         Desktop.desktop.getRootPane()
1411                 .putClientProperty("apple.awt.transparentTitleBar", true);
1412         Desktop.desktop.getRootPane()
1413                 .putClientProperty("apple.awt.fullscreenable", true);
1414       }
1415       SwingUtilities.invokeLater(() -> {
1416         FlatMacLightLaf.setup();
1417       });
1418       Console.debug("Using FlatMacLightLaf");
1419       set = true;
1420     }
1421     if (!set)
1422     {
1423       try
1424       {
1425         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1426         set = true;
1427         Console.debug("Using FlatLightLaf");
1428       } catch (ClassNotFoundException | InstantiationException
1429               | IllegalAccessException | UnsupportedLookAndFeelException e)
1430       {
1431         Console.debug("Exception loading FlatLightLaf", e);
1432       }
1433       // Windows specific properties here
1434       SwingUtilities.invokeLater(() -> {
1435         FlatLightLaf.setup();
1436       });
1437       Console.debug("Using FlatLightLaf");
1438       set = true;
1439     }
1440     else if (SystemInfo.isLinux)
1441     {
1442       try
1443       {
1444         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1445         set = true;
1446         Console.debug("Using FlatLightLaf");
1447       } catch (ClassNotFoundException | InstantiationException
1448               | IllegalAccessException | UnsupportedLookAndFeelException e)
1449       {
1450         Console.debug("Exception loading FlatLightLaf", e);
1451       }
1452       // enable custom window decorations
1453       JFrame.setDefaultLookAndFeelDecorated(true);
1454       JDialog.setDefaultLookAndFeelDecorated(true);
1455       SwingUtilities.invokeLater(() -> {
1456         FlatLightLaf.setup();
1457       });
1458       Console.debug("Using FlatLightLaf");
1459       set = true;
1460     }
1461
1462     if (!set)
1463     {
1464       try
1465       {
1466         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1467         set = true;
1468         Console.debug("Using FlatLightLaf");
1469       } catch (ClassNotFoundException | InstantiationException
1470               | IllegalAccessException | UnsupportedLookAndFeelException e)
1471       {
1472         Console.debug("Exception loading FlatLightLaf", e);
1473       }
1474     }
1475
1476     if (set)
1477     {
1478       UIManager.put("TabbedPane.tabType", "card");
1479       UIManager.put("TabbedPane.showTabSeparators", true);
1480       UIManager.put("TabbedPane.showContentSeparator", true);
1481       UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
1482       UIManager.put("TabbedPane.tabsOverlapBorder", true);
1483       UIManager.put("TabbedPane.hasFullBorder", true);
1484       UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
1485       UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
1486       UIManager.put("TabbedPane.smoothScrolling", true);
1487       UIManager.put("TabbedPane.tabWidthMode", "compact");
1488       UIManager.put("TabbedPane.selectedBackground", Color.white);
1489       UIManager.put("TabbedPane.background", new Color(236, 236, 236));
1490       UIManager.put("TabbedPane.hoverColor", Color.lightGray);
1491     }
1492
1493     Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
1494     return set;
1495   }
1496
1497   private static boolean setMacLookAndFeel()
1498   {
1499     boolean set = false;
1500     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
1501             ChannelProperties.getProperty("app_name"));
1502     System.setProperty("apple.laf.useScreenMenuBar", "true");
1503     /*
1504      * broken native LAFs on (ARM?) macbooks
1505     set = setQuaquaLookAndFeel();
1506     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
1507             .toLowerCase(Locale.ROOT).contains("quaqua"))
1508     {
1509       set = setVaquaLookAndFeel();
1510     }
1511      */
1512     set = setFlatLookAndFeel();
1513     return set;
1514   }
1515
1516   private static boolean setLinuxLookAndFeel()
1517   {
1518     boolean set = false;
1519     set = setFlatLookAndFeel();
1520     if (!set)
1521       set = setMetalLookAndFeel();
1522     // avoid GtkLookAndFeel -- not good results especially on HiDPI
1523     if (!set)
1524       set = setNimbusLookAndFeel();
1525     return set;
1526   }
1527
1528   /*
1529   private static void showUsage()
1530   {
1531     System.out.println(
1532             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
1533                     + "-nodisplay\tRun Jalview without User Interface.\n"
1534                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
1535                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
1536                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
1537                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
1538                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
1539                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
1540                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
1541                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
1542                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
1543                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
1544                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
1545                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
1546                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
1547                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
1548                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
1549                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
1550                     + "-html FILE\tCreate HTML file from alignment.\n"
1551                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
1552                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
1553                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
1554                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
1555                     + "-noquestionnaire\tTurn off questionnaire check.\n"
1556                     + "-nonews\tTurn off check for Jalview news.\n"
1557                     + "-nousagestats\tTurn off google analytics tracking for this session.\n"
1558                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
1559                     // +
1560                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
1561                     // after all other properties files have been read\n\t
1562                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
1563                     // passed in correctly)"
1564                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
1565                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
1566                     + "-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"
1567                     + "-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"
1568                     + "-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"
1569                     + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
1570   }
1571   */
1572
1573   private static void startUsageStats(final Desktop desktop)
1574   {
1575     /**
1576      * start a User Config prompt asking if we can log usage statistics.
1577      */
1578     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
1579             "USAGESTATS", "Jalview Usage Statistics",
1580             "Do you want to help make Jalview better by enabling "
1581                     + "the collection of usage statistics with Google Analytics ?"
1582                     + "\n\n(you can enable or disable usage tracking in the preferences)",
1583             new Runnable()
1584             {
1585               @Override
1586               public void run()
1587               {
1588                 Console.debug(
1589                         "Initialising googletracker for usage stats.");
1590                 Cache.initGoogleTracker();
1591                 Console.debug("Tracking enabled.");
1592               }
1593             }, new Runnable()
1594             {
1595               @Override
1596               public void run()
1597               {
1598                 Console.debug("Not enabling Google Tracking.");
1599               }
1600             }, null, true);
1601     desktop.addDialogThread(prompter);
1602   }
1603
1604   /**
1605    * Locate the given string as a file and pass it to the groovy interpreter.
1606    * 
1607    * @param groovyscript
1608    *          the script to execute
1609    * @param jalviewContext
1610    *          the Jalview Desktop object passed in to the groovy binding as the
1611    *          'Jalview' object.
1612    */
1613   protected void executeGroovyScript(String groovyscript, AlignFrame af)
1614   {
1615     /**
1616      * for scripts contained in files
1617      */
1618     File tfile = null;
1619     /**
1620      * script's URI
1621      */
1622     URL sfile = null;
1623     if (groovyscript.trim().equals("STDIN"))
1624     {
1625       // read from stdin into a tempfile and execute it
1626       try
1627       {
1628         tfile = File.createTempFile("jalview", "groovy");
1629         PrintWriter outfile = new PrintWriter(
1630                 new OutputStreamWriter(new FileOutputStream(tfile)));
1631         BufferedReader br = new BufferedReader(
1632                 new InputStreamReader(System.in));
1633         String line = null;
1634         while ((line = br.readLine()) != null)
1635         {
1636           outfile.write(line + "\n");
1637         }
1638         br.close();
1639         outfile.flush();
1640         outfile.close();
1641
1642       } catch (Exception ex)
1643       {
1644         System.err.println("Failed to read from STDIN into tempfile "
1645                 + ((tfile == null) ? "(tempfile wasn't created)"
1646                         : tfile.toString()));
1647         ex.printStackTrace();
1648         return;
1649       }
1650       try
1651       {
1652         sfile = tfile.toURI().toURL();
1653       } catch (Exception x)
1654       {
1655         System.err.println(
1656                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1657                         + tfile.toURI());
1658         x.printStackTrace();
1659         return;
1660       }
1661     }
1662     else
1663     {
1664       try
1665       {
1666         sfile = new URI(groovyscript).toURL();
1667       } catch (Exception x)
1668       {
1669         tfile = new File(groovyscript);
1670         if (!tfile.exists())
1671         {
1672           System.err.println("File '" + groovyscript + "' does not exist.");
1673           return;
1674         }
1675         if (!tfile.canRead())
1676         {
1677           System.err.println("File '" + groovyscript + "' cannot be read.");
1678           return;
1679         }
1680         if (tfile.length() < 1)
1681         {
1682           System.err.println("File '" + groovyscript + "' is empty.");
1683           return;
1684         }
1685         try
1686         {
1687           sfile = tfile.getAbsoluteFile().toURI().toURL();
1688         } catch (Exception ex)
1689         {
1690           System.err.println("Failed to create a file URL for "
1691                   + tfile.getAbsoluteFile());
1692           return;
1693         }
1694       }
1695     }
1696     try
1697     {
1698       Map<String, java.lang.Object> vbinding = new HashMap<>();
1699       vbinding.put("Jalview", this);
1700       if (af != null)
1701       {
1702         vbinding.put("currentAlFrame", af);
1703       }
1704       Binding gbinding = new Binding(vbinding);
1705       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1706       gse.run(sfile.toString(), gbinding);
1707       if ("STDIN".equals(groovyscript))
1708       {
1709         // delete temp file that we made -
1710         // only if it was successfully executed
1711         tfile.delete();
1712       }
1713     } catch (Exception e)
1714     {
1715       System.err.println("Exception Whilst trying to execute file " + sfile
1716               + " as a groovy script.");
1717       e.printStackTrace(System.err);
1718
1719     }
1720   }
1721
1722   public static boolean isHeadlessMode()
1723   {
1724     String isheadless = System.getProperty("java.awt.headless");
1725     if (isheadless != null && isheadless.equalsIgnoreCase("true"))
1726     {
1727       return true;
1728     }
1729     return false;
1730   }
1731
1732   public AlignFrame[] getAlignFrames()
1733   {
1734     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1735             : Desktop.getAlignFrames();
1736
1737   }
1738
1739   /**
1740    * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
1741    */
1742   public void quit()
1743   {
1744     // System.exit will run the shutdownHook first
1745     Jalview.exit("Quitting now. Bye!", 0);
1746   }
1747
1748   public static AlignFrame getCurrentAlignFrame()
1749   {
1750     return Jalview.currentAlignFrame;
1751   }
1752
1753   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
1754   {
1755     Jalview.currentAlignFrame = currentAlignFrame;
1756   }
1757
1758   protected Commands getCommands()
1759   {
1760     return cmds;
1761   }
1762
1763   public static void exit(String message, int exitcode)
1764   {
1765     if (Console.log == null)
1766     {
1767       // Don't start the logger just to exit!
1768       if (message != null)
1769       {
1770         if (exitcode == 0)
1771         {
1772           System.out.println(message);
1773         }
1774         else
1775         {
1776           System.err.println(message);
1777         }
1778       }
1779     }
1780     else
1781     {
1782       Console.debug("Using Jalview.exit");
1783       if (message != null)
1784       {
1785         if (exitcode == 0)
1786         {
1787           Console.info(message);
1788         }
1789         else
1790         {
1791           Console.error(message);
1792         }
1793       }
1794     }
1795     if (exitcode > -1)
1796     {
1797       System.exit(exitcode);
1798     }
1799   }
1800
1801   /******************************
1802    * 
1803    * TEST OUTPUT METHODS
1804    * 
1805    ******************************/
1806   /**
1807    * method for reporting string values parsed/processed during tests
1808    * 
1809    */
1810   protected static void testoutput(ArgParser ap, Arg a, String s1,
1811           String s2)
1812   {
1813     BootstrapArgs bsa = ap.getBootstrapArgs();
1814     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1815       return;
1816     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1817     {
1818       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1819               + s2 + "' for arg " + a.argString());
1820       return;
1821     }
1822     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1823             : ap.isSet(a);
1824     if (!isset)
1825     {
1826       Console.warn("Arg '" + a.getName() + "' not set at all");
1827       return;
1828     }
1829     testoutput(true, a, s1, s2);
1830   }
1831
1832   /**
1833    * method for reporting string values parsed/processed during tests
1834    */
1835
1836   protected static void testoutput(BootstrapArgs bsa, Arg a, String s1,
1837           String s2)
1838   {
1839     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1840       return;
1841     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1842     {
1843       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1844               + s2 + "' for arg " + a.argString());
1845       return;
1846     }
1847     if (!a.hasOption(Opt.BOOTSTRAP))
1848     {
1849       Console.error("Non-bootstrap Arg '" + a.getName()
1850               + "' given to testoutput(BootstrapArgs bsa, Arg a, String s1, String s2) with only BootstrapArgs");
1851     }
1852     if (!bsa.contains(a))
1853     {
1854       Console.warn("Arg '" + a.getName() + "' not set at all");
1855       return;
1856     }
1857     testoutput(true, a, s1, s2);
1858   }
1859
1860   /**
1861    * report value set for string values parsed/processed during tests
1862    */
1863   private static void testoutput(boolean yes, Arg a, String s1, String s2)
1864   {
1865     if (yes && ((s1 == null && s2 == null)
1866             || (s1 != null && s1.equals(s2))))
1867     {
1868       System.out.println("[TESTOUTPUT] arg " + a.argString() + "='" + s1
1869               + "' was set");
1870     }
1871   }
1872
1873   /*
1874    * testoutput for boolean and unary values
1875    */
1876   protected static void testoutput(ArgParser ap, Arg a)
1877   {
1878     if (ap == null)
1879       return;
1880     BootstrapArgs bsa = ap.getBootstrapArgs();
1881     if (bsa == null)
1882       return;
1883     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1884       return;
1885     boolean val = a.hasOption(Opt.BOOTSTRAP) ? bsa.getBoolean(a)
1886             : ap.getBoolean(a);
1887     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1888             : ap.isSet(a);
1889     if (!isset)
1890     {
1891       Console.warn("Arg '" + a.getName() + "' not set at all");
1892       return;
1893     }
1894     testoutput(val, a);
1895   }
1896
1897   protected static void testoutput(BootstrapArgs bsa, Arg a)
1898   {
1899     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1900       return;
1901     if (!a.hasOption(Opt.BOOTSTRAP))
1902     {
1903       Console.warn("Non-bootstrap Arg '" + a.getName()
1904               + "' given to testoutput(BootstrapArgs bsa, Arg a) with only BootstrapArgs");
1905
1906     }
1907     if (!bsa.contains(a))
1908     {
1909       Console.warn("Arg '" + a.getName() + "' not set at all");
1910       return;
1911     }
1912     testoutput(bsa.getBoolean(a), a);
1913   }
1914
1915   private static void testoutput(boolean yes, Arg a)
1916   {
1917     String message = null;
1918     if (a.hasOption(Opt.BOOLEAN))
1919     {
1920       message = (yes ? a.argString() : a.negateArgString()) + " was set";
1921     }
1922     else if (a.hasOption(Opt.UNARY))
1923     {
1924       message = a.argString() + (yes ? " was set" : " was not set");
1925     }
1926     System.out.println("[TESTOUTPUT] arg " + message);
1927   }
1928 }