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