JAL-629 Added 'Type' to args and argvalues
[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         System.out.println("##### HERE");
498         System.out
499                 .println(Arg.usage(bootstrapArgs.getArgsOfType(Type.HELP)));
500         Jalview.exit(null, 0);
501       }
502       if (aparser.contains("help") || aparser.contains("h"))
503       {
504         /*
505          * Now using new usage statement.
506         showUsage();
507         */
508         System.out.println(Arg.usage());
509         Jalview.exit(null, 0);
510       }
511
512       if (bootstrapArgs.contains(Arg.HEADLESS))
513       {
514         System.setProperty("java.awt.headless", "true");
515         // new
516         headlessArg = bootstrapArgs.getBoolean(Arg.HEADLESS);
517       }
518       if (aparser.contains("nodisplay") || aparser.contains("nogui")
519               || aparser.contains("headless"))
520       {
521         System.setProperty("java.awt.headless", "true");
522         // old
523         headless = true;
524       }
525       // anything else!
526
527       // allow https handshakes to download intermediate certs if necessary
528       System.setProperty("com.sun.security.enableAIAcaIssuers", "true");
529
530       String jabawsUrl = bootstrapArgs.get(Arg.JABAWS);
531       if (jabawsUrl == null)
532         jabawsUrl = aparser.getValue("jabaws");
533       if (jabawsUrl != null)
534       {
535         try
536         {
537           Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
538           System.out.println(
539                   "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
540           testoutput(bootstrapArgs, Arg.JABAWS,
541                   "http://www.compbio.dundee.ac.uk/jabaws", jabawsUrl);
542         } catch (MalformedURLException e)
543         {
544           System.err.println(
545                   "Invalid jabaws parameter: " + jabawsUrl + " ignored");
546         }
547       }
548     }
549
550     List<String> setprops = new ArrayList<>();
551     if (bootstrapArgs.contains(Arg.SETPROP))
552     {
553       setprops = bootstrapArgs.getValueList(Arg.SETPROP);
554     }
555     else
556     {
557       String sp = aparser.getValue("setprop");
558       while (sp != null)
559       {
560         setprops.add(sp);
561         sp = aparser.getValue("setprop");
562       }
563     }
564     for (String setprop : setprops)
565     {
566       int p = setprop.indexOf('=');
567       if (p == -1)
568       {
569         System.err
570                 .println("Ignoring invalid setprop argument : " + setprop);
571       }
572       else
573       {
574         System.out.println("Executing setprop argument: " + setprop);
575         if (Platform.isJS())
576         {
577           Cache.setProperty(setprop.substring(0, p),
578                   setprop.substring(p + 1));
579         }
580         // DISABLED FOR SECURITY REASONS
581         // TODO: add a property to allow properties to be overriden by cli args
582         // Cache.setProperty(setprop.substring(0,p), setprop.substring(p+1));
583       }
584     }
585     if (System.getProperty("java.awt.headless") != null
586             && System.getProperty("java.awt.headless").equals("true"))
587     {
588       headless = true;
589     }
590     System.setProperty("http.agent",
591             "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
592
593     try
594     {
595       Console.initLogger();
596     } catch (
597
598     NoClassDefFoundError error)
599     {
600       error.printStackTrace();
601       String message = "\nEssential logging libraries not found."
602               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
603       Jalview.exit(message, 0);
604     }
605     desktop = null;
606
607     if (!(headless || headlessArg))
608       setLookAndFeel();
609
610     /*
611      * configure 'full' SO model if preferences say to, else use the default (full SO)
612      * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
613      */
614     boolean soDefault = !Platform.isJS();
615     if (Cache.getDefault("USE_FULL_SO", soDefault))
616     {
617       SequenceOntologyFactory.setInstance(new SequenceOntology());
618     }
619
620     if (!(headless || headlessArg))
621     {
622       Desktop.nosplash = "false".equals(bootstrapArgs.get(Arg.SPLASH))
623               || aparser.contains("nosplash")
624               || Cache.getDefault("SPLASH", "true").equals("false");
625       desktop = new Desktop();
626       desktop.setInBatchMode(true); // indicate we are starting up
627
628       try
629       {
630         JalviewTaskbar.setTaskbar(this);
631       } catch (Exception e)
632       {
633         Console.info("Cannot set Taskbar");
634         Console.error(e.getMessage());
635         // e.printStackTrace();
636       } catch (Throwable t)
637       {
638         Console.info("Cannot set Taskbar");
639         Console.error(t.getMessage());
640         // t.printStackTrace();
641       }
642
643       // set Proxy settings before all the internet calls
644       Cache.setProxyPropertiesFromPreferences();
645
646       desktop.setVisible(true);
647
648       if (!Platform.isJS())
649       /**
650        * Java only
651        * 
652        * @j2sIgnore
653        */
654       {
655
656         /**
657          * Check to see that the JVM version being run is suitable for the Java
658          * version this Jalview was compiled for. Popup a warning if not.
659          */
660         if (!LaunchUtils.checkJavaVersion())
661         {
662           Console.warn("The Java version being used (Java "
663                   + LaunchUtils.getJavaVersion()
664                   + ") may lead to problems. This installation of Jalview should be used with Java "
665                   + LaunchUtils.getJavaCompileVersion() + ".");
666
667           if (!LaunchUtils
668                   .getBooleanUserPreference("IGNORE_JVM_WARNING_POPUP"))
669           {
670             Object[] options = {
671                 MessageManager.getString("label.continue") };
672             JOptionPane.showOptionDialog(null,
673                     MessageManager.formatMessage(
674                             "warning.wrong_jvm_version_message",
675                             LaunchUtils.getJavaVersion(),
676                             LaunchUtils.getJavaCompileVersion()),
677                     MessageManager
678                             .getString("warning.wrong_jvm_version_title"),
679                     JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
680                     null, options, options[0]);
681           }
682         }
683
684         boolean webservicediscovery = bootstrapArgs
685                 .getBoolean(Arg.WEBSERVICEDISCOVERY);
686         if (aparser.contains("nowebservicediscovery"))
687           webservicediscovery = false;
688         if (webservicediscovery)
689         {
690           desktop.startServiceDiscovery();
691         }
692         else
693         {
694           testoutput(argparser, Arg.WEBSERVICEDISCOVERY);
695         }
696
697         boolean usagestats = bootstrapArgs.getBoolean(Arg.USAGESTATS);
698         if (aparser.contains("nousagestats"))
699           usagestats = false;
700         if (usagestats)
701         {
702           startUsageStats(desktop);
703           testoutput(argparser, Arg.USAGESTATS);
704         }
705         else
706         {
707           System.out.println("CMD [-nousagestats] executed successfully!");
708           testoutput(argparser, Arg.USAGESTATS);
709         }
710
711         boolean questionnaire = bootstrapArgs.getBoolean(Arg.QUESTIONNAIRE);
712         if (aparser.contains("noquestionnaire"))
713           questionnaire = false;
714         if (questionnaire)
715         {
716           String url = aparser.getValue("questionnaire");
717           if (url != null)
718           {
719             // Start the desktop questionnaire prompter with the specified
720             // questionnaire
721             Console.debug("Starting questionnaire url at " + url);
722             desktop.checkForQuestionnaire(url);
723             System.out.println("CMD questionnaire[-" + url
724                     + "] executed successfully!");
725           }
726           else
727           {
728             if (Cache.getProperty("NOQUESTIONNAIRES") == null)
729             {
730               // Start the desktop questionnaire prompter with the specified
731               // questionnaire
732               // String defurl =
733               // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
734               // //
735               String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
736               Console.debug(
737                       "Starting questionnaire with default url: " + defurl);
738               desktop.checkForQuestionnaire(defurl);
739             }
740           }
741         }
742         else
743         {
744           System.out
745                   .println("CMD [-noquestionnaire] executed successfully!");
746           testoutput(argparser, Arg.QUESTIONNAIRE);
747         }
748
749         if ((!aparser.contains("nonews")
750                 && Cache.getProperty("NONEWS") == null
751                 && !"false".equals(bootstrapArgs.get(Arg.NEWS)))
752                 || "true".equals(bootstrapArgs.get(Arg.NEWS)))
753         {
754           desktop.checkForNews();
755         }
756
757         if (!aparser.contains("nohtmltemplates")
758                 && Cache.getProperty("NOHTMLTEMPLATES") == null)
759         {
760           BioJsHTMLOutput.updateBioJS();
761         }
762       }
763     }
764     // Run Commands from cli
765     cmds = new Commands(argparser, headlessArg);
766     boolean commandsSuccess = cmds.argsWereParsed();
767     if (commandsSuccess)
768     {
769       if (headlessArg)
770       {
771         Jalview.exit("Successfully completed commands in headless mode", 0);
772       }
773       Console.info("Successfully completed commands");
774     }
775     else
776     {
777       if (headlessArg)
778       {
779         Jalview.exit("Error when running Commands in headless mode", 1);
780       }
781       Console.warn("Error when running commands");
782     }
783
784     // Check if JVM and compile version might cause problems and log if it
785     // might.
786     if (headless && !Platform.isJS() && !LaunchUtils.checkJavaVersion())
787     {
788       Console.warn("The Java version being used (Java "
789               + LaunchUtils.getJavaVersion()
790               + ") may lead to problems. This installation of Jalview should be used with Java "
791               + LaunchUtils.getJavaCompileVersion() + ".");
792     }
793
794     String file = null, data = null;
795
796     FileFormatI format = null;
797
798     DataSourceType protocol = null;
799
800     FileLoader fileLoader = new FileLoader(!headless);
801
802     String groovyscript = null; // script to execute after all loading is
803     // completed one way or another
804     // extract groovy argument and execute if necessary
805     groovyscript = aparser.getValue("groovy", true);
806     file = aparser.getValue("open", true);
807
808     if (file == null && desktop == null && !commandsSuccess)
809     {
810       Jalview.exit("No files to open!", 1);
811     }
812
813     long progress = -1;
814     // Finally, deal with the remaining input data.
815     if (file != null)
816     {
817       if (!headless)
818       {
819         desktop.setProgressBar(
820                 MessageManager
821                         .getString("status.processing_commandline_args"),
822                 progress = System.currentTimeMillis());
823       }
824       System.out.println("CMD [-open " + file + "] executed successfully!");
825
826       if (!Platform.isJS())
827       /**
828        * ignore in JavaScript -- can't just file existence - could load it?
829        * 
830        * @j2sIgnore
831        */
832       {
833         if (!HttpUtils.startsWithHttpOrHttps(file))
834         {
835           if (!(new File(file)).exists())
836           {
837             if (headless)
838             {
839               Jalview.exit(
840                       "Can't find file '" + file + "' in headless mode", 1);
841             }
842             Console.warn("Can't find file'" + file + "'");
843           }
844         }
845       }
846
847       protocol = AppletFormatAdapter.checkProtocol(file);
848
849       try
850       {
851         format = new IdentifyFile().identify(file, protocol);
852       } catch (FileFormatException e1)
853       {
854         // TODO ?
855       }
856
857       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
858               format);
859       if (af == null)
860       {
861         System.out.println("error");
862       }
863       else
864       {
865         setCurrentAlignFrame(af);
866         data = aparser.getValue("colour", true);
867         if (data != null)
868         {
869           data.replaceAll("%20", " ");
870
871           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
872                   af.getViewport(), af.getViewport().getAlignment(), data);
873
874           if (cs != null)
875           {
876             System.out.println(
877                     "CMD [-colour " + data + "] executed successfully!");
878           }
879           af.changeColour(cs);
880         }
881
882         // Must maintain ability to use the groups flag
883         data = aparser.getValue("groups", true);
884         if (data != null)
885         {
886           af.parseFeaturesFile(data,
887                   AppletFormatAdapter.checkProtocol(data));
888           // System.out.println("Added " + data);
889           System.out.println(
890                   "CMD groups[-" + data + "]  executed successfully!");
891         }
892         data = aparser.getValue("features", true);
893         if (data != null)
894         {
895           af.parseFeaturesFile(data,
896                   AppletFormatAdapter.checkProtocol(data));
897           // System.out.println("Added " + data);
898           System.out.println(
899                   "CMD [-features " + data + "]  executed successfully!");
900         }
901
902         data = aparser.getValue("annotations", true);
903         if (data != null)
904         {
905           af.loadJalviewDataFile(data, null, null, null);
906           // System.out.println("Added " + data);
907           System.out.println(
908                   "CMD [-annotations " + data + "] executed successfully!");
909         }
910         // set or clear the sortbytree flag.
911         if (aparser.contains("sortbytree"))
912         {
913           af.getViewport().setSortByTree(true);
914           if (af.getViewport().getSortByTree())
915           {
916             System.out.println("CMD [-sortbytree] executed successfully!");
917           }
918         }
919         if (aparser.contains("no-annotation"))
920         {
921           af.getViewport().setShowAnnotation(false);
922           if (!af.getViewport().isShowAnnotation())
923           {
924             System.out.println("CMD no-annotation executed successfully!");
925           }
926         }
927         if (aparser.contains("nosortbytree"))
928         {
929           af.getViewport().setSortByTree(false);
930           if (!af.getViewport().getSortByTree())
931           {
932             System.out
933                     .println("CMD [-nosortbytree] executed successfully!");
934           }
935         }
936         data = aparser.getValue("tree", true);
937         if (data != null)
938         {
939           try
940           {
941             System.out.println(
942                     "CMD [-tree " + data + "] executed successfully!");
943             NewickFile nf = new NewickFile(data,
944                     AppletFormatAdapter.checkProtocol(data));
945             af.getViewport()
946                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
947           } catch (IOException ex)
948           {
949             System.err.println("Couldn't add tree " + data);
950             ex.printStackTrace(System.err);
951           }
952         }
953         // TODO - load PDB structure(s) to alignment JAL-629
954         // (associate with identical sequence in alignment, or a specified
955         // sequence)
956         if (groovyscript != null)
957         {
958           // Execute the groovy script after we've done all the rendering stuff
959           // and before any images or figures are generated.
960           System.out.println("Executing script " + groovyscript);
961           executeGroovyScript(groovyscript, af);
962           System.out.println("CMD groovy[" + groovyscript
963                   + "] executed successfully!");
964           groovyscript = null;
965         }
966         String imageName = "unnamed.png";
967         while (aparser.getSize() > 1)
968         {
969           String outputFormat = aparser.nextValue();
970           file = aparser.nextValue();
971
972           if (outputFormat.equalsIgnoreCase("png"))
973           {
974             af.createPNG(new File(file));
975             imageName = (new File(file)).getName();
976             System.out.println("Creating PNG image: " + file);
977             continue;
978           }
979           else if (outputFormat.equalsIgnoreCase("svg"))
980           {
981             File imageFile = new File(file);
982             imageName = imageFile.getName();
983             af.createSVG(imageFile);
984             System.out.println("Creating SVG image: " + file);
985             continue;
986           }
987           else if (outputFormat.equalsIgnoreCase("html"))
988           {
989             File imageFile = new File(file);
990             imageName = imageFile.getName();
991             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
992             htmlSVG.exportHTML(file);
993
994             System.out.println("Creating HTML image: " + file);
995             continue;
996           }
997           else if (outputFormat.equalsIgnoreCase("biojsmsa"))
998           {
999             if (file == null)
1000             {
1001               System.err.println("The output html file must not be null");
1002               return;
1003             }
1004             try
1005             {
1006               BioJsHTMLOutput.refreshVersionInfo(
1007                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
1008             } catch (URISyntaxException e)
1009             {
1010               e.printStackTrace();
1011             }
1012             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
1013             bjs.exportHTML(file);
1014             System.out
1015                     .println("Creating BioJS MSA Viwer HTML file: " + file);
1016             continue;
1017           }
1018           else if (outputFormat.equalsIgnoreCase("imgMap"))
1019           {
1020             af.createImageMap(new File(file), imageName);
1021             System.out.println("Creating image map: " + file);
1022             continue;
1023           }
1024           else if (outputFormat.equalsIgnoreCase("eps"))
1025           {
1026             File outputFile = new File(file);
1027             System.out.println(
1028                     "Creating EPS file: " + outputFile.getAbsolutePath());
1029             af.createEPS(outputFile);
1030             continue;
1031           }
1032           FileFormatI outFormat = null;
1033           try
1034           {
1035             outFormat = FileFormats.getInstance().forName(outputFormat);
1036           } catch (Exception formatP)
1037           {
1038             System.out.println("Couldn't parse " + outFormat
1039                     + " as a valid Jalview format string.");
1040           }
1041           if (outFormat != null)
1042           {
1043             if (!outFormat.isWritable())
1044             {
1045               System.out.println(
1046                       "This version of Jalview does not support alignment export as "
1047                               + outputFormat);
1048             }
1049             else
1050             {
1051               af.saveAlignment(file, outFormat);
1052               if (af.isSaveAlignmentSuccessful())
1053               {
1054                 System.out.println("Written alignment in "
1055                         + outFormat.getName() + " format to " + file);
1056               }
1057               else
1058               {
1059                 System.out.println("Error writing file " + file + " in "
1060                         + outFormat.getName() + " format!!");
1061               }
1062             }
1063           }
1064
1065         }
1066
1067         while (aparser.getSize() > 0)
1068         {
1069           System.out.println("Unknown arg: " + aparser.nextValue());
1070         }
1071       }
1072     }
1073
1074     AlignFrame startUpAlframe = null;
1075     // We'll only open the default file if the desktop is visible.
1076     // And the user
1077     // ////////////////////
1078
1079     if (!Platform.isJS() && !headless && file == null
1080             && Cache.getDefault("SHOW_STARTUP_FILE", true)
1081             && !cmds.commandArgsProvided())
1082     // don't open the startup file if command line args have been processed
1083     // (&& !Commands.commandArgsProvided())
1084     /**
1085      * Java only
1086      * 
1087      * @j2sIgnore
1088      */
1089     {
1090       file = Cache.getDefault("STARTUP_FILE",
1091               Cache.getDefault("www.jalview.org", "https://www.jalview.org")
1092                       + "/examples/exampleFile_2_7.jvp");
1093       if (file.equals("http://www.jalview.org/examples/exampleFile_2_3.jar")
1094               || file.equals(
1095                       "http://www.jalview.org/examples/exampleFile_2_7.jar"))
1096       {
1097         file.replace("http:", "https:");
1098         // hardwire upgrade of the startup file
1099         file.replace("_2_3", "_2_7");
1100         file.replace("2_7.jar", "2_7.jvp");
1101         // and remove the stale setting
1102         Cache.removeProperty("STARTUP_FILE");
1103       }
1104
1105       protocol = AppletFormatAdapter.checkProtocol(file);
1106
1107       if (file.endsWith(".jar"))
1108       {
1109         format = FileFormat.Jalview;
1110       }
1111       else
1112       {
1113         try
1114         {
1115           format = new IdentifyFile().identify(file, protocol);
1116         } catch (FileFormatException e)
1117         {
1118           // TODO what?
1119         }
1120       }
1121
1122       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
1123               format);
1124       // don't ask to save when quitting if only the startup file has been
1125       // opened
1126       Console.debug("Resetting up-to-date flag for startup file");
1127       startUpAlframe.getViewport().setSavedUpToDate(true);
1128       // extract groovy arguments before anything else.
1129     }
1130
1131     // Once all other stuff is done, execute any groovy scripts (in order)
1132     if (groovyscript != null)
1133     {
1134       if (Cache.groovyJarsPresent())
1135       {
1136         System.out.println("Executing script " + groovyscript);
1137         executeGroovyScript(groovyscript, startUpAlframe);
1138       }
1139       else
1140       {
1141         System.err.println(
1142                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
1143                         + groovyscript);
1144       }
1145     }
1146     // and finally, turn off batch mode indicator - if the desktop still exists
1147     if (desktop != null)
1148     {
1149       if (progress != -1)
1150       {
1151         desktop.setProgressBar(null, progress);
1152       }
1153       desktop.setInBatchMode(false);
1154     }
1155   }
1156
1157   private static void setLookAndFeel()
1158   {
1159     // property laf = "crossplatform", "system", "gtk", "metal", "nimbus",
1160     // "mac" or "flat"
1161     // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
1162     // try Quaqua/Vaqua.
1163     String lafProp = System.getProperty("laf");
1164     String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
1165     String laf = "none";
1166     if (lafProp != null)
1167     {
1168       laf = lafProp;
1169     }
1170     else if (lafSetting != null)
1171     {
1172       laf = lafSetting;
1173     }
1174     boolean lafSet = false;
1175     switch (laf)
1176     {
1177     case "crossplatform":
1178       lafSet = setCrossPlatformLookAndFeel();
1179       if (!lafSet)
1180       {
1181         Console.error("Could not set requested laf=" + laf);
1182       }
1183       break;
1184     case "system":
1185       lafSet = setSystemLookAndFeel();
1186       if (!lafSet)
1187       {
1188         Console.error("Could not set requested laf=" + laf);
1189       }
1190       break;
1191     case "gtk":
1192       lafSet = setGtkLookAndFeel();
1193       if (!lafSet)
1194       {
1195         Console.error("Could not set requested laf=" + laf);
1196       }
1197       break;
1198     case "metal":
1199       lafSet = setMetalLookAndFeel();
1200       if (!lafSet)
1201       {
1202         Console.error("Could not set requested laf=" + laf);
1203       }
1204       break;
1205     case "nimbus":
1206       lafSet = setNimbusLookAndFeel();
1207       if (!lafSet)
1208       {
1209         Console.error("Could not set requested laf=" + laf);
1210       }
1211       break;
1212     case "flat":
1213       lafSet = setFlatLookAndFeel();
1214       if (!lafSet)
1215       {
1216         Console.error("Could not set requested laf=" + laf);
1217       }
1218       break;
1219     case "mac":
1220       lafSet = setMacLookAndFeel();
1221       if (!lafSet)
1222       {
1223         Console.error("Could not set requested laf=" + laf);
1224       }
1225       break;
1226     case "none":
1227       break;
1228     default:
1229       Console.error("Requested laf=" + laf + " not implemented");
1230     }
1231     if (!lafSet)
1232     {
1233       setSystemLookAndFeel();
1234       if (Platform.isLinux())
1235       {
1236         setLinuxLookAndFeel();
1237       }
1238       if (Platform.isMac())
1239       {
1240         setMacLookAndFeel();
1241       }
1242     }
1243   }
1244
1245   private static boolean setCrossPlatformLookAndFeel()
1246   {
1247     boolean set = false;
1248     try
1249     {
1250       UIManager.setLookAndFeel(
1251               UIManager.getCrossPlatformLookAndFeelClassName());
1252       set = true;
1253     } catch (Exception ex)
1254     {
1255       Console.error("Unexpected Look and Feel Exception");
1256       Console.error(ex.getMessage());
1257       Console.debug(Cache.getStackTraceString(ex));
1258     }
1259     return set;
1260   }
1261
1262   private static boolean setSystemLookAndFeel()
1263   {
1264     boolean set = false;
1265     try
1266     {
1267       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1268       set = true;
1269     } catch (Exception ex)
1270     {
1271       Console.error("Unexpected Look and Feel Exception");
1272       Console.error(ex.getMessage());
1273       Console.debug(Cache.getStackTraceString(ex));
1274     }
1275     return set;
1276   }
1277
1278   private static boolean setSpecificLookAndFeel(String name,
1279           String className, boolean nameStartsWith)
1280   {
1281     boolean set = false;
1282     try
1283     {
1284       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
1285       {
1286         if (info.getName() != null && nameStartsWith
1287                 ? info.getName().toLowerCase(Locale.ROOT)
1288                         .startsWith(name.toLowerCase(Locale.ROOT))
1289                 : info.getName().toLowerCase(Locale.ROOT)
1290                         .equals(name.toLowerCase(Locale.ROOT)))
1291         {
1292           className = info.getClassName();
1293           break;
1294         }
1295       }
1296       UIManager.setLookAndFeel(className);
1297       set = true;
1298     } catch (Exception ex)
1299     {
1300       Console.error("Unexpected Look and Feel Exception");
1301       Console.error(ex.getMessage());
1302       Console.debug(Cache.getStackTraceString(ex));
1303     }
1304     return set;
1305   }
1306
1307   private static boolean setGtkLookAndFeel()
1308   {
1309     return setSpecificLookAndFeel("gtk",
1310             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
1311   }
1312
1313   private static boolean setMetalLookAndFeel()
1314   {
1315     return setSpecificLookAndFeel("metal",
1316             "javax.swing.plaf.metal.MetalLookAndFeel", false);
1317   }
1318
1319   private static boolean setNimbusLookAndFeel()
1320   {
1321     return setSpecificLookAndFeel("nimbus",
1322             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
1323   }
1324
1325   private static boolean setFlatLookAndFeel()
1326   {
1327     boolean set = false;
1328     if (SystemInfo.isMacOS)
1329     {
1330       try
1331       {
1332         UIManager.setLookAndFeel(
1333                 "com.formdev.flatlaf.themes.FlatMacLightLaf");
1334         set = true;
1335         Console.debug("Using FlatMacLightLaf");
1336       } catch (ClassNotFoundException | InstantiationException
1337               | IllegalAccessException | UnsupportedLookAndFeelException e)
1338       {
1339         Console.debug("Exception loading FlatLightLaf", e);
1340       }
1341       System.setProperty("apple.laf.useScreenMenuBar", "true");
1342       System.setProperty("apple.awt.application.name",
1343               ChannelProperties.getProperty("app_name"));
1344       System.setProperty("apple.awt.application.appearance", "system");
1345       if (SystemInfo.isMacFullWindowContentSupported
1346               && Desktop.desktop != null)
1347       {
1348         Console.debug("Setting transparent title bar");
1349         Desktop.desktop.getRootPane()
1350                 .putClientProperty("apple.awt.fullWindowContent", true);
1351         Desktop.desktop.getRootPane()
1352                 .putClientProperty("apple.awt.transparentTitleBar", true);
1353         Desktop.desktop.getRootPane()
1354                 .putClientProperty("apple.awt.fullscreenable", true);
1355       }
1356       SwingUtilities.invokeLater(() -> {
1357         FlatMacLightLaf.setup();
1358       });
1359       Console.debug("Using FlatMacLightLaf");
1360       set = true;
1361     }
1362     if (!set)
1363     {
1364       try
1365       {
1366         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1367         set = true;
1368         Console.debug("Using FlatLightLaf");
1369       } catch (ClassNotFoundException | InstantiationException
1370               | IllegalAccessException | UnsupportedLookAndFeelException e)
1371       {
1372         Console.debug("Exception loading FlatLightLaf", e);
1373       }
1374       // Windows specific properties here
1375       SwingUtilities.invokeLater(() -> {
1376         FlatLightLaf.setup();
1377       });
1378       Console.debug("Using FlatLightLaf");
1379       set = true;
1380     }
1381     else if (SystemInfo.isLinux)
1382     {
1383       try
1384       {
1385         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1386         set = true;
1387         Console.debug("Using FlatLightLaf");
1388       } catch (ClassNotFoundException | InstantiationException
1389               | IllegalAccessException | UnsupportedLookAndFeelException e)
1390       {
1391         Console.debug("Exception loading FlatLightLaf", e);
1392       }
1393       // enable custom window decorations
1394       JFrame.setDefaultLookAndFeelDecorated(true);
1395       JDialog.setDefaultLookAndFeelDecorated(true);
1396       SwingUtilities.invokeLater(() -> {
1397         FlatLightLaf.setup();
1398       });
1399       Console.debug("Using FlatLightLaf");
1400       set = true;
1401     }
1402
1403     if (!set)
1404     {
1405       try
1406       {
1407         UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
1408         set = true;
1409         Console.debug("Using FlatLightLaf");
1410       } catch (ClassNotFoundException | InstantiationException
1411               | IllegalAccessException | UnsupportedLookAndFeelException e)
1412       {
1413         Console.debug("Exception loading FlatLightLaf", e);
1414       }
1415     }
1416
1417     if (set)
1418     {
1419       UIManager.put("TabbedPane.tabType", "card");
1420       UIManager.put("TabbedPane.showTabSeparators", true);
1421       UIManager.put("TabbedPane.showContentSeparator", true);
1422       UIManager.put("TabbedPane.tabSeparatorsFullHeight", true);
1423       UIManager.put("TabbedPane.tabsOverlapBorder", true);
1424       UIManager.put("TabbedPane.hasFullBorder", true);
1425       UIManager.put("TabbedPane.tabLayoutPolicy", "scroll");
1426       UIManager.put("TabbedPane.scrollButtonsPolicy", "asNeeded");
1427       UIManager.put("TabbedPane.smoothScrolling", true);
1428       UIManager.put("TabbedPane.tabWidthMode", "compact");
1429       UIManager.put("TabbedPane.selectedBackground", Color.white);
1430     }
1431
1432     Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
1433     return set;
1434   }
1435
1436   private static boolean setMacLookAndFeel()
1437   {
1438     boolean set = false;
1439     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
1440             ChannelProperties.getProperty("app_name"));
1441     System.setProperty("apple.laf.useScreenMenuBar", "true");
1442     /*
1443      * broken native LAFs on (ARM?) macbooks
1444     set = setQuaquaLookAndFeel();
1445     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
1446             .toLowerCase(Locale.ROOT).contains("quaqua"))
1447     {
1448       set = setVaquaLookAndFeel();
1449     }
1450      */
1451     set = setFlatLookAndFeel();
1452     return set;
1453   }
1454
1455   private static boolean setLinuxLookAndFeel()
1456   {
1457     boolean set = false;
1458     set = setFlatLookAndFeel();
1459     if (!set)
1460       set = setMetalLookAndFeel();
1461     // avoid GtkLookAndFeel -- not good results especially on HiDPI
1462     if (!set)
1463       set = setNimbusLookAndFeel();
1464     return set;
1465   }
1466
1467   private static void showUsage()
1468   {
1469     System.out.println(
1470             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
1471                     + "-nodisplay\tRun Jalview without User Interface.\n"
1472                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
1473                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
1474                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
1475                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
1476                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
1477                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
1478                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
1479                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
1480                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
1481                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
1482                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
1483                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
1484                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
1485                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
1486                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
1487                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
1488                     + "-html FILE\tCreate HTML file from alignment.\n"
1489                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
1490                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
1491                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
1492                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
1493                     + "-noquestionnaire\tTurn off questionnaire check.\n"
1494                     + "-nonews\tTurn off check for Jalview news.\n"
1495                     + "-nousagestats\tTurn off google analytics tracking for this session.\n"
1496                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
1497                     // +
1498                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
1499                     // after all other properties files have been read\n\t
1500                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
1501                     // passed in correctly)"
1502                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
1503                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
1504                     + "-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"
1505                     + "-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"
1506                     + "-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"
1507                     + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
1508   }
1509
1510   private static void startUsageStats(final Desktop desktop)
1511   {
1512     /**
1513      * start a User Config prompt asking if we can log usage statistics.
1514      */
1515     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
1516             "USAGESTATS", "Jalview Usage Statistics",
1517             "Do you want to help make Jalview better by enabling "
1518                     + "the collection of usage statistics with Google Analytics ?"
1519                     + "\n\n(you can enable or disable usage tracking in the preferences)",
1520             new Runnable()
1521             {
1522               @Override
1523               public void run()
1524               {
1525                 Console.debug(
1526                         "Initialising googletracker for usage stats.");
1527                 Cache.initGoogleTracker();
1528                 Console.debug("Tracking enabled.");
1529               }
1530             }, new Runnable()
1531             {
1532               @Override
1533               public void run()
1534               {
1535                 Console.debug("Not enabling Google Tracking.");
1536               }
1537             }, null, true);
1538     desktop.addDialogThread(prompter);
1539   }
1540
1541   /**
1542    * Locate the given string as a file and pass it to the groovy interpreter.
1543    * 
1544    * @param groovyscript
1545    *          the script to execute
1546    * @param jalviewContext
1547    *          the Jalview Desktop object passed in to the groovy binding as the
1548    *          'Jalview' object.
1549    */
1550   protected void executeGroovyScript(String groovyscript, AlignFrame af)
1551   {
1552     /**
1553      * for scripts contained in files
1554      */
1555     File tfile = null;
1556     /**
1557      * script's URI
1558      */
1559     URL sfile = null;
1560     if (groovyscript.trim().equals("STDIN"))
1561     {
1562       // read from stdin into a tempfile and execute it
1563       try
1564       {
1565         tfile = File.createTempFile("jalview", "groovy");
1566         PrintWriter outfile = new PrintWriter(
1567                 new OutputStreamWriter(new FileOutputStream(tfile)));
1568         BufferedReader br = new BufferedReader(
1569                 new InputStreamReader(System.in));
1570         String line = null;
1571         while ((line = br.readLine()) != null)
1572         {
1573           outfile.write(line + "\n");
1574         }
1575         br.close();
1576         outfile.flush();
1577         outfile.close();
1578
1579       } catch (Exception ex)
1580       {
1581         System.err.println("Failed to read from STDIN into tempfile "
1582                 + ((tfile == null) ? "(tempfile wasn't created)"
1583                         : tfile.toString()));
1584         ex.printStackTrace();
1585         return;
1586       }
1587       try
1588       {
1589         sfile = tfile.toURI().toURL();
1590       } catch (Exception x)
1591       {
1592         System.err.println(
1593                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1594                         + tfile.toURI());
1595         x.printStackTrace();
1596         return;
1597       }
1598     }
1599     else
1600     {
1601       try
1602       {
1603         sfile = new URI(groovyscript).toURL();
1604       } catch (Exception x)
1605       {
1606         tfile = new File(groovyscript);
1607         if (!tfile.exists())
1608         {
1609           System.err.println("File '" + groovyscript + "' does not exist.");
1610           return;
1611         }
1612         if (!tfile.canRead())
1613         {
1614           System.err.println("File '" + groovyscript + "' cannot be read.");
1615           return;
1616         }
1617         if (tfile.length() < 1)
1618         {
1619           System.err.println("File '" + groovyscript + "' is empty.");
1620           return;
1621         }
1622         try
1623         {
1624           sfile = tfile.getAbsoluteFile().toURI().toURL();
1625         } catch (Exception ex)
1626         {
1627           System.err.println("Failed to create a file URL for "
1628                   + tfile.getAbsoluteFile());
1629           return;
1630         }
1631       }
1632     }
1633     try
1634     {
1635       Map<String, java.lang.Object> vbinding = new HashMap<>();
1636       vbinding.put("Jalview", this);
1637       if (af != null)
1638       {
1639         vbinding.put("currentAlFrame", af);
1640       }
1641       Binding gbinding = new Binding(vbinding);
1642       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1643       gse.run(sfile.toString(), gbinding);
1644       if ("STDIN".equals(groovyscript))
1645       {
1646         // delete temp file that we made -
1647         // only if it was successfully executed
1648         tfile.delete();
1649       }
1650     } catch (Exception e)
1651     {
1652       System.err.println("Exception Whilst trying to execute file " + sfile
1653               + " as a groovy script.");
1654       e.printStackTrace(System.err);
1655
1656     }
1657   }
1658
1659   public static boolean isHeadlessMode()
1660   {
1661     String isheadless = System.getProperty("java.awt.headless");
1662     if (isheadless != null && isheadless.equalsIgnoreCase("true"))
1663     {
1664       return true;
1665     }
1666     return false;
1667   }
1668
1669   public AlignFrame[] getAlignFrames()
1670   {
1671     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1672             : Desktop.getAlignFrames();
1673
1674   }
1675
1676   /**
1677    * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
1678    */
1679   public void quit()
1680   {
1681     // System.exit will run the shutdownHook first
1682     Jalview.exit("Quitting now. Bye!", 0);
1683   }
1684
1685   public static AlignFrame getCurrentAlignFrame()
1686   {
1687     return Jalview.currentAlignFrame;
1688   }
1689
1690   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
1691   {
1692     Jalview.currentAlignFrame = currentAlignFrame;
1693   }
1694
1695   protected Commands getCommands()
1696   {
1697     return cmds;
1698   }
1699
1700   public static void exit(String message, int exitcode)
1701   {
1702     Console.debug("Using Jalview.exit");
1703     if (message != null)
1704       if (exitcode == 0)
1705         Console.info(message);
1706       else
1707         Console.error(message);
1708     if (exitcode > -1)
1709       System.exit(exitcode);
1710   }
1711
1712   /*
1713    * testoutput for string values
1714    */
1715   protected static void testoutput(ArgParser ap, Arg a, String s1,
1716           String s2)
1717   {
1718     BootstrapArgs bsa = ap.getBootstrapArgs();
1719     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1720       return;
1721     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1722     {
1723       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1724               + s2 + "' for arg " + a.argString());
1725       return;
1726     }
1727     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1728             : ap.isSet(a);
1729     if (!isset)
1730     {
1731       Console.warn("Arg '" + a.getName() + "' not set at all");
1732       return;
1733     }
1734     testoutput(true, a, s1, s2);
1735   }
1736
1737   protected static void testoutput(BootstrapArgs bsa, Arg a, String s1,
1738           String s2)
1739   {
1740     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1741       return;
1742     if (!((s1 == null && s2 == null) || (s1 != null && s1.equals(s2))))
1743     {
1744       Console.debug("testoutput with unmatching values '" + s1 + "' and '"
1745               + s2 + "' for arg " + a.argString());
1746       return;
1747     }
1748     if (!a.hasOption(Opt.BOOTSTRAP))
1749     {
1750       Console.error("Non-bootstrap Arg '" + a.getName()
1751               + "' given to testoutput(BootstrapArgs bsa, Arg a, String s1, String s2) with only BootstrapArgs");
1752     }
1753     if (!bsa.contains(a))
1754     {
1755       Console.warn("Arg '" + a.getName() + "' not set at all");
1756       return;
1757     }
1758     testoutput(true, a, s1, s2);
1759   }
1760
1761   private static void testoutput(boolean yes, Arg a, String s1, String s2)
1762   {
1763     if (yes && ((s1 == null && s2 == null)
1764             || (s1 != null && s1.equals(s2))))
1765     {
1766       System.out.println("[TESTOUTPUT] arg " + a.argString() + "='" + s1
1767               + "' was set");
1768     }
1769   }
1770
1771   /*
1772    * testoutput for boolean values
1773    */
1774   protected static void testoutput(ArgParser ap, Arg a)
1775   {
1776     if (ap == null)
1777       return;
1778     BootstrapArgs bsa = ap.getBootstrapArgs();
1779     if (bsa == null)
1780       return;
1781     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1782       return;
1783     boolean val = a.hasOption(Opt.BOOTSTRAP) ? bsa.getBoolean(a)
1784             : ap.getBoolean(a);
1785     boolean isset = a.hasOption(Opt.BOOTSTRAP) ? bsa.contains(a)
1786             : ap.isSet(a);
1787     if (!isset)
1788     {
1789       Console.warn("Arg '" + a.getName() + "' not set at all");
1790       return;
1791     }
1792     testoutput(val, a);
1793   }
1794
1795   protected static void testoutput(BootstrapArgs bsa, Arg a)
1796   {
1797     if (!bsa.getBoolean(Arg.TESTOUTPUT))
1798       return;
1799     if (!a.hasOption(Opt.BOOTSTRAP))
1800     {
1801       Console.warn("Non-bootstrap Arg '" + a.getName()
1802               + "' given to testoutput(BootstrapArgs bsa, Arg a) with only BootstrapArgs");
1803
1804     }
1805     if (!bsa.contains(a))
1806     {
1807       Console.warn("Arg '" + a.getName() + "' not set at all");
1808       return;
1809     }
1810     testoutput(bsa.getBoolean(a), a);
1811   }
1812
1813   private static void testoutput(boolean yes, Arg a)
1814   {
1815     System.out.println("[TESTOUTPUT] arg "
1816             + (yes ? a.argString() : a.negateArgString()) + " was set");
1817   }
1818 }