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