JAL-3066 Implement clustaloWS using slivka system.
[jalview.git] / src / jalview / ws / gui / MsaWSJob.java
1 package jalview.ws.gui;
2
3 import jalview.analysis.AlignSeq;
4 import jalview.datamodel.AlignmentI;
5 import jalview.datamodel.AlignmentOrder;
6 import jalview.datamodel.Sequence;
7 import jalview.datamodel.SequenceI;
8 import jalview.util.MessageManager;
9 import jalview.ws.api.JobId;
10 import jalview.ws.jws2.dm.JabaWsParamSet;
11 import jalview.ws.params.ArgumentI;
12
13 import java.util.ArrayList;
14 import java.util.Vector;
15
16 class MsaWSJob extends WsJob
17 {
18   /**
19    * holds basic MSA analysis configuration - todo - encapsulate
20    */
21   private final MsaWSThread msaWSThread;
22
23   long lastChunk = 0;
24
25   /**
26    * input
27    */
28   ArrayList<SequenceI> seqs = new ArrayList<>();
29
30   /**
31    * output
32    */
33   AlignmentI alignment;
34
35   // set if the job didn't get run - then the input is simply returned to the
36   // user
37   private boolean returnInput = false;
38
39   /**
40    * MsaWSJob
41    * 
42    * @param jobNum
43    *          int
44    * @param msaWSThread
45    *          TODO - abstract the properties provided by the thread
46    * @param jobId
47    *          String
48    */
49   public MsaWSJob(MsaWSThread msaWSThread, int jobNum, SequenceI[] inSeqs)
50   {
51     this.msaWSThread = msaWSThread;
52     this.jobnum = jobNum;
53     if (!prepareInput(inSeqs, 2))
54     {
55       submitted = true;
56       subjobComplete = true;
57       returnInput = true;
58     } else
59     {
60       validInput = true;
61     }
62
63   }
64
65   Vector<String[]> emptySeqs = new Vector();
66
67   /**
68    * prepare input sequences for MsaWS service
69    * 
70    * @param seqs
71    *          jalview sequences to be prepared
72    * @param minlen
73    *          minimum number of residues required for this MsaWS service
74    * @return true if seqs contains sequences to be submitted to service.
75    */
76   // TODO: return compbio.seqs list or nothing to indicate validity.
77   private boolean prepareInput(SequenceI[] seqs, int minlen)
78   {
79     // TODO: service specific input data is generated in this method - for
80     // JABAWS it is client-side
81     // prepared, but for Slivka it could be uploaded at this stage.
82
83     int nseqs = 0;
84     if (minlen < 0)
85     {
86       throw new Error(MessageManager.getString(
87               "error.implementation_error_minlen_must_be_greater_zero"));
88     }
89     for (int i = 0; i < seqs.length; i++)
90     {
91       if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
92       {
93         nseqs++;
94       }
95     }
96     boolean valid = nseqs > 1; // need at least two seqs
97     Sequence seq;
98     for (int i = 0, n = 0; i < seqs.length; i++)
99     {
100       String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
101       // for
102       // any
103       // subjob
104       SeqNames.put(newname,
105               jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
106       if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
107       {
108         // make new input sequence with or without gaps
109         seq = new Sequence(newname,
110                 (this.msaWSThread.submitGaps) ? seqs[i].getSequenceAsString()
111                         : AlignSeq.extractGaps(
112                                 jalview.util.Comparison.GapChars,
113                                 seqs[i].getSequenceAsString()));
114         this.seqs.add(seq);
115       }
116       else
117       {
118         String empty = null;
119         if (seqs[i].getEnd() >= seqs[i].getStart())
120         {
121           empty = (this.msaWSThread.submitGaps) ? seqs[i].getSequenceAsString()
122                   : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
123                           seqs[i].getSequenceAsString());
124         }
125         emptySeqs.add(new String[] { newname, empty });
126       }
127     }
128     return valid;
129   }
130
131   /**
132    * 
133    * @return true if getAlignment will return a valid alignment result.
134    */
135   @Override
136   public boolean hasResults()
137   {
138     if (subjobComplete && isFinished() && (alignment != null
139             || (emptySeqs != null && emptySeqs.size() > 0)))
140     {
141       return true;
142     }
143     return false;
144   }
145
146   /**
147    * 
148    * get the alignment including any empty sequences in the original order
149    * with original ids. Caller must access the alignment.getMetadata() object
150    * to annotate the final result passsed to the user.
151    * 
152    * @return { SequenceI[], AlignmentOrder }
153    */
154   public Object[] getAlignment()
155   {
156     // TODO: make this generic based on MsaResultI
157     // TODO: decide if the data loss for this return signature is avoidable
158     // (ie should we just return AlignmentI instead ?)
159     if (hasResults())
160     {
161       SequenceI[] alseqs = null;
162       char alseq_gapchar = '-';
163       int alseq_l = 0;
164       alseqs = new SequenceI[alignment.getSequences().size()];
165       if (alignment.getSequences().size() > 0)
166       {
167         for (SequenceI seq : alignment
168                 .getSequences())
169         {
170           alseqs[alseq_l++] = new Sequence(seq);
171         }
172         alseq_gapchar = alignment.getGapCharacter();
173
174       }
175       // add in the empty seqs.
176       if (emptySeqs.size() > 0)
177       {
178         SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
179         // get width
180         int i, w = 0;
181         if (alseq_l > 0)
182         {
183           for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
184           {
185             if (w < alseqs[i].getLength())
186             {
187               w = alseqs[i].getLength();
188             }
189             t_alseqs[i] = alseqs[i];
190             alseqs[i] = null;
191           }
192         }
193         // check that aligned width is at least as wide as emptySeqs width.
194         int ow = w, nw = w;
195         for (i = 0, w = emptySeqs.size(); i < w; i++)
196         {
197           String[] es = emptySeqs.get(i);
198           if (es != null && es[1] != null)
199           {
200             int sw = es[1].length();
201             if (nw < sw)
202             {
203               nw = sw;
204             }
205           }
206         }
207         // make a gapped string.
208         StringBuffer insbuff = new StringBuffer(w);
209         for (i = 0; i < nw; i++)
210         {
211           insbuff.append(alseq_gapchar);
212         }
213         if (ow < nw)
214         {
215           for (i = 0; i < alseq_l; i++)
216           {
217             int sw = t_alseqs[i].getLength();
218             if (nw > sw)
219             {
220               // pad at end
221               alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
222                       + insbuff.substring(0, sw - nw));
223             }
224           }
225         }
226         for (i = 0, w = emptySeqs.size(); i < w; i++)
227         {
228           String[] es = emptySeqs.get(i);
229           if (es[1] == null)
230           {
231             t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
232                     insbuff.toString(), 1, 0);
233           }
234           else
235           {
236             if (es[1].length() < nw)
237             {
238               t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
239                       es[0],
240                       es[1] + insbuff.substring(0, nw - es[1].length()),
241                       1, 1 + es[1].length());
242             }
243             else
244             {
245               t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
246                       es[0], es[1]);
247             }
248           }
249         }
250         alseqs = t_alseqs;
251       }
252       AlignmentOrder msaorder = new AlignmentOrder(alseqs);
253       // always recover the order - makes parseResult()'s life easier.
254       jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
255       // account for any missing sequences
256       jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
257       return new Object[] { alseqs, msaorder };
258     }
259     return null;
260   }
261
262   /**
263    * mark subjob as cancelled and set result object appropriatly
264    */
265   void cancel()
266   {
267     cancelled = true;
268     subjobComplete = true;
269     alignment = null;
270   }
271
272   /**
273    * 
274    * @return boolean true if job can be submitted.
275    */
276   @Override
277   public boolean hasValidInput()
278   {
279     // TODO: get attributes for this MsaWS instance to check if it can do two
280     // sequence alignment.
281     if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
282     {
283       return true;
284     }
285     return false;
286   }
287
288   StringBuffer jobProgress = new StringBuffer();
289
290   @Override
291   public void setStatus(String string)
292   {
293     jobProgress.setLength(0);
294     jobProgress.append(string);
295   }
296
297   @Override
298   public String getStatus()
299   {
300     return jobProgress.toString();
301   }
302
303   @Override
304   public boolean hasStatus()
305   {
306     return jobProgress != null;
307   }
308
309   /**
310    * @return the lastChunk
311    */
312   public long getLastChunk()
313   {
314     return lastChunk;
315   }
316
317   /**
318    * @param lastChunk
319    *          the lastChunk to set
320    */
321   public void setLastChunk(long lastChunk)
322   {
323     this.lastChunk = lastChunk;
324   }
325
326   String alignmentProgram = null;
327
328   public String getAlignmentProgram()
329   {
330     return alignmentProgram;
331   }
332
333   public boolean hasArguments()
334   {
335     return (arguments != null && arguments.size() > 0)
336             || (preset != null && preset instanceof JabaWsParamSet);
337   }
338
339   /**
340    * add a progess header to status string containing presets/args used
341    */
342   public void addInitialStatus()
343   {
344     // TODO: decide if it is useful to report 'JABAWS format' argument lists
345     // rather than generic Jalview service arguments
346     if (preset != null)
347     {
348       jobProgress.append(
349               "Using " + (preset.isModifiable() ? "Server" : "User")
350                       + "Preset: " + preset.getName());
351       for (ArgumentI opt : preset.getArguments())
352       {
353         jobProgress.append(opt.getName() + " " + opt.getValue() + "\n");
354       }
355     }
356     else
357     {
358       if (arguments != null && arguments.size() > 0)
359       {
360         jobProgress.append("With custom parameters : \n");
361         // merge arguments with preset's own arguments.
362         for (ArgumentI opt : arguments)
363         {
364           jobProgress.append(opt.getName() + " " + opt.getValue() + "\n");
365         }
366       }
367       jobProgress.append("\nJob Output:\n");
368     }
369   }
370
371   JobId jobHandle = null;
372   public void setJobHandle(JobId align)
373   {
374     jobHandle = align;
375     setJobId(jobHandle.getJobId());
376
377   }
378
379   public JobId getJobHandle()
380   {
381     return jobHandle;
382   }
383
384 }