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