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