Merge branch 'Jalview-JS/develop' into merge_js_develop
[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.jws2.dm.JabaWsParamSet;
10 import jalview.ws.params.ArgumentI;
11
12 import java.util.ArrayList;
13 import java.util.Vector;
14
15 class MsaWSJob extends WsJob
16 {
17   /**
18    * holds basic MSA analysis configuration - todo - encapsulate
19    */
20   private final MsaWSThread msaWSThread;
21
22   long lastChunk = 0;
23
24   /**
25    * input
26    */
27   ArrayList<SequenceI> seqs = new ArrayList<>();
28
29   /**
30    * output
31    */
32   AlignmentI alignment;
33
34   // set if the job didn't get run - then the input is simply returned to the
35   // user
36   private boolean returnInput = false;
37
38   /**
39    * MsaWSJob
40    * 
41    * @param jobNum
42    *          int
43    * @param msaWSThread
44    *          TODO - abstract the properties provided by the thread
45    * @param jobId
46    *          String
47    */
48   public MsaWSJob(MsaWSThread msaWSThread, int jobNum, SequenceI[] inSeqs)
49   {
50     this.msaWSThread = msaWSThread;
51     this.jobnum = jobNum;
52     if (!prepareInput(inSeqs, 2))
53     {
54       submitted = true;
55       subjobComplete = true;
56       returnInput = true;
57     } else
58     {
59       validInput = true;
60     }
61
62   }
63
64   Vector<String[]> emptySeqs = new Vector();
65
66   /**
67    * prepare input sequences for MsaWS service
68    * 
69    * @param seqs
70    *          jalview sequences to be prepared
71    * @param minlen
72    *          minimum number of residues required for this MsaWS service
73    * @return true if seqs contains sequences to be submitted to service.
74    */
75   // TODO: return compbio.seqs list or nothing to indicate validity.
76   private boolean prepareInput(SequenceI[] seqs, int minlen)
77   {
78     // TODO: service specific input data is generated in this method - for
79     // JABAWS it is client-side
80     // prepared, but for Slivka it could be uploaded at this stage.
81
82     int nseqs = 0;
83     if (minlen < 0)
84     {
85       throw new Error(MessageManager.getString(
86               "error.implementation_error_minlen_must_be_greater_zero"));
87     }
88     for (int i = 0; i < seqs.length; i++)
89     {
90       if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
91       {
92         nseqs++;
93       }
94     }
95     boolean valid = nseqs > 1; // need at least two seqs
96     Sequence seq;
97     for (int i = 0, n = 0; i < seqs.length; i++)
98     {
99       String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
100       // for
101       // any
102       // subjob
103       SeqNames.put(newname,
104               jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
105       if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
106       {
107         // make new input sequence with or without gaps
108         seq = new Sequence(newname,
109                 (this.msaWSThread.submitGaps) ? seqs[i].getSequenceAsString()
110                         : AlignSeq.extractGaps(
111                                 jalview.util.Comparison.GapChars,
112                                 seqs[i].getSequenceAsString()));
113         this.seqs.add(seq);
114       }
115       else
116       {
117         String empty = null;
118         if (seqs[i].getEnd() >= seqs[i].getStart())
119         {
120           empty = (this.msaWSThread.submitGaps) ? seqs[i].getSequenceAsString()
121                   : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
122                           seqs[i].getSequenceAsString());
123         }
124         emptySeqs.add(new String[] { newname, empty });
125       }
126     }
127     return valid;
128   }
129
130   /**
131    * 
132    * @return true if getAlignment will return a valid alignment result.
133    */
134   @Override
135   public boolean hasResults()
136   {
137     if (subjobComplete && isFinished() && (alignment != null
138             || (emptySeqs != null && emptySeqs.size() > 0)))
139     {
140       return true;
141     }
142     return false;
143   }
144
145   /**
146    * 
147    * get the alignment including any empty sequences in the original order
148    * with original ids. Caller must access the alignment.getMetadata() object
149    * to annotate the final result passsed to the user.
150    * 
151    * @return { SequenceI[], AlignmentOrder }
152    */
153   public Object[] getAlignment()
154   {
155     // TODO: make this generic based on MsaResultI
156     // TODO: decide if the data loss for this return signature is avoidable
157     // (ie should we just return AlignmentI instead ?)
158     if (hasResults())
159     {
160       SequenceI[] alseqs = null;
161       char alseq_gapchar = '-';
162       int alseq_l = 0;
163       alseqs = new SequenceI[alignment.getSequences().size()];
164       if (alignment.getSequences().size() > 0)
165       {
166         for (SequenceI seq : alignment
167                 .getSequences())
168         {
169           alseqs[alseq_l++] = new Sequence(seq);
170         }
171         alseq_gapchar = alignment.getGapCharacter();
172
173       }
174       // add in the empty seqs.
175       if (emptySeqs.size() > 0)
176       {
177         SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
178         // get width
179         int i, w = 0;
180         if (alseq_l > 0)
181         {
182           for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
183           {
184             if (w < alseqs[i].getLength())
185             {
186               w = alseqs[i].getLength();
187             }
188             t_alseqs[i] = alseqs[i];
189             alseqs[i] = null;
190           }
191         }
192         // check that aligned width is at least as wide as emptySeqs width.
193         int ow = w, nw = w;
194         for (i = 0, w = emptySeqs.size(); i < w; i++)
195         {
196           String[] es = emptySeqs.get(i);
197           if (es != null && es[1] != null)
198           {
199             int sw = es[1].length();
200             if (nw < sw)
201             {
202               nw = sw;
203             }
204           }
205         }
206         // make a gapped string.
207         StringBuffer insbuff = new StringBuffer(w);
208         for (i = 0; i < nw; i++)
209         {
210           insbuff.append(alseq_gapchar);
211         }
212         if (ow < nw)
213         {
214           for (i = 0; i < alseq_l; i++)
215           {
216             int sw = t_alseqs[i].getLength();
217             if (nw > sw)
218             {
219               // pad at end
220               alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
221                       + insbuff.substring(0, sw - nw));
222             }
223           }
224         }
225         for (i = 0, w = emptySeqs.size(); i < w; i++)
226         {
227           String[] es = emptySeqs.get(i);
228           if (es[1] == null)
229           {
230             t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
231                     insbuff.toString(), 1, 0);
232           }
233           else
234           {
235             if (es[1].length() < nw)
236             {
237               t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
238                       es[0],
239                       es[1] + insbuff.substring(0, nw - es[1].length()),
240                       1, 1 + es[1].length());
241             }
242             else
243             {
244               t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
245                       es[0], es[1]);
246             }
247           }
248         }
249         alseqs = t_alseqs;
250       }
251       AlignmentOrder msaorder = new AlignmentOrder(alseqs);
252       // always recover the order - makes parseResult()'s life easier.
253       jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
254       // account for any missing sequences
255       jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
256       return new Object[] { alseqs, msaorder };
257     }
258     return null;
259   }
260
261   /**
262    * mark subjob as cancelled and set result object appropriatly
263    */
264   void cancel()
265   {
266     cancelled = true;
267     subjobComplete = true;
268     alignment = null;
269   }
270
271   /**
272    * 
273    * @return boolean true if job can be submitted.
274    */
275   @Override
276   public boolean hasValidInput()
277   {
278     // TODO: get attributes for this MsaWS instance to check if it can do two
279     // sequence alignment.
280     // TODO: check type of sequences are valid for this service
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 }