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