JAL-629 Help and code with threads
[jalview.git] / src / jalview / bin / Commands.java
index dafc342..8277d92 100644 (file)
@@ -2,6 +2,7 @@ package jalview.bin;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -11,6 +12,16 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import jalview.analysis.AlignmentUtils;
 import jalview.bin.argparser.Arg;
@@ -31,13 +42,17 @@ import jalview.gui.StructureChooser;
 import jalview.gui.StructureViewer;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.AppletFormatAdapter;
+import jalview.io.BackupFiles;
+import jalview.io.BioJsHTMLOutput;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
 import jalview.io.FileFormatException;
 import jalview.io.FileFormatI;
+import jalview.io.FileFormats;
 import jalview.io.FileLoader;
 import jalview.io.HtmlSvgOutput;
 import jalview.io.IdentifyFile;
+import jalview.io.NewickFile;
 import jalview.structure.StructureImportSettings.TFType;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.HttpUtils;
@@ -58,6 +73,11 @@ public class Commands
 
   private boolean argsWereParsed = false;
 
+  private ThreadPoolExecutor executor = null;
+
+  // have we opened a file?
+  boolean opened = false;
+
   public Commands(ArgParser argparser, boolean headless)
   {
     this(Desktop.instance, argparser, headless);
@@ -69,6 +89,61 @@ public class Commands
     headless = h;
     desktop = d;
     afMap = new HashMap<String, AlignFrame>();
+
+    int threads = 3;
+    if (argParser.getBootstrapArgs().contains(Arg.THREADS))
+    {
+      String threadsString = argParser.getBootstrapArgs().get(Arg.THREADS);
+      try
+      {
+        threads = Integer.parseInt(threadsString);
+      } catch (NumberFormatException e)
+      {
+        Console.debug("Could not parse number of threads from '"
+                + Arg.THREADS.argString() + "=" + threadsString
+                + "', fallback to 1.");
+        threads = 1;
+      }
+    }
+
+    BlockingQueue<Runnable> bq = new ArrayBlockingQueue<>(1);
+    if (threads > 0)
+    {
+      // executor = Executors.newFixedThreadPool(threads);
+      executor = new ThreadPoolExecutor(threads, threads, 600,
+              TimeUnit.SECONDS, bq);
+    }
+    else
+    {
+      // executor = Executors.newCachedThreadPool();
+      executor = new ThreadPoolExecutor(threads, Integer.MAX_VALUE, 600,
+              TimeUnit.SECONDS, null);
+    }
+
+    // set a rejectedExecution to block and resubmit.
+    executor.setRejectedExecutionHandler(new RejectedExecutionHandler()
+    {
+      @Override
+      public void rejectedExecution(Runnable r, ThreadPoolExecutor tpe)
+      {
+        try
+        {
+          // block until there's room
+          tpe.getQueue().put(r);
+          // check afterwards and throw if pool shutdown
+          if (tpe.isShutdown())
+          {
+            throw new RejectedExecutionException(
+                    "Task " + r + " rejected from " + tpe);
+          }
+        } catch (InterruptedException e)
+        {
+          Thread.currentThread().interrupt();
+          throw new RejectedExecutionException("Producer interrupted", e);
+        }
+      }
+    });
+
     if (argparser != null)
     {
       processArgs(argparser, headless);
@@ -79,44 +154,91 @@ public class Commands
   {
     argParser = argparser;
     headless = h;
-    boolean theseArgsWereParsed = false;
+    AtomicBoolean theseArgsWereParsed = new AtomicBoolean(false);
 
-    if (argParser != null && argParser.linkedIds() != null)
+    if (argParser != null && argParser.getLinkedIds() != null)
     {
-      for (String id : argParser.linkedIds())
+      long progress = -1;
+      boolean progressBarSet = false;
+      opened = false;
+      if (!headless && desktop != null)
       {
-        ArgValuesMap avm = argParser.linkedArgs(id);
-        theseArgsWereParsed = true;
-        if (id == null)
+        desktop.setProgressBar(
+                MessageManager
+                        .getString("status.processing_commandline_args"),
+                progress = System.currentTimeMillis());
+        progressBarSet = true;
+      }
+      List<Future<Void>> futures = new ArrayList<>();
+      for (String id : argParser.getLinkedIds())
+      {
+
+        Callable<Void> process = () -> {
+          ArgValuesMap avm = argParser.getLinkedArgs(id);
+          theseArgsWereParsed.set(true);
+          theseArgsWereParsed.compareAndSet(true, processLinked(id)); // &=
+          processGroovyScript(id);
+          boolean processLinkedOkay = theseArgsWereParsed.get();
+          theseArgsWereParsed.compareAndSet(true, processImages(id)); // &=
+          if (processLinkedOkay)
+            theseArgsWereParsed.compareAndSet(true, processOutput(id)); // &=
+
+          // close ap
+          if (avm.getBoolean(Arg.CLOSE))
+          {
+            AlignFrame af = afMap.get(id);
+            if (af != null)
+            {
+              af.closeMenuItem_actionPerformed(true);
+            }
+            afMap.remove(id);
+          }
+          return null;
+        };
+
+        futures.add(executor.submit(process));
+        Console.debug(
+                "Running " + executor.getActiveCount() + " processes.");
+      }
+
+      if (!opened) // first=true means nothing opened
+      {
+        if (headless)
         {
-          theseArgsWereParsed &= processUnlinked(id);
+          Jalview.exit("Did not open any files in headless mode", 1);
         }
         else
         {
-          theseArgsWereParsed &= processLinked(id);
+          Console.warn("No more files to open");
         }
-        theseArgsWereParsed &= processImages(id);
+      }
+      if (progressBarSet && desktop != null)
+      {
+        desktop.setProgressBar(null, progress);
+      }
 
-        // close ap
-        if (avm.getBoolean(Arg.CLOSE))
+      // wait for all to process
+      for (Future<Void> f : futures)
+      {
+        try
         {
-          AlignFrame af = afMap.get(id);
-          if (af != null)
-          {
-            af.closeMenuItem_actionPerformed(true);
-          }
+          f.get();
+        } catch (InterruptedException e1)
+        {
+          Console.warn("Processes were interrupted.", e1);
+        } catch (ExecutionException e2)
+        {
+          Console.warn("Processes failed.", e2);
         }
-
       }
-
     }
-    if (argParser.getBool(Arg.QUIT))
+    if (argParser.getBootstrapArgs().getBoolean(Arg.QUIT))
     {
       Jalview.getInstance().quit();
       return true;
     }
     // carry on with jalview.bin.Jalview
-    argsWereParsed = theseArgsWereParsed;
+    argsWereParsed |= theseArgsWereParsed.get();
     return argsWereParsed;
   }
 
@@ -130,15 +252,10 @@ public class Commands
     return argsWereParsed;
   }
 
-  protected boolean processUnlinked(String id)
-  {
-    return processLinked(id);
-  }
-
   protected boolean processLinked(String id)
   {
     boolean theseArgsWereParsed = false;
-    ArgValuesMap avm = argParser.linkedArgs(id);
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
     if (avm == null)
       return true;
 
@@ -152,10 +269,6 @@ public class Commands
     if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
     {
       commandArgsProvided = true;
-      long progress = -1;
-
-      boolean first = true;
-      boolean progressBarSet = false;
       AlignFrame af;
       // Combine the APPEND and OPEN files into one list, along with whether it
       // was APPEND or OPEN
@@ -167,23 +280,12 @@ public class Commands
       for (ArgValue av : openAvList)
       {
         Arg a = av.getArg();
+        SubVals sv = av.getSubVals();
         String openFile = av.getValue();
         if (openFile == null)
           continue;
 
         theseArgsWereParsed = true;
-        if (first)
-        {
-          first = false;
-          if (!headless && desktop != null)
-          {
-            desktop.setProgressBar(
-                    MessageManager.getString(
-                            "status.processing_commandline_args"),
-                    progress = System.currentTimeMillis());
-            progressBarSet = true;
-          }
-        }
 
         if (!Platform.isJS())
         /**
@@ -214,24 +316,17 @@ public class Commands
         }
 
         af = afMap.get(id);
+        // When to open a new AlignFrame
         if (af == null || "true".equals(av.getSubVal("new"))
                 || a == Arg.OPEN || format == FileFormat.Jalview)
         {
-          /*
-           * this approach isn't working yet // get default annotations before opening
-           * AlignFrame if (m.get(Arg.SSANNOTATIONS) != null) {
-           * Console.debug("##### SSANNOTATIONS=" + m.get(Arg.SSANNOTATIONS).getBoolean());
-           * } if (m.get(Arg.NOTEMPFAC) != null) { Console.debug( "##### NOTEMPFAC=" +
-           * m.get(Arg.NOTEMPFAC).getBoolean()); } boolean showSecondaryStructure =
-           * (m.get(Arg.SSANNOTATIONS) != null) ? m.get(Arg.SSANNOTATIONS).getBoolean() :
-           * false; boolean showTemperatureFactor = (m.get(Arg.NOTEMPFAC) != null) ?
-           * !m.get(Arg.NOTEMPFAC).getBoolean() : false; Console.debug("##### tempfac=" +
-           * showTemperatureFactor + ", showSS=" + showSecondaryStructure);
-           * StructureSelectionManager ssm = StructureSelectionManager
-           * .getStructureSelectionManager(Desktop.instance); if (ssm != null) {
-           * ssm.setAddTempFacAnnot(showTemperatureFactor);
-           * ssm.setProcessSecondaryStructure(showSecondaryStructure); }
-           */
+          opened = true;
+
+          if (a == Arg.OPEN)
+          {
+            Jalview.testoutput(argParser, Arg.OPEN, "examples/uniref50.fa",
+                    openFile);
+          }
 
           Console.debug(
                   "Opening '" + openFile + "' in new alignment frame");
@@ -239,42 +334,92 @@ public class Commands
 
           af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
                   format);
-          boolean showAnnotations = ArgParser.getFromSubValArgOrPref(avm,
-                  Arg.ANNOTATIONS, av.getSubVals(), null,
-                  "SHOW_ANNOTATIONS", true);
-          af.setAnnotationsVisibility(showAnnotations, false, true);
 
           // wrap alignment?
-          if (avm.getBoolean(Arg.WRAP))
+          boolean wrap = ArgParser.getFromSubValArgOrPref(avm, Arg.WRAP, sv,
+                  null, "WRAP_ALIGNMENT", false);
+          af.getCurrentView().setWrapAlignment(wrap);
+
+          // colour alignment?
+          String colour = ArgParser.getFromSubValArgOrPref(avm, av,
+                  Arg.COLOUR, sv, null, "DEFAULT_COLOUR_PROT", "");
+          if ("" != colour)
           {
-            af.getCurrentView().setWrapAlignment(true);
+            af.changeColour_actionPerformed(colour);
+            Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
           }
 
-          // colour aligment?
-          if (avm.containsArg(Arg.COLOUR))
+          // Change alignment frame title
+          String title = ArgParser.getFromSubValArgOrPref(avm, av,
+                  Arg.TITLE, sv, null, null, null);
+          if (title != null)
           {
-            af.changeColour_actionPerformed(avm.getValue(Arg.COLOUR));
+            af.setTitle(title);
+            Jalview.testoutput(argParser, Arg.TITLE, "test title", title);
           }
 
-          // change alignment frame title
-          if (avm.containsArg(Arg.TITLE))
-            af.setTitle(avm.getValue(Arg.TITLE));
+          // Add features
+          String featuresfile = ArgParser.getValueFromSubValOrArg(avm, av,
+                  Arg.FEATURES, sv);
+          if (featuresfile != null)
+          {
+            af.parseFeaturesFile(featuresfile,
+                    AppletFormatAdapter.checkProtocol(featuresfile));
+            Jalview.testoutput(argParser, Arg.FEATURES,
+                    "examples/testdata/plantfdx.features", featuresfile);
+          }
 
-          // show secondary structure annotations?
-          boolean showSSAnnotations = ArgParser.getFromSubValArgOrPref(avm,
-                  Arg.SSANNOTATIONS, av.getSubVals(), null,
-                  "STRUCT_FROM_PDB", true);
-          if (avm.getBoolean(Arg.SSANNOTATIONS))
+          // Add annotations from file
+          String annotationsfile = ArgParser.getValueFromSubValOrArg(avm,
+                  av, Arg.ANNOTATIONS, sv);
+          if (annotationsfile != null)
           {
-            af.setAnnotationsVisibility(showSSAnnotations, true, false);
-            /*
-            AlignmentUtils.showOrHideSequenceAnnotations(
-                    af.getCurrentView().getAlignment(),
-                    Collections.singleton("Secondary Structure"), null,
-                    false, false);
-             */
+            af.loadJalviewDataFile(annotationsfile, null, null, null);
+            Jalview.testoutput(argParser, Arg.ANNOTATIONS,
+                    "examples/testdata/plantfdx.annotations",
+                    annotationsfile);
           }
 
+          // Set or clear the sortbytree flag
+          boolean sortbytree = ArgParser.getBoolFromSubValOrArg(avm,
+                  Arg.SORTBYTREE, sv);
+          if (sortbytree)
+          {
+            af.getViewport().setSortByTree(true);
+            Jalview.testoutput(argParser, Arg.SORTBYTREE);
+          }
+
+          // Load tree from file
+          String treefile = ArgParser.getValueFromSubValOrArg(avm, av,
+                  Arg.TREE, sv);
+          if (treefile != null)
+          {
+            try
+            {
+              NewickFile nf = new NewickFile(treefile,
+                      AppletFormatAdapter.checkProtocol(treefile));
+              af.getViewport().setCurrentTree(
+                      af.showNewickTree(nf, treefile).getTree());
+              Jalview.testoutput(argParser, Arg.TREE,
+                      "examples/testdata/uniref50_test_tree", treefile);
+            } catch (IOException e)
+            {
+              Console.warn("Couldn't add tree " + treefile, e);
+            }
+          }
+
+          // Show secondary structure annotations?
+          boolean showSSAnnotations = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.SHOWSSANNOTATIONS, av.getSubVals(), null,
+                  "STRUCT_FROM_PDB", true);
+          af.setAnnotationsVisibility(showSSAnnotations, true, false);
+
+          // Show sequence annotations?
+          boolean showAnnotations = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.SHOWANNOTATIONS, av.getSubVals(), null,
+                  "SHOW_ANNOTATIONS", true);
+          af.setAnnotationsVisibility(showAnnotations, false, true);
+
           // show temperature factor annotations?
           if (avm.getBoolean(Arg.NOTEMPFAC))
           {
@@ -286,33 +431,6 @@ public class Commands
                     af.getCurrentView().getAlignment(), hideThese, null,
                     false, false);
           }
-          else
-          /*
-           * comment out hacky approach up to here and add this line: if
-           * (showTemperatureFactor)
-           */
-          {
-            /*
-            if (avm.containsArg(Arg.TEMPFAC_LABEL))
-            {
-              AlignmentAnnotation aa = AlignmentUtils
-                      .getFirstSequenceAnnotationOfType(
-                              af.getCurrentView().getAlignment(),
-                              AlignmentAnnotation.LINE_GRAPH);
-              String label = avm.getValue(Arg.TEMPFAC_LABEL);
-              if (aa != null)
-              {
-                aa.label = label;
-              }
-              else
-              {
-                Console.info(
-                        "Could not find annotation to apply tempfac_label '"
-                                + label);
-              }
-            }
-            */
-          }
 
           // store the AlignFrame for this id
           afMap.put(id, af);
@@ -331,25 +449,17 @@ public class Commands
         {
           Console.debug(
                   "Opening '" + openFile + "' in existing alignment frame");
-          af.getCurrentView().addFile(new File(openFile), format, false);
+          DataSourceType dst = HttpUtils.startsWithHttpOrHttps(openFile)
+                  ? DataSourceType.URL
+                  : DataSourceType.FILE;
+          FileLoader fileLoader = new FileLoader(!headless);
+          fileLoader.LoadFile(af.getCurrentView(), openFile, dst, null,
+                  false);
         }
 
         Console.debug("Command " + Arg.APPEND + " executed successfully!");
 
       }
-      if (first) // first=true means nothing opened
-      {
-        if (headless)
-        {
-          Jalview.exit("Could not open any files in headless mode", 1);
-        }
-        else
-        {
-          Console.warn("No more files to open");
-        }
-      }
-      if (progressBarSet && desktop != null)
-        desktop.setProgressBar(null, progress);
 
     }
 
@@ -364,7 +474,7 @@ public class Commands
         {
           String val = av.getValue();
           SubVals subVals = av.getSubVals();
-          SequenceI seq = getSpecifiedSequence(af, subVals);
+          SequenceI seq = getSpecifiedSequence(af, avm, av);
           if (seq == null)
           {
             // Could not find sequence from subId, let's assume the first
@@ -444,17 +554,14 @@ public class Commands
           String structureFilepath = structureFile.getAbsolutePath();
 
           // get PAEMATRIX file and label from subvals or Arg.PAEMATRIX
-          String paeFilepath = subVals.getWithSubstitutions(argParser, id,
-                  "paematrix");
-          String paeLabel = subVals.get("paelabel");
-          ArgValue paeAv = getArgAssociatedWithStructure(Arg.PAEMATRIX, avm,
-                  af, structureFilepath);
-          if (paeFilepath == null && paeAv != null)
+          String paeFilepath = ArgParser
+                  .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
+                          Arg.PAEMATRIX, Position.AFTER, av, subVals, null,
+                          null, null);
+          if (paeFilepath != null)
           {
-            SubVals sv = paeAv.getSubVals();
-            File paeFile = new File(sv.getContent());
+            File paeFile = new File(paeFilepath);
 
-            paeLabel = sv.get("label");
             try
             {
               paeFilepath = paeFile.getCanonicalPath();
@@ -468,21 +575,28 @@ public class Commands
 
           // showing annotations from structure file or not
           boolean ssFromStructure = ArgParser.getFromSubValArgOrPref(avm,
-                  Arg.SSANNOTATIONS, subVals, null, "STRUCT_FROM_PDB",
+                  Arg.SHOWSSANNOTATIONS, subVals, null, "STRUCT_FROM_PDB",
                   true);
 
           // get TEMPFAC type from subvals or Arg.TEMPFAC in case user Adds
           // reference annotations
+          String tftString = ArgParser
+                  .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
+                          Arg.TEMPFAC, Position.AFTER, av, subVals, null,
+                          null, null);
+          boolean notempfac = ArgParser.getBoolFromSubValOrArg(avm,
+                  Arg.NOTEMPFAC, subVals);
+          TFType tft = notempfac ? null : TFType.DEFAULT;
+          /*
           String tftString = subVals.get("tempfac");
-          TFType tft = avm.getBoolean(Arg.NOTEMPFAC) ? null
-                  : TFType.DEFAULT;
           ArgValue tftAv = getArgAssociatedWithStructure(Arg.TEMPFAC, avm,
                   af, structureFilepath);
           if (tftString == null && tftAv != null)
           {
             tftString = tftAv.getSubVals().getContent();
           }
-          if (tftString != null)
+          */
+          if (tftString != null && !notempfac)
           {
             // get kind of temperature factor annotation
             try
@@ -510,8 +624,8 @@ public class Commands
           }
 
           String sViewer = ArgParser.getFromSubValArgOrPref(avm,
-                  Arg.STRUCTUREVIEWER, Position.AFTER, av, subVals,
-                  "viewer", null, "jmol");
+                  Arg.STRUCTUREVIEWER, Position.AFTER, av, subVals, null,
+                  null, "jmol");
           ViewerType viewerType = null;
           if (!"none".equals(sViewer))
           {
@@ -527,8 +641,9 @@ public class Commands
             }
           }
 
-          boolean addTempFac = tft != null
-                  || Cache.getDefault("ADD_TEMPFACT_ANN", false);
+          boolean addTempFac = notempfac ? false
+                  : ((tft != null)
+                          || Cache.getDefault("ADD_TEMPFACT_ANN", false));
 
           // TODO use ssFromStructure
           StructureChooser.openStructureFileForSequence(null, null, ap, seq,
@@ -558,9 +673,33 @@ public class Commands
     return theseArgsWereParsed;
   }
 
+  protected void processGroovyScript(String id)
+  {
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
+    AlignFrame af = afMap.get(id);
+
+    if (af == null)
+    {
+      Console.warn("Did not have an alignment window for id=" + id);
+      return;
+    }
+
+    if (avm.containsArg(Arg.GROOVY))
+    {
+      String groovyscript = avm.getValue(Arg.GROOVY);
+      if (groovyscript != null)
+      {
+        // Execute the groovy script after we've done all the rendering stuff
+        // and before any images or figures are generated.
+        Console.info("Executing script " + groovyscript);
+        Jalview.getInstance().executeGroovyScript(groovyscript, af);
+      }
+    }
+  }
+
   protected boolean processImages(String id)
   {
-    ArgValuesMap avm = argParser.linkedArgs(id);
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
     AlignFrame af = afMap.get(id);
 
     if (af == null)
@@ -575,16 +714,62 @@ public class Commands
       {
         String val = av.getValue();
         SubVals subVal = av.getSubVals();
-        String type = "png"; // default
         String fileName = subVal.getContent();
         File file = new File(fileName);
-        if (subVal.has("type"))
+        String name = af.getName();
+        String renderer = ArgParser.getValueFromSubValOrArg(avm, av,
+                Arg.TEXTRENDERER, subVal);
+        if (renderer == null)
+          renderer = "text";
+        String type = "png"; // default
+
+        float bitmapscale = 0.0f;
+        int bitmapwidth = 0;
+        int bitmapheight = 0;
+        String scale = ArgParser.getValueFromSubValOrArg(avm, av, Arg.SCALE,
+                subVal);
+        if (scale != null)
         {
-          type = subVal.get("type");
+          try
+          {
+            bitmapscale = Float.parseFloat(scale);
+          } catch (NumberFormatException e)
+          {
+            Console.warn("Did not understand scale '" + scale
+                    + "', won't be used.");
+          }
+        }
+        String width = ArgParser.getValueFromSubValOrArg(avm, av, Arg.WIDTH,
+                subVal);
+        if (width != null)
+        {
+          try
+          {
+            bitmapwidth = Integer.parseInt(width);
+          } catch (NumberFormatException e)
+          {
+            Console.warn("Did not understand width '" + width
+                    + "', won't be used.");
+          }
         }
-        else if (fileName != null)
+        String height = ArgParser.getValueFromSubValOrArg(avm, av,
+                Arg.HEIGHT, subVal);
+        if (height != null)
+        {
+          try
+          {
+            bitmapheight = Integer.parseInt(height);
+          } catch (NumberFormatException e)
+          {
+            Console.warn("Did not understand height '" + height
+                    + "', won't be used.");
+          }
+        }
+
+        type = ArgParser.getValueFromSubValOrArg(avm, av, Arg.TYPE, subVal);
+        if (type == null && fileName != null)
         {
-          for (String ext : new String[] { "svg", "png", "html" })
+          for (String ext : new String[] { "svg", "png", "html", "eps" })
           {
             if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
             {
@@ -596,21 +781,51 @@ public class Commands
         Cache.setPropsAreReadOnly(true);
         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
 
+        Console.info("Writing " + file);
+
         switch (type)
         {
+
         case "svg":
           Console.debug("Outputting type '" + type + "' to " + fileName);
-          af.createSVG(file);
+          af.createSVG(file, renderer);
           break;
+
         case "png":
           Console.debug("Outputting type '" + type + "' to " + fileName);
-          af.createPNG(file);
+          af.createPNG(file, null, bitmapscale, bitmapwidth, bitmapheight);
           break;
+
         case "html":
           Console.debug("Outputting type '" + type + "' to " + fileName);
           HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
-          htmlSVG.exportHTML(fileName);
+          htmlSVG.exportHTML(fileName, renderer);
+          break;
+
+        case "biojs":
+          try
+          {
+            BioJsHTMLOutput.refreshVersionInfo(
+                    BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
+          } catch (URISyntaxException e)
+          {
+            e.printStackTrace();
+          }
+          BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
+          bjs.exportHTML(fileName);
+          Console.debug("Creating BioJS MSA Viwer HTML file: " + fileName);
+          break;
+
+        case "eps":
+          af.createEPS(file, name);
+          Console.debug("Creating EPS file: " + fileName);
+          break;
+
+        case "imagemap":
+          af.createImageMap(file, name);
+          Console.debug("Creating ImageMap file: " + fileName);
           break;
+
         default:
           Console.warn(Arg.IMAGE.argString() + " type '" + type
                   + "' not known. Ignoring");
@@ -621,21 +836,150 @@ public class Commands
     return true;
   }
 
-  private SequenceI getSpecifiedSequence(AlignFrame af, SubVals subId)
+  protected boolean processOutput(String id)
   {
-    if (subId == null)
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
+    AlignFrame af = afMap.get(id);
+
+    if (af == null)
+    {
+      Console.warn("Did not have an alignment window for id=" + id);
+      return false;
+    }
+
+    if (avm.containsArg(Arg.OUTPUT))
+    {
+      for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
+      {
+        String val = av.getValue();
+        SubVals subVals = av.getSubVals();
+        String fileName = subVals.getContent();
+        File file = new File(fileName);
+        boolean overwrite = ArgParser.getFromSubValArgOrPref(avm,
+                Arg.OVERWRITE, subVals, null, "OVERWRITE_OUTPUT", false);
+        // backups. Use the Arg.BACKUPS or subval "backups" setting first,
+        // otherwise if headless assume false, if not headless use the user
+        // preference with default true.
+        boolean backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS,
+                subVals, null,
+                Platform.isHeadless() ? null : BackupFiles.ENABLED,
+                !Platform.isHeadless());
+
+        // if backups is not true then --overwrite must be specified
+        if (file.exists() && !(overwrite || backups))
+        {
+          Console.error("Won't overwrite file '" + fileName + "' without "
+                  + Arg.OVERWRITE.argString() + " or "
+                  + Arg.BACKUPS.argString() + " set");
+          return false;
+        }
+
+        String name = af.getName();
+        String format = ArgParser.getValueFromSubValOrArg(avm, av,
+                Arg.FORMAT, subVals);
+        FileFormats ffs = FileFormats.getInstance();
+        List<String> validFormats = ffs.getWritableFormats(false);
+
+        FileFormatI ff = null;
+        if (format == null && fileName != null)
+        {
+          FORMAT: for (String fname : validFormats)
+          {
+            FileFormatI tff = ffs.forName(fname);
+            String[] extensions = tff.getExtensions().split(",");
+            for (String ext : extensions)
+            {
+              if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
+              {
+                ff = tff;
+                format = ff.getName();
+                break FORMAT;
+              }
+            }
+          }
+        }
+        if (ff == null && format != null)
+        {
+          ff = ffs.forName(format);
+        }
+        if (ff == null)
+        {
+          StringBuilder validSB = new StringBuilder();
+          for (String f : validFormats)
+          {
+            if (validSB.length() > 0)
+              validSB.append(", ");
+            validSB.append(f);
+            FileFormatI tff = ffs.forName(f);
+            validSB.append(" (");
+            validSB.append(tff.getExtensions());
+            validSB.append(")");
+          }
+
+          Jalview.exit("No valid format specified for "
+                  + Arg.OUTPUT.argString() + ". Valid formats are "
+                  + validSB.toString() + ".", 1);
+          // this return really shouldn't happen
+          return false;
+        }
+
+        String savedBackupsPreference = Cache
+                .getDefault(BackupFiles.ENABLED, null);
+        Console.debug("Setting backups to " + backups);
+        Cache.applicationProperties.put(BackupFiles.ENABLED,
+                Boolean.toString(backups));
+
+        Console.info("Writing " + fileName);
+
+        af.saveAlignment(fileName, ff);
+        Console.debug("Returning backups to " + savedBackupsPreference);
+        if (savedBackupsPreference != null)
+          Cache.applicationProperties.put(BackupFiles.ENABLED,
+                  savedBackupsPreference);
+        if (af.isSaveAlignmentSuccessful())
+        {
+          Console.debug("Written alignment '" + name + "' in "
+                  + ff.getName() + " format to " + file);
+        }
+        else
+        {
+          Console.warn("Error writing file " + file + " in " + ff.getName()
+                  + " format!");
+        }
+
+      }
+    }
+    return true;
+  }
+
+  private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
+          ArgValue av)
+  {
+    SubVals subVals = av.getSubVals();
+    ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID);
+    SequenceI seq = null;
+    if (subVals == null && idAv == null)
       return null;
     AlignmentI al = af.getCurrentView().getAlignment();
-    if (subId.has("seqid"))
+    if (al == null)
+      return null;
+    if (subVals != null)
     {
-      return al.findName(subId.get("seqid"));
+      if (subVals.has(Arg.SEQID.getName()))
+      {
+        seq = al.findName(subVals.get(Arg.SEQID.getName()));
+      }
+      else if (-1 < subVals.getIndex()
+              && subVals.getIndex() < al.getSequences().size())
+      {
+        seq = al.getSequenceAt(subVals.getIndex());
+      }
     }
-    else if (-1 < subId.getIndex()
-            && subId.getIndex() < al.getSequences().size())
+    else if (idAv != null)
     {
-      return al.getSequenceAt(subId.getIndex());
+      seq = al.findName(idAv.getValue());
     }
-    return null;
+    return seq;
   }
 
   // returns the first Arg value intended for the structure structFilename
@@ -666,9 +1010,6 @@ public class Commands
             else
             {
               structfile = likelyStructure.getValue();
-              Console.debug(
-                      "##### Comparing closest previous structure argument '"
-                              + structfile + "'");
             }
           }
         }