--- /dev/null
+package jalview.ws2.gui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+
+import jalview.gui.AlignFrame;
+import jalview.gui.WsJobParameters;
+import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.ParamDatastoreI;
+import jalview.ws.params.WsParamSetI;
+import jalview.ws2.operations.AnnotationOperation;
+import jalview.ws2.operations.AnnotationWorker;
+
+public class AnnotationMenuBuilder implements MenuEntryProviderI
+{
+ final AnnotationOperation operation;
+
+ public AnnotationMenuBuilder(AnnotationOperation operation)
+ {
+ this.operation = operation;
+ }
+
+ @Override
+ public void buildMenu(JMenu parent, AlignFrame frame)
+ {
+ if (operation.isInteractive())
+ buildInteractiveMenu(parent, frame);
+ else
+ buildClassicMenu(parent, frame);
+ }
+
+ protected void buildClassicMenu(JMenu parent, AlignFrame frame)
+ {
+ final var calcName = operation.getName();
+ final var calcManager = frame.getViewport().getCalcManager();
+ {
+ var item = new JMenuItem(MessageManager.formatMessage(
+ "label.calcname_with_default_settings", calcName));
+ item.addActionListener((event) -> {
+ var worker = createWorker(Collections.emptyList(), frame);
+ calcManager.startWorker(worker);
+ });
+ parent.add(item);
+ }
+ if (operation.hasParameters())
+ {
+ var item = new JMenuItem(
+ MessageManager.getString("label.edit_settings_and_run"));
+ item.setToolTipText(MessageManager.getString(
+ "label.view_and_change_parameters_before_running_calculation"));
+ item.addActionListener((event) -> {
+ openEditParamsDialog(operation.getParamStore(), null, null)
+ .thenAcceptAsync((arguments) -> {
+ if (arguments != null)
+ {
+ var worker = createWorker(arguments, frame);
+ calcManager.startWorker(worker);
+ }
+ });
+ });
+ parent.add(item);
+ }
+ }
+
+ protected void buildInteractiveMenu(JMenu parent, AlignFrame frame)
+ {
+ final var calcName = operation.getName();
+ final var calcManager = frame.getViewport().getCalcManager();
+ final var arguments = new ArrayList<ArgumentI>();
+ final JCheckBoxMenuItem runItem;
+ {
+ // TODO use MessageManager and set tool tip text
+ runItem = new JCheckBoxMenuItem(
+ String.format("%s calculations", calcName));
+ runItem.addActionListener((event) -> {
+ calcManager.removeWorkersForName(calcName);
+ var worker = createWorker(arguments, frame);
+ calcManager.registerWorker(worker);
+ });
+ parent.add(runItem);
+ }
+ JMenuItem _editItem = null;
+ if (operation.hasParameters())
+ {
+ // TODO use MessageManager and set tool tip text
+ _editItem = new JMenuItem(
+ String.format("Edit %s settings", calcName));
+ _editItem.addActionListener((event) -> {
+ openEditParamsDialog(operation.getParamStore(), null, null)
+ .thenAcceptAsync((args) -> {
+ if (arguments != null)
+ {
+ arguments.clear();
+ arguments.addAll(args);
+ calcManager.removeWorkersForName(calcName);
+ var worker = createWorker(arguments, frame);
+ calcManager.registerWorker(worker);
+ }
+ });
+ });
+ parent.add(_editItem);
+ }
+ final var editItem = _editItem;
+
+ parent.addMenuListener(new MenuListener()
+ {
+ @Override
+ public void menuSelected(MenuEvent e)
+ {
+ var isNuc = frame.getViewport().getAlignment().isNucleotide();
+ var menuEnabled = (isNuc && operation.isNucleotideOperation()) ||
+ (!isNuc && operation.isProteinOperation());
+ runItem.setEnabled(menuEnabled);
+ if (editItem != null)
+ editItem.setEnabled(menuEnabled);
+ boolean currentlyRunning = calcManager.getWorkersForName(calcName).size() > 0;
+ runItem.setSelected(currentlyRunning);
+ }
+
+ @Override
+ public void menuDeselected(MenuEvent e)
+ {
+ }
+
+ @Override
+ public void menuCanceled(MenuEvent e)
+ {
+ }
+ });
+ }
+
+ private CompletionStage<List<ArgumentI>> openEditParamsDialog(
+ ParamDatastoreI paramStore, WsParamSetI preset,
+ List<ArgumentI> arguments)
+ {
+ WsJobParameters jobParams;
+ if (preset == null && arguments != null && arguments.size() > 0)
+ jobParams = new WsJobParameters(paramStore, preset, arguments);
+ else
+ jobParams = new WsJobParameters(paramStore, preset, null);
+ if (preset != null)
+ {
+ jobParams.setName(MessageManager.getString(
+ "label.adjusting_parameters_for_calculation"));
+ }
+ var stage = jobParams.showRunDialog();
+ return stage.thenApply((startJob) -> {
+ if (startJob)
+ {
+ if (jobParams.getPreset() == null)
+ {
+ return jobParams.getJobParams();
+ }
+ else
+ {
+ return jobParams.getPreset().getArguments();
+ }
+ }
+ else
+ {
+ return null;
+ }
+ });
+ }
+
+ private AnnotationWorker createWorker(List<ArgumentI> arguments, AlignFrame frame)
+ {
+ /* What is the purpose of AlignViewport and AlignmentViewPanel? */
+ return new AnnotationWorker(operation, arguments, frame, frame);
+ }
+
+}
--- /dev/null
+package jalview.ws2.operations;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import jalview.analysis.AlignSeq;
+import jalview.analysis.AlignmentAnnotationUtils;
+import jalview.analysis.SeqsetUtils;
+import jalview.api.AlignCalcManagerI2;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureColourI;
+import jalview.api.PollableAlignCalcWorkerI;
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AnnotatedCollectionI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.ContiguousI;
+import jalview.datamodel.Mapping;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.FeatureMatcherSetI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.gui.IProgressIndicator;
+import jalview.gui.IProgressIndicatorHandler;
+import jalview.schemes.FeatureSettingsAdapter;
+import jalview.schemes.ResidueProperties;
+import jalview.util.MapList;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.WSJob;
+import jalview.ws2.WSJobStatus;
+import jalview.ws2.gui.ProgressBarUpdater;
+
+import static java.lang.String.format;
+
+public class AnnotationWorker extends AbstractWorker
+ implements PollableAlignCalcWorkerI
+{
+ AnnotationOperation operation;
+
+ private WSJobList<AnnotationJob> jobs = new WSJobList<>();
+
+ AnnotationJob job;
+
+ private List<ArgumentI> args = Collections.emptyList();
+
+ private AlignViewport viewport;
+
+ private AlignmentViewPanel alignPanel;
+
+ private IProgressIndicator progressIndicator;
+
+ private AlignFrame frame;
+
+ private AlignCalcManagerI2 calcMan;
+
+ protected List<AlignmentAnnotation> ourAnnots;
+
+ // TODO: convert to bitset
+ private boolean[] gapMap = new boolean[0];
+
+ private class JobInput
+ {
+ List<SequenceI> sequences;
+
+ Map<String, SequenceI> seqNames;
+
+ int start, end;
+ }
+
+ public class AnnotationJob extends WSJob
+ {
+ private List<SequenceI> sequences;
+
+ private int start, end;
+
+ private Map<String, SequenceI> seqNames;
+
+ private boolean transferSequenceFeatures = false;
+
+ private AnnotationJob(String serviceProvider, String serviceName,
+ String hostName)
+ {
+ super(serviceProvider, serviceName, hostName);
+ }
+
+ private void setInput(JobInput input)
+ {
+ this.sequences = input.sequences;
+ this.start = input.start;
+ this.end = input.end;
+ this.seqNames = input.seqNames;
+ }
+ }
+
+ public AnnotationWorker(AnnotationOperation operation,
+ List<ArgumentI> args, AlignFrame frame,
+ IProgressIndicator progressIndicator)
+ {
+ this.operation = operation;
+ this.args = args;
+ this.viewport = frame.getCurrentView();
+ this.alignPanel = frame.alignPanel;
+ this.progressIndicator = progressIndicator;
+ this.frame = frame;
+ this.calcMan = viewport.getCalcManager();
+ }
+
+ @Override
+ public String getCalcName()
+ {
+ return operation.getName();
+ }
+
+ @Override
+ public Operation getOperation()
+ {
+ return operation;
+ }
+
+ @Override
+ public WSJobList<? extends WSJob> getJobs()
+ {
+ return jobs;
+ }
+
+ @Override
+ public boolean involves(AlignmentAnnotation annot)
+ {
+ return ourAnnots != null && ourAnnots.contains(annot);
+ }
+
+ @Override
+ public void updateAnnotation()
+ {
+ updateResultAnnotation(ourAnnots);
+ }
+
+ @Override
+ public void removeAnnotation()
+ {
+ if (ourAnnots != null && viewport != null)
+ {
+ AlignmentI alignment = viewport.getAlignment();
+ synchronized (ourAnnots)
+ {
+ for (AlignmentAnnotation aa : ourAnnots)
+ {
+ alignment.deleteAnnotation(aa, true);
+ }
+ }
+ ourAnnots.clear();
+ }
+ }
+
+ @Override
+ public boolean isDeletable()
+ {
+ return true;
+ }
+
+ @Override
+ public void startUp() throws IOException
+ {
+ if (viewport.isClosed())
+ {
+ return;
+ }
+
+ /* What "bySequence" means in this context and
+ * what is the SelectionGroup and why is it only relevant when
+ * not dealing with alignment analysis? */
+ boolean bySequence = !operation.isAlignmentAnalysis();
+ var input = prepareInput(viewport.getAlignment(),
+ bySequence ? viewport.getSelectionGroup() : null);
+ if (input.sequences == null || !checkInputSequencesValid(input.sequences))
+ {
+ Cache.log.info("Sequences for analysis service were null");
+ return;
+ }
+ Cache.log.debug(format("submitting %d sequences to %s",
+ input.sequences.size(), operation.getName()));
+ job = new AnnotationJob(operation.getWebService().getProviderName(),
+ operation.getWebService().getName(), operation.getWebService().getHostName());
+ jobs.add(job);
+ listeners.fireJobCreated(job);
+ job.setInput(input);
+ // Should this part be moved out of this class to one of the gui
+ // classes?
+ if (progressIndicator != null)
+ {
+ job.addPropertyChangeListener("status", new ProgressBarUpdater(progressIndicator));
+ progressIndicator.registerHandler(job.getUid(), new IProgressIndicatorHandler()
+ {
+ @Override
+ public boolean cancelActivity(long id)
+ {
+ calcMan.cancelWorker(AnnotationWorker.this);
+ return true;
+ }
+
+ @Override
+ public boolean canCancel()
+ {
+ return isDeletable();
+ }
+ });
+ }
+ String jobId = operation.getWebService().submit(input.sequences, args);
+ job.setJobId(jobId);
+ Cache.log.debug(format("Service %s: submitted job id %s",
+ operation.getHostName(), jobId));
+ listeners.fireWorkerStarted();
+ }
+
+ private JobInput prepareInput(AlignmentI alignment,
+ AnnotatedCollectionI inputSeqs)
+ {
+ if (alignment == null || alignment.getWidth() <= 0 ||
+ alignment.getSequences() == null)
+ return null;
+ if (alignment.isNucleotide() && !operation.isNucleotideOperation())
+ return null;
+ if (!alignment.isNucleotide() && !operation.isProteinOperation())
+ return null;
+ if (inputSeqs == null || inputSeqs.getWidth() <= 0 ||
+ inputSeqs.getSequences() == null || inputSeqs.getSequences().size() < 1)
+ inputSeqs = alignment;
+
+ List<SequenceI> seqs = new ArrayList<>();
+ final boolean submitGaps = operation.isAlignmentAnalysis();
+ final int minlen = 10;
+ int ln = -1; // I think this variable is redundant
+ Map<String, SequenceI> seqNames = null;
+ if (!operation.isAlignmentAnalysis())
+ seqNames = new HashMap<>();
+ int start = inputSeqs.getStartRes();
+ int end = inputSeqs.getEndRes();
+ // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
+ // correctly
+ // TODO: push attributes into WsJob instance (so they can be safely
+ // persisted/restored
+ for (SequenceI sq : inputSeqs.getSequences())
+ {
+ int sqlen;
+ // is it trying to find the length of a sequence excluding gaps?
+ if (!operation.isAlignmentAnalysis())
+ // why starting at positions to the right from the end/start?
+ sqlen = sq.findPosition(end + 1) - sq.findPosition(start + 1);
+ else
+ sqlen = sq.getEnd() - sq.getStart();
+ if (sqlen >= minlen)
+ {
+ String newName = SeqsetUtils.unique_name(seqs.size());
+ if (seqNames != null)
+ {
+ seqNames.put(newName, sq);
+ }
+ SequenceI seq;
+ if (submitGaps)
+ {
+ seq = new Sequence(newName, sq.getSequenceAsString());
+ seqs.add(seq);
+ if (gapMap == null || gapMap.length < seq.getLength())
+ {
+ boolean[] tg = gapMap;
+ gapMap = new boolean[seq.getLength()];
+ System.arraycopy(tg, 0, gapMap, 0, tg.length);
+ for (int p = tg.length; p < gapMap.length; p++)
+ {
+ gapMap[p] = false; // init as a gap
+ }
+ }
+ for (int apos : sq.gapMap())
+ {
+ char sqc = sq.getCharAt(apos);
+ boolean isStandard = sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
+ : ResidueProperties.nucleotideIndex[sqc] < 5;
+ if (!operation.getFilterNonStandardSymbols() || isStandard)
+ {
+ gapMap[apos] = true;
+ }
+ }
+ }
+ else
+ {
+ // TODO: add ability to exclude hidden regions
+ String sqstring = sq.getSequenceAsString(start, end + 1);
+ seq = new Sequence(newName,
+ AlignSeq.extractGaps(jalview.util.Comparison.GapChars, sqstring));
+ seqs.add(seq);
+ // for annotation need to also record map to sequence start/end
+ // position in range
+ // then transfer back to original sequence on return.
+ }
+ ln = Integer.max(seq.getLength(), ln);
+ }
+ }
+ if (operation.getNeedsAlignedSequences() && submitGaps)
+ {
+ int realw = 0;
+ for (int i = 0; i < gapMap.length; i++)
+ {
+ if (gapMap[i])
+ {
+ realw++;
+ }
+ }
+ // try real hard to return something submittable
+ // TODO: some of AAcon measures need a minimum of two or three amino
+ // acids at each position, and AAcon doesn't gracefully degrade.
+ for (int p = 0; p < seqs.size(); p++)
+ {
+ SequenceI sq = seqs.get(p);
+ // strip gapped columns
+ char[] padded = new char[realw];
+ char[] orig = sq.getSequence();
+ for (int i = 0, pp = 0; i < realw; pp++)
+ {
+ if (gapMap[pp])
+ {
+ if (orig.length > pp)
+ {
+ padded[i++] = orig[pp];
+ }
+ else
+ {
+ padded[i++] = '-';
+ }
+ }
+ }
+ seqs.set(p, new Sequence(sq.getName(), new String(padded)));
+ }
+ }
+ var inp = new JobInput();
+ inp.sequences = seqs;
+ inp.seqNames = seqNames;
+ inp.start = start;
+ inp.end = end;
+ return inp;
+ }
+
+ private boolean checkInputSequencesValid(List<SequenceI> sequences)
+ {
+ int nvalid = 0;
+ boolean allowProtein = operation.isProteinOperation(),
+ allowNucleotides = operation.isNucleotideOperation();
+ for (SequenceI sq : sequences)
+ {
+ if (sq.getStart() <= sq.getEnd() &&
+ (sq.isProtein() ? allowProtein : allowNucleotides))
+ {
+ nvalid++;
+ }
+ }
+ return nvalid >= operation.getMinSequences();
+ }
+
+ @Override
+ public void cancel()
+ {
+ try
+ {
+ operation.getWebService().cancel(job);
+ } catch (IOException e)
+ {
+ Cache.log.error(format("Failed to cancel job %s.", job), e);
+ }
+ }
+
+ @Override
+ public void done()
+ {
+ Cache.log.debug(format("Polling loop exited, job %s is %s", job, job.getStatus()));
+ if (!job.getStatus().isCompleted())
+ {
+ return;
+ }
+ var featureRenderer = alignPanel.cloneFeatureRenderer();
+ Map<String, FeatureColourI> featureColours = new HashMap<>();
+ Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
+ List<AlignmentAnnotation> returnedAnnot = null;
+ try
+ {
+ returnedAnnot = operation.annotationSupplier.attachAnnotations(
+ job, job.sequences, featureColours, featureFilters);
+ } catch (Exception e)
+ {
+ if (!operation.getWebService().handleCollectionError(job, e))
+ {
+ Cache.log.error("Couldn't get annotations for job.", e);
+ job.setStatus(WSJobStatus.SERVER_ERROR);
+ listeners.firePollException(job, e);
+ }
+ return;
+ }
+ Cache.log.debug("Obtained " + (returnedAnnot == null ? "no rows"
+ : ("" + returnedAnnot.size())));
+ Cache.log.debug(
+ String.format("There were %s feature colours and %s filters defined",
+ featureColours.size(), featureFilters.size()));
+ if (returnedAnnot != null)
+ {
+ for (AlignmentAnnotation aa : returnedAnnot)
+ {
+ // assume that any CalcIds already set
+ if (aa.getCalcId() == null || aa.getCalcId().equals(""))
+ {
+ aa.setCalcId(operation.getName());
+ }
+ // autocalculated annotation are created by interactive alignment
+ // analysis services
+ aa.autoCalculated = operation.isAlignmentAnalysis()
+ && operation.isInteractive();
+ }
+ }
+ updateResultAnnotation(returnedAnnot);
+ if (job.transferSequenceFeatures)
+ {
+ Cache.log.debug(format("Updating feature display settings and transferring"
+ + "features from job %s at %s", job, operation.getHostName()));
+ 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);
+ }
+ });
+ if (frame.alignPanel == alignPanel)
+ {
+ viewport.setShowSequenceFeatures(true);
+ frame.setMenusForViewport();
+ }
+ }
+ Cache.log.debug("Annotation service task finished.");
+ }
+
+ // What is the purpose of this method?
+ // When is it called (apart from the above)?
+ private void updateResultAnnotation(List<AlignmentAnnotation> annotations)
+ {
+ var currentAnnotations = Objects.requireNonNullElse(
+ viewport.getAlignment().getAlignmentAnnotation(),
+ new AlignmentAnnotation[0]);
+ List<AlignmentAnnotation> newAnnots = new ArrayList<>();
+ // what is the graph group and why starting from 1?
+ int graphGroup = 1;
+ for (AlignmentAnnotation alna : currentAnnotations)
+ {
+ graphGroup = Integer.max(graphGroup, alna.graphGroup);
+ }
+ for (AlignmentAnnotation ala : annotations)
+ {
+ if (ala.graphGroup > 0)
+ {
+ ala.graphGroup += graphGroup;
+ }
+
+ // stores original sequence, in what case it ends up as null?
+ SequenceI aseq = null;
+ if (ala.sequenceRef != null)
+ {
+ SequenceI seq = job.seqNames.get(ala.sequenceRef.getName());
+ aseq = seq;
+ while (seq.getDatasetSequence() != null)
+ {
+ seq = seq.getDatasetSequence();
+ }
+ }
+ Annotation[] resAnnot = ala.annotations;
+ Annotation[] gappedAnnot = new Annotation[Math
+ .max(viewport.getAlignment().getWidth(), gapMap.length)];
+ // is it adding gaps which were previously removed to the annotation?
+ for (int p = 0, ap = job.start; ap < gappedAnnot.length; ap++)
+ {
+ if (gapMap != null && gapMap.length > ap && !gapMap[ap])
+ {
+ gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
+ }
+ else if (p < resAnnot.length)
+ {
+ gappedAnnot[ap] = resAnnot[p++];
+ }
+ }
+ // replacing sequence with the original one?
+ ala.sequenceRef = aseq;
+ 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);
+ }
+
+ for (SequenceI sq : job.sequences)
+ {
+ // what are DBRefs? why are they relevant here?
+ if (!sq.getFeatures().hasFeatures() &&
+ (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
+ {
+ continue;
+ }
+ job.transferSequenceFeatures = true;
+ SequenceI seq = job.seqNames.get(sq.getName());
+ SequenceI dseq;
+ ContiguousI seqRange = seq.findPositions(job.start, job.end);
+
+ while ((dseq = seq).getDatasetSequence() != null)
+ {
+ seq = seq.getDatasetSequence();
+ }
+ List<ContiguousI> sourceRange = new ArrayList<>();
+ if (gapMap != null && gapMap.length > job.end)
+ {
+ int lastcol = job.start, col = job.start;
+ do
+ {
+ if (col == job.end || !gapMap[col])
+ {
+ if (lastcol <= col - 1)
+ {
+ seqRange = seq.findPositions(lastcol, col);
+ sourceRange.add(seqRange);
+ }
+ lastcol = col + 1;
+ }
+ } while (++col < job.end);
+ }
+ else
+ {
+ sourceRange.add(seq.findPositions(job.start, job.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);
+ }
+ updateOurAnnots(newAnnots);
+ }
+
+ protected void updateOurAnnots(List<AlignmentAnnotation> annots)
+ {
+ List<AlignmentAnnotation> our = ourAnnots;
+ ourAnnots = Collections.synchronizedList(annots);
+ AlignmentI alignment = viewport.getAlignment();
+ if (our != null)
+ {
+ if (our.size() > 0)
+ {
+ for (AlignmentAnnotation an : our)
+ {
+ if (!ourAnnots.contains(an))
+ {
+ // remove the old annotation
+ alignment.deleteAnnotation(an);
+ }
+ }
+ }
+ our.clear();
+ }
+ // validate rows and update Alignment state
+ synchronized (ourAnnots)
+ {
+ for (AlignmentAnnotation an : ourAnnots)
+ {
+ viewport.getAlignment().validateAnnotation(an);
+ }
+ }
+ // TODO: may need a menu refresh after this
+ // af.setMenusForViewport();
+ alignPanel.adjustAnnotationHeight();
+ }
+}
package jalview.ws2.slivka;
+import static java.lang.String.format;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import jalview.api.AlignViewportI;
+import jalview.api.FeatureColourI;
import jalview.bin.Cache;
import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.FeatureMatcherSetI;
import jalview.io.AnnotationFile;
import jalview.io.DataSourceType;
import jalview.io.FeaturesFile;
return null;
}
- public FeaturesFile getFeaturesFile(WSJob job,
- List<SequenceI> dataset, AlignViewportI viewport) throws IOException
+ public List<AlignmentAnnotation> attachAnnotations(WSJob job,
+ List<SequenceI> dataset, Map<String, FeatureColourI> featureColours,
+ Map<String, FeatureMatcherSetI> featureFilters) throws IOException
{
+ RemoteFile annotFile = null;
+ RemoteFile featFile = null;
+
var slivkaJob = client.getJob(job.getJobId());
Collection<RemoteFile> files = slivkaJob.getResults();
for (RemoteFile f : files)
{
- if (f.getMediaType().equals("application/jalview-features"))
- {
- return new FeaturesFile(f.getContentUrl().toString(), DataSourceType.URL);
- }
+ if (f.getMediaType().equals("application/jalview-annotations"))
+ annotFile = f;
+ else if (f.getMediaType().equals("application/jalview-features"))
+ featFile = f;
}
- return null;
- }
+ Alignment aln = new Alignment(dataset.toArray(new SequenceI[0]));
- public List<AlignmentAnnotation> getAnnotations(WSJob job,
- List<SequenceI> dataset, AlignViewportI viewport) throws IOException
- {
- var slivkaJob = client.getJob(job.getJobId());
- Collection<RemoteFile> files = slivkaJob.getResults();
- for (RemoteFile f : files)
+ boolean annotPresent = annotFile != null;
+ if (annotFile != null)
{
- if (f.getMediaType().equals("application/jalview-annotations"))
- {
- Alignment aln = new Alignment(dataset.toArray(new SequenceI[0]));
- AnnotationFile af = new AnnotationFile();
- boolean valid = af.readAnnotationFileWithCalcId(aln, service.getId(),
- f.getContentUrl().toString(), DataSourceType.URL);
- if (valid)
- {
- return Arrays.asList(aln.getAlignmentAnnotation());
- }
- else
- {
- throw new IOException("Unable to read annotations from file " +
- f.getContentUrl().toString());
- }
- }
+ AnnotationFile af = new AnnotationFile();
+ annotPresent = af.readAnnotationFileWithCalcId(
+ aln, service.getId(), annotFile.getContentUrl().toString(),
+ DataSourceType.URL);
}
- return null;
- }
+ if (annotPresent)
+ Cache.log.debug(format("Annotation file loaded %s", annotFile));
+ else
+ Cache.log.debug(format("No annotations loaded from %s", annotFile));
- public JPredFile getPrediction(WSJob job, List<SequenceI> dataset,
- AlignViewportI viewport) throws IOException
- {
- Collection<RemoteFile> files = client.getJob(job.getJobId()).getResults();
- for (RemoteFile f : files)
+ boolean featPresent = featFile != null;
+ if (featFile != null)
{
- if (f.getLabel().equals("concise"))
- {
- return new JPredFile(f.getContentUrl(), DataSourceType.URL);
- }
+ FeaturesFile ff = new FeaturesFile(featFile.getContentUrl().toString(),
+ DataSourceType.URL);
+ featPresent = ff.parse(aln, featureColours, true);
}
- return null;
+ if (featPresent)
+ Cache.log.debug(format("Features file loaded %s", featFile));
+ else
+ Cache.log.debug(format("No features loaded from %s", annotFile));
+ return Arrays.asList(aln.getAlignmentAnnotation());
}
@Override