68360d63026841349316adbd0d90fdc5d76813f9
[jalview.git] / src / jalview / ws / JPredThread.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)\r
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  * \r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  * \r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  * \r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.ws;\r
20 \r
21 import java.util.*;\r
22 \r
23 import jalview.analysis.*;\r
24 import jalview.bin.*;\r
25 import jalview.datamodel.*;\r
26 import jalview.datamodel.Alignment;\r
27 import jalview.gui.*;\r
28 import jalview.io.*;\r
29 import jalview.util.*;\r
30 import vamsas.objects.simple.JpredResult;\r
31 \r
32 class JPredThread extends WSThread implements WSClientI\r
33 {\r
34   // TODO: put mapping between JPredJob input and input data here -\r
35   // JNetAnnotation adding is done after result parsing.\r
36   class JPredJob extends WSThread.WSJob\r
37   {\r
38     // TODO: make JPredJob deal only with what was sent to and received from a\r
39     // JNet service\r
40     int[] predMap = null; // mapping from sequence(i) to the original\r
41                           // sequence(predMap[i]) being predicted on\r
42 \r
43     vamsas.objects.simple.Sequence sequence;\r
44 \r
45     vamsas.objects.simple.Msfalignment msa;\r
46 \r
47     java.util.Hashtable SequenceInfo = null;\r
48 \r
49     int msaIndex = 0; // the position of the original sequence in the array of\r
50                       // Sequences in the input object that this job holds a\r
51                       // prediction for\r
52 \r
53     /**\r
54      * \r
55      * @return true if getResultSet will return a valid alignment and prediction\r
56      *         result.\r
57      */\r
58     public boolean hasResults()\r
59     {\r
60       if (subjobComplete && result != null && result.isFinished()\r
61               && ((JpredResult) result).getPredfile() != null\r
62               && ((JpredResult) result).getAligfile() != null)\r
63       {\r
64         return true;\r
65       }\r
66       return false;\r
67     }\r
68 \r
69     boolean hasValidInput()\r
70     {\r
71       if (sequence != null)\r
72       {\r
73         return true;\r
74       }\r
75       return false;\r
76     }\r
77 \r
78     /**\r
79      * \r
80      * @return null or Object[] { annotated alignment for this prediction,\r
81      *         ColumnSelection for this prediction} or null if no results\r
82      *         available.\r
83      * @throws Exception\r
84      */\r
85     public Object[] getResultSet() throws Exception\r
86     {\r
87       if (result == null || !result.isFinished())\r
88       {\r
89         return null;\r
90       }\r
91       Alignment al = null;\r
92       ColumnSelection alcsel = null;\r
93       int FirstSeq = -1; // the position of the query sequence in Alignment al\r
94 \r
95       JpredResult result = (JpredResult) this.result;\r
96 \r
97       jalview.bin.Cache.log.debug("Parsing output from JNet job.");\r
98       // JPredFile prediction = new JPredFile("C:/JalviewX/files/jpred.txt",\r
99       // "File");\r
100       jalview.io.JPredFile prediction = new jalview.io.JPredFile(result\r
101               .getPredfile(), "Paste");\r
102       SequenceI[] preds = prediction.getSeqsAsArray();\r
103       jalview.bin.Cache.log.debug("Got prediction profile.");\r
104 \r
105       if ((this.msa != null) && (result.getAligfile() != null))\r
106       {\r
107         jalview.bin.Cache.log.debug("Getting associated alignment.");\r
108         // we ignore the returned alignment if we only predicted on a single\r
109         // sequence\r
110         String format = new jalview.io.IdentifyFile().Identify(result\r
111                 .getAligfile(), "Paste");\r
112 \r
113         if (jalview.io.FormatAdapter.isValidFormat(format))\r
114         {\r
115           SequenceI sqs[];\r
116           if (predMap != null)\r
117           {\r
118             Object[] alandcolsel = input\r
119                     .getAlignmentAndColumnSelection(getGapChar());\r
120             sqs = (SequenceI[]) alandcolsel[0];\r
121             al = new Alignment(sqs);\r
122             alcsel = (ColumnSelection) alandcolsel[1];\r
123           }\r
124           else\r
125           {\r
126             al = new FormatAdapter().readFile(result.getAligfile(),\r
127                     "Paste", format);\r
128             sqs = new SequenceI[al.getHeight()];\r
129 \r
130             for (int i = 0, j = al.getHeight(); i < j; i++)\r
131             {\r
132               sqs[i] = al.getSequenceAt(i);\r
133             }\r
134             if (!jalview.analysis.SeqsetUtils.deuniquify(\r
135                     (Hashtable) SequenceInfo, sqs))\r
136             {\r
137               throw (new Exception(\r
138                       "Couldn't recover sequence properties for alignment."));\r
139             }\r
140           }\r
141           FirstSeq = 0;\r
142           al.setDataset(null);\r
143 \r
144           jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,\r
145                   FirstSeq, false, predMap);\r
146 \r
147         }\r
148         else\r
149         {\r
150           throw (new Exception("Unknown format " + format\r
151                   + " for file : \n" + result.getAligfile()));\r
152         }\r
153       }\r
154       else\r
155       {\r
156         al = new Alignment(preds);\r
157         FirstSeq = prediction.getQuerySeqPosition();\r
158         if (predMap != null)\r
159         {\r
160           char gc = getGapChar();\r
161           SequenceI[] sqs = (SequenceI[]) ((java.lang.Object[]) input\r
162                   .getAlignmentAndColumnSelection(gc))[0];\r
163           if (this.msaIndex >= sqs.length)\r
164           {\r
165             throw new Error(\r
166                     "Implementation Error! Invalid msaIndex for JPredJob on parent MSA input object!");\r
167           }\r
168 \r
169           // ///\r
170           // Uses RemoveGapsCommand\r
171           // ///\r
172           new jalview.commands.RemoveGapsCommand("Remove Gaps",\r
173                   new SequenceI[]\r
174                   { sqs[msaIndex] }, currentView);\r
175 \r
176           SequenceI profileseq = al.getSequenceAt(FirstSeq);\r
177           profileseq.setSequence(sqs[msaIndex].getSequenceAsString());\r
178         }\r
179 \r
180         if (!jalview.analysis.SeqsetUtils.SeqCharacterUnhash(al\r
181                 .getSequenceAt(FirstSeq), SequenceInfo))\r
182         {\r
183           throw (new Exception(\r
184                   "Couldn't recover sequence properties for JNet Query sequence!"));\r
185         }\r
186         else\r
187         {\r
188           al.setDataset(null);\r
189           jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,\r
190                   FirstSeq, true, predMap);\r
191           SequenceI profileseq = al.getSequenceAt(0); // this includes any gaps.\r
192           alignToProfileSeq(al, profileseq);\r
193           if (predMap != null)\r
194           {\r
195             // Adjust input view for gaps\r
196             // propagate insertions into profile\r
197             alcsel = propagateInsertions(profileseq, al, input);\r
198           }\r
199         }\r
200       }\r
201       return new Object[]\r
202       { al, alcsel }; // , FirstSeq, noMsa};\r
203     }\r
204 \r
205     /**\r
206      * Given an alignment where all other sequences except profileseq are\r
207      * aligned to the ungapped profileseq, insert gaps in the other sequences to\r
208      * realign them with the residues in profileseq\r
209      * \r
210      * @param al\r
211      * @param profileseq\r
212      */\r
213     private void alignToProfileSeq(Alignment al, SequenceI profileseq)\r
214     {\r
215       char gc = al.getGapCharacter();\r
216       int[] gapMap = profileseq.gapMap();\r
217       // insert gaps into profile\r
218       for (int lp = 0, r = 0; r < gapMap.length; r++)\r
219       {\r
220         if (gapMap[r] - lp > 1)\r
221         {\r
222           StringBuffer sb = new StringBuffer();\r
223           for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)\r
224           {\r
225             sb.append(gc);\r
226           }\r
227           for (int s = 1, ns = al.getHeight(); s < ns; s++)\r
228           {\r
229             String sq = al.getSequenceAt(s).getSequenceAsString();\r
230             int diff = gapMap[r] - sq.length();\r
231             if (diff > 0)\r
232             {\r
233               // pad gaps\r
234               sq = sq + sb;\r
235               while ((diff = gapMap[r] - sq.length()) > 0)\r
236               {\r
237                 sq = sq\r
238                         + ((diff >= sb.length()) ? sb.toString() : sb\r
239                                 .substring(0, diff));\r
240               }\r
241               al.getSequenceAt(s).setSequence(sq);\r
242             }\r
243             else\r
244             {\r
245               al.getSequenceAt(s).setSequence(\r
246                       sq.substring(0, gapMap[r]) + sb.toString()\r
247                               + sq.substring(gapMap[r]));\r
248             }\r
249           }\r
250         }\r
251         lp = gapMap[r];\r
252       }\r
253     }\r
254 \r
255     /**\r
256      * Add gaps into the sequences aligned to profileseq under the given\r
257      * AlignmentView\r
258      * \r
259      * @param profileseq\r
260      * @param al\r
261      * @param input\r
262      */\r
263     private ColumnSelection propagateInsertions(SequenceI profileseq,\r
264             Alignment al, AlignmentView input)\r
265     {\r
266       char gc = al.getGapCharacter();\r
267       Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);\r
268       ColumnSelection nview = (ColumnSelection) alandcolsel[1];\r
269       SequenceI origseq;\r
270       nview.pruneDeletions(ShiftList\r
271               .parseMap((origseq = ((SequenceI[]) alandcolsel[0])[0])\r
272                       .gapMap())); // recover original prediction sequence's\r
273                                     // mapping to view.\r
274       int[] viscontigs = nview.getVisibleContigs(0, profileseq.getLength());\r
275       int spos = 0;\r
276       int offset = 0;\r
277       // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])\r
278       // alandcolsel[0])[0].gapMap()))\r
279       // add profile to visible contigs\r
280       for (int v = 0; v < viscontigs.length; v += 2)\r
281       {\r
282         if (viscontigs[v] > spos)\r
283         {\r
284           StringBuffer sb = new StringBuffer();\r
285           for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)\r
286           {\r
287             sb.append(gc);\r
288           }\r
289           for (int s = 0, ns = al.getHeight(); s < ns; s++)\r
290           {\r
291             SequenceI sqobj = al.getSequenceAt(s);\r
292             if (sqobj != profileseq)\r
293             {\r
294               String sq = al.getSequenceAt(s).getSequenceAsString();\r
295               if (sq.length() <= spos + offset)\r
296               {\r
297                 // pad sequence\r
298                 int diff = spos + offset - sq.length() - 1;\r
299                 if (diff > 0)\r
300                 {\r
301                   // pad gaps\r
302                   sq = sq + sb;\r
303                   while ((diff = spos + offset - sq.length() - 1) > 0)\r
304                   {\r
305                     sq = sq\r
306                             + ((diff >= sb.length()) ? sb.toString() : sb\r
307                                     .substring(0, diff));\r
308                   }\r
309                 }\r
310                 sq += sb.toString();\r
311               }\r
312               else\r
313               {\r
314                 al.getSequenceAt(s).setSequence(\r
315                         sq.substring(0, spos + offset) + sb.toString()\r
316                                 + sq.substring(spos + offset));\r
317               }\r
318             }\r
319           }\r
320           // offset+=sb.length();\r
321         }\r
322         spos = viscontigs[v + 1] + 1;\r
323       }\r
324       if ((offset + spos) < profileseq.getLength())\r
325       {\r
326         StringBuffer sb = new StringBuffer();\r
327         for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)\r
328         {\r
329           sb.append(gc);\r
330         }\r
331         for (int s = 1, ns = al.getHeight(); s < ns; s++)\r
332         {\r
333           String sq = al.getSequenceAt(s).getSequenceAsString();\r
334           // pad sequence\r
335           int diff = origseq.getLength() - sq.length();\r
336           while (diff > 0)\r
337           {\r
338             sq = sq\r
339                     + ((diff >= sb.length()) ? sb.toString() : sb\r
340                             .substring(0, diff));\r
341             diff = origseq.getLength() - sq.length();\r
342           }\r
343         }\r
344       }\r
345       return nview;\r
346     }\r
347 \r
348     public JPredJob(Hashtable SequenceInfo, SequenceI seq, int[] delMap)\r
349     {\r
350       super();\r
351       this.predMap = delMap;\r
352       String sq = AlignSeq.extractGaps(Comparison.GapChars, seq\r
353               .getSequenceAsString());\r
354       if (sq.length() >= 20)\r
355       {\r
356         this.SequenceInfo = SequenceInfo;\r
357         sequence = new vamsas.objects.simple.Sequence();\r
358         sequence.setId(seq.getName());\r
359         sequence.setSeq(sq);\r
360       }\r
361     }\r
362 \r
363     public JPredJob(Hashtable SequenceInfo, SequenceI[] msf, int[] delMap)\r
364     {\r
365       this(SequenceInfo, msf[0], delMap);\r
366       if (sequence != null)\r
367       {\r
368         if (msf.length > 1)\r
369         {\r
370           msa = new vamsas.objects.simple.Msfalignment();\r
371           jalview.io.PileUpfile pileup = new jalview.io.PileUpfile();\r
372           msa.setMsf(pileup.print(msf));\r
373         }\r
374       }\r
375     }\r
376   }\r
377 \r
378   ext.vamsas.Jpred server;\r
379 \r
380   String altitle = "";\r
381 \r
382   JPredThread(WebserviceInfo wsinfo, String altitle,\r
383           ext.vamsas.Jpred server, String wsurl, AlignmentView alview,\r
384           AlignFrame alframe)\r
385   {\r
386     super(alframe, wsinfo, alview, wsurl);\r
387     this.altitle = altitle;\r
388     this.server = server;\r
389   }\r
390 \r
391   JPredThread(WebserviceInfo wsinfo, String altitle,\r
392           ext.vamsas.Jpred server, String wsurl, Hashtable SequenceInfo,\r
393           SequenceI seq, int[] delMap, AlignmentView alview,\r
394           AlignFrame alframe)\r
395   {\r
396     this(wsinfo, altitle, server, wsurl, alview, alframe);\r
397     JPredJob job = new JPredJob(SequenceInfo, seq, delMap);\r
398     if (job.hasValidInput())\r
399     {\r
400       OutputHeader = wsInfo.getProgressText();\r
401       jobs = new WSJob[]\r
402       { job };\r
403       job.jobnum = 0;\r
404     }\r
405   }\r
406 \r
407   JPredThread(WebserviceInfo wsinfo, String altitle,\r
408           ext.vamsas.Jpred server, Hashtable SequenceInfo, SequenceI[] msf,\r
409           int[] delMap, AlignmentView alview, AlignFrame alframe,\r
410           String wsurl)\r
411   {\r
412     this(wsinfo, altitle, server, wsurl, alview, alframe);\r
413     JPredJob job = new JPredJob(SequenceInfo, msf, delMap);\r
414     if (job.hasValidInput())\r
415     {\r
416       jobs = new WSJob[]\r
417       { job };\r
418       OutputHeader = wsInfo.getProgressText();\r
419       job.jobnum = 0;\r
420     }\r
421   }\r
422 \r
423   void StartJob(WSJob j)\r
424   {\r
425     if (!(j instanceof JPredJob))\r
426     {\r
427       throw new Error(\r
428               "Implementation error - StartJob(JpredJob) called on "\r
429                       + j.getClass());\r
430     }\r
431     try\r
432     {\r
433       JPredJob job = (JPredJob) j;\r
434       if (job.msa != null)\r
435       {\r
436         job.jobId = server.predictOnMsa(job.msa);\r
437       }\r
438       else if (job.sequence != null)\r
439       {\r
440         job.jobId = server.predict(job.sequence); // debug like : job.jobId =\r
441                                                   // "/jobs/www-jpred/jp_Yatat29";//\r
442       }\r
443 \r
444       if (job.jobId != null)\r
445       {\r
446         if (job.jobId.startsWith("Broken"))\r
447         {\r
448           job.result = (vamsas.objects.simple.Result) new JpredResult();\r
449           job.result.setInvalid(true);\r
450           job.result.setStatus("Submission " + job.jobId);\r
451           throw new Exception(job.jobId);\r
452         }\r
453         else\r
454         {\r
455           job.submitted = true;\r
456           job.subjobComplete = false;\r
457           Cache.log.info(WsUrl + " Job Id '" + job.jobId + "'");\r
458         }\r
459       }\r
460       else\r
461       {\r
462         throw new Exception("Server timed out - try again later\n");\r
463       }\r
464     } catch (Exception e)\r
465     {\r
466       // kill the whole job.\r
467       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);\r
468       if (e.getMessage().indexOf("Exception") > -1)\r
469       {\r
470         wsInfo\r
471                 .setStatus(j.jobnum,\r
472                         WebserviceInfo.STATE_STOPPED_SERVERERROR);\r
473         wsInfo\r
474                 .setProgressText(\r
475                         j.jobnum,\r
476                         "Failed to submit the prediction. (Just close the window)\n"\r
477                                 + "It is most likely that there is a problem with the server.\n");\r
478         System.err\r
479                 .println("JPredWS Client: Failed to submit the prediction. Quite possibly because of a server error - see below)\n"\r
480                         + e.getMessage() + "\n");\r
481 \r
482         jalview.bin.Cache.log.warn("Server Exception", e);\r
483       }\r
484       else\r
485       {\r
486         wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);\r
487         // JBPNote - this could be a popup informing the user of the problem.\r
488         wsInfo.appendProgressText(j.jobnum,\r
489                 "Failed to submit the prediction:\n" + e.getMessage()\r
490                         + wsInfo.getProgressText());\r
491 \r
492         jalview.bin.Cache.log.debug("Failed Submission of job " + j.jobnum,\r
493                 e);\r
494 \r
495       }\r
496       j.allowedServerExceptions = -1;\r
497       j.subjobComplete = true;\r
498     }\r
499   }\r
500 \r
501   void parseResult()\r
502   {\r
503     int results = 0; // number of result sets received\r
504     JobStateSummary finalState = new JobStateSummary();\r
505     try\r
506     {\r
507       for (int j = 0; j < jobs.length; j++)\r
508       {\r
509         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);\r
510         if (jobs[j].submitted && jobs[j].subjobComplete\r
511                 && jobs[j].hasResults())\r
512         {\r
513           results++;\r
514         }\r
515       }\r
516     } catch (Exception ex)\r
517     {\r
518 \r
519       Cache.log.error("Unexpected exception when processing results for "\r
520               + altitle, ex);\r
521       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);\r
522     }\r
523     if (results > 0)\r
524     {\r
525       wsInfo.showResultsNewFrame\r
526               .addActionListener(new java.awt.event.ActionListener()\r
527               {\r
528                 public void actionPerformed(java.awt.event.ActionEvent evt)\r
529                 {\r
530                   displayResults(true);\r
531                 }\r
532               });\r
533       wsInfo.mergeResults\r
534               .addActionListener(new java.awt.event.ActionListener()\r
535               {\r
536                 public void actionPerformed(java.awt.event.ActionEvent evt)\r
537                 {\r
538                   displayResults(false);\r
539                 }\r
540               });\r
541       wsInfo.setResultsReady();\r
542     }\r
543     else\r
544     {\r
545       wsInfo.setFinishedNoResults();\r
546     }\r
547   }\r
548 \r
549   void displayResults(boolean newWindow)\r
550   {\r
551     // TODO: cope with multiple subjobs.\r
552     if (jobs != null)\r
553     {\r
554       Object[] res = null;\r
555       boolean msa = false;\r
556       for (int jn = 0; jn < jobs.length; jn++)\r
557       {\r
558         Object[] jobres = null;\r
559         JPredJob j = (JPredJob) jobs[jn];\r
560 \r
561         if (j.hasResults())\r
562         {\r
563           // hack - we only deal with all single seuqence predictions or all\r
564           // profile predictions\r
565           msa = (j.msa != null) ? true : msa;\r
566           try\r
567           {\r
568             jalview.bin.Cache.log.debug("Parsing output of job " + jn);\r
569             jobres = j.getResultSet();\r
570             jalview.bin.Cache.log.debug("Finished parsing output.");\r
571             if (jobs.length == 1)\r
572             {\r
573               res = jobres;\r
574             }\r
575             else\r
576             {\r
577               // do merge with other job results\r
578               throw new Error(\r
579                       "Multiple JNet subjob merging not yet implemented.");\r
580             }\r
581           } catch (Exception e)\r
582           {\r
583             jalview.bin.Cache.log.error(\r
584                     "JNet Client: JPred Annotation Parse Error", e);\r
585             wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);\r
586             wsInfo.appendProgressText(j.jobnum, OutputHeader + "\n"\r
587                     + j.result.getStatus()\r
588                     + "\nInvalid JNet job result data!\n" + e.getMessage());\r
589             j.result.setBroken(true);\r
590           }\r
591         }\r
592       }\r
593 \r
594       if (res != null)\r
595       {\r
596         if (newWindow)\r
597         {\r
598           AlignFrame af;\r
599           if (input == null)\r
600           {\r
601             if (res[1] != null)\r
602             {\r
603               af = new AlignFrame((Alignment) res[0],\r
604                       (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,\r
605                       AlignFrame.DEFAULT_HEIGHT);\r
606             }\r
607             else\r
608             {\r
609               af = new AlignFrame((Alignment) res[0],\r
610                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);\r
611             }\r
612           }\r
613           else\r
614           {\r
615             /*\r
616              * java.lang.Object[] alandcolsel =\r
617              * input.getAlignmentAndColumnSelection(alignFrame.getViewport().getGapCharacter());\r
618              * if (((SequenceI[])alandcolsel[0])[0].getLength()!=res.getWidth()) {\r
619              * if (msa) { throw new Error("Implementation Error! ColumnSelection\r
620              * from input alignment will not map to result alignment!"); } } if\r
621              * (!msa) { // update hidden regions to account for loss of gaps in\r
622              * profile. - if any // gapMap returns insert list, interpreted as\r
623              * delete list by pruneDeletions //((ColumnSelection)\r
624              * alandcolsel[1]).pruneDeletions(ShiftList.parseMap(((SequenceI[])\r
625              * alandcolsel[0])[0].gapMap())); }\r
626              */\r
627 \r
628             af = new AlignFrame((Alignment) res[0],\r
629                     (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,\r
630                     AlignFrame.DEFAULT_HEIGHT);\r
631           }\r
632           Desktop.addInternalFrame(af, altitle, AlignFrame.DEFAULT_WIDTH,\r
633                   AlignFrame.DEFAULT_HEIGHT);\r
634         }\r
635         else\r
636         {\r
637           Cache.log.info("Append results onto existing alignment.");\r
638         }\r
639       }\r
640     }\r
641   }\r
642 \r
643   void pollJob(WSJob job) throws Exception\r
644   {\r
645     job.result = server.getresult(job.jobId);\r
646   }\r
647 \r
648   public boolean isCancellable()\r
649   {\r
650     return false;\r
651   }\r
652 \r
653   public void cancelJob()\r
654   {\r
655     throw new Error("Implementation error!");\r
656   }\r
657 \r
658   public boolean canMergeResults()\r
659   {\r
660     return false;\r
661   }\r
662 \r
663 }\r