056511114c3750464e2904f0f92c0395420ef695
[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             addError("Failed to import and open structure view for file '"
586                     + structureFile + "'.");
587             continue;
588           }
589           try
590           {
591             long tries = 1000;
592             while (structureViewer.isBusy() && tries > 0)
593             {
594               Thread.sleep(25);
595               if (structureViewer.isBusy())
596               {
597                 tries--;
598                 Console.debug(
599                         "Waiting for viewer for " + structureFilepath);
600               }
601             }
602             if (tries == 0 && structureViewer.isBusy())
603             {
604               addWarn("Gave up waiting for structure viewer to load file '"
605                       + structureFile
606                       + "'. Something may have gone wrong.");
607             }
608           } catch (Exception x)
609           {
610             addError("Exception whilst waiting for structure viewer "
611                     + structureFilepath, x);
612             isError = true;
613           }
614
615           // add StructureViewer to svMap list
616           if (svMap == null)
617           {
618             svMap = new HashMap<>();
619           }
620           if (svMap.get(id) == null)
621           {
622             svMap.put(id, new ArrayList<>());
623           }
624           svMap.get(id).add(structureViewer);
625
626           Console.debug(
627                   "Successfully opened viewer for " + structureFilepath);
628
629           if (avm.containsArg(Arg.STRUCTUREIMAGE))
630           {
631             for (ArgValue structureImageArgValue : avm
632                     .getArgValueList(Arg.STRUCTUREIMAGE))
633             {
634               String structureImageFilename = structureImageArgValue
635                       .getValue();
636               if (structureViewer != null && structureImageFilename != null)
637               {
638                 SubVals structureImageSubVals = null;
639                 structureImageSubVals = structureImageArgValue.getSubVals();
640                 File structureImageFile = new File(structureImageFilename);
641                 String width = avm.getValueFromSubValOrArg(
642                         structureImageArgValue, Arg.WIDTH,
643                         structureImageSubVals);
644                 String height = avm.getValueFromSubValOrArg(
645                         structureImageArgValue, Arg.HEIGHT,
646                         structureImageSubVals);
647                 String scale = avm.getValueFromSubValOrArg(
648                         structureImageArgValue, Arg.SCALE,
649                         structureImageSubVals);
650                 String renderer = avm.getValueFromSubValOrArg(
651                         structureImageArgValue, Arg.TEXTRENDERER,
652                         structureImageSubVals);
653                 String typeS = avm.getValueFromSubValOrArg(
654                         structureImageArgValue, Arg.TYPE,
655                         structureImageSubVals);
656                 if (typeS == null || typeS.length() == 0)
657                 {
658                   typeS = FileUtils.getExtension(structureImageFile);
659                 }
660                 TYPE imageType;
661                 try
662                 {
663                   imageType = Enum.valueOf(TYPE.class,
664                           typeS.toUpperCase(Locale.ROOT));
665                 } catch (IllegalArgumentException e)
666                 {
667                   addWarn("Do not know image format '" + typeS
668                           + "', using PNG");
669                   imageType = TYPE.PNG;
670                 }
671                 BitmapImageSizing userBis = ImageMaker
672                         .parseScaleWidthHeightStrings(scale, width, height);
673
674                 String imageColour = avm.getValueFromSubValOrArg(
675                         structureImageArgValue, Arg.IMAGECOLOUR,
676                         structureImageSubVals);
677                 ColourSchemeI originalColourScheme = this
678                         .getColourScheme(af);
679                 this.colourAlignFrame(af, imageColour);
680
681                 List<String> extraCommands = new ArrayList<>();
682
683                 String bgcolour = avm.getValueFromSubValOrArg(
684                         structureImageArgValue, Arg.BGCOLOUR,
685                         structureImageSubVals);
686                 if (bgcolour != null && bgcolour.length() > 0)
687                 {
688                   if (bgcolour.charAt(0) == '#')
689                   {
690                     bgcolour = "[x" + bgcolour.substring(1) + "]";
691                   }
692                   extraCommands.add("background " + bgcolour);
693                 }
694
695                 // TODO MAKE THIS VIEWER INDEPENDENT!!
696                 switch (StructureViewer.getViewerType())
697                 {
698                 case JMOL:
699                   JalviewStructureDisplayI sview = structureViewer
700                           .getJalviewStructureDisplay();
701                   if (sview instanceof AppJmol)
702                   {
703                     AppJmol jmol = (AppJmol) sview;
704                     try
705                     {
706                       boolean success = this.checksBeforeWritingToFile(avm,
707                               subVals, false, structureImageFilename,
708                               "structure image", isError);
709                       if (!success)
710                       {
711                         continue;
712                       }
713
714                       Console.debug(
715                               "Rendering image to " + structureImageFile);
716                       jmol.makePDBImage(structureImageFile, imageType,
717                               renderer, userBis, extraCommands);
718                       Console.debug("Finished Rendering image to "
719                               + structureImageFile);
720
721                     } catch (ImageOutputException ioexc)
722                     {
723                       addError("Unexpected error whilst exporting image to "
724                               + structureImageFile, ioexc);
725                       isError = true;
726                       continue;
727                     }
728
729                   }
730                   break;
731                 default:
732                   addWarn("Cannot export image for structure viewer "
733                           + structureViewer.getViewerType() + " yet");
734                   continue;
735                 }
736                 this.colourAlignFrame(af, originalColourScheme);
737               }
738             }
739           }
740         }
741       }
742     }
743
744     if (wrap)
745     {
746       AlignFrame af = afMap.get(id);
747       if (af != null)
748       {
749         af.setWrapFormat(wrap, true);
750       }
751     }
752
753     /*
754     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
755     if (doShading)
756     {
757       AlignFrame af = afMap.get(id);
758       for (AlignmentAnnotation aa : af.alignPanel.getAlignment()
759               .findAnnotation(PDBChain.class.getName().toString()))
760       {
761         AnnotationColourGradient acg = new AnnotationColourGradient(aa,
762                 af.alignPanel.av.getGlobalColourScheme(), 0);
763         acg.setSeqAssociated(true);
764         af.changeColour(acg);
765         Console.info("Changed colour " + acg.toString());
766       }
767     }
768     */
769
770     return theseArgsWereParsed && !isError;
771   }
772
773   protected void processGroovyScript(String id)
774   {
775     ArgValuesMap avm = argParser.getLinkedArgs(id);
776     AlignFrame af = afMap.get(id);
777
778     if (af == null)
779     {
780       addWarn("Did not have an alignment window for id=" + id);
781       return;
782     }
783
784     if (avm.containsArg(Arg.GROOVY))
785     {
786       String groovyscript = avm.getValue(Arg.GROOVY);
787       if (groovyscript != null)
788       {
789         // Execute the groovy script after we've done all the rendering stuff
790         // and before any images or figures are generated.
791         Console.info("Executing script " + groovyscript);
792         Jalview.getInstance().executeGroovyScript(groovyscript, af);
793       }
794     }
795   }
796
797   protected boolean processImages(String id)
798   {
799     ArgValuesMap avm = argParser.getLinkedArgs(id);
800     AlignFrame af = afMap.get(id);
801
802     if (af == null)
803     {
804       addWarn("Did not have an alignment window for id=" + id);
805       return false;
806     }
807
808     Boolean isError = Boolean.valueOf(false);
809     if (avm.containsArg(Arg.IMAGE))
810     {
811       for (ArgValue imageAv : avm.getArgValueList(Arg.IMAGE))
812       {
813         String val = imageAv.getValue();
814         SubVals imageSubVals = imageAv.getSubVals();
815         String fileName = imageSubVals.getContent();
816         File file = new File(fileName);
817         String name = af.getName();
818         String renderer = avm.getValueFromSubValOrArg(imageAv,
819                 Arg.TEXTRENDERER, imageSubVals);
820         if (renderer == null)
821           renderer = "text";
822         String type = "png"; // default
823
824         String scale = avm.getValueFromSubValOrArg(imageAv, Arg.SCALE,
825                 imageSubVals);
826         String width = avm.getValueFromSubValOrArg(imageAv, Arg.WIDTH,
827                 imageSubVals);
828         String height = avm.getValueFromSubValOrArg(imageAv, Arg.HEIGHT,
829                 imageSubVals);
830         BitmapImageSizing userBis = ImageMaker
831                 .parseScaleWidthHeightStrings(scale, width, height);
832
833         type = avm.getValueFromSubValOrArg(imageAv, Arg.TYPE, imageSubVals);
834         if (type == null && fileName != null)
835         {
836           for (String ext : new String[] { "svg", "png", "html", "eps" })
837           {
838             if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
839             {
840               type = ext;
841             }
842           }
843         }
844         // for moment we disable JSON export
845         Cache.setPropsAreReadOnly(true);
846         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
847
848         String imageColour = avm.getValueFromSubValOrArg(imageAv,
849                 Arg.IMAGECOLOUR, imageSubVals);
850         ColourSchemeI originalColourScheme = this.getColourScheme(af);
851         this.colourAlignFrame(af, imageColour);
852
853         Console.info("Writing " + file);
854
855         boolean success = checksBeforeWritingToFile(avm, imageSubVals,
856                 false, fileName, "image", isError);
857         if (!success)
858         {
859           continue;
860         }
861
862         try
863         {
864           switch (type)
865           {
866
867           case "svg":
868             Console.debug("Outputting type '" + type + "' to " + fileName);
869             af.createSVG(file, renderer);
870             break;
871
872           case "png":
873             Console.debug("Outputting type '" + type + "' to " + fileName);
874             af.createPNG(file, null, userBis);
875             break;
876
877           case "html":
878             Console.debug("Outputting type '" + type + "' to " + fileName);
879             HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
880             htmlSVG.exportHTML(fileName, renderer);
881             break;
882
883           case "biojs":
884             Console.debug(
885                     "Outputting BioJS MSA Viwer HTML file: " + fileName);
886             try
887             {
888               BioJsHTMLOutput.refreshVersionInfo(
889                       BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
890             } catch (URISyntaxException e)
891             {
892               e.printStackTrace();
893             }
894             BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
895             bjs.exportHTML(fileName);
896             break;
897
898           case "eps":
899             Console.debug("Outputting EPS file: " + fileName);
900             af.createEPS(file, renderer);
901             break;
902
903           case "imagemap":
904             Console.debug("Outputting ImageMap file: " + fileName);
905             af.createImageMap(file, name);
906             break;
907
908           default:
909             addWarn(Arg.IMAGE.argString() + " type '" + type
910                     + "' not known. Ignoring");
911             break;
912           }
913         } catch (Exception ioex)
914         {
915           addError("Unexpected error during export to '" + fileName + "'",
916                   ioex);
917           isError = true;
918         }
919
920         this.colourAlignFrame(af, originalColourScheme);
921       }
922     }
923     return !isError;
924   }
925
926   protected boolean processOutput(String id)
927   {
928     ArgValuesMap avm = argParser.getLinkedArgs(id);
929     AlignFrame af = afMap.get(id);
930
931     if (af == null)
932     {
933       addWarn("Did not have an alignment window for id=" + id);
934       return false;
935     }
936
937     Boolean isError = Boolean.valueOf(false);
938
939     if (avm.containsArg(Arg.OUTPUT))
940     {
941       for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
942       {
943         String val = av.getValue();
944         SubVals subVals = av.getSubVals();
945         String fileName = subVals.getContent();
946         boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
947         File file = new File(fileName);
948
949         String name = af.getName();
950         String format = avm.getValueFromSubValOrArg(av, Arg.FORMAT,
951                 subVals);
952         FileFormats ffs = FileFormats.getInstance();
953         List<String> validFormats = ffs.getWritableFormats(false);
954
955         FileFormatI ff = null;
956         if (format == null && fileName != null)
957         {
958           FORMAT: for (String fname : validFormats)
959           {
960             FileFormatI tff = ffs.forName(fname);
961             String[] extensions = tff.getExtensions().split(",");
962             for (String ext : extensions)
963             {
964               if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
965               {
966                 ff = tff;
967                 format = ff.getName();
968                 break FORMAT;
969               }
970             }
971           }
972         }
973         if (ff == null && format != null)
974         {
975           ff = ffs.forName(format);
976         }
977         if (ff == null)
978         {
979           if (stdout)
980           {
981             ff = FileFormat.Fasta;
982           }
983           else
984           {
985             StringBuilder validSB = new StringBuilder();
986             for (String f : validFormats)
987             {
988               if (validSB.length() > 0)
989                 validSB.append(", ");
990               validSB.append(f);
991               FileFormatI tff = ffs.forName(f);
992               validSB.append(" (");
993               validSB.append(tff.getExtensions());
994               validSB.append(")");
995             }
996
997             addError("No valid format specified for "
998                     + Arg.OUTPUT.argString() + ". Valid formats are "
999                     + validSB.toString() + ".");
1000             continue;
1001           }
1002         }
1003
1004         boolean success = checksBeforeWritingToFile(avm, subVals, true,
1005                 fileName, ff.getName(), isError);
1006         if (!success)
1007         {
1008           continue;
1009         }
1010
1011         boolean backups = avm.getFromSubValArgOrPref(Arg.BACKUPS, subVals,
1012                 null, Platform.isHeadless() ? null : BackupFiles.ENABLED,
1013                 !Platform.isHeadless());
1014
1015         Console.info("Writing " + fileName);
1016
1017         af.saveAlignment(fileName, ff, stdout, backups);
1018         if (af.isSaveAlignmentSuccessful())
1019         {
1020           Console.debug("Written alignment '" + name + "' in "
1021                   + ff.getName() + " format to '" + file + "'");
1022         }
1023         else
1024         {
1025           addError("Error writing file '" + file + "' in " + ff.getName()
1026                   + " format!");
1027           isError = true;
1028           continue;
1029         }
1030
1031       }
1032     }
1033     return !isError;
1034   }
1035
1036   private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
1037           ArgValue av)
1038   {
1039     SubVals subVals = av.getSubVals();
1040     ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID, true);
1041     SequenceI seq = null;
1042     if (subVals == null && idAv == null)
1043       return null;
1044     if (af == null || af.getCurrentView() == null)
1045     {
1046       return null;
1047     }
1048     AlignmentI al = af.getCurrentView().getAlignment();
1049     if (al == null)
1050     {
1051       return null;
1052     }
1053     if (subVals != null)
1054     {
1055       if (subVals.has(Arg.SEQID.getName()))
1056       {
1057         seq = al.findName(subVals.get(Arg.SEQID.getName()));
1058       }
1059       else if (-1 < subVals.getIndex()
1060               && subVals.getIndex() < al.getSequences().size())
1061       {
1062         seq = al.getSequenceAt(subVals.getIndex());
1063       }
1064     }
1065     if (seq == null && idAv != null)
1066     {
1067       seq = al.findName(idAv.getValue());
1068     }
1069     return seq;
1070   }
1071
1072   public AlignFrame[] getAlignFrames()
1073   {
1074     AlignFrame[] afs = null;
1075     if (afMap != null)
1076     {
1077       afs = (AlignFrame[]) afMap.values().toArray();
1078     }
1079
1080     return afs;
1081   }
1082
1083   public List<StructureViewer> getStructureViewers()
1084   {
1085     List<StructureViewer> svs = null;
1086     if (svMap != null)
1087     {
1088       for (List<StructureViewer> svList : svMap.values())
1089       {
1090         if (svs == null)
1091         {
1092           svs = new ArrayList<>();
1093         }
1094         svs.addAll(svList);
1095       }
1096     }
1097     return svs;
1098   }
1099
1100   private void colourAlignFrame(AlignFrame af, String colour)
1101   {
1102     // use string "none" to remove colour scheme
1103     if (colour != null && "" != colour)
1104     {
1105       ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
1106               af.getViewport(), af.getViewport().getAlignment(), colour);
1107       if (cs == null && !StringUtils.equalsIgnoreCase(colour, "none"))
1108       {
1109         addWarn("Couldn't parse '" + colour + "' as a colourscheme.");
1110       }
1111       else
1112       {
1113         Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
1114         colourAlignFrame(af, cs);
1115       }
1116     }
1117   }
1118
1119   private void colourAlignFrame(AlignFrame af, ColourSchemeI cs)
1120   {
1121     // Note that cs == null removes colour scheme from af
1122     af.changeColour(cs);
1123   }
1124
1125   private ColourSchemeI getColourScheme(AlignFrame af)
1126   {
1127     return af.getViewport().getGlobalColourScheme();
1128   }
1129
1130   private void addInfo(String errorMessage)
1131   {
1132     Console.info(errorMessage);
1133     errors.add(errorMessage);
1134   }
1135
1136   private void addWarn(String errorMessage)
1137   {
1138     Console.warn(errorMessage);
1139     errors.add(errorMessage);
1140   }
1141
1142   private void addError(String errorMessage)
1143   {
1144     addError(errorMessage, null);
1145   }
1146
1147   private void addError(String errorMessage, Exception e)
1148   {
1149     Console.error(errorMessage, e);
1150     errors.add(errorMessage);
1151   }
1152
1153   private boolean checksBeforeWritingToFile(ArgValuesMap avm,
1154           SubVals subVal, boolean includeBackups, String filename,
1155           String adjective, Boolean isError)
1156   {
1157     File file = new File(filename);
1158
1159     boolean overwrite = avm.getFromSubValArgOrPref(Arg.OVERWRITE, subVal,
1160             null, "OVERWRITE_OUTPUT", false);
1161     boolean stdout = false;
1162     boolean backups = false;
1163     if (includeBackups)
1164     {
1165       stdout = ArgParser.STDOUTFILENAME.equals(filename);
1166       // backups. Use the Arg.BACKUPS or subval "backups" setting first,
1167       // otherwise if headless assume false, if not headless use the user
1168       // preference with default true.
1169       backups = avm.getFromSubValArgOrPref(Arg.BACKUPS, subVal, null,
1170               Platform.isHeadless() ? null : BackupFiles.ENABLED,
1171               !Platform.isHeadless());
1172     }
1173
1174     if (file.exists() && !(overwrite || backups || stdout))
1175     {
1176       addWarn("Won't overwrite file '" + filename + "' without "
1177               + Arg.OVERWRITE.argString()
1178               + (includeBackups ? " or " + Arg.BACKUPS.argString() : "")
1179               + " set");
1180       return false;
1181     }
1182
1183     boolean mkdirs = avm.getFromSubValArgOrPref(Arg.MKDIRS, subVal, null,
1184             "MKDIRS_OUTPUT", false);
1185
1186     if (!FileUtils.checkParentDir(file, mkdirs))
1187     {
1188       addError("Directory '"
1189               + FileUtils.getParentDir(file).getAbsolutePath()
1190               + "' does not exist for " + adjective + " file '" + filename
1191               + "'."
1192               + (mkdirs ? "" : "  Try using " + Arg.MKDIRS.argString()));
1193       isError = true;
1194       return false;
1195     }
1196
1197     return true;
1198   }
1199
1200   public List<String> getErrors()
1201   {
1202     return errors;
1203   }
1204
1205   public String errorsToString()
1206   {
1207     StringBuilder sb = new StringBuilder();
1208     for (String error : errors)
1209     {
1210       if (sb.length() > 0)
1211         sb.append("\n");
1212       sb.append("- " + error);
1213     }
1214     return sb.toString();
1215   }
1216 }