JAL-629 All applications System.exits going through Jalview.exit
[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     // get args needed before proper ArgParser
290     BootstrapArgs bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
291
292     if (!Platform.isJS())
293     {
294       // are we being --quiet ?
295       if (bootstrapArgs.contains(Arg.QUIET))
296       {
297         OutputStream devNull = new OutputStream()
298         {
299           @Override
300           public void write(int b)
301           {
302             // DO NOTHING
303           }
304         };
305         System.setOut(new PrintStream(devNull));
306         // redirecting stderr not working
307         if (bootstrapArgs.getList(Arg.QUIET).size() > 1)
308         {
309           System.setErr(new PrintStream(devNull));
310         }
311       }
312     }
313
314     System.out
315             .println("Java version: " + System.getProperty("java.version"));
316     System.out.println("Java Home: " + System.getProperty("java.home"));
317     System.out.println(System.getProperty("os.arch") + " "
318             + System.getProperty("os.name") + " "
319             + System.getProperty("os.version"));
320
321     String val = System.getProperty("sys.install4jVersion");
322     if (val != null)
323     {
324       System.out.println("Install4j version: " + val);
325     }
326     val = System.getProperty("installer_template_version");
327     if (val != null)
328     {
329       System.out.println("Install4j template version: " + val);
330     }
331     val = System.getProperty("launcher_version");
332     if (val != null)
333     {
334       System.out.println("Launcher version: " + val);
335     }
336
337     if (Platform.isLinux() && LaunchUtils.getJavaVersion() < 11)
338     {
339       System.setProperty("flatlaf.uiScale", "1");
340     }
341
342     // get bootstrap properties (mainly for the logger level)
343     Properties bootstrapProperties = Cache
344             .bootstrapProperties(bootstrapArgs.get(Arg.PROPS));
345
346     // report Jalview version
347     Cache.loadBuildProperties(true);
348
349     // old ArgsParser
350     ArgsParser aparser = new ArgsParser(args);
351
352     // old
353     boolean headless = false;
354     // new
355     boolean headlessArg = false;
356
357     try
358     {
359       String logLevel = bootstrapArgs.contains(Arg.DEBUG) ? "DEBUG" : null;
360       if (logLevel == null && !(bootstrapProperties == null))
361       {
362         logLevel = bootstrapProperties.getProperty(Cache.JALVIEWLOGLEVEL);
363       }
364       Console.initLogger(logLevel);
365     } catch (NoClassDefFoundError error)
366     {
367       error.printStackTrace();
368       String message = "\nEssential logging libraries not found."
369               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
370       Jalview.exit(message, 0);
371     }
372
373     // register SIGTERM listener
374     Runtime.getRuntime().addShutdownHook(new Thread()
375     {
376       public void run()
377       {
378         Console.debug("Running shutdown hook");
379         if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
380         {
381           // Got to here by a SIGTERM signal.
382           // Note we will not actually cancel the quit from here -- it's too
383           // late -- but we can wait for saving files.
384           Console.debug("Checking for saving files");
385           QuitHandler.getQuitResponse(false);
386         }
387         else
388         {
389           Console.debug("Nothing more to do");
390         }
391         Console.debug("Exiting, bye!");
392         // shutdownHook cannot be cancelled, JVM will now halt
393       }
394     });
395
396     String usrPropsFile = bootstrapArgs.contains(Arg.PROPS)
397             ? bootstrapArgs.get(Arg.PROPS)
398             : aparser.getValue("props");
399     Cache.loadProperties(usrPropsFile);
400     if (usrPropsFile != null)
401     {
402       System.out.println(
403               "CMD [-props " + usrPropsFile + "] executed successfully!");
404     }
405
406     // new ArgParser
407     ArgParser argparser;
408     // --argfile=... -- OVERRIDES ALL NON-BOOTSTRAP ARGS
409     if (bootstrapArgs.contains(Arg.ARGFILE))
410     {
411       argparser = ArgParser
412               .parseArgFiles(bootstrapArgs.getList(Arg.ARGFILE));
413     }
414     else
415     {
416       argparser = new ArgParser(args);
417     }
418
419     if (!Platform.isJS())
420     /**
421      * Java only
422      * 
423      * @j2sIgnore
424      */
425     {
426       if (aparser.contains("help") || aparser.contains("h")
427               || argparser.getBool(Arg.HELP))
428       {
429         showUsage();
430         Jalview.exit(null, 0);
431       }
432
433       if (bootstrapArgs.contains(Arg.HEADLESS))
434       {
435         System.setProperty("java.awt.headless", "true");
436         // new
437         headlessArg = argparser.getBool(Arg.HEADLESS);
438       }
439       if (aparser.contains("nodisplay") || aparser.contains("nogui")
440               || aparser.contains("headless"))
441       {
442         System.setProperty("java.awt.headless", "true");
443         // old
444         headless = true;
445       }
446       // anything else!
447
448       // allow https handshakes to download intermediate certs if necessary
449       System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
450
451       final String jabawsUrl = aparser.getValue("jabaws");
452       if (jabawsUrl != null)
453       {
454         try
455         {
456           Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
457           System.out.println(
458                   "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
459         } catch (MalformedURLException e)
460         {
461           System.err.println(
462                   "Invalid jabaws parameter: " + jabawsUrl + " ignored");
463         }
464       }
465     }
466
467     String defs = aparser.getValue("setprop");
468     while (defs != null)
469     {
470       int p = defs.indexOf('=');
471       if (p == -1)
472       {
473         System.err.println("Ignoring invalid setprop argument : " + defs);
474       }
475       else
476       {
477         System.out.println("Executing setprop argument: " + defs);
478         if (Platform.isJS())
479         {
480           Cache.setProperty(defs.substring(0, p), defs.substring(p + 1));
481         }
482         // DISABLED FOR SECURITY REASONS
483         // TODO: add a property to allow properties to be overriden by cli args
484         // Cache.setProperty(defs.substring(0,p), defs.substring(p+1));
485       }
486       defs = aparser.getValue("setprop");
487     }
488     if (System.getProperty("java.awt.headless") != null
489             && System.getProperty("java.awt.headless").equals("true"))
490     {
491       headless = true;
492     }
493     System.setProperty("http.agent",
494             "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
495
496     try
497     {
498       Console.initLogger();
499     } catch (
500
501     NoClassDefFoundError error)
502     {
503       error.printStackTrace();
504       String message = "\nEssential logging libraries not found."
505               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
506       Jalview.exit(message, 0);
507     }
508     desktop = null;
509
510     if (!(headless || headlessArg))
511       setLookAndFeel();
512
513     /*
514      * configure 'full' SO model if preferences say to, else use the default (full SO)
515      * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
516      */
517     boolean soDefault = !Platform.isJS();
518     if (Cache.getDefault("USE_FULL_SO", soDefault))
519     {
520       SequenceOntologyFactory.setInstance(new SequenceOntology());
521     }
522
523     if (!(headless || headlessArg))
524     {
525       Desktop.nosplash = aparser.contains("nosplash");
526       desktop = new Desktop();
527       desktop.setInBatchMode(true); // indicate we are starting up
528
529       try
530       {
531         JalviewTaskbar.setTaskbar(this);
532       } catch (Exception e)
533       {
534         Console.info("Cannot set Taskbar");
535         Console.error(e.getMessage());
536         // e.printStackTrace();
537       } catch (Throwable t)
538       {
539         Console.info("Cannot set Taskbar");
540         Console.error(t.getMessage());
541         // t.printStackTrace();
542       }
543
544       // set Proxy settings before all the internet calls
545       Cache.setProxyPropertiesFromPreferences();
546
547       desktop.setVisible(true);
548
549       if (!Platform.isJS())
550       /**
551        * Java only
552        * 
553        * @j2sIgnore
554        */
555       {
556
557         /**
558          * Check to see that the JVM version being run is suitable for the Java
559          * version this Jalview was compiled for. Popup a warning if not.
560          */
561         if (!LaunchUtils.checkJavaVersion())
562         {
563           Console.warn("The Java version being used (Java "
564                   + LaunchUtils.getJavaVersion()
565                   + ") may lead to problems. This installation of Jalview should be used with Java "
566                   + LaunchUtils.getJavaCompileVersion() + ".");
567
568           if (!LaunchUtils
569                   .getBooleanUserPreference("IGNORE_JVM_WARNING_POPUP"))
570           {
571             Object[] options = {
572                 MessageManager.getString("label.continue") };
573             JOptionPane.showOptionDialog(null,
574                     MessageManager.formatMessage(
575                             "warning.wrong_jvm_version_message",
576                             LaunchUtils.getJavaVersion(),
577                             LaunchUtils.getJavaCompileVersion()),
578                     MessageManager
579                             .getString("warning.wrong_jvm_version_title"),
580                     JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
581                     null, options, options[0]);
582           }
583         }
584
585         if (!aparser.contains("nowebservicediscovery"))
586         {
587           desktop.startServiceDiscovery();
588         }
589         if (!aparser.contains("nousagestats"))
590         {
591           startUsageStats(desktop);
592         }
593         else
594         {
595           System.err.println("CMD [-nousagestats] executed successfully!");
596         }
597
598         if (!aparser.contains("noquestionnaire"))
599         {
600           String url = aparser.getValue("questionnaire");
601           if (url != null)
602           {
603             // Start the desktop questionnaire prompter with the specified
604             // questionnaire
605             Console.debug("Starting questionnaire url at " + url);
606             desktop.checkForQuestionnaire(url);
607             System.out.println("CMD questionnaire[-" + url
608                     + "] executed successfully!");
609           }
610           else
611           {
612             if (Cache.getProperty("NOQUESTIONNAIRES") == null)
613             {
614               // Start the desktop questionnaire prompter with the specified
615               // questionnaire
616               // String defurl =
617               // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
618               // //
619               String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
620               Console.debug(
621                       "Starting questionnaire with default url: " + defurl);
622               desktop.checkForQuestionnaire(defurl);
623             }
624           }
625         }
626         else
627         {
628           System.err
629                   .println("CMD [-noquestionnaire] executed successfully!");
630         }
631
632         if (!aparser.contains("nonews")
633                 || Cache.getProperty("NONEWS") == null)
634         {
635           desktop.checkForNews();
636         }
637
638         if (!aparser.contains("nohtmltemplates")
639                 || Cache.getProperty("NOHTMLTEMPLATES") == null)
640         {
641           BioJsHTMLOutput.updateBioJS();
642         }
643       }
644     }
645     // Run Commands from cli
646     cmds = new Commands(argparser, headlessArg);
647     boolean commandsSuccess = cmds.argsWereParsed();
648     if (commandsSuccess)
649     {
650       if (headlessArg)
651       {
652         Jalview.exit("Successfully completed commands in headless mode", 0);
653       }
654       Console.info("Successfully completed commands");
655     }
656     else
657     {
658       if (headlessArg)
659       {
660         Jalview.exit("Error when running Commands in headless mode", 1);
661       }
662       Console.warn("Error when running commands");
663     }
664
665     // Check if JVM and compile version might cause problems and log if it
666     // might.
667     if (headless && !Platform.isJS() && !LaunchUtils.checkJavaVersion())
668     {
669       Console.warn("The Java version being used (Java "
670               + LaunchUtils.getJavaVersion()
671               + ") may lead to problems. This installation of Jalview should be used with Java "
672               + LaunchUtils.getJavaCompileVersion() + ".");
673     }
674
675     // Move any new getdown-launcher-new.jar into place over old
676     // getdown-launcher.jar
677     String appdirString = System.getProperty("getdownappdir");
678     if (appdirString != null && appdirString.length() > 0)
679     {
680       final File appdir = new File(appdirString);
681       new Thread()
682       {
683         @Override
684         public void run()
685         {
686           LaunchUtil.upgradeGetdown(
687                   new File(appdir, "getdown-launcher-old.jar"),
688                   new File(appdir, "getdown-launcher.jar"),
689                   new File(appdir, "getdown-launcher-new.jar"));
690         }
691       }.start();
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)
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 }