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