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