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