--- /dev/null
+package jalview.ws2.operations;
+
+import static java.lang.String.format;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+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.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.ToolTipManager;
+
+import org.codehaus.groovy.ast.GenericsType.GenericsTypeName;
+
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.JvSwingUtils;
+import jalview.gui.WebserviceInfo;
+import jalview.gui.WsJobParameters;
+import jalview.util.MathUtils;
+import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.WsParamSetI;
+import jalview.ws2.MenuEntryProviderI;
+import jalview.ws2.ResultSupplier;
+import jalview.ws2.WSJob;
+import jalview.ws2.WSJobStatus;
+import jalview.ws2.WebServiceExecutor;
+import jalview.ws2.WebServiceI;
+import jalview.ws2.WebServiceInfoUpdater;
+import jalview.ws2.WebServiceWorkerI;
+import jalview.ws2.utils.WSJobList;
+
+/**
+ *
+ * @author mmwarowny
+ *
+ */
+public class AlignmentOperation implements Operation
+{
+ final WebServiceI service;
+ final ResultSupplier<AlignmentI> supplier;
+
+ public AlignmentOperation(WebServiceI service, ResultSupplier<AlignmentI> supplier) {
+ this.service = service;
+ this.supplier = supplier;
+ }
+
+ @Override public int getMinSequences() { return 2; }
+ @Override public int getMaxSequences() { return Integer.MAX_VALUE; }
+ @Override public boolean isProteinOperation() { return true; }
+ @Override public boolean isNucleotideOperation() { return true; }
+ @Override public boolean canSubmitGaps() {
+ // hack copied from original jabaws code, don't blame me
+ return service.getName().contains("lustal");
+ }
+ @Override public MenuEntryProviderI getMenuBuilder() { return this::buildMenu; }
+
+ protected void buildMenu(JMenu parent, AlignFrame frame) {
+ if (canSubmitGaps()) {
+ var alignSubmenu = new JMenu(service.getName());
+ buildMenu(alignSubmenu, frame, false);
+ parent.add(alignSubmenu);
+ var realignSubmenu = new JMenu(MessageManager.formatMessage(
+ "label.realign_with_params", service.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 = service.getName();
+
+ final AlignmentView msa = frame.gatherSequencesForAlignment();
+ final AlignmentI dataset = frame.getViewport().getAlignment().getDataset();
+ String title = frame.getTitle();
+ WebServiceExecutor executor = frame.getViewport().getWSExecutor();
+ {
+ 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) -> {
+ if (msa != null)
+ {
+ WebServiceWorkerI worker = new AlignmentWorker(
+ msa, Collections.emptyList(), title, submitGaps, true, dataset);
+ executor.submit(worker);
+ }
+ });
+ parent.add(item);
+ }
+
+ if (service.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) -> {
+ if (msa != null)
+ {
+ openEditParamsDialog(service, null, null).thenAcceptAsync((arguments) ->{
+ if (arguments != null)
+ {
+ WebServiceWorkerI worker = new AlignmentWorker(
+ msa, arguments, title, submitGaps, true, dataset);
+ executor.submit(worker);
+ }
+ });
+ }
+ });
+ parent.add(item);
+ }
+
+ var presets = service.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) -> {
+ if (msa != null)
+ {
+ WebServiceWorkerI worker = new AlignmentWorker(
+ msa, preset.getArguments(), title, submitGaps, true, dataset);
+ executor.submit(worker);
+ }
+ });
+ presetList.add(item);
+ }
+ parent.add(presetList);
+ }
+ }
+
+
+ private CompletionStage<List<ArgumentI>> openEditParamsDialog(WebServiceI service,
+ WsParamSetI preset, List<ArgumentI> arguments)
+ {
+ WsJobParameters jobParams;
+ if (preset == null && arguments != null && arguments.size() > 0)
+ jobParams = new WsJobParameters(service.getParamStore(), preset, arguments);
+ else
+ jobParams = new WsJobParameters(service.getParamStore(), preset, null);
+ 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;
+ }
+ });
+ }
+
+ /**
+ * Implementation of the web service worker performing multiple sequence
+ * alignment.
+ *
+ * @author mmwarowny
+ *
+ */
+ private class AlignmentWorker implements WebServiceWorkerI {
+
+ private long uid = MathUtils.getUID();
+ private final AlignmentView msa;
+ private final AlignmentI seqdata;
+ private List<ArgumentI> args = Collections.emptyList();
+ private String alnTitle = "";
+ private boolean submitGaps = false;
+ private boolean preserveOrder = false;
+ private WSJobList jobs = new WSJobList();
+ private WebserviceInfo wsInfo;
+ private Map<Long, Integer> exceptionCount = new HashMap<>();
+ private final int MAX_RETRY = 5;
+
+ AlignmentWorker(AlignmentView msa, List<ArgumentI> args, String alnTitle,
+ boolean submitGaps, boolean preserveOrder, AlignmentI seqdata)
+ {
+ this.msa = msa;
+ this.seqdata = seqdata;
+ this.args = args;
+ this.alnTitle = alnTitle;
+ this.submitGaps = submitGaps;
+ this.preserveOrder = preserveOrder;
+
+ String panelInfo = String.format("%s using service hosted at %s%n%s",
+ service.getName(), service.getHostName(),
+ Objects.requireNonNullElse(service.getDescription(), ""));
+ wsInfo = new WebserviceInfo(service.getName(), panelInfo, false);
+ }
+
+
+ @Override
+ public long getUID() { return uid; }
+
+ @Override
+ public List<WSJob> getJobs()
+ {
+ return Collections.unmodifiableList(jobs);
+ }
+
+ @Override
+ public void startJobs() throws IOException
+ {
+ String outputHeader = String.format("%s of %s%nJob details%n",
+ submitGaps ? "Re-alignment" : "Alignment", alnTitle);
+ SequenceI[][] conmsa = msa.getVisibleContigs('-');
+ if (conmsa == null) {
+ return;
+ }
+ WebServiceInfoUpdater updater = new WebServiceInfoUpdater(wsInfo);
+ updater.setOutputHeader(outputHeader);
+ for (int i = 0; i < conmsa.length; i++) {
+ WSJob job = service.submit(List.of(conmsa[i]), args);
+ job.setJobNum(wsInfo.addJobPane());
+ job.addPropertyChangeListener(updater);
+ jobs.add(job);
+ if (conmsa.length > 0) {
+ wsInfo.setProgressName(String.format("region %d", i), job.getJobNum());
+ }
+ wsInfo.setProgressText(job.getJobNum(), outputHeader);
+ }
+ }
+
+ @Override
+ public boolean pollJobs()
+ {
+ boolean done = true;
+ for (WSJob job : getJobs()) {
+ if (!job.getStatus().isDone() ) {
+ try {
+ service.updateProgress(job);
+ exceptionCount.remove(job.getUid());
+ }
+ catch (IOException e) {
+ int count = exceptionCount.getOrDefault(job.getUid(), MAX_RETRY);
+ if (--count <= 0) {
+ job.setStatus(WSJobStatus.SERVER_ERROR);
+ }
+ exceptionCount.put(job.getUid(), count);
+ }
+ }
+ done &= job.getStatus().isDone();
+ }
+ updateWSInfoGlobalStatus();
+ return done;
+ }
+
+ private void updateWSInfoGlobalStatus() {
+ 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);
+ }
+ }
+ }
+
+ @Override
+ public void done() {
+ long progbarId = MathUtils.getUID();
+ wsInfo.setProgressBar(
+ MessageManager.getString("status.collecting_job_results"),
+ progbarId);
+ Map<Long, AlignmentI> results = new LinkedHashMap<>();
+ for (WSJob job : getJobs()) {
+ try {
+ AlignmentI alignment = supplier.getResult(job);
+ if (alignment != null) {
+ results.put(job.getUid(), alignment);
+ }
+ }
+ catch (Exception e) {
+ if (!service.handleCollectionError(job, e)) {
+ Cache.log.error("Couldn't get alignment for job.", e);
+ // TODO: Increment exception count and retry.
+ job.setStatus(WSJobStatus.SERVER_ERROR);
+ }
+ }
+ }
+ updateWSInfoGlobalStatus();
+ if (results.size() > 0) {
+ wsInfo.showResultsNewFrame.addActionListener(
+ evt -> displayResults(results));
+ wsInfo.setResultsReady();
+ }
+ else {
+ wsInfo.setFinishedNoResults();
+ }
+ wsInfo.removeProgressBar(progbarId);
+ }
+
+ private void displayResults(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++) {
+ WSJob job = jobs.get(i);
+ AlignmentI aln = alignments.get(job.getUid());
+ if (aln != null) {
+ /*
+ * Get the alignment including any empty sequences in the original
+ * order with original ids.
+ */
+ char gapChar = aln.getGapCharacter();
+ int alSeqLen = aln.getSequences().size();
+ SequenceI[] alSeqs = new SequenceI[alSeqLen];
+ alSeqs = aln.getSequences().toArray(alSeqs);
+ }
+ else {
+ results[i] = null;
+ }
+ }
+ }
+
+ @Override
+ public WebServiceI getWebService()
+ {
+ return service;
+ }
+
+ }
+
+}