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