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