import jalview.util.Pair;
import jalview.workers.AlignCalcWorker;
import jalview.ws.params.ArgumentI;
-import jalview.ws2.actions.AbstractPollableTask;
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.helpers.DelegateJobEventListener;
import jalview.ws2.helpers.TaskEventSupport;
-public class AnnotationTask implements TaskI<AnnotationResult>
+public class AnnotationTask extends BaseTask<AnnotationJob, AnnotationResult>
{
- private final long uid = MathUtils.getUID();
-
private AnnotationWebServiceClientI client;
private final AnnotationAction action;
- private final List<ArgumentI> args;
-
- private final Credentials credentials;
-
private final AlignViewportI viewport;
- private final TaskEventSupport<AnnotationResult> eventHandler;
-
private JobStatus taskStatus = null;
private AlignCalcWorkerAdapter worker = null;
- private List<AnnotationJob> jobs = Collections.emptyList();
-
- private AnnotationResult result = null;
-
private DelegateJobEventListener<AnnotationResult> jobEventHandler;
- private class AlignCalcWorkerAdapter extends AlignCalcWorker
- implements PollableAlignCalcWorkerI
- {
- private boolean restarting = false;
-
- AlignCalcWorkerAdapter(AlignCalcManagerI2 calcMan)
- {
- super(viewport, null);
- this.calcMan = calcMan;
- }
-
- String getServiceName()
- {
- return action.getWebService().getName();
- }
-
- @Override
- public void startUp() throws Throwable
- {
- if (alignViewport.isClosed())
- {
- stop();
- throw new IllegalStateException("Starting annotation for closed viewport");
- }
- if (restarting)
- eventHandler.fireTaskRestarted();
- else
- restarting = true;
- jobs = Collections.emptyList();
- try
- {
- jobs = prepare();
- } catch (ServiceInputInvalidException e)
- {
- setStatus(JobStatus.INVALID);
- eventHandler.fireTaskException(e);
- throw e;
- }
- setStatus(JobStatus.READY);
- eventHandler.fireTaskStarted(jobs);
- for (var job : jobs)
- {
- job.addPropertyChagneListener(jobEventHandler);
- }
- try
- {
- startJobs();
- } catch (IOException e)
- {
- eventHandler.fireTaskException(e);
- cancelJobs();
- setStatus(JobStatus.SERVER_ERROR);
- throw e;
- }
- setStatus(JobStatus.SUBMITTED);
- }
-
- @Override
- public boolean poll() throws Throwable
- {
- boolean done = AnnotationTask.this.poll();
- updateGlobalStatus();
- if (done)
- {
- retrieveAndProcessResult();
- eventHandler.fireTaskCompleted(result);
- }
- return done;
- }
-
- private void retrieveAndProcessResult() throws IOException
- {
- result = retrieveResult();
- updateOurAnnots(result.annotations);
- if (result.transferFeatures)
- {
- final var featureColours = result.featureColours;
- final var featureFilters = result.featureFilters;
- viewport.applyFeaturesStyle(new FeatureSettingsAdapter()
- {
- @Override
- public FeatureColourI getFeatureColour(String type)
- {
- return featureColours.get(type);
- }
-
- @Override
- public FeatureMatcherSetI getFeatureFilters(String type)
- {
- return featureFilters.get(type);
- }
-
- @Override
- public boolean isFeatureDisplayed(String type)
- {
- return featureColours.containsKey(type);
- }
- });
- }
- }
-
- @Override
- public void updateAnnotation()
- {
- var job = jobs.size() > 0 ? jobs.get(0) : null;
- if (!calcMan.isWorking(this) && job != null)
- {
- var ret = updateResultAnnotation(job, job.returnedAnnotations);
- updateOurAnnots(ret.get0());
- }
- }
-
- private void updateOurAnnots(List<AlignmentAnnotation> newAnnots)
- {
- List<AlignmentAnnotation> oldAnnots = ourAnnots != null ? ourAnnots : Collections.emptyList();
- ourAnnots = newAnnots;
- AlignmentI alignment = viewport.getAlignment();
- for (AlignmentAnnotation an : oldAnnots)
- {
- if (!newAnnots.contains(an))
- {
- alignment.deleteAnnotation(an);
- }
- }
- oldAnnots.clear();
- for (AlignmentAnnotation an : ourAnnots)
- {
- viewport.getAlignment().validateAnnotation(an);
- }
- }
-
- @Override
- public void cancel()
- {
- cancelJobs();
- }
-
- void stop()
- {
- calcMan.disableWorker(this);
- super.abortAndDestroy();
- }
-
- @Override
- public void done()
- {
- for (var job : jobs)
- {
- if (job.isInputValid() && !job.isCompleted())
- {
- /* if done was called but job is not completed then it
- * must have been stopped by an exception */
- job.setStatus(JobStatus.SERVER_ERROR);
- }
- }
- updateGlobalStatus();
- // dispose of unfinished jobs just in case
- cancelJobs();
- }
-
- @Override
- public String toString()
- {
- return AnnotationTask.this.toString() + "$AlignCalcWorker@"
- + Integer.toHexString(hashCode());
- }
- }
-
public AnnotationTask(AnnotationWebServiceClientI client,
AnnotationAction action, List<ArgumentI> args, Credentials credentials,
- AlignViewportI viewport,
- TaskEventListener<AnnotationResult> eventListener)
+ AlignViewportI viewport)
{
+ super(client, args, credentials);
this.client = client;
this.action = action;
- this.args = args;
- this.credentials = credentials;
this.viewport = viewport;
- this.eventHandler = new TaskEventSupport<>(this, eventListener);
- this.jobEventHandler = new DelegateJobEventListener<>(this.eventHandler);
- }
-
- @Override
- public long getUid()
- {
- return uid;
}
- 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);
- }
- }
-
- /*
- * The following methods are mostly copied from the {@link AbstractPollableTask}
- * TODO: move common functionality to a base class
- */
- @Override
- public JobStatus getStatus()
- {
- return taskStatus;
- }
-
- private void setStatus(JobStatus status)
- {
- if (this.taskStatus != status)
- {
- Console.debug(String.format("%s status change to %s", this, status.name()));
- this.taskStatus = status;
- eventHandler.fireTaskStatusChanged(status);
- }
- }
-
- private void updateGlobalStatus()
- {
- int precedence = -1;
- for (BaseJob job : jobs)
- {
- JobStatus status = job.getStatus();
- int jobPrecedence = ArrayUtils.indexOf(JobStatus.statusPrecedence, status);
- if (precedence < jobPrecedence)
- precedence = jobPrecedence;
- }
- if (precedence >= 0)
- {
- setStatus(JobStatus.statusPrecedence[precedence]);
- }
- }
-
- @Override
- public List<? extends JobI> getSubJobs()
- {
- return jobs;
- }
+ // 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
* @throws ServiceInputInvalidException
* input data is not valid
*/
- private List<AnnotationJob> prepare() throws ServiceInputInvalidException
+ @Override
+ public List<AnnotationJob> prepareJobs() throws ServiceInputInvalidException
{
AlignmentI alignment = viewport.getAlignment();
if (alignment == null || alignment.getWidth() <= 0 ||
return List.of(job);
}
- private void startJobs() throws IOException
- {
- for (BaseJob job : jobs)
- {
- if (job.isInputValid() && job.getStatus() == JobStatus.READY)
- {
- var serverJob = client.submit(job.getInputSequences(),
- args, credentials);
- job.setServerJob(serverJob);
- job.setStatus(JobStatus.SUBMITTED);
- }
- }
- }
-
- private boolean poll() throws IOException
- {
- boolean allDone = true;
- for (BaseJob job : jobs)
- {
- if (job.isInputValid() && !job.getStatus().isDone())
- {
- WebServiceJobHandle serverJob = job.getServerJob();
- job.setStatus(client.getStatus(serverJob));
- job.setLog(client.getLog(serverJob));
- job.setErrorLog(client.getErrorLog(serverJob));
- }
- allDone &= job.isCompleted();
- }
- return allDone;
- }
-
- private AnnotationResult retrieveResult() throws IOException
+ @Override
+ protected AnnotationResult collectResult(List<AnnotationJob> jobs) throws IOException
{
final Map<String, FeatureColourI> featureColours = new HashMap<>();
final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
* sequence, excluding regions not annotated due to gapMap/column
* visibility */
- // update calcId if it is not already set on returned annotation
- for (AlignmentAnnotation annot : returnedAnnot)
+ udpateCalcId(returnedAnnot);
+ int graphGroup = getNextGraphGroup(viewport.getAlignment());
+ shiftGraphGroup(returnedAnnot, graphGroup);
+ List<AlignmentAnnotation> annotations = new ArrayList<>();
+ for (AlignmentAnnotation ala : returnedAnnot)
{
- if (annot.getCalcId() == null || annot.getCalcId().isEmpty())
- {
- annot.setCalcId(action.getFullName());
- }
- annot.autoCalculated = action.isAlignmentAnalysis() &&
- action.getWebService().isInteractive();
- }
- job.returnedAnnotations = returnedAnnot;
- job.featureColours = featureColours;
- job.featureFilters = featureFilters;
- var ret = updateResultAnnotation(job, returnedAnnot);
- var annotations = ret.get0();
- var transferFeatures = ret.get1();
- return new AnnotationResult(annotations, transferFeatures, featureColours,
- featureFilters);
- }
-
- private Pair<List<AlignmentAnnotation>, Boolean> updateResultAnnotation(
- AnnotationJob job, List<AlignmentAnnotation> annotations)
- {
- List<AlignmentAnnotation> newAnnots = new ArrayList<>();
- // update graphGroup for all annotation
- /* find a graphGroup greater than any existing one, could be moved
- * to Alignment#getNewGraphGroup() - returns next unused graph group */
- int graphGroup = 1;
- if (viewport.getAlignment().getAlignmentAnnotation() != null)
- {
- for (var ala : viewport.getAlignment().getAlignmentAnnotation())
- {
- graphGroup = Math.max(graphGroup, ala.graphGroup);
- }
- }
- // update graphGroup in the annotation rows returned form service'
- /* TODO: look at sequence annotation rows and update graph groups in the
- * case of reference annotation */
- for (AlignmentAnnotation ala : annotations)
- {
- if (ala.graphGroup > 0)
- ala.graphGroup += graphGroup;
- SequenceI aseq = null;
- // transfer sequence refs and adjust gapMap
- if (ala.sequenceRef != null)
- {
- aseq = job.seqNames.get(ala.sequenceRef.getName());
- }
+ SequenceI seq = job.seqNames.get(ala.sequenceRef.getName());
+ SequenceI aseq = getRootDatasetSequence(seq);
+ Annotation[] gappedAnnots = createGappedAnnotations(ala.annotations, job.start, job.gapMap);
ala.sequenceRef = aseq;
-
- Annotation[] resAnnot = ala.annotations;
- boolean[] gapMap = job.gapMap;
- Annotation[] gappedAnnot = new Annotation[Math.max(
- viewport.getAlignment().getWidth(), gapMap.length)];
- for (int p = 0, ap = job.start; ap < gappedAnnot.length; ap++)
- {
- if (gapMap.length > ap && !gapMap[ap])
- gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
- else if (p < resAnnot.length)
- gappedAnnot[ap] = resAnnot[p++];
- // is this loop exhaustive of resAnnot?
- }
- ala.annotations = gappedAnnot;
+ ala.annotations = gappedAnnots;
AlignmentAnnotation newAnnot = viewport.getAlignment()
.updateFromOrCopyAnnotation(ala);
- if (aseq != null)
+ if (aseq != null) // I suspect it's always true
{
aseq.addAlignmentAnnotation(newAnnot);
newAnnot.adjustForAlignment();
AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
newAnnot, newAnnot.label, newAnnot.getCalcId());
}
- newAnnots.add(newAnnot);
+ annotations.add(newAnnot);
}
- boolean transferFeatures = false;
+ boolean hasFeatures = false;
for (SequenceI sq : job.getInputSequences())
{
- if (!sq.getFeatures().hasFeatures() &&
- (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
+ if (!sq.getFeatures().hasFeatures() && (sq.getDBRefs() == null || sq.getDBRefs().isEmpty()))
continue;
- transferFeatures = true;
+ hasFeatures = true;
SequenceI seq = job.seqNames.get(sq.getName());
- SequenceI dseq;
- int start = job.start, end = job.end;
- boolean[] gapMap = job.gapMap;
- ContiguousI seqRange = seq.findPositions(start, end);
- while ((dseq = seq).getDatasetSequence() != null)
- {
- seq = seq.getDatasetSequence();
- }
- List<ContiguousI> sourceRange = new ArrayList<>();
- if (gapMap.length >= end)
- {
- int lastcol = start, col = start;
- do
- {
- if (col == end || !gapMap[col])
- {
- if (lastcol <= (col - 1))
- {
- seqRange = seq.findPositions(lastcol, col);
- sourceRange.add(seqRange);
- }
- lastcol = col + 1;
- }
- } while (col++ < end);
- }
- else
- {
- sourceRange.add(seq.findPositions(start, end));
- }
+ SequenceI datasetSeq = getRootDatasetSequence(seq);
+ List<ContiguousI> sourceRange = findContiguousRanges(datasetSeq, job.gapMap, job.start, job.end);
+ int[] sourceStartEnd = ContiguousI.toStartEndArray(sourceRange);
+ 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);
+ }
- int i = 0;
- int sourceStartEnd[] = new int[sourceRange.size() * 2];
- for (ContiguousI range : sourceRange)
+ /**
+ * Updates calcId on provided annotations if not already set.
+ */
+ public void udpateCalcId(Iterable<AlignmentAnnotation> annotations)
+ {
+ for (var annotation : annotations)
+ {
+ if (annotation.getCalcId() == null || annotation.getCalcId().isEmpty())
{
- sourceStartEnd[i++] = range.getBegin();
- sourceStartEnd[i++] = range.getEnd();
+ annotation.setCalcId(action.getFullName());
}
- Mapping mp = new Mapping(new MapList(
- sourceStartEnd, new int[]
- { seq.getStart(), seq.getEnd() }, 1, 1));
- dseq.transferAnnotation(sq, mp);
+ annotation.autoCalculated = action.isAlignmentAnalysis() &&
+ action.getWebService().isInteractive();
}
+ }
- return new Pair<>(newAnnots, transferFeatures);
+ private static int getNextGraphGroup(AlignmentI alignment)
+ {
+ if (alignment == null || alignment.getAlignmentAnnotation() == null)
+ return 1;
+ int graphGroup = 1;
+ for (AlignmentAnnotation ala : alignment.getAlignmentAnnotation())
+ graphGroup = Math.max(graphGroup, ala.graphGroup);
+ return graphGroup;
}
- @Override
- public AnnotationResult getResult()
+ private static void shiftGraphGroup(Iterable<AlignmentAnnotation> annotations, int shift)
{
- return result;
+ for (AlignmentAnnotation ala : annotations)
+ {
+ if (ala.graphGroup > 0)
+ {
+ ala.graphGroup += shift;
+ }
+ }
}
- @Override
- public void cancel()
+ private static SequenceI getRootDatasetSequence(SequenceI sequence)
{
- setStatus(JobStatus.CANCELLED);
- if (worker != null)
+ while (sequence.getDatasetSequence() != null)
{
- worker.stop();
+ sequence = sequence.getDatasetSequence();
}
- cancelJobs();
+ return sequence;
}
- public void cancelJobs()
+ private Annotation[] createGappedAnnotations(Annotation[] annotations, int start, boolean[] gapMap)
{
- for (BaseJob job : jobs)
+ var size = Math.max(viewport.getAlignment().getWidth(), gapMap.length);
+ Annotation[] gappedAnnotations = new Annotation[size];
+ for (int p = 0, ap = start; ap < size; ap++)
{
- if (!job.isCompleted())
+ if (gapMap != null && gapMap.length > ap && !gapMap[ap])
{
- try
- {
- if (job.getServerJob() != null)
- {
- client.cancel(job.getServerJob());
- }
- job.setStatus(JobStatus.CANCELLED);
- } catch (IOException e)
- {
- Console.error(String.format(
- "failed to cancel job %s", job.getServerJob()), e);
- }
+ gappedAnnotations[ap] = new Annotation("", "", ' ', Float.NaN);
+ }
+ else if (p < annotations.length)
+ {
+ gappedAnnotations[ap] = annotations[p++];
}
}
+ return gappedAnnotations;
+ }
+
+ 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));
+ List<ContiguousI> ranges = new ArrayList<>();
+ int lastcol = start, col = start;
+ do
+ {
+ if (col == end || !gapMap[col])
+ {
+ if (lastcol < col)
+ ranges.add(seq.findPositions(lastcol, col));
+ lastcol = col + 1;
+ }
+ } while (++col <= end);
+ return ranges;
}
@Override