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