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