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