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