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