Refactored Jnet and added hidden columns support.
[jalview.git] / src / jalview / ws / JPredClient.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2005 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 javax.swing.*;
24
25 import ext.vamsas.*;
26 import jalview.analysis.*;
27 import jalview.bin.*;
28 import jalview.datamodel.*;
29 import jalview.datamodel.Alignment;
30 import jalview.datamodel.AlignmentView;
31 import jalview.gui.*;
32 import jalview.io.*;
33 import jalview.util.*;
34 import jalview.ws.WSThread.*;
35 import vamsas.objects.simple.*;
36
37 public class JPredClient
38     extends WSClient
39 {
40     /**
41      * crate a new GUI JPred Job
42      * @param sh ServiceHandle
43      * @param title String
44      * @param msa boolean - true - submit alignment as a sequence profile
45      * @param alview AlignmentView
46      */
47     public JPredClient(ext.vamsas.ServiceHandle sh, String title, boolean msa, AlignmentView alview, AlignFrame parentFrame) {
48     super();
49     wsInfo=setWebService(sh);
50     startJPredClient(title, msa, alview, parentFrame);
51
52   }
53   /**
54    * startJPredClient
55    * TODO: refine submission to cope with local prediction of visible regions or multiple single sequence jobs
56    * TODO: sequence representative support - could submit alignment of representatives as msa.
57    * TODO:  msa hidden region prediction - submit each chunk for prediction. concatenate results of each.
58    * TODO:  single seq prediction - submit each contig of each sequence for prediction (but must cope with flanking regions and short seqs)
59    * @param title String
60    * @param msa boolean
61    * @param alview AlignmentView
62    */
63   private void startJPredClient(String title, boolean msa,
64                                 jalview.datamodel.AlignmentView alview, AlignFrame parentFrame)
65   {
66     AlignmentView input = alview;
67     if (wsInfo == null)
68     {
69       wsInfo = setWebService();
70     }
71     Jpred server = locateWebService();
72     if (server == null)
73     {
74       Cache.log.warn("Couldn't find a Jpred webservice to invoke!");
75       return;
76     }
77
78     SeqCigar[] msf = input.getSequences();
79     SequenceI seq = msf[0].getSeq('-');
80     if (msa && msf.length > 1)
81     {
82
83       String altitle = "JNet prediction on " + seq.getName() +
84           " using alignment from " + title;
85
86       wsInfo.setProgressText("Job details for MSA based prediction (" +
87                              title + ") on sequence :\n>" + seq.getName() +
88                              "\n" +
89                              AlignSeq.extractGaps("-. ", seq.getSequence()) +
90                              "\n");
91       SequenceI aln[] = new SequenceI[msf.length];
92       for (int i = 0, j = msf.length; i < j; i++)
93       {
94         aln[i] = msf[i].getSeq('-');
95       }
96
97       Hashtable SequenceInfo = jalview.analysis.SeqsetUtils.uniquify(aln, true);
98
99       JPredThread jthread = new JPredThread(wsInfo, altitle, server,
100                                             SequenceInfo, aln, alview, parentFrame);
101       wsInfo.setthisService(jthread);
102       jthread.start();
103     }
104     else
105     {
106       if (!msa && msf.length>1)
107         throw new Error("Implementation Error! Multiple single sequence prediction jobs are not yet supported.");
108       wsInfo.setProgressText("Job details for prediction on sequence :\n>" +
109                              seq.getName() + "\n" +
110                              AlignSeq.extractGaps("-. ", seq.getSequence()) +
111                              "\n");
112       String altitle = "JNet prediction for sequence " + seq.getName() +
113           " from " +
114           title;
115
116       Hashtable SequenceInfo = jalview.analysis.SeqsetUtils.SeqCharacterHash(
117           seq);
118
119       JPredThread jthread = new JPredThread(wsInfo, altitle, server,
120                                             SequenceInfo, seq, alview, parentFrame);
121       wsInfo.setthisService(jthread);
122       jthread.start();
123     }
124   }
125   public JPredClient(ext.vamsas.ServiceHandle sh, String title, SequenceI seq, AlignFrame parentFrame)
126   {
127     super();
128     wsInfo = setWebService(sh);
129     startJPredClient(title, seq, parentFrame);
130   }
131
132   public JPredClient(ext.vamsas.ServiceHandle sh, String title, SequenceI[] msa, AlignFrame parentFrame)
133   {
134     wsInfo = setWebService(sh);
135     startJPredClient(title, msa, parentFrame);
136   }
137
138   public JPredClient(String title, SequenceI[] msf)
139   {
140     startJPredClient(title, msf, null);
141   }
142
143   public JPredClient(String title, SequenceI seq)
144   {
145     startJPredClient(title, seq, null);
146   }
147
148   private void startJPredClient(String title, SequenceI[] msf, AlignFrame parentFrame)
149   {
150     if (wsInfo == null)
151     {
152       wsInfo = setWebService();
153     }
154
155     SequenceI seq = msf[0];
156
157     String altitle = "JNet prediction on " + seq.getName() +
158         " using alignment from " + title;
159
160     wsInfo.setProgressText("Job details for MSA based prediction (" +
161                            title + ") on sequence :\n>" + seq.getName() + "\n" +
162                            AlignSeq.extractGaps("-. ", seq.getSequence()) +
163                            "\n");
164     SequenceI aln[] = new SequenceI[msf.length];
165     for (int i = 0, j = msf.length; i < j; i++)
166     {
167       aln[i] = new jalview.datamodel.Sequence(msf[i]);
168     }
169
170     Hashtable SequenceInfo = jalview.analysis.SeqsetUtils.uniquify(aln, true);
171
172     Jpred server = locateWebService();
173     if (server==null)
174     {
175       return;
176     }
177
178     JPredThread jthread = new JPredThread(wsInfo, altitle, server, SequenceInfo, aln,null, parentFrame);
179     wsInfo.setthisService(jthread);
180     jthread.start();
181   }
182
183   public void startJPredClient(String title, SequenceI seq, AlignFrame parentFrame)
184   {
185     if (wsInfo == null)
186     {
187       wsInfo = setWebService();
188     }
189     wsInfo.setProgressText("Job details for prediction on sequence :\n>" +
190                            seq.getName() + "\n" +
191                            AlignSeq.extractGaps("-. ", seq.getSequence()) +
192                            "\n");
193     String altitle = "JNet prediction for sequence " + seq.getName() + " from " +
194         title;
195
196     Hashtable SequenceInfo = jalview.analysis.SeqsetUtils.SeqCharacterHash(seq);
197
198     Jpred server = locateWebService();
199     if (server==null)
200     {
201       return;
202     }
203
204     JPredThread jthread = new JPredThread(wsInfo, altitle, server, SequenceInfo, seq,null, parentFrame);
205     wsInfo.setthisService(jthread);
206     jthread.start();
207   }
208
209   private WebserviceInfo setWebService()
210   {
211     WebServiceName = "JNetWS";
212     WebServiceJobTitle = "JNet secondary structure prediction";
213     WebServiceReference =
214         "\"Cuff J. A and Barton G.J (2000) Application of " +
215         "multiple sequence alignment profiles to improve protein secondary structure prediction, " +
216         "Proteins 40:502-511\".";
217     WsURL = "http://www.compbio.dundee.ac.uk/JalviewWS/services/jpred";
218
219     WebserviceInfo wsInfo = new WebserviceInfo(WebServiceJobTitle,
220                                                WebServiceReference);
221
222     return wsInfo;
223   }
224
225   private ext.vamsas.Jpred locateWebService()
226   {
227     ext.vamsas.JpredServiceLocator loc = new JpredServiceLocator(); // Default
228     ext.vamsas.Jpred server=null;
229     try
230     {
231       server = loc.getjpred(new java.net.URL(WsURL)); // JBPNote will be set from properties
232       ( (JpredSoapBindingStub)server).setTimeout(60000); // one minute stub
233       //((JpredSoapBindingStub)this.server)._setProperty(org.apache.axis.encoding.C, Boolean.TRUE);
234
235     }
236     catch (Exception ex)
237     {
238       JOptionPane.showMessageDialog(Desktop.desktop,
239                                     "The Secondary Structure Prediction Service named " +
240                                     WebServiceName + " at " + WsURL +
241                                     " couldn't be located.",
242                                     "Internal Jalview Error",
243                                     JOptionPane.WARNING_MESSAGE);
244       wsInfo.setProgressText("Serious! " + WebServiceName +
245                              " Service location failed\nfor URL :" + WsURL +
246                              "\n" +
247                              ex.getMessage());
248       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
249
250     }
251
252     return server;
253   }
254
255   class JPredThread
256       extends WSThread
257       implements WSClientI
258   {
259     class JPredJob
260         extends WSThread.WSJob
261     {
262
263       vamsas.objects.simple.Sequence sequence;
264       vamsas.objects.simple.Msfalignment msa;
265       java.util.Hashtable SequenceInfo = null;
266       /**
267        *
268        * @return true if getResultSet will return a valid alignment and prediction result.
269        */
270       public boolean hasResults()
271       {
272         if (subjobComplete && result != null && result.isFinished()
273             && ( (JpredResult) result).getPredfile() != null &&
274             ( (JpredResult) result).getAligfile() != null)
275         {
276           return true;
277         }
278         return false;
279       }
280
281       boolean hasValidInput()
282       {
283         if (sequence != null)
284         {
285           return true;
286         }
287         return false;
288       }
289
290       public Alignment getResultSet()
291           throws Exception
292       {
293         if (result == null || !result.isFinished())
294         {
295           return null;
296         }
297         Alignment al = null;
298         int FirstSeq = -1; // the position of the query sequence in Alignment al
299
300         JpredResult result = (JpredResult)this.result;
301
302         jalview.bin.Cache.log.debug("Parsing output from JNet job.");
303         // JPredFile prediction = new JPredFile("C:/JalviewX/files/jpred.txt", "File");
304         jalview.io.JPredFile prediction = new jalview.io.JPredFile(result.
305             getPredfile(),
306             "Paste");
307         SequenceI[] preds = prediction.getSeqsAsArray();
308         jalview.bin.Cache.log.debug("Got prediction profile.");
309
310         if ( (this.msa != null) && (result.getAligfile() != null))
311         {
312           jalview.bin.Cache.log.debug("Getting associated alignment.");
313           // we ignore the returned alignment if we only predicted on a single sequence
314           String format = new jalview.io.IdentifyFile().Identify(result.
315               getAligfile(),
316               "Paste");
317
318           if (jalview.io.FormatAdapter.isValidFormat(format))
319           {
320             al = new Alignment(new FormatAdapter().readFile(result.getAligfile(),
321                 "Paste", format));
322             SequenceI sqs[] = new SequenceI[al.getHeight()];
323             for (int i = 0, j = al.getHeight(); i < j; i++)
324             {
325               sqs[i] = al.getSequenceAt(i);
326             }
327             if (!jalview.analysis.SeqsetUtils.deuniquify( (Hashtable)
328                 SequenceInfo, sqs))
329             {
330               throw (new Exception(
331                   "Couldn't recover sequence properties for alignment."));
332             }
333
334             FirstSeq = 0;
335             al.setDataset(null);
336
337             jalview.io.JnetAnnotationMaker.add_annotation(prediction, al, FirstSeq,
338                                                           false);
339
340           }
341           else
342           {
343             throw (new Exception(
344                 "Unknown format "+format+" for file : \n" +
345                 result.getAligfile()));
346           }
347         }
348         else
349         {
350           al = new Alignment(preds);
351           FirstSeq = prediction.getQuerySeqPosition();
352           if (!jalview.analysis.SeqsetUtils.SeqCharacterUnhash(
353               al.getSequenceAt(FirstSeq), SequenceInfo))
354           {
355             throw (new Exception(
356                 "Couldn't recover sequence properties for JNet Query sequence!"));
357           } else {
358             al.setDataset(null);
359             jalview.io.JnetAnnotationMaker.add_annotation(prediction, al, FirstSeq,
360                 true);
361           }
362         }
363
364         return al; // , FirstSeq, noMsa};
365       }
366       public JPredJob(Hashtable SequenceInfo, SequenceI seq)
367       {
368         super();
369         String sq = AlignSeq.extractGaps(Comparison.GapChars, seq.getSequence());
370         if (sq.length() >= 20)
371         {
372           this.SequenceInfo = SequenceInfo;
373           sequence = new vamsas.objects.simple.Sequence();
374           sequence.setId(seq.getName());
375           sequence.setSeq(sq);
376         }
377       }
378
379       public JPredJob(Hashtable SequenceInfo, SequenceI[] msf)
380       {
381         this(SequenceInfo, msf[0]);
382         if (sequence != null)
383         {
384           if (msf.length > 1)
385           {
386             msa = new vamsas.objects.simple.Msfalignment();
387             jalview.io.PileUpfile pileup = new jalview.io.PileUpfile();
388             msa.setMsf(pileup.print(msf));
389           }
390         }
391       }
392     }
393     ext.vamsas.Jpred server;
394     String altitle = "";
395     JPredThread(WebserviceInfo wsinfo, String altitle, ext.vamsas.Jpred server, AlignmentView alview, AlignFrame alframe) {
396       this.altitle = altitle;
397       this.server = server;
398       this.wsInfo = wsinfo;
399       this.input = alview;
400       this.alignFrame = alframe;
401     }
402
403 //    String OutputHeader;
404 //    vamsas.objects.simple.JpredResult result;
405
406     JPredThread(WebserviceInfo wsinfo, String altitle, ext.vamsas.Jpred server, Hashtable SequenceInfo,SequenceI seq, AlignmentView alview, AlignFrame alframe)
407     {
408       this(wsinfo, altitle, server,alview, alframe);
409       JPredJob job = new JPredJob(SequenceInfo, seq);
410       if (job.hasValidInput())
411       {
412         OutputHeader = wsInfo.getProgressText();
413         jobs = new WSJob[]
414             {
415             job};
416         job.jobnum = 0;
417       }
418     }
419
420     JPredThread(WebserviceInfo wsinfo, String altitle, ext.vamsas.Jpred server, Hashtable SequenceInfo, SequenceI[] msf, AlignmentView alview, AlignFrame alframe)
421     {
422       this(wsinfo, altitle, server,alview, alframe);
423       JPredJob job = new JPredJob(SequenceInfo, msf);
424       if (job.hasValidInput())
425       {
426         jobs = new WSJob[]
427             {
428             job};
429         OutputHeader = wsInfo.getProgressText();
430         job.jobnum = 0;
431       }
432     }
433
434     /*
435         public void run()
436         {
437           StartJob();
438
439           while (!jobComplete && (allowedServerExceptions > 0))
440           {
441             try
442             {
443               if ( (result = server.getresult(jobId)) == null)
444               {
445                 throw (new Exception(
446      "Timed out when communicating with server\nTry again later.\n"));
447               }
448               if (result.getState()==0)
449                 jalview.bin.Cache.log.debug("Finished "+jobId);
450               if (result.isRunning())
451               {
452                 wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
453               }
454               if (result.isQueued())
455               {
456                 wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
457               }
458
459               wsInfo.setProgressText(OutputHeader + "\n" +
460                                      result.getStatus());
461
462               if (result.isFinished())
463               {
464
465                 parseResult();
466                 jobComplete = true;
467                 jobsRunning--;
468               } else {
469                 // catch exceptions
470                 if (! (result.isJobFailed() || result.isServerError()))
471                 {
472                   try
473                   {
474                     Thread.sleep(5000);
475                   }
476                   catch (InterruptedException ex1)
477                   {
478                   }
479
480                   //  System.out.println("I'm alive "+seqid+" "+jobid);
481                 }
482                 else
483                 {
484                   wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
485                   jobsRunning--;
486                   jobComplete = true;
487                 }
488               }
489             }
490             catch (Exception ex)
491             {
492               allowedServerExceptions--;
493
494               wsInfo.appendProgressText("\nJPredWS Server exception!\n" +
495                                         ex.getMessage());
496
497               try
498               {
499                 if (allowedServerExceptions > 0)
500                 {
501                   Thread.sleep(5000);
502                 }
503               }
504               catch (InterruptedException ex1)
505               {
506               }
507             }
508             catch (OutOfMemoryError er)
509             {
510               jobComplete = true;
511               wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
512               JOptionPane.showInternalMessageDialog(Desktop.desktop,
513      "Out of memory handling result!!"
514                                                     +
515      "\nSee help files for increasing Java Virtual Machine memory."
516                                                     , "Out of memory",
517      JOptionPane.WARNING_MESSAGE);
518               System.out.println("JPredClient: "+er);
519               System.gc();
520             }
521           }
522           if (result!=null)
523             if (! (result.isJobFailed() || result.isServerError()))
524             {
525               wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
526             }
527             else
528             {
529               wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
530             }
531         }
532      */
533     void StartJob(WSJob j)
534     {
535       if (! (j instanceof JPredJob))
536       {
537         throw new Error("Implementation error - StartJob(JpredJob) called on " +
538                         j.getClass());
539       }
540       try
541       {
542         JPredJob job = (JPredJob) j;
543         if (job.msa != null)
544         {
545           job.jobId = server.predictOnMsa(job.msa);
546         }
547         else
548           if (job.sequence!=null)
549           {
550             job.jobId = server.predict(job.sequence);
551           }
552
553         if (job.jobId != null)
554         {
555           if (job.jobId.startsWith("Broken"))
556           {
557             job.result = (vamsas.objects.simple.Result)new JpredResult();
558             job.result.setInvalid(true);
559             job.result.setStatus("Submission " + job.jobId);
560           }
561           else
562           {
563             job.submitted = true;
564             job.subjobComplete = false;
565             Cache.log.info(WsURL + " Job Id '" + job.jobId + "'");
566           }
567         }
568         else
569         {
570           throw new Exception("Server timed out - try again later\n");
571         }
572       }
573       catch (Exception e)
574       {
575         if (e.getMessage().indexOf("Exception") > -1)
576         {
577           wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_SERVERERROR);
578           wsInfo.setProgressText(j.jobnum,
579                                  "Failed to submit the prediction. (Just close the window)\n"
580                                  +
581                                  "It is most likely that there is a problem with the server.\n");
582           System.err.println(
583               "JPredWS Client: Failed to submit the prediction. Quite possibly because of a server error - see below)\n" +
584               e.getMessage() + "\n");
585
586           jalview.bin.Cache.log.warn("Server Exception", e);
587         }
588         else
589         {
590           wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);
591           // JBPNote - this could be a popup informing the user of the problem.
592           wsInfo.appendProgressText(j.jobnum,
593                                     "Failed to submit the prediction:\n"
594                                     + e.getMessage() +
595                                     wsInfo.getProgressText());
596
597           jalview.bin.Cache.log.debug("Failed Submission of job " + j.jobnum, e);
598
599         }
600         j.allowedServerExceptions = -1;
601         j.subjobComplete = true;
602       }
603     }
604
605     /*  private void addFloatAnnotations(Alignment al, int[] gapmap,
606                                        Vector values, String Symname,
607                                        String Visname, float min,
608                                        float max, int winLength)
609       {
610         Annotation[] annotations = new Annotation[al.getWidth()];
611
612         for (int j = 0; j < values.size(); j++)
613         {
614           float value = Float.parseFloat(values.get(j).toString());
615           annotations[gapmap[j]] = new Annotation("", value + "", ' ',
616                                                   value);
617         }
618
619         al.addAnnotation(new AlignmentAnnotation(Symname, Visname,
620      annotations, min, max, winLength));
621       }*/
622
623     void parseResult()
624     {
625       int results = 0; // number of result sets received
626       JobStateSummary finalState = new JobStateSummary();
627       try
628       {
629         for (int j = 0; j < jobs.length; j++)
630         {
631           finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
632           if (jobs[j].submitted && jobs[j].subjobComplete && jobs[j].hasResults())
633           {
634             results++;
635           }
636         }
637       }
638       catch (Exception ex)
639       {
640
641         Cache.log.error("Unexpected exception when processing results for " +
642                         altitle, ex);
643         wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
644       }
645       if (results > 0)
646       {
647         wsInfo.showResultsNewFrame
648             .addActionListener(new java.awt.event.ActionListener()
649         {
650           public void actionPerformed(
651               java.awt.event.ActionEvent evt)
652           {
653             displayResults(true);
654           }
655         });
656         wsInfo.mergeResults
657             .addActionListener(new java.awt.event.ActionListener()
658         {
659           public void actionPerformed(
660               java.awt.event.ActionEvent evt)
661           {
662             displayResults(false);
663           }
664         });
665         wsInfo.setResultsReady();
666       }
667       else
668       {
669         wsInfo.setFinishedNoResults();
670       }
671     }
672
673     void displayResults(boolean newWindow)
674     {
675       // TODO: cope with multiple subjobs.
676       if (jobs != null)
677       {
678         Alignment res = null;
679         boolean msa=false;
680         for (int jn = 0; jn < jobs.length; jn++)
681         {
682           Alignment jobres = null;
683           JPredJob j = (JPredJob) jobs[jn];
684
685           if (j.hasResults())
686           {
687             // hack - we only deal with all single seuqence predictions or all profile predictions
688             msa = (j.msa!=null) ? true : msa;
689             try
690             {
691               jalview.bin.Cache.log.debug("Parsing output of job " + jn);
692               jobres = j.getResultSet();
693               jalview.bin.Cache.log.debug("Finished parsing output.");
694               if (jobs.length==1)
695                 res = jobres;
696               else {
697                   // do merge with other job results
698                   throw new Error("Multiple JNet subjob merging not yet implemented.");
699               }
700             }
701             catch (Exception e)
702             {
703               jalview.bin.Cache.log.error(
704                   "JNet Client: JPred Annotation Parse Error",
705                   e);
706               wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);
707               wsInfo.appendProgressText(j.jobnum,
708                                         OutputHeader + "\n" +
709                                         j.result.getStatus() +
710                                         "\nInvalid JNet job result data!\n" +
711                                         e.getMessage());
712               j.result.setBroken(true);
713             }
714           }
715         }
716
717         if (res != null)
718         {
719           if (newWindow)
720           {
721             AlignFrame af;
722            if (input==null) {
723              af = new AlignFrame(res);
724            } else {
725              java.lang.Object[] alandcolsel = input.getAlignmentAndColumnSelection(alignFrame.getViewport().getGapCharacter());
726              
727              if (((SequenceI[])alandcolsel[0])[0].getLength()!=res.getWidth()) {
728                if (msa) {
729                  throw new Error("Implementation Error! ColumnSelection from input alignment will not map to result alignment!");
730                } else {
731                  // test this.
732                  ((ColumnSelection) alandcolsel[1]).compensateForEdits(ShiftList.parseMap(((SequenceI[]) alandcolsel[0])[0].gapMap()));
733                }
734              }
735              af = new AlignFrame(res, (ColumnSelection) alandcolsel[1]);
736            }
737             Desktop.addInternalFrame(af, altitle,
738                                      AlignFrame.NEW_WINDOW_WIDTH,
739                                      AlignFrame.NEW_WINDOW_HEIGHT);
740           }
741           else
742           {
743             Cache.log.info("Append results onto existing alignment.");
744           }
745         }
746       }
747     }
748     void pollJob(WSJob job)
749         throws Exception
750     {
751       job.result = server.getresult(job.jobId);
752     }
753     public boolean isCancellable()
754     {
755       return false;
756     }
757
758     public void cancelJob()
759     {
760       throw new Error("Implementation error!");
761     }
762
763     public boolean canMergeResults()
764     {
765       return false;
766     }
767
768   }
769 }
770