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