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