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