JAL-4262 Dialog for GUI and WARN for CLI of old style arguments, or mixed arguments...
[jalview.git] / src / jalview / bin / Commands.java
1 package jalview.bin;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.net.URISyntaxException;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.Collections;
9 import java.util.HashMap;
10 import java.util.Iterator;
11 import java.util.List;
12 import java.util.Locale;
13 import java.util.Map;
14
15 import jalview.analysis.AlignmentUtils;
16 import jalview.api.structures.JalviewStructureDisplayI;
17 import jalview.bin.Jalview.ExitCode;
18 import jalview.bin.argparser.Arg;
19 import jalview.bin.argparser.ArgParser;
20 import jalview.bin.argparser.ArgParser.Position;
21 import jalview.bin.argparser.ArgValue;
22 import jalview.bin.argparser.ArgValuesMap;
23 import jalview.bin.argparser.SubVals;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.SequenceI;
26 import jalview.datamodel.annotations.AlphaFoldAnnotationRowBuilder;
27 import jalview.gui.AlignFrame;
28 import jalview.gui.AlignmentPanel;
29 import jalview.gui.AppJmol;
30 import jalview.gui.Desktop;
31 import jalview.gui.Preferences;
32 import jalview.gui.StructureChooser;
33 import jalview.gui.StructureViewer;
34 import jalview.gui.StructureViewer.ViewerType;
35 import jalview.io.AppletFormatAdapter;
36 import jalview.io.BackupFiles;
37 import jalview.io.BioJsHTMLOutput;
38 import jalview.io.DataSourceType;
39 import jalview.io.FileFormat;
40 import jalview.io.FileFormatException;
41 import jalview.io.FileFormatI;
42 import jalview.io.FileFormats;
43 import jalview.io.FileLoader;
44 import jalview.io.HtmlSvgOutput;
45 import jalview.io.IdentifyFile;
46 import jalview.io.NewickFile;
47 import jalview.io.exceptions.ImageOutputException;
48 import jalview.schemes.ColourSchemeI;
49 import jalview.schemes.ColourSchemeProperty;
50 import jalview.structure.StructureImportSettings.TFType;
51 import jalview.structure.StructureSelectionManager;
52 import jalview.util.FileUtils;
53 import jalview.util.HttpUtils;
54 import jalview.util.ImageMaker;
55 import jalview.util.ImageMaker.TYPE;
56 import jalview.util.MessageManager;
57 import jalview.util.Platform;
58 import jalview.util.imagemaker.BitmapImageSizing;
59
60 public class Commands
61 {
62   Desktop desktop;
63
64   private boolean headless;
65
66   private ArgParser argParser;
67
68   private Map<String, AlignFrame> afMap;
69
70   private Map<String, List<StructureViewer>> svMap;
71
72   private boolean commandArgsProvided = false;
73
74   private boolean argsWereParsed = false;
75
76   public Commands(ArgParser argparser, boolean headless)
77   {
78     this(Desktop.instance, argparser, headless);
79   }
80
81   public Commands(Desktop d, ArgParser argparser, boolean h)
82   {
83     argParser = argparser;
84     headless = h;
85     desktop = d;
86     afMap = new HashMap<>();
87   }
88
89   protected boolean processArgs()
90   {
91     if (argParser == null)
92     {
93       return true;
94     }
95
96     boolean theseArgsWereParsed = false;
97
98     if (argParser != null && argParser.getLinkedIds() != null)
99     {
100       for (String id : argParser.getLinkedIds())
101       {
102         ArgValuesMap avm = argParser.getLinkedArgs(id);
103         theseArgsWereParsed = true;
104         boolean processLinkedOkay = processLinked(id);
105         theseArgsWereParsed &= processLinkedOkay;
106
107         processGroovyScript(id);
108
109         // wait around until alignFrame isn't busy
110         AlignFrame af = afMap.get(id);
111         while (af != null && af.getViewport().isCalcInProgress())
112         {
113           try
114           {
115             Thread.sleep(25);
116           } catch (Exception q)
117           {
118           }
119           ;
120         }
121
122         theseArgsWereParsed &= processImages(id);
123
124         if (processLinkedOkay)
125         {
126           theseArgsWereParsed &= processOutput(id);
127         }
128
129         // close ap
130         if (avm.getBoolean(Arg.CLOSE))
131         {
132           af = afMap.get(id);
133           if (af != null)
134           {
135             af.closeMenuItem_actionPerformed(true);
136           }
137         }
138
139       }
140
141     }
142     if (argParser.getBoolean(Arg.QUIT))
143     {
144       Jalview.getInstance().exit(
145               "Exiting due to " + Arg.QUIT.argString() + " argument.",
146               ExitCode.OK);
147       return true;
148     }
149     // carry on with jalview.bin.Jalview
150     argsWereParsed = theseArgsWereParsed;
151     return argsWereParsed;
152   }
153
154   public boolean commandArgsProvided()
155   {
156     return commandArgsProvided;
157   }
158
159   public boolean argsWereParsed()
160   {
161     return argsWereParsed;
162   }
163
164   protected boolean processLinked(String id)
165   {
166     boolean theseArgsWereParsed = false;
167     ArgValuesMap avm = argParser.getLinkedArgs(id);
168     if (avm == null)
169       return true;
170
171     // set wrap scope here so it can be applied after structures are opened
172     boolean wrap = false;
173
174     if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
175     {
176       commandArgsProvided = true;
177       long progress = -1;
178
179       boolean first = true;
180       boolean progressBarSet = false;
181       AlignFrame af;
182       // Combine the APPEND and OPEN files into one list, along with whether it
183       // was APPEND or OPEN
184       List<ArgValue> openAvList = new ArrayList<>();
185       openAvList.addAll(avm.getArgValueList(Arg.OPEN));
186       openAvList.addAll(avm.getArgValueList(Arg.APPEND));
187       // sort avlist based on av.getArgIndex()
188       Collections.sort(openAvList);
189       for (ArgValue av : openAvList)
190       {
191         Arg a = av.getArg();
192         SubVals sv = av.getSubVals();
193         String openFile = av.getValue();
194         if (openFile == null)
195           continue;
196
197         theseArgsWereParsed = true;
198         if (first)
199         {
200           first = false;
201           if (!headless && desktop != null)
202           {
203             desktop.setProgressBar(
204                     MessageManager.getString(
205                             "status.processing_commandline_args"),
206                     progress = System.currentTimeMillis());
207             progressBarSet = true;
208           }
209         }
210
211         if (!Platform.isJS())
212         /**
213          * ignore in JavaScript -- can't just file existence - could load it?
214          * 
215          * @j2sIgnore
216          */
217         {
218           if (!HttpUtils.startsWithHttpOrHttps(openFile))
219           {
220             if (!(new File(openFile)).exists())
221             {
222               Console.warn("Can't find file '" + openFile + "'");
223               continue;
224             }
225           }
226         }
227
228         DataSourceType protocol = AppletFormatAdapter
229                 .checkProtocol(openFile);
230
231         FileFormatI format = null;
232         try
233         {
234           format = new IdentifyFile().identify(openFile, protocol);
235         } catch (FileFormatException e1)
236         {
237           Console.error("Unknown file format for '" + openFile + "'");
238           continue;
239         }
240
241         af = afMap.get(id);
242         // When to open a new AlignFrame
243         if (af == null || "true".equals(av.getSubVal("new"))
244                 || a == Arg.OPEN || format == FileFormat.Jalview)
245         {
246           if (a == Arg.OPEN)
247           {
248             Jalview.testoutput(argParser, Arg.OPEN, "examples/uniref50.fa",
249                     openFile);
250           }
251
252           Console.debug(
253                   "Opening '" + openFile + "' in new alignment frame");
254           FileLoader fileLoader = new FileLoader(!headless);
255           boolean xception = false;
256           try
257           {
258             af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
259                     format);
260           } catch (Throwable thr)
261           {
262             xception = true;
263             Console.error("Couldn't open '" + openFile + "' as " + format
264                     + " " + thr.getLocalizedMessage()
265                     + " (Enable debug for full stack trace)");
266             Console.debug("Exception when opening '" + openFile + "'", thr);
267           } finally
268           {
269             if (af == null && !xception)
270             {
271               Console.info("Ignoring '" + openFile
272                       + "' - no alignment data found.");
273               continue;
274             }
275           }
276
277           // colour alignment?
278           String colour = ArgParser.getFromSubValArgOrPref(avm, av,
279                   Arg.COLOUR, sv, null, "DEFAULT_COLOUR_PROT", "");
280           if ("" != colour)
281           {
282             ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
283                     af.getViewport(), af.getViewport().getAlignment(),
284                     colour);
285
286             if (cs == null && !"None".equals(colour))
287             {
288               Console.warn(
289                       "Couldn't parse '" + colour + "' as a colourscheme.");
290             }
291             else
292             {
293               af.changeColour(cs);
294             }
295             Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
296           }
297
298           // Change alignment frame title
299           String title = ArgParser.getFromSubValArgOrPref(avm, av,
300                   Arg.TITLE, sv, null, null, null);
301           if (title != null)
302           {
303             af.setTitle(title);
304             Jalview.testoutput(argParser, Arg.TITLE, "test title", title);
305           }
306
307           // Add features
308           String featuresfile = ArgParser.getValueFromSubValOrArg(avm, av,
309                   Arg.FEATURES, sv);
310           if (featuresfile != null)
311           {
312             af.parseFeaturesFile(featuresfile,
313                     AppletFormatAdapter.checkProtocol(featuresfile));
314             Jalview.testoutput(argParser, Arg.FEATURES,
315                     "examples/testdata/plantfdx.features", featuresfile);
316           }
317
318           // Add annotations from file
319           String annotationsfile = ArgParser.getValueFromSubValOrArg(avm,
320                   av, Arg.ANNOTATIONS, sv);
321           if (annotationsfile != null)
322           {
323             af.loadJalviewDataFile(annotationsfile, null, null, null);
324             Jalview.testoutput(argParser, Arg.ANNOTATIONS,
325                     "examples/testdata/plantfdx.annotations",
326                     annotationsfile);
327           }
328
329           // Set or clear the sortbytree flag
330           boolean sortbytree = ArgParser.getBoolFromSubValOrArg(avm,
331                   Arg.SORTBYTREE, sv);
332           if (sortbytree)
333           {
334             af.getViewport().setSortByTree(true);
335             Jalview.testoutput(argParser, Arg.SORTBYTREE);
336           }
337
338           // Load tree from file
339           String treefile = ArgParser.getValueFromSubValOrArg(avm, av,
340                   Arg.TREE, sv);
341           if (treefile != null)
342           {
343             try
344             {
345               NewickFile nf = new NewickFile(treefile,
346                       AppletFormatAdapter.checkProtocol(treefile));
347               af.getViewport().setCurrentTree(
348                       af.showNewickTree(nf, treefile).getTree());
349               Jalview.testoutput(argParser, Arg.TREE,
350                       "examples/testdata/uniref50_test_tree", treefile);
351             } catch (IOException e)
352             {
353               Console.warn("Couldn't add tree " + treefile, e);
354             }
355           }
356
357           // Show secondary structure annotations?
358           boolean showSSAnnotations = ArgParser.getFromSubValArgOrPref(avm,
359                   Arg.SHOWSSANNOTATIONS, av.getSubVals(), null,
360                   "STRUCT_FROM_PDB", true);
361           af.setAnnotationsVisibility(showSSAnnotations, true, false);
362
363           // Show sequence annotations?
364           boolean showAnnotations = ArgParser.getFromSubValArgOrPref(avm,
365                   Arg.SHOWANNOTATIONS, av.getSubVals(), null,
366                   "SHOW_ANNOTATIONS", true);
367           af.setAnnotationsVisibility(showAnnotations, false, true);
368
369           // show temperature factor annotations?
370           if (avm.getBoolean(Arg.NOTEMPFAC))
371           {
372             // do this better (annotation types?)
373             List<String> hideThese = new ArrayList<>();
374             hideThese.add("Temperature Factor");
375             hideThese.add(AlphaFoldAnnotationRowBuilder.LABEL);
376             AlignmentUtils.showOrHideSequenceAnnotations(
377                     af.getCurrentView().getAlignment(), hideThese, null,
378                     false, false);
379           }
380
381           // wrap alignment? do this last for formatting reasons
382           wrap = ArgParser.getFromSubValArgOrPref(avm, Arg.WRAP, sv, null,
383                   "WRAP_ALIGNMENT", false);
384           // af.setWrapFormat(wrap) is applied after structures are opened for
385           // annotation reasons
386
387           // store the AlignFrame for this id
388           afMap.put(id, af);
389
390           // is it its own structure file?
391           if (format.isStructureFile())
392           {
393             StructureSelectionManager ssm = StructureSelectionManager
394                     .getStructureSelectionManager(Desktop.instance);
395             SequenceI seq = af.alignPanel.getAlignment().getSequenceAt(0);
396             ssm.computeMapping(false, new SequenceI[] { seq }, null,
397                     openFile, DataSourceType.FILE, null, null, null, false);
398           }
399         }
400         else
401         {
402           Console.debug(
403                   "Opening '" + openFile + "' in existing alignment frame");
404           DataSourceType dst = HttpUtils.startsWithHttpOrHttps(openFile)
405                   ? DataSourceType.URL
406                   : DataSourceType.FILE;
407           FileLoader fileLoader = new FileLoader(!headless);
408           fileLoader.LoadFile(af.getCurrentView(), openFile, dst, null,
409                   false);
410         }
411
412         Console.debug("Command " + Arg.APPEND + " executed successfully!");
413
414       }
415       if (first) // first=true means nothing opened
416       {
417         if (headless)
418         {
419           Jalview.exit("Could not open any files in headless mode",
420                   ExitCode.NO_FILES);
421         }
422         else
423         {
424           Console.warn("No more files to open");
425         }
426       }
427       if (progressBarSet && desktop != null)
428         desktop.setProgressBar(null, progress);
429
430     }
431
432     // open the structure (from same PDB file or given PDBfile)
433     if (!avm.getBoolean(Arg.NOSTRUCTURE))
434     {
435       AlignFrame af = afMap.get(id);
436       if (avm.containsArg(Arg.STRUCTURE))
437       {
438         commandArgsProvided = true;
439         for (ArgValue av : avm.getArgValueList(Arg.STRUCTURE))
440         {
441           String val = av.getValue();
442           SubVals subVals = av.getSubVals();
443           SequenceI seq = getSpecifiedSequence(af, avm, av);
444           if (seq == null)
445           {
446             // Could not find sequence from subId, let's assume the first
447             // sequence in the alignframe
448             AlignmentI al = af.getCurrentView().getAlignment();
449             seq = al.getSequenceAt(0);
450           }
451
452           if (seq == null)
453           {
454             Console.warn("Could not find sequence for argument "
455                     + Arg.STRUCTURE.argString() + "=" + val);
456             // you probably want to continue here, not break
457             // break;
458             continue;
459           }
460           File structureFile = null;
461           if (subVals.getContent() != null
462                   && subVals.getContent().length() != 0)
463           {
464             structureFile = new File(subVals.getContent());
465             Console.debug("Using structure file (from argument) '"
466                     + structureFile.getAbsolutePath() + "'");
467           }
468           // TRY THIS
469           /*
470            * PDBEntry fileEntry = new AssociatePdbFileWithSeq()
471            * .associatePdbWithSeq(selectedPdbFileName, DataSourceType.FILE,
472            * selectedSequence, true, Desktop.instance);
473            * 
474            * sViewer = launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap, new
475            * SequenceI[] { selectedSequence });
476            * 
477            */
478           /* THIS DOESN'T WORK */
479           else if (seq.getAllPDBEntries() != null
480                   && seq.getAllPDBEntries().size() > 0)
481           {
482             structureFile = new File(
483                     seq.getAllPDBEntries().elementAt(0).getFile());
484             Console.debug("Using structure file (from sequence) '"
485                     + structureFile.getAbsolutePath() + "'");
486           }
487
488           if (structureFile == null)
489           {
490             Console.warn("Not provided structure file with '" + val + "'");
491             continue;
492           }
493
494           if (!structureFile.exists())
495           {
496             Console.warn("Structure file '"
497                     + structureFile.getAbsoluteFile() + "' not found.");
498             continue;
499           }
500
501           Console.debug("Using structure file "
502                   + structureFile.getAbsolutePath());
503
504           // open structure view
505           AlignmentPanel ap = af.alignPanel;
506           if (headless)
507           {
508             Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
509                     StructureViewer.ViewerType.JMOL.toString());
510           }
511
512           String structureFilepath = structureFile.getAbsolutePath();
513
514           // get PAEMATRIX file and label from subvals or Arg.PAEMATRIX
515           String paeFilepath = ArgParser
516                   .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
517                           Arg.PAEMATRIX, Position.AFTER, av, subVals, null,
518                           null, null);
519           if (paeFilepath != null)
520           {
521             File paeFile = new File(paeFilepath);
522
523             try
524             {
525               paeFilepath = paeFile.getCanonicalPath();
526             } catch (IOException e)
527             {
528               paeFilepath = paeFile.getAbsolutePath();
529               Console.warn("Problem with the PAE file path: '"
530                       + paeFile.getPath() + "'");
531             }
532           }
533
534           // showing annotations from structure file or not
535           boolean ssFromStructure = ArgParser.getFromSubValArgOrPref(avm,
536                   Arg.SHOWSSANNOTATIONS, subVals, null, "STRUCT_FROM_PDB",
537                   true);
538
539           // get TEMPFAC type from subvals or Arg.TEMPFAC in case user Adds
540           // reference annotations
541           String tftString = ArgParser
542                   .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
543                           Arg.TEMPFAC, Position.AFTER, av, subVals, null,
544                           null, null);
545           boolean notempfac = ArgParser.getFromSubValArgOrPref(avm,
546                   Arg.NOTEMPFAC, subVals, null, "ADD_TEMPFACT_ANN", false,
547                   true);
548           TFType tft = notempfac ? null : TFType.DEFAULT;
549           if (tftString != null && !notempfac)
550           {
551             // get kind of temperature factor annotation
552             try
553             {
554               tft = TFType.valueOf(tftString.toUpperCase(Locale.ROOT));
555               Console.debug("Obtained Temperature Factor type of '" + tft
556                       + "' for structure '" + structureFilepath + "'");
557             } catch (IllegalArgumentException e)
558             {
559               // Just an error message!
560               StringBuilder sb = new StringBuilder().append("Cannot set ")
561                       .append(Arg.TEMPFAC.argString()).append(" to '")
562                       .append(tft)
563                       .append("', ignoring.  Valid values are: ");
564               Iterator<TFType> it = Arrays.stream(TFType.values())
565                       .iterator();
566               while (it.hasNext())
567               {
568                 sb.append(it.next().toString().toLowerCase(Locale.ROOT));
569                 if (it.hasNext())
570                   sb.append(", ");
571               }
572               Console.warn(sb.toString());
573             }
574           }
575
576           String sViewer = ArgParser.getFromSubValArgOrPref(avm,
577                   Arg.STRUCTUREVIEWER, Position.AFTER, av, subVals, null,
578                   null, "jmol");
579           ViewerType viewerType = ViewerType.getFromString(sViewer);
580
581           // TODO use ssFromStructure
582           StructureViewer sv = StructureChooser
583                   .openStructureFileForSequence(null, null, ap, seq, false,
584                           structureFilepath, tft, paeFilepath, false,
585                           ssFromStructure, false, viewerType);
586
587           if (sv == null)
588           {
589             Console.error("Failed to import and open structure view.");
590             continue;
591           }
592           try
593           {
594             long tries = 1000;
595             while (sv.isBusy() && tries > 0)
596             {
597               Thread.sleep(25);
598               if (sv.isBusy())
599               {
600                 tries--;
601                 Console.debug(
602                         "Waiting for viewer for " + structureFilepath);
603               }
604             }
605             if (tries == 0 && sv.isBusy())
606             {
607               Console.warn(
608                       "Gave up waiting for structure viewer to load. Something may have gone wrong.");
609             }
610           } catch (Exception x)
611           {
612             Console.warn("Exception whilst waiting for structure viewer "
613                     + structureFilepath, x);
614           }
615
616           // add StructureViewer to svMap list
617           if (svMap == null)
618           {
619             svMap = new HashMap<>();
620           }
621           if (svMap.get(id) == null)
622           {
623             svMap.put(id, new ArrayList<>());
624           }
625           svMap.get(id).add(sv);
626
627           Console.debug(
628                   "Successfully opened viewer for " + structureFilepath);
629           String structureImageFilename = ArgParser.getValueFromSubValOrArg(
630                   avm, av, Arg.STRUCTUREIMAGE, subVals);
631           if (sv != null && structureImageFilename != null)
632           {
633             ArgValue siAv = avm.getClosestNextArgValueOfArg(av,
634                     Arg.STRUCTUREIMAGE);
635             SubVals sisv = null;
636             if (structureImageFilename.equals(siAv.getValue()))
637             {
638               sisv = siAv.getSubVals();
639             }
640             File structureImageFile = new File(structureImageFilename);
641             String width = ArgParser.getValueFromSubValOrArg(avm, av,
642                     Arg.STRUCTUREIMAGEWIDTH, sisv);
643             String height = ArgParser.getValueFromSubValOrArg(avm, av,
644                     Arg.STRUCTUREIMAGEHEIGHT, sisv);
645             String scale = ArgParser.getValueFromSubValOrArg(avm, av,
646                     Arg.STRUCTUREIMAGESCALE, sisv);
647             String renderer = ArgParser.getValueFromSubValOrArg(avm, av,
648                     Arg.STRUCTUREIMAGETEXTRENDERER, sisv);
649             String typeS = ArgParser.getValueFromSubValOrArg(avm, av,
650                     Arg.STRUCTUREIMAGETYPE, sisv);
651             if (typeS == null || typeS.length() == 0)
652             {
653               typeS = FileUtils.getExtension(structureImageFile);
654             }
655             TYPE imageType;
656             try
657             {
658               imageType = Enum.valueOf(TYPE.class,
659                       typeS.toUpperCase(Locale.ROOT));
660             } catch (IllegalArgumentException e)
661             {
662               Console.warn("Do not know image format '" + typeS
663                       + "', using PNG");
664               imageType = TYPE.PNG;
665             }
666             BitmapImageSizing userBis = ImageMaker
667                     .parseScaleWidthHeightStrings(scale, width, height);
668             // TODO MAKE THIS VIEWER INDEPENDENT!!
669             switch (StructureViewer.getViewerType())
670             {
671             case JMOL:
672               JalviewStructureDisplayI sview = sv
673                       .getJalviewStructureDisplay();
674               if (sview instanceof AppJmol)
675               {
676                 AppJmol jmol = (AppJmol) sview;
677                 try
678                 {
679                   Console.debug("Rendering image to " + structureImageFile);
680                   jmol.makePDBImage(structureImageFile, imageType, renderer,
681                           userBis);
682                   Console.debug("Finished Rendering image to "
683                           + structureImageFile);
684
685                 } catch (ImageOutputException ioexc)
686                 {
687                   Console.warn("Unexpected error whilst exporting image to "
688                           + structureImageFile, ioexc);
689                 }
690
691               }
692               break;
693             default:
694               Console.warn("Cannot export image for structure viewer "
695                       + sv.getViewerType() + " yet");
696               break;
697             }
698           }
699         }
700       }
701     }
702
703     if (wrap)
704     {
705       AlignFrame af = afMap.get(id);
706       if (af != null)
707       {
708         af.setWrapFormat(wrap, true);
709       }
710     }
711
712     /*
713     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
714     if (doShading)
715     {
716       AlignFrame af = afMap.get(id);
717       for (AlignmentAnnotation aa : af.alignPanel.getAlignment()
718               .findAnnotation(PDBChain.class.getName().toString()))
719       {
720         AnnotationColourGradient acg = new AnnotationColourGradient(aa,
721                 af.alignPanel.av.getGlobalColourScheme(), 0);
722         acg.setSeqAssociated(true);
723         af.changeColour(acg);
724         Console.info("Changed colour " + acg.toString());
725       }
726     }
727     */
728
729     return theseArgsWereParsed;
730   }
731
732   protected void processGroovyScript(String id)
733   {
734     ArgValuesMap avm = argParser.getLinkedArgs(id);
735     AlignFrame af = afMap.get(id);
736
737     if (af == null)
738     {
739       Console.warn("Did not have an alignment window for id=" + id);
740       return;
741     }
742
743     if (avm.containsArg(Arg.GROOVY))
744     {
745       String groovyscript = avm.getValue(Arg.GROOVY);
746       if (groovyscript != null)
747       {
748         // Execute the groovy script after we've done all the rendering stuff
749         // and before any images or figures are generated.
750         Console.info("Executing script " + groovyscript);
751         Jalview.getInstance().executeGroovyScript(groovyscript, af);
752       }
753     }
754   }
755
756   protected boolean processImages(String id)
757   {
758     ArgValuesMap avm = argParser.getLinkedArgs(id);
759     AlignFrame af = afMap.get(id);
760
761     if (af == null)
762     {
763       Console.warn("Did not have an alignment window for id=" + id);
764       return false;
765     }
766
767     if (avm.containsArg(Arg.IMAGE))
768     {
769       for (ArgValue av : avm.getArgValueList(Arg.IMAGE))
770       {
771         String val = av.getValue();
772         SubVals subVal = av.getSubVals();
773         String fileName = subVal.getContent();
774         File file = new File(fileName);
775         String name = af.getName();
776         String renderer = ArgParser.getValueFromSubValOrArg(avm, av,
777                 Arg.TEXTRENDERER, subVal);
778         if (renderer == null)
779           renderer = "text";
780         String type = "png"; // default
781
782         String scale = ArgParser.getValueFromSubValOrArg(avm, av, Arg.SCALE,
783                 subVal);
784         String width = ArgParser.getValueFromSubValOrArg(avm, av, Arg.WIDTH,
785                 subVal);
786         String height = ArgParser.getValueFromSubValOrArg(avm, av,
787                 Arg.HEIGHT, subVal);
788         BitmapImageSizing userBis = ImageMaker
789                 .parseScaleWidthHeightStrings(scale, width, height);
790
791         type = ArgParser.getValueFromSubValOrArg(avm, av, Arg.TYPE, subVal);
792         if (type == null && fileName != null)
793         {
794           for (String ext : new String[] { "svg", "png", "html", "eps" })
795           {
796             if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
797             {
798               type = ext;
799             }
800           }
801         }
802         // for moment we disable JSON export
803         Cache.setPropsAreReadOnly(true);
804         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
805
806         Console.info("Writing " + file);
807         try
808         {
809           switch (type)
810           {
811
812           case "svg":
813             Console.debug("Outputting type '" + type + "' to " + fileName);
814             af.createSVG(file, renderer);
815             break;
816
817           case "png":
818             Console.debug("Outputting type '" + type + "' to " + fileName);
819             af.createPNG(file, null, userBis);
820             break;
821
822           case "html":
823             Console.debug("Outputting type '" + type + "' to " + fileName);
824             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
825             htmlSVG.exportHTML(fileName, renderer);
826             break;
827
828           case "biojs":
829             Console.debug(
830                     "Outputting BioJS MSA Viwer HTML file: " + fileName);
831             try
832             {
833               BioJsHTMLOutput.refreshVersionInfo(
834                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
835             } catch (URISyntaxException e)
836             {
837               e.printStackTrace();
838             }
839             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
840             bjs.exportHTML(fileName);
841             break;
842
843           case "eps":
844             Console.debug("Outputting EPS file: " + fileName);
845             af.createEPS(file, renderer);
846             break;
847
848           case "imagemap":
849             Console.debug("Outputting ImageMap file: " + fileName);
850             af.createImageMap(file, name);
851             break;
852
853           default:
854             Console.warn(Arg.IMAGE.argString() + " type '" + type
855                     + "' not known. Ignoring");
856             break;
857           }
858         } catch (Exception ioex)
859         {
860           Console.warn("Unexpected error during export", ioex);
861         }
862       }
863     }
864     return true;
865   }
866
867   protected boolean processOutput(String id)
868   {
869     ArgValuesMap avm = argParser.getLinkedArgs(id);
870     AlignFrame af = afMap.get(id);
871
872     if (af == null)
873     {
874       Console.warn("Did not have an alignment window for id=" + id);
875       return false;
876     }
877
878     if (avm.containsArg(Arg.OUTPUT))
879     {
880       for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
881       {
882         String val = av.getValue();
883         SubVals subVals = av.getSubVals();
884         String fileName = subVals.getContent();
885         boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
886         File file = new File(fileName);
887         boolean overwrite = ArgParser.getFromSubValArgOrPref(avm,
888                 Arg.OVERWRITE, subVals, null, "OVERWRITE_OUTPUT", false);
889         // backups. Use the Arg.BACKUPS or subval "backups" setting first,
890         // otherwise if headless assume false, if not headless use the user
891         // preference with default true.
892         boolean backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS,
893                 subVals, null,
894                 Platform.isHeadless() ? null : BackupFiles.ENABLED,
895                 !Platform.isHeadless());
896
897         // if backups is not true then --overwrite must be specified
898         if (file.exists() && !(overwrite || backups || stdout))
899         {
900           Console.error("Won't overwrite file '" + fileName + "' without "
901                   + Arg.OVERWRITE.argString() + " or "
902                   + Arg.BACKUPS.argString() + " set");
903           return false;
904         }
905
906         String name = af.getName();
907         String format = ArgParser.getValueFromSubValOrArg(avm, av,
908                 Arg.FORMAT, subVals);
909         FileFormats ffs = FileFormats.getInstance();
910         List<String> validFormats = ffs.getWritableFormats(false);
911
912         FileFormatI ff = null;
913         if (format == null && fileName != null)
914         {
915           FORMAT: for (String fname : validFormats)
916           {
917             FileFormatI tff = ffs.forName(fname);
918             String[] extensions = tff.getExtensions().split(",");
919             for (String ext : extensions)
920             {
921               if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
922               {
923                 ff = tff;
924                 format = ff.getName();
925                 break FORMAT;
926               }
927             }
928           }
929         }
930         if (ff == null && format != null)
931         {
932           ff = ffs.forName(format);
933         }
934         if (ff == null)
935         {
936           if (stdout)
937           {
938             ff = FileFormat.Fasta;
939           }
940           else
941           {
942             StringBuilder validSB = new StringBuilder();
943             for (String f : validFormats)
944             {
945               if (validSB.length() > 0)
946                 validSB.append(", ");
947               validSB.append(f);
948               FileFormatI tff = ffs.forName(f);
949               validSB.append(" (");
950               validSB.append(tff.getExtensions());
951               validSB.append(")");
952             }
953
954             Jalview.exit("No valid format specified for "
955                     + Arg.OUTPUT.argString() + ". Valid formats are "
956                     + validSB.toString() + ".", ExitCode.INVALID_FORMAT);
957             // this return really shouldn't happen
958             return false;
959           }
960         }
961
962         String savedBackupsPreference = Cache
963                 .getDefault(BackupFiles.ENABLED, null);
964         Console.debug("Setting backups to " + backups);
965         Cache.applicationProperties.put(BackupFiles.ENABLED,
966                 Boolean.toString(backups));
967
968         Console.info("Writing " + fileName);
969
970         af.saveAlignment(fileName, ff, stdout);
971         Console.debug("Returning backups to " + savedBackupsPreference);
972         if (savedBackupsPreference != null)
973           Cache.applicationProperties.put(BackupFiles.ENABLED,
974                   savedBackupsPreference);
975         if (af.isSaveAlignmentSuccessful())
976         {
977           Console.debug("Written alignment '" + name + "' in "
978                   + ff.getName() + " format to " + file);
979         }
980         else
981         {
982           Console.warn("Error writing file " + file + " in " + ff.getName()
983                   + " format!");
984         }
985
986       }
987     }
988     return true;
989   }
990
991   private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
992           ArgValue av)
993   {
994     SubVals subVals = av.getSubVals();
995     ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID);
996     SequenceI seq = null;
997     if (subVals == null && idAv == null)
998       return null;
999     if (af == null || af.getCurrentView() == null)
1000     {
1001       return null;
1002     }
1003     AlignmentI al = af.getCurrentView().getAlignment();
1004     if (al == null)
1005     {
1006       return null;
1007     }
1008     if (subVals != null)
1009     {
1010       if (subVals.has(Arg.SEQID.getName()))
1011       {
1012         seq = al.findName(subVals.get(Arg.SEQID.getName()));
1013       }
1014       else if (-1 < subVals.getIndex()
1015               && subVals.getIndex() < al.getSequences().size())
1016       {
1017         seq = al.getSequenceAt(subVals.getIndex());
1018       }
1019     }
1020     if (seq == null && idAv != null)
1021     {
1022       seq = al.findName(idAv.getValue());
1023     }
1024     return seq;
1025   }
1026
1027   public AlignFrame[] getAlignFrames()
1028   {
1029     AlignFrame[] afs = null;
1030     if (afMap != null)
1031     {
1032       afs = (AlignFrame[]) afMap.values().toArray();
1033     }
1034
1035     return afs;
1036   }
1037
1038   public List<StructureViewer> getStructureViewers()
1039   {
1040     List<StructureViewer> svs = null;
1041     if (svMap != null)
1042     {
1043       for (List<StructureViewer> svList : svMap.values())
1044       {
1045         if (svs == null)
1046         {
1047           svs = new ArrayList<>();
1048         }
1049         svs.addAll(svList);
1050       }
1051     }
1052     return svs;
1053   }
1054 }