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