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