moved visible sub-alignment extraction and update from MsaWSThread to
[jalview.git] / src / jalview / ws / MsaWSThread.java
1 package jalview.ws;
2
3 import java.util.*;
4
5 import jalview.analysis.*;
6 import jalview.bin.*;
7 import jalview.datamodel.*;
8 import jalview.datamodel.Alignment;
9 import jalview.datamodel.Sequence;
10 import jalview.gui.*;
11 import vamsas.objects.simple.MsaResult;
12
13 /**
14  * <p>
15  * Title:
16  * </p>
17  *
18  * <p>
19  * Description:
20  * </p>
21  *
22  * <p>
23  * Copyright: Copyright (c) 2004
24  * </p>
25  *
26  * <p>
27  * Company: Dundee University
28  * </p>
29  *
30  * @author not attributable
31  * @version 1.0
32  */
33 class MsaWSThread
34     extends WSThread implements WSClientI
35 {
36   boolean submitGaps = false; // pass sequences including gaps to alignment
37
38   // service
39
40   boolean preserveOrder = true; // and always store and recover sequence
41
42   // order
43
44   class MsaWSJob extends WSThread.WSJob
45   {
46     // hold special input for this
47     vamsas.objects.simple.SequenceSet seqs = new vamsas.objects.simple.
48         SequenceSet();
49
50     /**
51      * MsaWSJob
52      *
53      * @param jobNum
54      *            int
55      * @param jobId
56      *            String
57      */
58     public MsaWSJob(int jobNum, SequenceI[] inSeqs)
59     {
60       this.jobnum = jobNum;
61       if (!prepareInput(inSeqs, 2))
62       {
63         submitted = true;
64         subjobComplete = true;
65         result = new MsaResult();
66         result.setFinished(true);
67         result.setStatus("Job never ran - input returned to user.");
68       }
69
70     }
71
72     Hashtable SeqNames = new Hashtable();
73     Vector emptySeqs = new Vector();
74     /**
75      * prepare input sequences for MsaWS service
76      * @param seqs jalview sequences to be prepared
77      * @param minlen minimum number of residues required for this MsaWS service
78      * @return true if seqs contains sequences to be submitted to service.
79      */
80     private boolean prepareInput(SequenceI[] seqs, int minlen)
81     {
82       int nseqs = 0;
83       if (minlen < 0)
84       {
85         throw new Error("Implementation error: minlen must be zero or more.");
86       }
87       for (int i = 0; i < seqs.length; i++)
88       {
89         if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
90         {
91           nseqs++;
92         }
93       }
94       boolean valid = nseqs > 1; // need at least two seqs
95       vamsas.objects.simple.Sequence[] seqarray =
96           (valid)
97           ? new vamsas.objects.simple.Sequence[nseqs]
98           : null;
99       for (int i = 0, n = 0; i < seqs.length; i++)
100       {
101
102         String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
103         // for
104         // any
105         // subjob
106         SeqNames.put(newname, jalview.analysis.SeqsetUtils
107                      .SeqCharacterHash(seqs[i]));
108         if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
109         {
110           seqarray[n] = new vamsas.objects.simple.Sequence();
111           seqarray[n].setId(newname);
112           seqarray[n++].setSeq( (submitGaps) ? seqs[i].getSequence()
113                                : AlignSeq.extractGaps(
114                                    jalview.util.Comparison.GapChars, seqs[i]
115                                    .getSequence()));
116         }
117         else
118         {
119           String empty = null;
120           if (seqs[i].getEnd() >= seqs[i].getStart())
121           {
122             empty = (submitGaps) ? seqs[i].getSequence()
123                 : AlignSeq.extractGaps(
124                     jalview.util.Comparison.GapChars, seqs[i]
125                     .getSequence());
126           }
127           emptySeqs.add(new String[]
128                         {newname, empty});
129         }
130       }
131       this.seqs = new vamsas.objects.simple.SequenceSet();
132       this.seqs.setSeqs(seqarray);
133       return valid;
134     }
135
136     /**
137      *
138      * @return true if getAlignment will return a valid alignment result.
139      */
140     public boolean hasResults()
141     {
142       if (subjobComplete && result != null && result.isFinished()
143           && ((MsaResult) result).getMsa() != null && ((MsaResult) result).getMsa().getSeqs() != null)
144       {
145         return true;
146       }
147       return false;
148     }
149
150     public Object[] getAlignment()
151     {
152
153       if (result != null && result.isFinished())
154       {
155         SequenceI[] alseqs = null;
156         char alseq_gapchar = '-';
157         int alseq_l = 0;
158         if (((MsaResult) result).getMsa() != null)
159         {
160           alseqs = getVamsasAlignment(((MsaResult) result).getMsa());
161           alseq_gapchar = ((MsaResult) result).getMsa().getGapchar().charAt(0);
162           alseq_l = alseqs.length;
163         }
164         if (emptySeqs.size() > 0)
165         {
166           SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
167           // get width
168           int i, w = 0;
169           if (alseq_l > 0)
170           {
171             for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
172             {
173               if (w < alseqs[i].getLength())
174               {
175                 w = alseqs[i].getLength();
176               }
177               t_alseqs[i] = alseqs[i];
178               alseqs[i] = null;
179             }
180           }
181           // check that aligned width is at least as wide as emptySeqs width.
182           int ow = w, nw = w;
183           for (i = 0, w = emptySeqs.size(); i < w; i++)
184           {
185             String[] es = (String[]) emptySeqs.get(i);
186             if (es != null && es[1] != null)
187             {
188               int sw = es[1].length();
189               if (nw < sw)
190               {
191                 nw = sw;
192               }
193             }
194           }
195           // make a gapped string.
196           StringBuffer insbuff = new StringBuffer(w);
197           for (i = 0; i < nw; i++)
198           {
199             insbuff.append(alseq_gapchar);
200           }
201           if (ow < nw)
202           {
203             for (i = 0; i < alseq_l; i++)
204             {
205               int sw = t_alseqs[i].getLength();
206               if (nw > sw)
207               {
208                 // pad at end
209                 alseqs[i].setSequence(t_alseqs[i].getSequence() +
210                                       insbuff.substring(0, sw - nw));
211               }
212             }
213           }
214           for (i = 0, w = emptySeqs.size(); i < w; i++)
215           {
216             String[] es = (String[]) emptySeqs.get(i);
217             if (es[1] == null)
218             {
219               t_alseqs[i +
220                   alseq_l] = new jalview.datamodel.Sequence(es[0],
221                   insbuff.toString(), 1, 0);
222             }
223             else
224             {
225               if (es[1].length() < nw)
226               {
227                 t_alseqs[i +
228                     alseq_l] = new jalview.datamodel.Sequence(es[0],
229                     es[1] + insbuff.substring(0, nw - es[1].length()), 1,
230                     1 + es[1].length());
231               }
232               else
233               {
234                 t_alseqs[i +
235                     alseq_l] = new jalview.datamodel.Sequence(es[0], es[1]);
236               }
237             }
238           }
239           alseqs = t_alseqs;
240         }
241         AlignmentOrder msaorder = new AlignmentOrder(alseqs);
242         // always recover the order - makes parseResult()'s life easier.
243         jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
244         // account for any missing sequences
245         jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
246         return new Object[]
247             {
248             alseqs, msaorder};
249       }
250       return null;
251     }
252     /**
253      * mark subjob as cancelled and set result object appropriatly
254      */
255     void cancel() {
256       cancelled=true;
257       subjobComplete = true;
258       result = null;
259     }
260     /**
261      *
262      * @return boolean true if job can be submitted.
263      */
264     boolean hasValidInput() {
265       if (seqs.getSeqs()!=null)
266         return true;
267       return false;
268     }
269   }
270
271
272   String alTitle; // name which will be used to form new alignment window.
273   Alignment dataset; // dataset to which the new alignment will be
274
275   // associated.
276
277   ext.vamsas.MuscleWS server = null;
278   /**
279    * set basic options for this (group) of Msa jobs
280    *
281    * @param subgaps
282    *            boolean
283    * @param presorder
284    *            boolean
285    */
286   MsaWSThread(ext.vamsas.MuscleWS server, String wsUrl,
287               WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
288               AlignmentView alview,
289               String wsname, boolean subgaps, boolean presorder)
290   {
291     this.server = server;
292     this.WsUrl = wsUrl;
293     this.wsInfo = wsinfo;
294     this.WebServiceName = wsname;
295     this.input = alview;
296     this.submitGaps = subgaps;
297     this.preserveOrder = presorder;
298     this.alignFrame = alFrame;
299   }
300
301   /**
302    * create one or more Msa jobs to align visible seuqences in _msa
303    *
304    * @param title
305    *            String
306    * @param _msa
307    *            AlignmentView
308    * @param subgaps
309    *            boolean
310    * @param presorder
311    *            boolean
312    * @param seqset
313    *            Alignment
314    */
315   MsaWSThread(ext.vamsas.MuscleWS server, String wsUrl,
316               WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
317               String wsname, String title, AlignmentView _msa, boolean subgaps,
318               boolean presorder, Alignment seqset)
319   {
320     this(server, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
321     OutputHeader = wsInfo.getProgressText();
322     alTitle = title;
323     dataset = seqset;
324     SequenceI[][] conmsa = _msa.getVisibleContigs('-');
325     if (conmsa != null)
326     {
327       int njobs = conmsa.length;
328       jobs = new MsaWSJob[njobs];
329       for (int j = 0; j < njobs; j++)
330       {
331         if (j != 0)
332         {
333           jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
334         }
335         else
336         {
337           jobs[j] = new MsaWSJob(0, conmsa[j]);
338         }
339         if (njobs > 0)
340           wsinfo.setProgressName("region " + jobs[j].jobnum, jobs[j].jobnum);
341         wsinfo.setProgressText(jobs[j].jobnum, OutputHeader);
342       }
343     }
344   }
345   public boolean isCancellable()
346   {
347     return true;
348   }
349
350   public void cancelJob()
351   {
352     if (!jobComplete && jobs != null)
353     {
354       boolean cancelled = true;
355       for (int job = 0; job < jobs.length; job++)
356       {
357         if (jobs[job].submitted && !jobs[job].subjobComplete)
358         {
359           String cancelledMessage = "";
360           try
361           {
362             vamsas.objects.simple.WsJobId cancelledJob = server
363                 .cancel(jobs[job].jobId);
364             if (cancelledJob.getStatus() == 2)
365             {
366               // CANCELLED_JOB
367               cancelledMessage = "Job cancelled.";
368               ((MsaWSJob) jobs[job]).cancel();
369               wsInfo.setStatus(jobs[job].jobnum,
370                                WebserviceInfo.STATE_CANCELLED_OK);
371             }
372             else if (cancelledJob.getStatus() == 3)
373             {
374               // VALID UNSTOPPABLE JOB
375               cancelledMessage +=
376                   "Server cannot cancel this job. just close the window.\n";
377               cancelled = false;
378               // wsInfo.setStatus(jobs[job].jobnum,
379               //                 WebserviceInfo.STATE_RUNNING);
380             }
381
382             if (cancelledJob.getJobId() != null)
383             {
384               cancelledMessage += ("[" + cancelledJob.getJobId() + "]");
385             }
386
387             cancelledMessage += "\n";
388           }
389           catch (Exception exc)
390           {
391             cancelledMessage +=
392                 ("\nProblems cancelling the job : Exception received...\n"
393                  + exc + "\n");
394             Cache.log.warn("Exception whilst cancelling "+jobs[job].jobId,exc);
395           }
396           wsInfo.setProgressText(jobs[job].jobnum, OutputHeader
397                                  + cancelledMessage + "\n");
398         }
399       }
400       if (cancelled)
401       {
402         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
403         jobComplete = true;
404       }
405       this.interrupt(); // kick thread to update job states.
406     }
407     else
408     {
409       if (!jobComplete)
410       {
411         wsInfo
412             .setProgressText(OutputHeader
413                              + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
414       }
415     }
416   }
417   void pollJob(WSJob job) throws Exception {
418     ((MsaWSJob) job).result = server.getResult(((MsaWSJob) job).jobId);
419   }
420   void StartJob(WSJob job)
421   {
422     if (!(job instanceof MsaWSJob)) {
423       throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "+job.getClass());
424     }
425     MsaWSJob j = (MsaWSJob) job;
426     if (j.submitted)
427     {
428       if (Cache.log.isDebugEnabled())
429       {
430         Cache.log.debug("Tried to submit an already submitted job " + j.jobId);
431       }
432       return;
433     }
434     if (j.seqs.getSeqs() == null)
435     {
436       // special case - selection consisted entirely of empty sequences...
437       j.submitted = true;
438       j.result = new MsaResult();
439       j.result.setFinished(true);
440       j.result.setStatus("Empty Alignment Job");
441       ((MsaResult) j.result).setMsa(null);
442     }
443     try
444     {
445       vamsas.objects.simple.WsJobId jobsubmit = server.align(j.seqs);
446
447       if ( (jobsubmit != null) && (jobsubmit.getStatus() == 1))
448       {
449         j.jobId = jobsubmit.getJobId();
450         j.submitted = true;
451         j.subjobComplete = false;
452         // System.out.println(WsURL + " Job Id '" + jobId + "'");
453       }
454       else
455       {
456         if (jobsubmit == null)
457         {
458           throw new Exception(
459               "Server at "
460               + WsUrl
461               +
462               " returned null object, it probably cannot be contacted. Try again later ?");
463         }
464
465         throw new Exception(jobsubmit.getJobId());
466       }
467     }
468     catch (Exception e)
469     {
470       // TODO: JBPNote catch timeout or other fault types explicitly
471       // For unexpected errors
472       System.err
473           .println(WebServiceName
474                    + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
475                    + "When contacting Server:" + WsUrl + "\n"
476                    + e.toString() + "\n");
477       j.allowedServerExceptions = 0;
478       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
479       wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_SERVERERROR);
480       wsInfo
481           .appendProgressText(
482               j.jobnum,
483               "Failed to submit sequences for alignment.\n"
484               + "It is most likely that there is a problem with the server.\n"
485               + "Just close the window\n");
486
487       // e.printStackTrace(); // TODO: JBPNote DEBUG
488     }
489   }
490
491   private jalview.datamodel.Sequence[] getVamsasAlignment(
492       vamsas.objects.simple.Alignment valign)
493   {
494     vamsas.objects.simple.Sequence[] seqs = valign.getSeqs().getSeqs();
495     jalview.datamodel.Sequence[] msa = new jalview.datamodel.Sequence[seqs.
496         length];
497
498     for (int i = 0, j = seqs.length; i < j; i++)
499     {
500       msa[i] = new jalview.datamodel.Sequence(seqs[i].getId(), seqs[i]
501                                               .getSeq());
502     }
503
504     return msa;
505   }
506
507   void parseResult()
508   {
509     int results = 0; // number of result sets received
510     JobStateSummary finalState = new JobStateSummary();
511     try
512     {
513       for (int j = 0; j < jobs.length; j++)
514       {
515         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
516         if (jobs[j].submitted && jobs[j].subjobComplete && jobs[j].hasResults())
517         {
518           results++;
519           vamsas.objects.simple.Alignment valign = ((MsaResult) jobs[j].result).getMsa();
520           if (valign != null)
521           {
522             wsInfo.appendProgressText(jobs[j].jobnum,
523                                       "\nAlignment Object Method Notes\n");
524             String[] lines = valign.getMethod();
525             for (int line = 0; line < lines.length; line++)
526             {
527               wsInfo.appendProgressText(jobs[j].jobnum, lines[line] + "\n");
528             }
529             // JBPNote The returned files from a webservice could be
530             //  hidden behind icons in the monitor window that,
531             // when clicked, pop up their corresponding data
532           }
533         }
534       }
535     }
536     catch (Exception ex)
537     {
538
539       Cache.log.error("Unexpected exception when processing results for " +
540                       alTitle, ex);
541       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
542     }
543     if (results > 0)
544     {
545       wsInfo.showResultsNewFrame
546           .addActionListener(new java.awt.event.ActionListener()
547       {
548         public void actionPerformed(
549             java.awt.event.ActionEvent evt)
550         {
551           displayResults(true);
552         }
553       });
554       wsInfo.mergeResults
555           .addActionListener(new java.awt.event.ActionListener()
556       {
557         public void actionPerformed(
558             java.awt.event.ActionEvent evt)
559         {
560           displayResults(false);
561         }
562       });
563       wsInfo.setResultsReady();
564     } else
565       wsInfo.setFinishedNoResults();
566   }
567
568   void displayResults(boolean newFrame)
569   {
570     // view input or result data for each block
571     Vector alorders = new Vector();
572     SequenceI[][] results=new SequenceI[jobs.length][];
573     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
574     SequenceI[] first=null;
575     for (int j=0; j<jobs.length; j++) {
576       if (jobs[j].hasResults()) {
577         Object[] res = ( (MsaWSJob) jobs[j]).getAlignment();
578         alorders.add(res[1]);
579         results[j] = (SequenceI[]) res[0];
580         orders[j] = (AlignmentOrder) res[1];
581 //    SequenceI[] alignment = input.getUpdated
582       } else {
583         results[j]=null;
584       }
585     }
586     Object[] newview = input.getUpdatedView(results, orders, '-');
587     // trash references to original result data
588     for (int j=0; j<jobs.length; j++) {
589       results[j] = null;
590       orders[j] = null;
591     }
592     SequenceI[] alignment = (SequenceI[]) newview[0];
593     ColumnSelection columnselection = (ColumnSelection) newview[1];
594     Alignment al = new Alignment(alignment);
595     if (dataset != null)
596     {
597       al.setDataset(dataset);
598     }
599
600     // JBNote- TODO: warn user if a block is input rather than aligned data ?
601
602     if (newFrame)
603     {
604       AlignFrame af = new AlignFrame(al, columnselection);
605
606       // >>>This is a fix for the moment, until a better solution is
607       // found!!<<<
608       af.getFeatureRenderer().transferSettings(
609           alignFrame.getFeatureRenderer());
610       // update orders
611       if (alorders.size() > 0)
612       {
613         if (alorders.size() == 1)
614         {
615           af.addSortByOrderMenuItem(WebServiceName + " Ordering",
616                                     (AlignmentOrder) alorders.get(0));
617         }
618         else
619         {
620           // construct a non-redundant ordering set
621           Vector names = new Vector();
622           for (int i = 0, l = alorders.size(); i < l; i++)
623           {
624             String orderName = new String(" Region " + i);
625             int j = i + 1;
626             int r = l;
627             while (j < l)
628             {
629               if ( ( (AlignmentOrder) alorders.get(i)).equals( ( (
630                   AlignmentOrder) alorders.get(j))))
631               {
632                 alorders.remove(j);
633                 l--;
634                 orderName += "," + j;
635               }
636               else
637               {
638                 j++;
639               }
640             }
641
642             if (i == 0 && j == 1)
643             {
644               names.add(new String(""));
645             }
646             else
647             {
648               names.add(orderName);
649             }
650           }
651           for (int i = 0, l = alorders.size(); i < l; i++)
652           {
653             af.addSortByOrderMenuItem(WebServiceName
654                                       +( (String) names.get(i)) +
655                                       " Ordering",
656                                       (AlignmentOrder) alorders.get(i));
657           }
658         }
659       }
660
661       Desktop.addInternalFrame(af, alTitle,
662                                AlignFrame.NEW_WINDOW_WIDTH,
663                                AlignFrame.NEW_WINDOW_HEIGHT);
664
665     }
666     else
667     {
668       System.out.println("MERGE WITH OLD FRAME");
669
670     }
671   }
672
673   public boolean canMergeResults()
674   {
675     return false;
676   }
677 }