--- /dev/null
+package jalview.ws2.gui;
+
+import static java.lang.String.format;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletionStage;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.ToolTipManager;
+
+import jalview.datamodel.AlignmentView;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.gui.JvSwingUtils;
+import jalview.gui.WebserviceInfo;
+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.PollingTaskExecutor;
+import jalview.ws2.operations.AlignmentOperation;
+import jalview.ws2.operations.AlignmentWorker;
+
+public class AlignmentMenuBuilder implements MenuEntryProviderI
+{
+ AlignmentOperation operation;
+
+ public AlignmentMenuBuilder(AlignmentOperation operation)
+ {
+ this.operation = operation;
+ }
+
+ public void buildMenu(JMenu parent, AlignFrame frame)
+ {
+ if (operation.canSubmitGaps())
+ {
+ var alignSubmenu = new JMenu(operation.getName());
+ buildMenu(alignSubmenu, frame, false);
+ parent.add(alignSubmenu);
+ var realignSubmenu = new JMenu(MessageManager.formatMessage(
+ "label.realign_with_params", operation.getName()));
+ realignSubmenu.setToolTipText(MessageManager
+ .getString("label.align_sequences_to_existing_alignment"));
+ buildMenu(realignSubmenu, frame, true);
+ parent.add(realignSubmenu);
+ }
+ else
+ {
+ buildMenu(parent, frame, false);
+ }
+ }
+
+ protected void buildMenu(JMenu parent, AlignFrame frame,
+ boolean submitGaps)
+ {
+ final String action = submitGaps ? "Align" : "Realign";
+ final var calcName = operation.getName();
+
+ {
+ var item = new JMenuItem(MessageManager.formatMessage(
+ "label.calcname_with_default_settings", calcName));
+ item.setToolTipText(MessageManager
+ .formatMessage("label.action_with_default_settings", action));
+ item.addActionListener((event) -> {
+ final AlignmentView msa = frame.gatherSequencesForAlignment();
+ if (msa != null)
+ {
+ startWorker(frame, msa, Collections.emptyList(), submitGaps);
+ }
+ });
+ 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_alignment"));
+ item.addActionListener((event) -> {
+ AlignmentView msa = frame.gatherSequencesForAlignment();
+ if (msa != null)
+ {
+ openEditParamsDialog(operation.getParamStore(), null, null)
+ .thenAcceptAsync((arguments) -> {
+ if (arguments != null)
+ {
+ startWorker(frame, msa, arguments, submitGaps);
+ }
+ });
+ }
+ });
+ parent.add(item);
+ }
+
+ var presets = operation.getParamStore().getPresets();
+ if (presets != null && presets.size() > 0)
+ {
+ final var presetList = new JMenu(MessageManager
+ .formatMessage("label.run_with_preset_params", calcName));
+ final var showToolTipFor = ToolTipManager.sharedInstance()
+ .getDismissDelay();
+ for (final var preset : presets)
+ {
+ var item = new JMenuItem(preset.getName());
+ final int QUICK_TOOLTIP = 1500;
+ item.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mouseEntered(MouseEvent e)
+ {
+ ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e)
+ {
+ ToolTipManager.sharedInstance().setDismissDelay(showToolTipFor);
+ }
+ });
+ String tooltip = JvSwingUtils.wrapTooltip(true,
+ format("<strong>%s</strong><br/>%s",
+ MessageManager.getString(
+ preset.isModifiable() ? "label.user_preset"
+ : "label.service_preset"),
+ preset.getDescription()));
+ item.setToolTipText(tooltip);
+ item.addActionListener((event) -> {
+ AlignmentView msa = frame.gatherSequencesForAlignment();
+ startWorker(frame, msa, preset.getArguments(), submitGaps);
+ });
+ presetList.add(item);
+ }
+ parent.add(presetList);
+ }
+ }
+
+ 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 void startWorker(AlignFrame frame, AlignmentView msa,
+ List<ArgumentI> arguments, boolean submitGaps)
+ {
+ AlignViewport viewport = frame.getViewport();
+ PollingTaskExecutor executor = frame.getViewport().getWSExecutor();
+ if (msa != null)
+ {
+
+ String panelInfo = String.format("%s using service hosted at %s%n%s",
+ operation.getName(), operation.getHostName(),
+ Objects.requireNonNullElse(operation.getDescription(), ""));
+ var wsInfo = new WebserviceInfo(operation.getName(), panelInfo, false);
+
+ final String alnTitle = frame.getTitle();
+ AlignmentWorker worker = new AlignmentWorker(operation, msa,
+ arguments, submitGaps, true, viewport);
+ String outputHeader = String.format("%s of %s%nJob details%n",
+ submitGaps ? "Re-alignment" : "Alignment", alnTitle);
+
+ var awl = new AlignmentProgressUpdater(worker, wsInfo, frame,
+ outputHeader);
+ worker.setResultConsumer(awl);
+ worker.addListener(awl);
+
+ executor.submit(worker);
+ }
+
+ }
+
+}
--- /dev/null
+package jalview.ws2.gui;
+
+import static java.lang.String.format;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import jalview.ws2.operations.WebServiceWorkerListener;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.HiddenColumns;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.WebserviceInfo;
+import jalview.util.MessageManager;
+import jalview.ws2.operations.AlignmentWorker.AlignmentResult;
+import jalview.ws2.WSJob;
+import jalview.ws2.operations.WebServiceWorkerI;
+
+public class AlignmentProgressUpdater
+ implements WebServiceWorkerListener, Consumer<AlignmentResult>
+{
+ WebServiceWorkerI worker;
+
+ WebserviceInfo wsInfo;
+
+ AlignFrame frame;
+
+ private final WebServiceInfoUpdater wsInfoUpdater;
+
+ String outputHeader;
+
+ AlignmentProgressUpdater(WebServiceWorkerI worker,
+ WebserviceInfo wsInfo, AlignFrame frame, String header)
+ {
+ this.worker = worker;
+ this.wsInfo = wsInfo;
+ this.outputHeader = header;
+ this.wsInfoUpdater = new WebServiceInfoUpdater(worker, wsInfo);
+ wsInfoUpdater.setOutputHeader(header);
+ }
+
+ @Override
+ public void workerStarted(WebServiceWorkerI source)
+ {
+ wsInfo.setVisible(true);
+ }
+
+ @Override
+ public void workerNotStarted(WebServiceWorkerI source)
+ {
+ wsInfo.setVisible(false);
+ // TODO show notification dialog.
+ JvOptionPane.showMessageDialog(frame,
+ MessageManager.getString("info.invalid_msa_input_mininfo"),
+ MessageManager.getString("info.invalid_msa_notenough"),
+ JvOptionPane.INFORMATION_MESSAGE);
+ }
+
+ @Override
+ public void jobCreated(WebServiceWorkerI source, WSJob job)
+ {
+ int tabIndex = wsInfo.addJobPane();
+ wsInfo.setProgressName(format("region %d", job.getJobNum()), tabIndex);
+ wsInfo.setProgressText(tabIndex, outputHeader);
+ job.addPropertyChangeListener(wsInfoUpdater);
+ }
+
+ @Override
+ public void pollException(WebServiceWorkerI source, WSJob job, Exception e)
+ {
+ wsInfo.appendProgressText(job.getJobNum(),
+ MessageManager.formatMessage("info.server_exception",
+ source.getOperation().getName(), e.getMessage()));
+ }
+
+ @Override
+ public void workerCompleting(WebServiceWorkerI source)
+ {
+ wsInfo.setProgressBar(
+ MessageManager.getString("status.collecting_job_results"),
+ worker.getUID());
+ }
+
+ @Override
+ public void workerCompleted(WebServiceWorkerI source)
+ {
+ wsInfo.removeProgressBar(worker.getUID());
+ }
+
+ @Override
+ public void accept(AlignmentResult out)
+ {
+ if (out != null)
+ {
+ wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
+ new Alignment(out.getAlignment()), out.getAlignmentOrders(),
+ out.getHiddenColumns()));
+ wsInfo.setResultsReady();
+ }
+ else
+ {
+ wsInfo.setFinishedNoResults();
+ }
+ }
+
+ private void displayNewFrame(AlignmentI aln,
+ List<AlignmentOrder> alorders, HiddenColumns hidden)
+ {
+ AlignFrame frame = new AlignFrame(aln, hidden,
+ AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+ // TODO store feature renderer settings in worker object
+ // frame.getFeatureRenderer().transferSettings(featureSettings);
+ var regions = sortOrders(alorders);
+ if (alorders.size() == 1)
+ {
+ frame.addSortByOrderMenuItem(format("%s Ordering",
+ worker.getOperation().getName()), alorders.get(0));
+ }
+ else
+ {
+ for (int i = 0; i < alorders.size(); i++)
+ {
+ final int j = i;
+ Iterable<String> iter = () -> regions.get(j).stream()
+ .map(it -> Integer.toString(it)).iterator();
+ var orderName = format("%s Region %s Ordering",
+ worker.getOperation().getName(), String.join(",", iter));
+ frame.addSortByOrderMenuItem(orderName, alorders.get(i));
+ }
+ }
+
+ /* TODO
+ * If alignment was requested from one half of a SplitFrame, show in a
+ * SplitFrame with the other pane similarly aligned.
+ */
+
+ Desktop.addInternalFrame(frame, frame.getTitle(), AlignFrame.DEFAULT_WIDTH,
+ AlignFrame.DEFAULT_HEIGHT);
+ }
+
+ private List<List<Integer>> sortOrders(List<?> alorders)
+ {
+ List<List<Integer>> regions = new ArrayList<>();
+ for (int i = 0; i < alorders.size(); i++)
+ {
+ List<Integer> regs = new ArrayList<>();
+ regs.add(i);
+ int j = i + 1;
+ while (j < alorders.size())
+ {
+ if (alorders.get(i).equals(alorders.get(j)))
+ {
+ alorders.remove(j);
+ regs.add(j);
+ }
+ else
+ {
+ j++;
+ }
+ }
+ regions.add(regs);
+ }
+ return regions;
+ }
+
+}
--- /dev/null
+package jalview.ws2.gui;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Objects;
+
+import jalview.gui.WebserviceInfo;
+import jalview.ws2.WSJob;
+import jalview.ws2.WSJobStatus;
+import jalview.ws2.operations.WebServiceWorkerI;
+
+/**
+ * A helper class that can be attached as a listener to the {@link WSJob}
+ * object. It updates the job status in the {@link jalview.gui.WebServiceInfo}
+ * window according to the state changes of the job object.
+ *
+ * The {@link WebServiceInfoUpdater} object allows to decouple GUI updates
+ * from the web service worker logic.
+ *
+ * @author mmwarowny
+ *
+ */
+public class WebServiceInfoUpdater implements PropertyChangeListener
+{
+ private final WebServiceWorkerI worker;
+ private final WebserviceInfo wsInfo;
+
+ private String outputHeader = "";
+
+ public WebServiceInfoUpdater(WebServiceWorkerI worker, WebserviceInfo wsInfo)
+ {
+ this.worker = worker;
+ this.wsInfo = wsInfo;
+ }
+
+ public String getOutputHeader()
+ {
+ return outputHeader;
+ }
+
+ public void setOutputHeader(String header)
+ {
+ this.outputHeader = header;
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt)
+ {
+ switch (evt.getPropertyName())
+ {
+ case "status":
+ statusChanged(evt);
+ break;
+ case "log":
+ logChanged(evt);
+ break;
+ case "errorLog":
+ errorLogChanged(evt);
+ break;
+ }
+ }
+
+ private void statusChanged(PropertyChangeEvent evt)
+ {
+ WSJob job = (WSJob) evt.getSource();
+ WSJobStatus status = (WSJobStatus) evt.getNewValue();
+ int wsInfoStatus = 0;
+ switch (status)
+ {
+ case READY:
+ case SUBMITTED:
+ case QUEUED:
+ wsInfoStatus = WebserviceInfo.STATE_QUEUING;
+ break;
+ case RUNNING:
+ wsInfoStatus = WebserviceInfo.STATE_RUNNING;
+ break;
+ case FINISHED:
+ wsInfoStatus = WebserviceInfo.STATE_STOPPED_OK;
+ break;
+ case CANCELLED:
+ wsInfoStatus = WebserviceInfo.STATE_CANCELLED_OK;
+ break;
+ case INVALID:
+ case BROKEN:
+ case FAILED:
+ case UNKNOWN:
+ wsInfoStatus = WebserviceInfo.STATE_STOPPED_ERROR;
+ break;
+ case SERVER_ERROR:
+ wsInfoStatus = WebserviceInfo.STATE_STOPPED_SERVERERROR;
+ break;
+ }
+ wsInfo.setStatus(job.getJobNum(), wsInfoStatus);
+ updateWSInfoGlobalStatus();
+ }
+
+ private void logChanged(PropertyChangeEvent evt)
+ {
+ WSJob job = (WSJob) evt.getSource();
+ String oldLog = (String) evt.getOldValue();
+ String newLog = (String) evt.getNewValue();
+ wsInfo.appendProgressText(job.getJobNum(),
+ newLog.substring(oldLog.length()));
+ }
+
+ private void errorLogChanged(PropertyChangeEvent evt)
+ {
+ WSJob job = (WSJob) evt.getSource();
+ String oldLog = (String) evt.getOldValue();
+ String newLog = (String) evt.getNewValue();
+ wsInfo.appendProgressText(job.getJobNum(),
+ newLog.substring(oldLog.length()));
+ }
+
+
+ private void updateWSInfoGlobalStatus()
+ {
+ var jobs = worker.getJobs();
+ if (jobs.countRunning() > 0)
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
+ }
+ else if (jobs.countQueuing() > 0
+ || jobs.countSubmitted() < jobs.size())
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
+ }
+ else
+ {
+ if (jobs.countSuccessful() > 0)
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
+ }
+ else if (jobs.countCancelled() > 0)
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
+ }
+ else if (jobs.countFailed() > 0)
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
+ }
+ }
+ }
+}
--- /dev/null
+package jalview.ws2.operations;
+
+import java.io.IOException;
+
+import jalview.datamodel.AlignmentI;
+import jalview.ws2.WSJob;
+import jalview.ws2.WebServiceI;
+import jalview.ws2.gui.AlignmentMenuBuilder;
+import jalview.ws2.gui.MenuEntryProviderI;
+
+public class AlignmentOperation extends AbstractOperation
+{
+ public static interface AlignmentResultSupplier {
+ public AlignmentI getAlignment(WSJob job) throws IOException;
+ }
+
+ private AlignmentResultSupplier alignmentSupplier;
+
+ public AlignmentOperation(WebServiceI service, AlignmentResultSupplier alignmentSupplier)
+ {
+ super(service, "Alignment");
+ this.alignmentSupplier = alignmentSupplier;
+ }
+
+ @Override
+ public MenuEntryProviderI getMenuBuilder()
+ {
+ return new AlignmentMenuBuilder(this);
+ }
+
+ public AlignmentResultSupplier getAlignmentSupplier()
+ {
+ return alignmentSupplier;
+ }
+}
--- /dev/null
+package jalview.ws2.operations;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import jalview.analysis.AlignSeq;
+import jalview.analysis.AlignmentSorter;
+import jalview.analysis.SeqsetUtils;
+import jalview.analysis.SeqsetUtils.SequenceInfo;
+import jalview.bin.Cache;
+import jalview.datamodel.AlignedCodonFrame;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignViewport;
+import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.WSJob;
+import jalview.ws2.WSJobStatus;
+
+public class AlignmentWorker extends AbstractPollableWorker
+{
+ private AlignmentOperation operation;
+
+ private Consumer<AlignmentResult> resultConsumer;
+
+ private AlignmentView msa;
+
+ private AlignmentI dataset;
+
+ private AlignViewport viewport;
+
+ private List<AlignedCodonFrame> codonFrame = new ArrayList<>();
+
+ private List<ArgumentI> args = Collections.emptyList();
+
+ private boolean submitGaps = false;
+
+ private boolean preserveOrder = false;
+
+ private char gapCharacter;
+
+ private WSJobList<AlignmentJob> jobs = new WSJobList<>();
+
+ private Map<Long, Integer> exceptionCount = new HashMap<>();
+
+ private static final int MAX_RETRY = 5;
+
+ private static class JobInput
+ {
+ final List<SequenceI> inputSequences;
+
+ final List<SequenceI> emptySequences;
+
+ final Map<String, SequenceInfo> sequenceNames;
+
+ private JobInput(List<SequenceI> inputSequences,
+ List<SequenceI> emptySequences,
+ Map<String, SequenceInfo> names)
+ {
+ this.inputSequences = Collections.unmodifiableList(inputSequences);
+ this.emptySequences = Collections.unmodifiableList(emptySequences);
+ this.sequenceNames = names;
+ }
+
+ boolean isInputValid()
+ {
+ return inputSequences.size() >= 2;
+ }
+ }
+
+ public class AlignmentJob extends WSJob
+ {
+ private List<SequenceI> inputSequences;
+ private List<SequenceI> emptySequences;
+ private Map<String, SequenceInfo> sequenceNames;
+
+ private AlignmentJob(String serviceProvider, String serviceName,
+ String hostName)
+ {
+ super(serviceProvider, serviceName, hostName);
+ }
+
+ private void setInput(JobInput input) {
+ inputSequences = input.inputSequences;
+ emptySequences = input.emptySequences;
+ sequenceNames = input.sequenceNames;
+ }
+ }
+
+ public class AlignmentResult
+ {
+ AlignmentI aln;
+
+ List<AlignmentOrder> alorders;
+
+ HiddenColumns hidden;
+
+ AlignmentResult(AlignmentI aln, List<AlignmentOrder> alorders,
+ HiddenColumns hidden)
+ {
+ this.aln = aln;
+ this.alorders = alorders;
+ this.hidden = hidden;
+ }
+
+ public AlignmentI getAlignment()
+ {
+ return aln;
+ }
+
+ public List<AlignmentOrder> getAlignmentOrders()
+ {
+ return alorders;
+ }
+
+ public HiddenColumns getHiddenColumns()
+ {
+ return hidden;
+ }
+ }
+
+ public AlignmentWorker(AlignmentOperation operation,
+ AlignmentView msa, List<ArgumentI> args,
+ boolean submitGaps, boolean preserveOrder, AlignViewport viewport)
+ {
+ this.operation = operation;
+ this.msa = msa;
+ this.dataset = viewport.getAlignment().getDataset();
+ List<AlignedCodonFrame> cf = Objects.requireNonNullElse(
+ viewport.getAlignment().getCodonFrames(), Collections.emptyList());
+ this.codonFrame.addAll(cf);
+ this.args = args;
+ this.submitGaps = submitGaps;
+ this.preserveOrder = preserveOrder;
+ this.viewport = viewport;
+ this.gapCharacter = viewport.getGapCharacter();
+ }
+
+ @Override
+ public Operation getOperation()
+ {
+ return operation;
+ }
+
+ @Override
+ public WSJobList<? extends WSJob> getJobs()
+ {
+ return jobs;
+ }
+
+ public void setResultConsumer(Consumer<AlignmentResult> consumer)
+ {
+ this.resultConsumer = consumer;
+ }
+
+ @Override
+ public void start() throws IOException
+ {
+ Cache.log.info(String.format("Starting new %s job.", operation.getName()));
+ SequenceI[][] conmsa = msa.getVisibleContigs('-');
+ if (conmsa == null)
+ {
+ return;
+ }
+ int numValid = 0;
+ for (int i = 0; i < conmsa.length; i++)
+ {
+ JobInput input = prepareInputData(conmsa[i], 2, submitGaps);
+ AlignmentJob job = new AlignmentJob(operation.service.getProviderName(),
+ operation.getName(), operation.getHostName());
+ job.setJobNum(i);
+ job.setInput(input);
+ jobs.add(job);
+ listeners.fireJobCreated(job);
+ if (input.isInputValid())
+ {
+ int count;
+ String jobId = null;
+ do
+ {
+ count = exceptionCount.getOrDefault(job.getUid(), MAX_RETRY);
+ try
+ {
+ jobId = operation.service.submit(input.inputSequences, args);
+ Cache.log.debug((String.format("Job %s submitted", job)));
+ exceptionCount.remove(job.getUid());
+ } catch (IOException e)
+ {
+ exceptionCount.put(job.getUid(), --count);
+ }
+ } while (jobId == null && count > 0);
+ if (jobId != null)
+ {
+ job.setJobId(jobId);
+ job.setStatus(WSJobStatus.SUBMITTED);
+ numValid++;
+ }
+ else
+ {
+ job.setStatus(WSJobStatus.SERVER_ERROR);
+ }
+ }
+ else
+ {
+ job.setStatus(WSJobStatus.INVALID);
+ job.setErrorLog(
+ MessageManager.getString("label.empty_alignment_job"));
+ }
+ }
+ if (numValid > 0)
+ {
+ listeners.fireWorkerStarted();
+ }
+ else
+ {
+ listeners.fireWorkerNotStarted();
+ }
+ }
+
+
+ private static JobInput prepareInputData(SequenceI[] sequences,
+ int minLength, boolean submitGaps)
+ {
+ assert minLength >= 0 : MessageManager.getString(
+ "error.implementation_error_minlen_must_be_greater_zero");
+ int numSeq = 0;
+ for (SequenceI seq : sequences)
+ {
+ if (seq.getEnd() - seq.getStart() >= minLength)
+ {
+ numSeq++;
+ }
+ }
+
+ List<SequenceI> inputSequences = new ArrayList<>();
+ List<SequenceI> emptySequences = new ArrayList<>();
+ Map<String, SequenceInfo> names = new LinkedHashMap<>();
+ for (int i = 0; i < sequences.length; i++)
+ {
+ SequenceI seq = sequences[i];
+ String newName = SeqsetUtils.unique_name(i);
+ var hash = SeqsetUtils.SeqCharacterHash(seq);
+ names.put(newName, hash);
+ if (numSeq > 1 && seq.getEnd() - seq.getStart() >= minLength)
+ {
+ String seqString = seq.getSequenceAsString();
+ if (!submitGaps)
+ {
+ seqString = AlignSeq.extractGaps(
+ jalview.util.Comparison.GapChars, seqString);
+ }
+ inputSequences.add(new Sequence(newName, seqString));
+ }
+ else
+ {
+ String seqString = "";
+ if (seq.getEnd() >= seq.getStart()) // true if gaps only
+ {
+ seqString = seq.getSequenceAsString();
+ if (!submitGaps)
+ {
+ seqString = AlignSeq.extractGaps(
+ jalview.util.Comparison.GapChars, seqString);
+ }
+ }
+ emptySequences.add(new Sequence(newName, seqString));
+ }
+ }
+
+ return new JobInput(inputSequences, emptySequences, names);
+ }
+
+ @Override
+ public void done()
+ {
+ listeners.fireWorkerCompleting();
+ Map<Long, AlignmentI> results = new LinkedHashMap<>();
+ for (WSJob job : getJobs())
+ {
+ if (job.getStatus().isFailed())
+ continue;
+ try
+ {
+ AlignmentI alignment = operation.getAlignmentSupplier().getAlignment(job);
+ if (alignment != null)
+ {
+ results.put(job.getUid(), alignment);
+ }
+ } catch (Exception e)
+ {
+ if (!operation.getWebService().handleCollectionError(job, e))
+ {
+ Cache.log.error("Couldn't get alignment for job.", e);
+ // TODO: Increment exception count and retry.
+ job.setStatus(WSJobStatus.SERVER_ERROR);
+ }
+ }
+ }
+ if (results.size() > 0)
+ {
+ AlignmentResult out = prepareResult(results);
+ resultConsumer.accept(out);
+ }
+ else
+ {
+ resultConsumer.accept(null);
+ }
+ listeners.fireWorkerCompleted();
+ }
+
+ private AlignmentResult prepareResult(Map<Long, AlignmentI> alignments)
+ {
+ List<AlignmentOrder> alorders = new ArrayList<>();
+ SequenceI[][] results = new SequenceI[jobs.size()][];
+ AlignmentOrder[] orders = new AlignmentOrder[jobs.size()];
+ for (int i = 0; i < jobs.size(); i++)
+ {
+ AlignmentJob job = jobs.get(i);
+ AlignmentI aln = alignments.get(job.getUid());
+ if (aln != null) // equivalent of job.hasResults()
+ {
+ /* Get the alignment including any empty sequences in the original
+ * order with original ids. */
+ char gapChar = aln.getGapCharacter();
+ List<SequenceI> emptySeqs = job.emptySequences;
+ List<SequenceI> alnSeqs = aln.getSequences();
+ // find the width of the longest sequence
+ int width = 0;
+ for (var seq : alnSeqs)
+ width = Integer.max(width, seq.getLength());
+ for (var emptySeq : emptySeqs)
+ width = Integer.max(width, emptySeq.getLength());
+ // pad shorter sequences with gaps
+ String gapSeq = String.join("",
+ Collections.nCopies(width, Character.toString(gapChar)));
+ List<SequenceI> seqs = new ArrayList<>(
+ alnSeqs.size() + emptySeqs.size());
+ seqs.addAll(alnSeqs);
+ seqs.addAll(emptySeqs);
+ for (var seq : seqs)
+ {
+ if (seq.getLength() < width)
+ seq.setSequence(seq.getSequenceAsString()
+ + gapSeq.substring(seq.getLength()));
+ }
+ SequenceI[] result = seqs.toArray(new SequenceI[0]);
+ AlignmentOrder msaOrder = new AlignmentOrder(result);
+ AlignmentSorter.recoverOrder(result);
+ Map<String, SequenceInfo> names = new HashMap<>(job.sequenceNames);
+ // FIXME first call to deuniquify alters original alignment
+ SeqsetUtils.deuniquify(names, result);
+ alorders.add(msaOrder);
+ results[i] = result;
+ orders[i] = msaOrder;
+ }
+ else
+ {
+ results[i] = null;
+ }
+ }
+
+ Object[] newView = msa.getUpdatedView(results, orders, gapCharacter);
+ // free references to original data
+ for (int i = 0; i < jobs.size(); i++)
+ {
+ results[i] = null;
+ orders[i] = null;
+ }
+ SequenceI[] alignment = (SequenceI[]) newView[0];
+ HiddenColumns hidden = (HiddenColumns) newView[1];
+ Alignment aln = new Alignment(alignment);
+ aln.setProperty("Alignment Program", operation.getName());
+ if (dataset != null)
+ aln.setDataset(dataset);
+
+ propagateDatasetMappings(aln);
+ return new AlignmentResult(aln, alorders, hidden);
+ // displayNewFrame(aln, alorders, hidden);
+ }
+
+ /*
+ * conserves dataset references to sequence objects returned from web
+ * services. propagate codon frame data to alignment.
+ */
+ private void propagateDatasetMappings(Alignment aln)
+ {
+ if (codonFrame != null)
+ {
+ SequenceI[] alignment = aln.getSequencesArray();
+ for (SequenceI seq : alignment)
+ {
+ for (AlignedCodonFrame acf : codonFrame)
+ {
+ if (acf != null && acf.involvesSequence(seq))
+ {
+ aln.addCodonFrame(acf);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
public interface WebServiceWorkerI
{
+ long getUID();
+
Operation getOperation();
WSJobList<? extends WSJob> getJobs();
op = new OperationStub(webService, "Protein Disorder");
break;
case "multiple sequence alignment":
- op = new OperationStub(webService, "Alignment");
+ op = new AlignmentOperation(webService, webService::getAlignment);
break;
}
if (op != null)
return false;
}
- public AlignmentI getAlignment(WSJob job, List<SequenceI> dataset,
- AlignViewportI viewport) throws IOException
+ public AlignmentI getAlignment(WSJob job) throws IOException
{
Collection<RemoteFile> files;
var slivkaJob = client.getJob(job.getJobId());