0ac84ee18af139cb999ab1323a6c9a45d7ddfd10
[jalview.git] / src / jalview / ws2 / actions / alignment / AlignmentTask.java
1 package jalview.ws2.actions.alignment;
2
3 import static java.lang.String.format;
4
5 import java.io.IOException;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.HashMap;
9 import java.util.List;
10 import java.util.Map;
11
12 import jalview.analysis.AlignmentSorter;
13 import jalview.analysis.SeqsetUtils;
14 import jalview.analysis.SeqsetUtils.SequenceInfo;
15 import jalview.api.AlignViewportI;
16 import jalview.bin.Console;
17 import jalview.datamodel.AlignedCodonFrame;
18 import jalview.datamodel.Alignment;
19 import jalview.datamodel.AlignmentI;
20 import jalview.datamodel.AlignmentOrder;
21 import jalview.datamodel.AlignmentView;
22 import jalview.datamodel.HiddenColumns;
23 import jalview.datamodel.Sequence;
24 import jalview.datamodel.SequenceI;
25 import jalview.ws.params.ArgumentI;
26 import jalview.ws2.actions.BaseTask;
27 import jalview.ws2.actions.ServiceInputInvalidException;
28 import jalview.ws2.api.Credentials;
29 import jalview.ws2.api.JobStatus;
30 import jalview.ws2.client.api.AlignmentWebServiceClientI;
31
32 /**
33  * Implementation of an abstract pollable task used by alignment service
34  * actions.
35  * 
36  * @author mmwarowny
37  *
38  */
39 class AlignmentTask extends BaseTask<AlignmentJob, AlignmentResult>
40 {
41   /* task parameters set in the constructor */
42   private final AlignmentWebServiceClientI client;
43
44   private final AlignmentAction action;
45
46   private final AlignmentView msa; // a.k.a. input
47
48   private final AlignViewportI viewport;
49
50   private final boolean submitGaps;
51
52   private final AlignmentI currentView;
53
54   private final AlignmentI dataset;
55
56   private final char gapChar;
57
58   private final List<AlignedCodonFrame> codonFrame = new ArrayList<>();
59
60   AlignmentTask(AlignmentWebServiceClientI client, AlignmentAction action,
61       List<ArgumentI> args, Credentials credentials,
62       AlignViewportI viewport, boolean submitGaps)
63   {
64     super(client, args, credentials);
65     this.client = client;
66     this.action = action;
67     this.msa = viewport.getAlignmentView(true);
68     this.viewport = viewport;
69     this.submitGaps = submitGaps;
70     this.currentView = viewport.getAlignment();
71     this.dataset = viewport.getAlignment().getDataset();
72     this.gapChar = viewport.getGapCharacter();
73     List<AlignedCodonFrame> cf = viewport.getAlignment().getCodonFrames();
74     if (cf != null)
75       this.codonFrame.addAll(cf);
76   }
77   
78   @Override
79   protected List<AlignmentJob> prepareJobs() throws ServiceInputInvalidException
80   { 
81     Console.info(format("starting alignment service %s:%s",
82         client.getClientName(), action.getName()));
83     SequenceI[][] conmsa = msa.getVisibleContigs(gapChar);
84     if (conmsa == null)
85     {
86       throw new ServiceInputInvalidException("no visible contigs for alignment");
87     }
88     List<AlignmentJob> jobs = new ArrayList<>(conmsa.length);
89     boolean validInput = false;
90     for (int i = 0; i < conmsa.length; i++)
91     {
92       AlignmentJob job = AlignmentJob.create(conmsa[i], 2, submitGaps);
93       validInput |= job.isInputValid();  // at least one input is valid
94       job.setStatus(job.isInputValid() ? JobStatus.READY : JobStatus.INVALID);
95       jobs.add(job);
96     }
97     this.jobs = jobs;
98     if (!validInput)
99     {
100       throw new ServiceInputInvalidException("no valid sequences for alignment");
101     }
102     return jobs;
103   }
104
105   @Override
106   protected AlignmentResult collectResult(List<AlignmentJob> jobs) throws IOException
107   {
108     IOException lastIOE = null;
109     for (AlignmentJob job : jobs)
110     {
111       if (job.isInputValid() && job.getStatus() == JobStatus.COMPLETED &&
112           !job.hasResult())
113       {
114         try
115         {
116           job.setAlignmentResult(client.getAlignment(job.getServerJob()));
117         } catch (IOException e)
118         {
119           lastIOE = e;
120         }
121       }
122     }
123     if (lastIOE != null)
124       throw lastIOE;  // do not proceed unless all results has been retrieved
125     
126     List<AlignmentOrder> alorders = new ArrayList<>();
127     SequenceI[][] results = new SequenceI[jobs.size()][];
128     AlignmentOrder[] orders = new AlignmentOrder[jobs.size()];
129     for (int i = 0; i < jobs.size(); i++)
130     {
131       /* alternative implementation of MsaWSJob#getAlignment */
132       AlignmentJob job = jobs.get(i);
133       if (!job.hasResult())
134         continue;
135       AlignmentI alignment = job.getAlignmentResult();
136       int alnSize = alignment.getSequences().size();
137       char gapChar = alnSize > 0 ? alignment.getGapCharacter() : '-';
138       List<SequenceI> emptySeqs = job.getEmptySequences();
139       List<SequenceI> alnSeqs = new ArrayList<>(alnSize);
140       // create copies of all sequences involved
141       for (SequenceI seq : alignment.getSequences())
142       {
143         alnSeqs.add(new Sequence(seq));
144       }
145       for (SequenceI seq : emptySeqs)
146       {
147         alnSeqs.add(new Sequence(seq));
148       }
149       // find the width of the longest sequence
150       int width = 0;
151       for (var seq: alnSeqs)
152         width = Integer.max(width, seq.getLength());
153       // make a sequence of gaps only to cut/paste
154       String gapSeq;
155       {
156         char[] gaps = new char[width];
157         Arrays.fill(gaps, gapChar);
158         gapSeq = new String(gaps);
159       }
160       for (var seq: alnSeqs)
161       {
162         if (seq.getLength() < width)
163         {
164           // pad sequences shorter than the target width with gaps
165           seq.setSequence(seq.getSequenceAsString()
166               + gapSeq.substring(seq.getLength()));
167         }
168       }
169       SequenceI[] result = alnSeqs.toArray(new SequenceI[0]);
170       AlignmentOrder msaOrder = new AlignmentOrder(result);
171       AlignmentSorter.recoverOrder(result);
172       Map<String, SequenceInfo> names = new HashMap<>(job.getNames());
173       SeqsetUtils.deuniquify(names, result);
174       
175       alorders.add(msaOrder);
176       results[i] = result;
177       orders[i] = msaOrder;
178     }
179     Object[] newView = msa.getUpdatedView(results, orders, gapChar);
180     // free references to original data
181     for (int i = 0; i < jobs.size(); i++)
182     {
183       results[i] = null;
184       orders[i] = null;
185     }
186     SequenceI[] alignment = (SequenceI[]) newView[0];
187     HiddenColumns hidden = (HiddenColumns) newView[1];
188     Alignment aln = new Alignment(alignment);
189     aln.setProperty("Alignment Program", action.getName());
190     if (dataset != null)
191       aln.setDataset(dataset);
192     
193     propagateDatasetMappings(aln);
194     return new AlignmentResult(aln, alorders, hidden);
195   }
196   
197   /**
198    * Conserve dataset references to sequence objects returned from web services.
199    * Propagate AlignedCodonFrame data from {@code codonFrame} to {@code aln}.
200    * TODO: Refactor to datamodel
201    */
202   private void propagateDatasetMappings(AlignmentI aln)
203   {
204     if (codonFrame != null)
205     {
206       SequenceI[] alignment = aln.getSequencesArray();
207       for (final SequenceI seq : alignment)
208       {
209         for (AlignedCodonFrame acf : codonFrame)
210         {
211           if (acf != null && acf.involvesSequence(seq))
212           {
213             aln.addCodonFrame(acf);
214             break;
215           }
216         }
217       }
218     }
219   }
220 }