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