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