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