JAL-4241 Flter empty columns from annotation service input
[jalview.git] / src / jalview / ws2 / actions / annotation / AnnotationTask.java
index ed67643..1d02264 100644 (file)
@@ -2,19 +2,12 @@ package jalview.ws2.actions.annotation;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import jalview.analysis.AlignmentAnnotationUtils;
-import jalview.api.AlignCalcManagerI2;
-import jalview.api.AlignCalcWorkerI;
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureColourI;
-import jalview.api.PollableAlignCalcWorkerI;
-import jalview.bin.Cache;
-import jalview.bin.Console;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AnnotatedCollectionI;
@@ -23,173 +16,133 @@ import jalview.datamodel.ContiguousI;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.FeatureMatcherSetI;
-import jalview.schemes.FeatureSettingsAdapter;
-import jalview.util.ArrayUtils;
 import jalview.util.MapList;
-import jalview.util.MathUtils;
-import jalview.util.Pair;
-import jalview.workers.AlignCalcWorker;
 import jalview.ws.params.ArgumentI;
-import jalview.ws2.actions.BaseJob;
 import jalview.ws2.actions.BaseTask;
 import jalview.ws2.actions.ServiceInputInvalidException;
-import jalview.ws2.actions.api.JobI;
-import jalview.ws2.actions.api.TaskEventListener;
-import jalview.ws2.actions.api.TaskI;
 import jalview.ws2.api.Credentials;
 import jalview.ws2.api.JobStatus;
-import jalview.ws2.api.WebServiceJobHandle;
 import jalview.ws2.client.api.AnnotationWebServiceClientI;
-import jalview.ws2.helpers.DelegateJobEventListener;
-import jalview.ws2.helpers.TaskEventSupport;
 
-public class AnnotationTask extends BaseTask<AnnotationJob, AnnotationResult>
+public class AnnotationTask
+        extends BaseTask<AnnotationJob, AnnotationResult>
 {
   private AnnotationWebServiceClientI client;
 
   private final AnnotationAction action;
 
-  private final AlignViewportI viewport;
+  private final AlignmentI alignment;
 
-  private JobStatus taskStatus = null;
-
-  private AlignCalcWorkerAdapter worker = null;
-
-  private DelegateJobEventListener<AnnotationResult> jobEventHandler;
+  private final AnnotatedCollectionI selectionGroup;
 
   public AnnotationTask(AnnotationWebServiceClientI client,
-      AnnotationAction action, List<ArgumentI> args, Credentials credentials,
-      AlignViewportI viewport)
+          AnnotationAction action, List<ArgumentI> args,
+          Credentials credentials, AlignViewportI viewport)
   {
     super(client, args, credentials);
     this.client = client;
     this.action = action;
-    this.viewport = viewport;
+    this.alignment = viewport.getAlignment();
+    this.selectionGroup = viewport.getSelectionGroup();
   }
 
-  // public void start(AlignCalcManagerI2 calcManager)
-  // {
-  // if (this.worker != null)
-  // throw new IllegalStateException("task already started");
-  // this.worker = new AlignCalcWorkerAdapter(calcManager);
-  // if (taskStatus != JobStatus.CANCELLED)
-  // {
-  // List<AlignCalcWorkerI> oldWorkers = calcManager.getWorkersOfClass(
-  // AlignCalcWorkerAdapter.class);
-  // for (var worker : oldWorkers)
-  // {
-  // if (action.getWebService().getName().equalsIgnoreCase(
-  // ((AlignCalcWorkerAdapter) worker).getServiceName()))
-  // {
-  // // remove interactive workers for the same service.
-  // calcManager.removeWorker(worker);
-  // calcManager.cancelWorker(worker);
-  // }
-  // }
-  // if (action.getWebService().isInteractive())
-  // calcManager.registerWorker(worker);
-  // else
-  // calcManager.startWorker(worker);
-  // }
-  // }
-
   /**
    * Create and return a list of annotation jobs from the current state of the
    * viewport. Returned job are not started by this method and should be stored
    * in a field and started separately.
-   * 
+   *
    * @return list of annotation jobs
    * @throws ServiceInputInvalidException
    *           input data is not valid
    */
   @Override
-  public List<AnnotationJob> prepareJobs() throws ServiceInputInvalidException
+  public List<AnnotationJob> prepareJobs()
+          throws ServiceInputInvalidException
   {
-    AlignmentI alignment = viewport.getAlignment();
-    if (alignment == null || alignment.getWidth() <= 0 ||
-        alignment.getSequences() == null)
-      throw new ServiceInputInvalidException("Alignment does not contain sequences");
-    if (alignment.isNucleotide() && !action.doAllowNucleotide())
+    if (alignment == null || alignment.getWidth() <= 0
+            || alignment.getSequences() == null)
       throw new ServiceInputInvalidException(
-          action.getFullName() + " does not allow nucleotide sequences");
+              "Alignment does not contain sequences");
+    if (alignment.isNucleotide() && !action.doAllowNucleotide())
+      throw new ServiceInputInvalidException(action.getFullName()
+              + " does not allow nucleotide sequences");
     if (!alignment.isNucleotide() && !action.doAllowProtein())
       throw new ServiceInputInvalidException(
-          action.getFullName() + " does not allow protein sequences");
+              action.getFullName() + " does not allow protein sequences");
     boolean bySequence = !action.isAlignmentAnalysis();
-    AnnotatedCollectionI inputSeqs = bySequence ? viewport.getSelectionGroup() : null;
-    if (inputSeqs == null || inputSeqs.getWidth() <= 0 ||
-        inputSeqs.getSequences() == null || inputSeqs.getSequences().size() < 1)
+    AnnotatedCollectionI inputSeqs = bySequence ? selectionGroup : null;
+    if (inputSeqs == null || inputSeqs.getWidth() <= 0
+            || inputSeqs.getSequences() == null
+            || inputSeqs.getSequences().size() < 1)
       inputSeqs = alignment;
     boolean submitGaps = action.isAlignmentAnalysis();
     boolean requireAligned = action.getRequireAlignedSequences();
     boolean filterSymbols = action.getFilterSymbols();
     int minSize = action.getMinSequences();
     AnnotationJob job = AnnotationJob.create(inputSeqs, bySequence,
-        submitGaps, requireAligned, filterSymbols, minSize);
+            submitGaps, requireAligned, filterSymbols, minSize);
     if (!job.isInputValid())
     {
       job.setStatus(JobStatus.INVALID);
-      throw new ServiceInputInvalidException("Annotation job has invalid input");
+      throw new ServiceInputInvalidException(
+              "Annotation job has invalid input");
     }
     job.setStatus(JobStatus.READY);
     return List.of(job);
   }
 
   @Override
-  protected AnnotationResult collectResult(List<AnnotationJob> jobs) throws IOException
+  protected AnnotationResult collectResult(List<AnnotationJob> jobs)
+          throws IOException
   {
     final Map<String, FeatureColourI> featureColours = new HashMap<>();
     final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
     var job = jobs.get(0);
     List<AlignmentAnnotation> returnedAnnot = client.attachAnnotations(
-        job.getServerJob(), job.getInputSequences(), featureColours,
-        featureFilters);
+            job.getServerJob(), job.getInputSequences(), featureColours,
+            featureFilters);
     /* TODO
      * copy over each annotation row returned and also defined on each
      * sequence, excluding regions not annotated due to gapMap/column
      * visibility */
 
     udpateCalcId(returnedAnnot);
-    int graphGroup = viewport.getAlignment().getLastGraphGroup();
-    shiftGraphGroup(returnedAnnot, graphGroup);
-    List<AlignmentAnnotation> annotations = new ArrayList<>();
     for (AlignmentAnnotation ala : returnedAnnot)
     {
-      SequenceI seq = job.seqNames.get(ala.sequenceRef.getName());
-      SequenceI aseq = seq.getRootDatasetSequence();
-      Annotation[] gappedAnnots = createGappedAnnotations(ala.annotations, job.start, job.gapMap);
-      ala.sequenceRef = aseq;
-      ala.annotations = gappedAnnots;
-
-      AlignmentAnnotation newAnnot = viewport.getAlignment()
-          .updateFromOrCopyAnnotation(ala);
-      if (aseq != null) // I suspect it's always true
+      SequenceI seq = (ala.sequenceRef == null) ? null
+              : job.seqNames.get(ala.sequenceRef.getName());
+      if (job.gapMap != null && job.gapMap.length > 0)
+        ala.annotations = createGappedAnnotations(ala.annotations,
+                job.gapMap);
+      if (seq != null)
       {
-        aseq.addAlignmentAnnotation(newAnnot);
-        newAnnot.adjustForAlignment();
-        AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
-            newAnnot, newAnnot.label, newAnnot.getCalcId());
+        int startRes = seq.findPosition(job.regionStart);
+        ala.createSequenceMapping(seq, startRes, false);
       }
-      annotations.add(newAnnot);
     }
 
     boolean hasFeatures = false;
     for (SequenceI sq : job.getInputSequences())
     {
-      if (!sq.getFeatures().hasFeatures() && (sq.getDBRefs() == null || sq.getDBRefs().isEmpty()))
+      if (!sq.getFeatures().hasFeatures()
+              && (sq.getDBRefs() == null || sq.getDBRefs().isEmpty()))
         continue;
       hasFeatures = true;
       SequenceI seq = job.seqNames.get(sq.getName());
       SequenceI datasetSeq = seq.getRootDatasetSequence();
-      List<ContiguousI> sourceRange = findContiguousRanges(datasetSeq, job.gapMap, job.start, job.end);
+      List<ContiguousI> sourceRange = findContiguousRanges(seq,
+              job.gapMap, job.regionStart, job.regionEnd);
       int[] sourceStartEnd = ContiguousI.toStartEndArray(sourceRange);
-      Mapping mp = new Mapping(new MapList(
-          sourceStartEnd, new int[]
-          { datasetSeq.getStart(), datasetSeq.getEnd() }, 1, 1));
+      Mapping mp = new Mapping(
+          new MapList(
+              sourceStartEnd,
+              new int[] { datasetSeq.getStart(), datasetSeq.getEnd() },
+              1, 1));
       datasetSeq.transferAnnotation(sq, mp);
     }
 
-    return new AnnotationResult(annotations, hasFeatures, featureColours, featureFilters);
+    return new AnnotationResult(returnedAnnot, hasFeatures, featureColours,
+            featureFilters);
   }
 
   /**
@@ -199,33 +152,26 @@ public class AnnotationTask extends BaseTask<AnnotationJob, AnnotationResult>
   {
     for (var annotation : annotations)
     {
-      if (annotation.getCalcId() == null || annotation.getCalcId().isEmpty())
+      if (annotation.getCalcId() == null
+              || annotation.getCalcId().isEmpty())
       {
         annotation.setCalcId(action.getFullName());
       }
-      annotation.autoCalculated = action.isAlignmentAnalysis() &&
-          action.getWebService().isInteractive();
-    }
-  }
-
-  private static void shiftGraphGroup(Iterable<AlignmentAnnotation> annotations, int shift)
-  {
-    for (AlignmentAnnotation ala : annotations)
-    {
-      if (ala.graphGroup > 0)
-      {
-        ala.graphGroup += shift;
-      }
+      annotation.autoCalculated = action.isAlignmentAnalysis()
+              && action.getWebService().isInteractive();
     }
   }
 
-  private Annotation[] createGappedAnnotations(Annotation[] annotations, int start, boolean[] gapMap)
+  // TODO: review and test
+  //       may produce wrong output if annotations longer than gapMap
+  private Annotation[] createGappedAnnotations(Annotation[] annotations,
+          boolean[] gapMap)
   {
-    var size = Math.max(viewport.getAlignment().getWidth(), gapMap.length);
+    var size = Math.max(annotations.length, gapMap.length);
     Annotation[] gappedAnnotations = new Annotation[size];
-    for (int p = 0, ap = start; ap < size; ap++)
+    for (int p = 0, ap = 0; ap < size; ap++)
     {
-      if (gapMap != null && gapMap.length > ap && !gapMap[ap])
+      if (ap < gapMap.length && !gapMap[ap])
       {
         gappedAnnotations[ap] = new Annotation("", "", ' ', Float.NaN);
       }
@@ -237,10 +183,12 @@ public class AnnotationTask extends BaseTask<AnnotationJob, AnnotationResult>
     return gappedAnnotations;
   }
 
-  private List<ContiguousI> findContiguousRanges(SequenceI seq, boolean[] gapMap, int start, int end)
+  // TODO: review ant test!!!
+  private List<ContiguousI> findContiguousRanges(SequenceI seq,
+          boolean[] gapMap, int start, int end)
   {
     if (gapMap == null || gapMap.length < end)
-      return List.of(seq.findPositions(start, end));
+      return List.of(seq.findPositions(start + 1, end + 1));
     List<ContiguousI> ranges = new ArrayList<>();
     int lastcol = start, col = start;
     do
@@ -254,11 +202,4 @@ public class AnnotationTask extends BaseTask<AnnotationJob, AnnotationResult>
     } while (++col <= end);
     return ranges;
   }
-
-  @Override
-  public String toString()
-  {
-    var status = taskStatus != null ? taskStatus.name() : "UNSET";
-    return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, status);
-  }
 }