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