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 supplier; public AlignmentOperation(WebServiceI service, ResultSupplier 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( "%s
%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> openEditParamsDialog(WebServiceI service, WsParamSetI preset, List 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 args = Collections.emptyList(); private String alnTitle = ""; private boolean submitGaps = false; private boolean preserveOrder = false; private WSJobList jobs = new WSJobList(); private WebserviceInfo wsInfo; private Map exceptionCount = new HashMap<>(); private final int MAX_RETRY = 5; AlignmentWorker(AlignmentView msa, List 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 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 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 alignments) { List 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; } } }