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