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