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