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