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;
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.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.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 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();
- }
+ private final AlignmentI alignment;
- @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());
- }
- }
+ private final AnnotatedCollectionI selectionGroup;
public AnnotationTask(AnnotationWebServiceClientI client,
- AnnotationAction action, List<ArgumentI> args, Credentials credentials,
- AlignViewportI viewport,
- TaskEventListener<AnnotationResult> eventListener)
+ AnnotationAction action, List<ArgumentI> args,
+ Credentials credentials, 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;
+ this.alignment = viewport.getAlignment();
+ this.selectionGroup = viewport.getSelectionGroup();
}
/**
* 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
*/
- private List<AnnotationJob> prepare() throws ServiceInputInvalidException
+ @Override
+ 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);
}
- 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<>();
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 */
- // update calcId if it is not already set on returned annotation
- for (AlignmentAnnotation annot : 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)
+ udpateCalcId(returnedAnnot);
+ for (AlignmentAnnotation ala : returnedAnnot)
{
- if (ala.graphGroup > 0)
- ala.graphGroup += graphGroup;
- SequenceI aseq = null;
- // transfer sequence refs and adjust gapMap
- if (ala.sequenceRef != null)
+ 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 = job.seqNames.get(ala.sequenceRef.getName());
+ int startRes = seq.findPosition(job.regionStart);
+ ala.createSequenceMapping(seq, startRes, false);
}
- 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;
-
- AlignmentAnnotation newAnnot = viewport.getAlignment()
- .updateFromOrCopyAnnotation(ala);
- if (aseq != null)
- {
- aseq.addAlignmentAnnotation(newAnnot);
- newAnnot.adjustForAlignment();
- AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
- newAnnot, newAnnot.label, newAnnot.getCalcId());
- }
- newAnnots.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));
- }
-
- int i = 0;
- int sourceStartEnd[] = new int[sourceRange.size() * 2];
- for (ContiguousI range : sourceRange)
- {
- sourceStartEnd[i++] = range.getBegin();
- sourceStartEnd[i++] = range.getEnd();
- }
- Mapping mp = new Mapping(new MapList(
- sourceStartEnd, new int[]
- { seq.getStart(), seq.getEnd() }, 1, 1));
- dseq.transferAnnotation(sq, mp);
+ SequenceI datasetSeq = seq.getRootDatasetSequence();
+ 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));
+ datasetSeq.transferAnnotation(sq, mp);
}
- return new Pair<>(newAnnots, transferFeatures);
+ return new AnnotationResult(returnedAnnot, hasFeatures, featureColours,
+ featureFilters);
}
- @Override
- public AnnotationResult getResult()
- {
- return result;
- }
-
- @Override
- public void cancel()
+ /**
+ * Updates calcId on provided annotations if not already set.
+ */
+ public void udpateCalcId(Iterable<AlignmentAnnotation> annotations)
{
- setStatus(JobStatus.CANCELLED);
- if (worker != null)
+ for (var annotation : annotations)
{
- worker.stop();
+ if (annotation.getCalcId() == null
+ || annotation.getCalcId().isEmpty())
+ {
+ annotation.setCalcId(action.getFullName());
+ }
+ annotation.autoCalculated = action.isAlignmentAnalysis()
+ && action.getWebService().isInteractive();
}
- cancelJobs();
}
- public void cancelJobs()
+ // TODO: review and test
+ // may produce wrong output if annotations longer than gapMap
+ private Annotation[] createGappedAnnotations(Annotation[] annotations,
+ boolean[] gapMap)
{
- for (BaseJob job : jobs)
+ var size = Math.max(annotations.length, gapMap.length);
+ Annotation[] gappedAnnotations = new Annotation[size];
+ for (int p = 0, ap = 0; ap < size; ap++)
{
- if (!job.isCompleted())
+ if (ap < gapMap.length && !gapMap[ap])
+ {
+ gappedAnnotations[ap] = new Annotation("", "", ' ', Float.NaN);
+ }
+ else if (p < annotations.length)
{
- 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] = annotations[p++];
}
}
+ return gappedAnnotations;
}
- @Override
- public String toString()
+ // TODO: review ant test!!!
+ private List<ContiguousI> findContiguousRanges(SequenceI seq,
+ boolean[] gapMap, int start, int end)
{
- var status = taskStatus != null ? taskStatus.name() : "UNSET";
- return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, status);
+ if (gapMap == null || gapMap.length < end)
+ return List.of(seq.findPositions(start + 1, end + 1));
+ 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;
}
}