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