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