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