JAL-3750 parse protocol before checking if argument to -open can be opened as a file
[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       
455
456       protocol = AppletFormatAdapter.checkProtocol(file);
457       
458       if (protocol == DataSourceType.FILE)
459       {
460         if (!(new File(file)).exists())
461         {
462           System.out.println("Can't find " + file);
463           if (headless)
464           {
465             System.exit(1);
466           }
467         }
468       }
469
470       try
471       {
472         format = new IdentifyFile().identify(file, protocol);
473       } catch (FileFormatException e1)
474       {
475         // TODO ?
476       }
477
478       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
479               format);
480       if (af == null)
481       {
482         System.out.println("error");
483       }
484       else
485       {
486         setCurrentAlignFrame(af);
487         data = aparser.getValue("colour", true);
488         if (data != null)
489         {
490           data.replaceAll("%20", " ");
491
492           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
493                   af.getViewport(), af.getViewport().getAlignment(), data);
494
495           if (cs != null)
496           {
497             System.out.println(
498                     "CMD [-color " + data + "] executed successfully!");
499           }
500           af.changeColour(cs);
501         }
502
503         // Must maintain ability to use the groups flag
504         data = aparser.getValue("groups", true);
505         if (data != null)
506         {
507           af.parseFeaturesFile(data,
508                   AppletFormatAdapter.checkProtocol(data));
509           // System.out.println("Added " + data);
510           System.out.println(
511                   "CMD groups[-" + data + "]  executed successfully!");
512         }
513         data = aparser.getValue("features", true);
514         if (data != null)
515         {
516           af.parseFeaturesFile(data,
517                   AppletFormatAdapter.checkProtocol(data));
518           // System.out.println("Added " + data);
519           System.out.println(
520                   "CMD [-features " + data + "]  executed successfully!");
521         }
522
523         data = aparser.getValue("annotations", true);
524         if (data != null)
525         {
526           af.loadJalviewDataFile(data, null, null, null);
527           // System.out.println("Added " + data);
528           System.out.println(
529                   "CMD [-annotations " + data + "] executed successfully!");
530         }
531         // set or clear the sortbytree flag.
532         if (aparser.contains("sortbytree"))
533         {
534           af.getViewport().setSortByTree(true);
535           if (af.getViewport().getSortByTree())
536           {
537             System.out.println("CMD [-sortbytree] executed successfully!");
538           }
539         }
540         if (aparser.contains("no-annotation"))
541         {
542           af.getViewport().setShowAnnotation(false);
543           if (!af.getViewport().isShowAnnotation())
544           {
545             System.out.println("CMD no-annotation executed successfully!");
546           }
547         }
548         if (aparser.contains("nosortbytree"))
549         {
550           af.getViewport().setSortByTree(false);
551           if (!af.getViewport().getSortByTree())
552           {
553             System.out
554                     .println("CMD [-nosortbytree] executed successfully!");
555           }
556         }
557         data = aparser.getValue("tree", true);
558         if (data != null)
559         {
560           try
561           {
562             System.out.println(
563                     "CMD [-tree " + data + "] executed successfully!");
564             NewickFile nf = new NewickFile(data,
565                     AppletFormatAdapter.checkProtocol(data));
566             af.getViewport()
567                     .setCurrentTree(af.showNewickTree(nf, data).getTree());
568           } catch (IOException ex)
569           {
570             System.err.println("Couldn't add tree " + data);
571             ex.printStackTrace(System.err);
572           }
573         }
574         // TODO - load PDB structure(s) to alignment JAL-629
575         // (associate with identical sequence in alignment, or a specified
576         // sequence)
577         if (groovyscript != null)
578         {
579           // Execute the groovy script after we've done all the rendering stuff
580           // and before any images or figures are generated.
581           System.out.println("Executing script " + groovyscript);
582           executeGroovyScript(groovyscript, af);
583           System.out.println("CMD groovy[" + groovyscript
584                   + "] executed successfully!");
585           groovyscript = null;
586         }
587         String imageName = "unnamed.png";
588         while (aparser.getSize() > 1)
589         {
590           String outputFormat = aparser.nextValue();
591           file = aparser.nextValue();
592
593           if (outputFormat.equalsIgnoreCase("png"))
594           {
595             af.createPNG(new File(file));
596             imageName = (new File(file)).getName();
597             System.out.println("Creating PNG image: " + file);
598             continue;
599           }
600           else if (outputFormat.equalsIgnoreCase("svg"))
601           {
602             File imageFile = new File(file);
603             imageName = imageFile.getName();
604             af.createSVG(imageFile);
605             System.out.println("Creating SVG image: " + file);
606             continue;
607           }
608           else if (outputFormat.equalsIgnoreCase("html"))
609           {
610             File imageFile = new File(file);
611             imageName = imageFile.getName();
612             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
613             htmlSVG.exportHTML(file);
614
615             System.out.println("Creating HTML image: " + file);
616             continue;
617           }
618           else if (outputFormat.equalsIgnoreCase("biojsmsa"))
619           {
620             if (file == null)
621             {
622               System.err.println("The output html file must not be null");
623               return;
624             }
625             try
626             {
627               BioJsHTMLOutput.refreshVersionInfo(
628                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
629             } catch (URISyntaxException e)
630             {
631               e.printStackTrace();
632             }
633             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
634             bjs.exportHTML(file);
635             System.out
636                     .println("Creating BioJS MSA Viwer HTML file: " + file);
637             continue;
638           }
639           else if (outputFormat.equalsIgnoreCase("imgMap"))
640           {
641             af.createImageMap(new File(file), imageName);
642             System.out.println("Creating image map: " + file);
643             continue;
644           }
645           else if (outputFormat.equalsIgnoreCase("eps"))
646           {
647             File outputFile = new File(file);
648             System.out.println(
649                     "Creating EPS file: " + outputFile.getAbsolutePath());
650             af.createEPS(outputFile);
651             continue;
652           }
653           FileFormatI outFormat=null;
654           try {
655             outFormat = FileFormats.getInstance().forName(outputFormat);
656           } catch (Exception formatP) {
657             System.out.println("Couldn't parse "+outFormat+" as a valid Jalview format string.");
658           }
659           if (outFormat != null)
660           {
661             if (!outFormat.isWritable())
662             {
663               System.out.println(
664                       "This version of Jalview does not support alignment export as "
665                               + outputFormat);
666             }
667             else
668             {
669               if (af.saveAlignment(file, outFormat))
670               {
671                 System.out.println("Written alignment in " + format
672                         + " format to " + file);
673               }
674               else
675               {
676                 System.out.println("Error writing file " + file + " in "
677                         + format + " format!!");
678               }
679             }
680           }
681         }
682
683         while (aparser.getSize() > 0)
684         {
685           System.out.println("Unknown arg: " + aparser.nextValue());
686         }
687       }
688     }
689     AlignFrame startUpAlframe = null;
690     // We'll only open the default file if the desktop is visible.
691     // And the user
692     // ////////////////////
693
694     if (!headless && file == null
695             && jalview.bin.Cache.getDefault("SHOW_STARTUP_FILE", true))
696     {
697       file = jalview.bin.Cache.getDefault("STARTUP_FILE",
698               jalview.bin.Cache.getDefault("www.jalview.org",
699                       "http://www.jalview.org")
700                       + "/examples/exampleFile_2_7.jar");
701       if (file.equals(
702               "http://www.jalview.org/examples/exampleFile_2_3.jar"))
703       {
704         // hardwire upgrade of the startup file
705         file.replace("_2_3.jar", "_2_7.jar");
706         // and remove the stale setting
707         jalview.bin.Cache.removeProperty("STARTUP_FILE");
708       }
709
710       protocol = DataSourceType.FILE;
711
712       if (file.indexOf("http:") > -1)
713       {
714         protocol = DataSourceType.URL;
715       }
716
717       if (file.endsWith(".jar"))
718       {
719         format = FileFormat.Jalview;
720       }
721       else
722       {
723         try
724         {
725           format = new IdentifyFile().identify(file, protocol);
726         } catch (FileFormatException e)
727         {
728           // TODO what?
729         }
730       }
731
732       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
733               format);
734       // extract groovy arguments before anything else.
735     }
736
737     // Once all other stuff is done, execute any groovy scripts (in order)
738     if (groovyscript != null)
739     {
740       if (Cache.groovyJarsPresent())
741       {
742         System.out.println("Executing script " + groovyscript);
743         executeGroovyScript(groovyscript, startUpAlframe);
744       }
745       else
746       {
747         System.err.println(
748                 "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
749                         + groovyscript);
750       }
751     }
752     // and finally, turn off batch mode indicator - if the desktop still exists
753     if (desktop != null)
754     {
755       if (progress != -1)
756       {
757         desktop.setProgressBar(null, progress);
758       }
759       desktop.setInBatchMode(false);
760     }
761   }
762
763   private static void setLookAndFeel()
764   {
765     // property laf = "crossplatform", "system", "gtk", "metal", "nimbus" or
766     // "mac"
767     // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
768     // try Quaqua/Vaqua.
769     String lafProp = System.getProperty("laf");
770     String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
771     String laf = "none";
772     if (lafProp != null)
773     {
774       laf = lafProp;
775     }
776     else if (lafSetting != null)
777     {
778       laf = lafSetting;
779     }
780     boolean lafSet = false;
781     switch (laf)
782     {
783     case "crossplatform":
784       lafSet = setCrossPlatformLookAndFeel();
785       if (!lafSet)
786       {
787         Cache.log.error("Could not set requested laf=" + laf);
788       }
789       break;
790     case "system":
791       lafSet = setSystemLookAndFeel();
792       if (!lafSet)
793       {
794         Cache.log.error("Could not set requested laf=" + laf);
795       }
796       break;
797     case "gtk":
798       lafSet = setGtkLookAndFeel();
799       if (!lafSet)
800       {
801         Cache.log.error("Could not set requested laf=" + laf);
802       }
803       break;
804     case "metal":
805       lafSet = setMetalLookAndFeel();
806       if (!lafSet)
807       {
808         Cache.log.error("Could not set requested laf=" + laf);
809       }
810       break;
811     case "nimbus":
812       lafSet = setNimbusLookAndFeel();
813       if (!lafSet)
814       {
815         Cache.log.error("Could not set requested laf=" + laf);
816       }
817       break;
818     case "quaqua":
819       lafSet = setQuaquaLookAndFeel();
820       if (!lafSet)
821       {
822         Cache.log.error("Could not set requested laf=" + laf);
823       }
824       break;
825     case "vaqua":
826       lafSet = setVaquaLookAndFeel();
827       if (!lafSet)
828       {
829         Cache.log.error("Could not set requested laf=" + laf);
830       }
831       break;
832     case "mac":
833       lafSet = setMacLookAndFeel();
834       if (!lafSet)
835       {
836         Cache.log.error("Could not set requested laf=" + laf);
837       }
838       break;
839     case "none":
840       break;
841     default:
842       Cache.log.error("Requested laf=" + laf + " not implemented");
843     }
844     if (!lafSet)
845     {
846       setSystemLookAndFeel();
847       if (Platform.isLinux())
848       {
849         setMetalLookAndFeel();
850       }
851       if (Platform.isAMac())
852       {
853         setMacLookAndFeel();
854       }
855     }
856   }
857
858   private static boolean setCrossPlatformLookAndFeel()
859   {
860     return setGenericLookAndFeel(false);
861   }
862
863   private static boolean setSystemLookAndFeel()
864   {
865     return setGenericLookAndFeel(true);
866   }
867
868   private static boolean setGenericLookAndFeel(boolean system)
869   {
870     boolean set = false;
871     try
872     {
873       UIManager.setLookAndFeel(
874               system ? UIManager.getSystemLookAndFeelClassName()
875                       : UIManager.getCrossPlatformLookAndFeelClassName());
876       set = true;
877     } catch (Exception ex)
878     {
879       Cache.log.error("Unexpected Look and Feel Exception");
880       Cache.log.error(ex.getMessage());
881       Cache.log.debug(Cache.getStackTraceString(ex));
882     }
883     return set;
884   }
885
886   private static boolean setSpecificLookAndFeel(String name,
887           String className, boolean nameStartsWith)
888   {
889     boolean set = false;
890     try
891     {
892       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
893       {
894         if (info.getName() != null && nameStartsWith
895                 ? info.getName().toLowerCase()
896                         .startsWith(name.toLowerCase())
897                 : info.getName().toLowerCase().equals(name.toLowerCase()))
898         {
899           className = info.getClassName();
900           break;
901         }
902       }
903       UIManager.setLookAndFeel(className);
904       set = true;
905     } catch (Exception ex)
906     {
907       Cache.log.error("Unexpected Look and Feel Exception");
908       Cache.log.error(ex.getMessage());
909       Cache.log.debug(Cache.getStackTraceString(ex));
910     }
911     return set;
912   }
913
914   private static boolean setGtkLookAndFeel()
915   {
916     return setSpecificLookAndFeel("gtk",
917             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
918   }
919
920   private static boolean setMetalLookAndFeel()
921   {
922     return setSpecificLookAndFeel("metal",
923             "javax.swing.plaf.metal.MetalLookAndFeel", false);
924   }
925
926   private static boolean setNimbusLookAndFeel()
927   {
928     return setSpecificLookAndFeel("nimbus",
929             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
930   }
931
932   private static boolean setQuaquaLookAndFeel()
933   {
934     return setSpecificLookAndFeel("quaqua",
935             ch.randelshofer.quaqua.QuaquaManager.getLookAndFeel().getClass()
936                     .getName(),
937             false);
938   }
939
940   private static boolean setVaquaLookAndFeel()
941   {
942     return setSpecificLookAndFeel("vaqua",
943             "org.violetlib.aqua.AquaLookAndFeel", false);
944   }
945
946   private static boolean setMacLookAndFeel()
947   {
948     boolean set = false;
949     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
950             "Jalview");
951     System.setProperty("apple.laf.useScreenMenuBar", "true");
952     set = setQuaquaLookAndFeel();
953     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
954             .toLowerCase().contains("quaqua"))
955     {
956       set = setVaquaLookAndFeel();
957     }
958     return set;
959   }
960
961   private static void showUsage()
962   {
963     System.out.println(
964             "Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
965                     + "-nodisplay\tRun Jalview without User Interface.\n"
966                     + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
967                     + "-colour COLOURSCHEME\tThe colourscheme to be applied to the alignment\n"
968                     + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
969                     + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
970                     + "-features FILE\tUse the given file to mark features on the alignment.\n"
971                     + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
972                     + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
973                     + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
974                     + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
975                     + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
976                     + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
977                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
978                     + "-json FILE\tCreate alignment file FILE in JSON format.\n"
979                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
980                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
981                     + "-svg FILE\tCreate SVG image FILE from alignment.\n"
982                     + "-html FILE\tCreate HTML file from alignment.\n"
983                     + "-biojsMSA FILE\tCreate BioJS MSA Viewer HTML file from alignment.\n"
984                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
985                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
986                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
987                     + "-noquestionnaire\tTurn off questionnaire check.\n"
988                     + "-nonews\tTurn off check for Jalview news.\n"
989                     + "-nousagestats\tTurn off google analytics tracking for this session.\n"
990                     + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
991                     // +
992                     // "-setprop PROPERTY=VALUE\tSet the given Jalview property,
993                     // after all other properties files have been read\n\t
994                     // (quote the 'PROPERTY=VALUE' pair to ensure spaces are
995                     // passed in correctly)"
996                     + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
997                     + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
998                     + "-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"
999                     + "-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"
1000                     + "-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"
1001                     + "\n~Read documentation in Application or visit http://www.jalview.org for description of Features and Annotations file~\n\n");
1002   }
1003
1004   private static void startUsageStats(final Desktop desktop)
1005   {
1006     /**
1007      * start a User Config prompt asking if we can log usage statistics.
1008      */
1009     PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
1010             "USAGESTATS", "Jalview Usage Statistics",
1011             "Do you want to help make Jalview better by enabling "
1012                     + "the collection of usage statistics with Google Analytics ?"
1013                     + "\n\n(you can enable or disable usage tracking in the preferences)",
1014             new Runnable()
1015             {
1016               @Override
1017               public void run()
1018               {
1019                 Cache.log.debug(
1020                         "Initialising googletracker for usage stats.");
1021                 Cache.initGoogleTracker();
1022                 Cache.log.debug("Tracking enabled.");
1023               }
1024             }, new Runnable()
1025             {
1026               @Override
1027               public void run()
1028               {
1029                 Cache.log.debug("Not enabling Google Tracking.");
1030               }
1031             }, null, true);
1032     desktop.addDialogThread(prompter);
1033   }
1034
1035   /**
1036    * Locate the given string as a file and pass it to the groovy interpreter.
1037    * 
1038    * @param groovyscript
1039    *          the script to execute
1040    * @param jalviewContext
1041    *          the Jalview Desktop object passed in to the groovy binding as the
1042    *          'Jalview' object.
1043    */
1044   private void executeGroovyScript(String groovyscript, AlignFrame af)
1045   {
1046     /**
1047      * for scripts contained in files
1048      */
1049     File tfile = null;
1050     /**
1051      * script's URI
1052      */
1053     URL sfile = null;
1054     if (groovyscript.trim().equals("STDIN"))
1055     {
1056       // read from stdin into a tempfile and execute it
1057       try
1058       {
1059         tfile = File.createTempFile("jalview", "groovy");
1060         PrintWriter outfile = new PrintWriter(
1061                 new OutputStreamWriter(new FileOutputStream(tfile)));
1062         BufferedReader br = new BufferedReader(
1063                 new InputStreamReader(System.in));
1064         String line = null;
1065         while ((line = br.readLine()) != null)
1066         {
1067           outfile.write(line + "\n");
1068         }
1069         br.close();
1070         outfile.flush();
1071         outfile.close();
1072
1073       } catch (Exception ex)
1074       {
1075         System.err.println("Failed to read from STDIN into tempfile "
1076                 + ((tfile == null) ? "(tempfile wasn't created)"
1077                         : tfile.toString()));
1078         ex.printStackTrace();
1079         return;
1080       }
1081       try
1082       {
1083         sfile = tfile.toURI().toURL();
1084       } catch (Exception x)
1085       {
1086         System.err.println(
1087                 "Unexpected Malformed URL Exception for temporary file created from STDIN: "
1088                         + tfile.toURI());
1089         x.printStackTrace();
1090         return;
1091       }
1092     }
1093     else
1094     {
1095       try
1096       {
1097         sfile = new URI(groovyscript).toURL();
1098       } catch (Exception x)
1099       {
1100         tfile = new File(groovyscript);
1101         if (!tfile.exists())
1102         {
1103           System.err.println("File '" + groovyscript + "' does not exist.");
1104           return;
1105         }
1106         if (!tfile.canRead())
1107         {
1108           System.err.println("File '" + groovyscript + "' cannot be read.");
1109           return;
1110         }
1111         if (tfile.length() < 1)
1112         {
1113           System.err.println("File '" + groovyscript + "' is empty.");
1114           return;
1115         }
1116         try
1117         {
1118           sfile = tfile.getAbsoluteFile().toURI().toURL();
1119         } catch (Exception ex)
1120         {
1121           System.err.println("Failed to create a file URL for "
1122                   + tfile.getAbsoluteFile());
1123           return;
1124         }
1125       }
1126     }
1127     try
1128     {
1129       Map<String, java.lang.Object> vbinding = new HashMap<>();
1130       vbinding.put("Jalview", this);
1131       if (af != null)
1132       {
1133         vbinding.put("currentAlFrame", af);
1134       }
1135       Binding gbinding = new Binding(vbinding);
1136       GroovyScriptEngine gse = new GroovyScriptEngine(new URL[] { sfile });
1137       gse.run(sfile.toString(), gbinding);
1138       if ("STDIN".equals(groovyscript))
1139       {
1140         // delete temp file that we made -
1141         // only if it was successfully executed
1142         tfile.delete();
1143       }
1144     } catch (Exception e)
1145     {
1146       System.err.println("Exception Whilst trying to execute file " + sfile
1147               + " as a groovy script.");
1148       e.printStackTrace(System.err);
1149
1150     }
1151   }
1152
1153   public static boolean isHeadlessMode()
1154   {
1155     String isheadless = System.getProperty("java.awt.headless");
1156     if (isheadless != null && isheadless.equalsIgnoreCase("true"))
1157     {
1158       return true;
1159     }
1160     return false;
1161   }
1162
1163   public AlignFrame[] getAlignFrames()
1164   {
1165     return desktop == null ? new AlignFrame[] { getCurrentAlignFrame() }
1166             : Desktop.getAlignFrames();
1167
1168   }
1169
1170   /**
1171    * Quit method delegates to Desktop.quit - unless running in headless mode
1172    * when it just ends the JVM
1173    */
1174   public void quit()
1175   {
1176     if (desktop != null)
1177     {
1178       desktop.quit();
1179     }
1180     else
1181     {
1182       System.exit(0);
1183     }
1184   }
1185
1186   public static AlignFrame getCurrentAlignFrame()
1187   {
1188     return Jalview.currentAlignFrame;
1189   }
1190
1191   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
1192   {
1193     Jalview.currentAlignFrame = currentAlignFrame;
1194   }
1195 }