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