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