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