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