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