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