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