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