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