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