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