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