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