package jalview.ws2.actions.alignment; import static java.lang.String.format; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import jalview.analysis.AlignmentSorter; import jalview.analysis.SeqsetUtils; import jalview.analysis.SeqsetUtils.SequenceInfo; import jalview.api.AlignViewportI; import jalview.bin.Console; 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.ws.params.ArgumentI; import jalview.ws2.actions.BaseTask; import jalview.ws2.actions.ServiceInputInvalidException; import jalview.ws2.api.Credentials; import jalview.ws2.api.JobStatus; import jalview.ws2.client.api.AlignmentWebServiceClientI; /** * Implementation of an abstract pollable task used by alignment service * actions. * * @author mmwarowny * */ class AlignmentTask extends BaseTask { /* task parameters set in the constructor */ private final AlignmentWebServiceClientI client; private final AlignmentAction action; private final AlignmentView msa; // a.k.a. input private final AlignViewportI viewport; private final boolean submitGaps; private final AlignmentI currentView; private final AlignmentI dataset; private final char gapChar; private final List codonFrame = new ArrayList<>(); AlignmentTask(AlignmentWebServiceClientI client, AlignmentAction action, List args, Credentials credentials, AlignViewportI viewport, boolean submitGaps) { super(client, args, credentials); this.client = client; this.action = action; this.msa = viewport.getAlignmentView(true); this.viewport = viewport; this.submitGaps = submitGaps; this.currentView = viewport.getAlignment(); this.dataset = viewport.getAlignment().getDataset(); this.gapChar = viewport.getGapCharacter(); List cf = viewport.getAlignment().getCodonFrames(); if (cf != null) this.codonFrame.addAll(cf); } @Override protected List prepareJobs() throws ServiceInputInvalidException { Console.info(format("starting alignment service %s:%s", client.getClientName(), action.getName())); SequenceI[][] conmsa = msa.getVisibleContigs(gapChar); if (conmsa == null) { throw new ServiceInputInvalidException("no visible contigs for alignment"); } List jobs = new ArrayList<>(conmsa.length); boolean validInput = false; for (int i = 0; i < conmsa.length; i++) { AlignmentJob job = AlignmentJob.create(conmsa[i], 2, submitGaps); validInput |= job.isInputValid(); // at least one input is valid job.setStatus(job.isInputValid() ? JobStatus.READY : JobStatus.INVALID); jobs.add(job); } this.jobs = jobs; if (!validInput) { throw new ServiceInputInvalidException("no valid sequences for alignment"); } return jobs; } @Override protected AlignmentResult collectResult(List jobs) throws IOException { IOException lastIOE = null; for (AlignmentJob job : jobs) { if (job.isInputValid() && job.getStatus() == JobStatus.COMPLETED && !job.hasResult()) { try { job.setAlignmentResult(client.getAlignment(job.getServerJob())); } catch (IOException e) { lastIOE = e; } } } if (lastIOE != null) throw lastIOE; // do not proceed unless all results has been retrieved List alorders = new ArrayList<>(); SequenceI[][] results = new SequenceI[jobs.size()][]; AlignmentOrder[] orders = new AlignmentOrder[jobs.size()]; for (int i = 0; i < jobs.size(); i++) { /* alternative implementation of MsaWSJob#getAlignment */ AlignmentJob job = jobs.get(i); if (!job.hasResult()) continue; AlignmentI alignment = job.getAlignmentResult(); int alnSize = alignment.getSequences().size(); char gapChar = alnSize > 0 ? alignment.getGapCharacter() : '-'; List emptySeqs = job.getEmptySequences(); List alnSeqs = new ArrayList<>(alnSize); // create copies of all sequences involved for (SequenceI seq : alignment.getSequences()) { alnSeqs.add(new Sequence(seq)); } for (SequenceI seq : emptySeqs) { alnSeqs.add(new Sequence(seq)); } // find the width of the longest sequence int width = 0; for (var seq: alnSeqs) width = Integer.max(width, seq.getLength()); // make a sequence of gaps only to cut/paste String gapSeq; { char[] gaps = new char[width]; Arrays.fill(gaps, gapChar); gapSeq = new String(gaps); } for (var seq: alnSeqs) { if (seq.getLength() < width) { // pad sequences shorter than the target width with gaps seq.setSequence(seq.getSequenceAsString() + gapSeq.substring(seq.getLength())); } } SequenceI[] result = alnSeqs.toArray(new SequenceI[0]); AlignmentOrder msaOrder = new AlignmentOrder(result); AlignmentSorter.recoverOrder(result); Map names = new HashMap<>(job.getNames()); SeqsetUtils.deuniquify(names, result); alorders.add(msaOrder); results[i] = result; orders[i] = msaOrder; } Object[] newView = msa.getUpdatedView(results, orders, gapChar); // 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", action.getName()); if (dataset != null) aln.setDataset(dataset); propagateDatasetMappings(aln); return new AlignmentResult(aln, alorders, hidden); } /** * Conserve dataset references to sequence objects returned from web services. * Propagate AlignedCodonFrame data from {@code codonFrame} to {@code aln}. * TODO: Refactor to datamodel */ private void propagateDatasetMappings(AlignmentI aln) { if (codonFrame != null) { SequenceI[] alignment = aln.getSequencesArray(); for (final SequenceI seq : alignment) { for (AlignedCodonFrame acf : codonFrame) { if (acf != null && acf.involvesSequence(seq)) { aln.addCodonFrame(acf); break; } } } } } }