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