1 package jalview.ws2.actions.alignment;
3 import static java.lang.String.format;
5 import java.io.IOException;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.Collections;
9 import java.util.HashMap;
10 import java.util.List;
13 import jalview.analysis.AlignmentSorter;
14 import jalview.analysis.SeqsetUtils;
15 import jalview.analysis.SeqsetUtils.SequenceInfo;
16 import jalview.api.AlignViewportI;
17 import jalview.bin.Cache;
18 import jalview.bin.Console;
19 import jalview.datamodel.AlignedCodonFrame;
20 import jalview.datamodel.Alignment;
21 import jalview.datamodel.AlignmentI;
22 import jalview.datamodel.AlignmentOrder;
23 import jalview.datamodel.AlignmentView;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.Sequence;
26 import jalview.datamodel.SequenceI;
27 import jalview.ws.params.ArgumentI;
28 import jalview.ws2.actions.AbstractPollableTask;
29 import jalview.ws2.actions.ServiceInputInvalidException;
30 import jalview.ws2.actions.api.TaskEventListener;
31 import jalview.ws2.api.Credentials;
32 import jalview.ws2.api.JobStatus;
33 import jalview.ws2.client.api.AlignmentWebServiceClientI;
36 * Implementation of an abstract pollable task used by alignment service
42 class AlignmentTask extends AbstractPollableTask<AlignmentJob, AlignmentResult>
44 /* task parameters set in the constructor */
45 private final AlignmentWebServiceClientI client;
47 private final AlignmentAction action;
49 private final AlignmentView msa; // a.k.a. input
51 private final AlignViewportI viewport;
53 private final boolean submitGaps;
55 private final AlignmentI currentView;
57 private final AlignmentI dataset;
59 private final char gapChar;
61 private final List<AlignedCodonFrame> codonFrame = new ArrayList<>();
63 AlignmentTask(AlignmentWebServiceClientI client, AlignmentAction action,
64 List<ArgumentI> args, Credentials credentials,
65 AlignmentView msa, AlignViewportI viewport, boolean submitGaps,
66 TaskEventListener<AlignmentResult> eventListener)
68 super(client, args, credentials, eventListener);
72 this.viewport = viewport;
73 this.submitGaps = submitGaps;
74 this.currentView = viewport.getAlignment();
75 this.dataset = viewport.getAlignment().getDataset();
76 this.gapChar = viewport.getGapCharacter();
77 List<AlignedCodonFrame> cf = viewport.getAlignment().getCodonFrames();
79 this.codonFrame.addAll(cf);
83 protected List<AlignmentJob> prepare() throws ServiceInputInvalidException
85 Console.info(format("starting alignment service %s:%s",
86 client.getClientName(), action.getName()));
87 SequenceI[][] conmsa = msa.getVisibleContigs(gapChar);
90 throw new ServiceInputInvalidException("no visible contigs for alignment");
92 List<AlignmentJob> jobs = new ArrayList<>(conmsa.length);
93 boolean validInput = false;
94 for (int i = 0; i < conmsa.length; i++)
96 AlignmentJob job = AlignmentJob.create(conmsa[i], 2, submitGaps);
97 validInput |= job.isInputValid(); // at least one input is valid
98 job.setStatus(job.isInputValid() ? JobStatus.READY : JobStatus.INVALID);
104 throw new ServiceInputInvalidException("no valid sequences for alignment");
110 protected AlignmentResult done() throws IOException
112 IOException lastIOE = null;
113 for (AlignmentJob job : jobs)
115 if (job.isInputValid() && job.getStatus() == JobStatus.COMPLETED &&
120 job.setAlignmentResult(client.getAlignment(job.getServerJob()));
121 } catch (IOException e)
128 throw lastIOE; // do not proceed unless all results has been retrieved
130 List<AlignmentOrder> alorders = new ArrayList<>();
131 SequenceI[][] results = new SequenceI[jobs.size()][];
132 AlignmentOrder[] orders = new AlignmentOrder[jobs.size()];
133 for (int i = 0; i < jobs.size(); i++)
135 /* alternative implementation of MsaWSJob#getAlignment */
136 AlignmentJob job = jobs.get(i);
137 if (!job.hasResult())
139 AlignmentI alignment = job.getAlignmentResult();
140 int alnSize = alignment.getSequences().size();
141 char gapChar = alnSize > 0 ? alignment.getGapCharacter() : '-';
142 List<SequenceI> emptySeqs = job.getEmptySequences();
143 List<SequenceI> alnSeqs = new ArrayList<>(alnSize);
144 // create copies of all sequences involved
145 for (SequenceI seq : alignment.getSequences())
147 alnSeqs.add(new Sequence(seq));
149 for (SequenceI seq : emptySeqs)
151 alnSeqs.add(new Sequence(seq));
153 // find the width of the longest sequence
155 for (var seq: alnSeqs)
156 width = Integer.max(width, seq.getLength());
157 // make a sequence of gaps only to cut/paste
160 char[] gaps = new char[width];
161 Arrays.fill(gaps, gapChar);
162 gapSeq = new String(gaps);
164 for (var seq: alnSeqs)
166 if (seq.getLength() < width)
168 // pad sequences shorter than the target width with gaps
169 seq.setSequence(seq.getSequenceAsString()
170 + gapSeq.substring(seq.getLength()));
173 SequenceI[] result = alnSeqs.toArray(new SequenceI[0]);
174 AlignmentOrder msaOrder = new AlignmentOrder(result);
175 AlignmentSorter.recoverOrder(result);
176 Map<String, SequenceInfo> names = new HashMap<>(job.getNames());
177 SeqsetUtils.deuniquify(names, result);
179 alorders.add(msaOrder);
181 orders[i] = msaOrder;
183 Object[] newView = msa.getUpdatedView(results, orders, gapChar);
184 // free references to original data
185 for (int i = 0; i < jobs.size(); i++)
190 SequenceI[] alignment = (SequenceI[]) newView[0];
191 HiddenColumns hidden = (HiddenColumns) newView[1];
192 Alignment aln = new Alignment(alignment);
193 aln.setProperty("Alignment Program", action.getName());
195 aln.setDataset(dataset);
197 propagateDatasetMappings(aln);
198 return new AlignmentResult(aln, alorders, hidden);
202 * Conserve dataset references to sequence objects returned from web services.
203 * Propagate AlignedCodonFrame data from {@code codonFrame} to {@code aln}.
204 * TODO: Refactor to datamodel
206 private void propagateDatasetMappings(AlignmentI aln)
208 if (codonFrame != null)
210 SequenceI[] alignment = aln.getSequencesArray();
211 for (final SequenceI seq : alignment)
213 for (AlignedCodonFrame acf : codonFrame)
215 if (acf != null && acf.involvesSequence(seq))
217 aln.addCodonFrame(acf);