set program name to service type (see related issues to JAL-493 re setting provenance...
[jalview.git] / src / jalview / ws / jws2 / MsaWSThread.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ws.jws2;
19
20 import java.util.*;
21
22 import compbio.data.msa.MsaWS;
23 import compbio.data.sequence.AlignmentMetadata;
24 import compbio.data.sequence.Program;
25 import compbio.metadata.Argument;
26 import compbio.metadata.ChunkHolder;
27 import compbio.metadata.JobStatus;
28 import compbio.metadata.Preset;
29
30 import jalview.analysis.*;
31 import jalview.bin.*;
32 import jalview.datamodel.*;
33 import jalview.gui.*;
34 import jalview.ws.AWsJob;
35 import jalview.ws.WSClientI;
36 import jalview.ws.JobStateSummary;
37
38 /**
39  * <p>
40  * Title:
41  * </p>
42  * 
43  * <p>
44  * Description:
45  * </p>
46  * 
47  * <p>
48  * Copyright: Copyright (c) 2004
49  * </p>
50  * 
51  * <p>
52  * Company: Dundee University
53  * </p>
54  * 
55  * @author not attributable
56  * @version 1.0
57  */
58 class MsaWSThread extends AWS2Thread implements WSClientI
59 {
60   boolean submitGaps = false; // pass sequences including gaps to alignment
61
62   // service
63
64   boolean preserveOrder = true; // and always store and recover sequence
65
66   // order
67
68   class MsaWSJob extends JWs2Job
69   {
70     long lastChunk=0;
71     Preset preset=null;
72     List<Argument> arguments =  null;
73     /**
74      * input 
75      */
76     ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
77     /**
78      * output
79      */
80     compbio.data.sequence.Alignment alignment;
81     // set if the job didn't get run - then the input is simply returned to the user
82     private boolean returnInput=false;
83     
84     /**
85      * MsaWSJob
86      * 
87      * @param jobNum
88      *          int
89      * @param jobId
90      *          String
91      */
92     public MsaWSJob(int jobNum, SequenceI[] inSeqs)
93     {
94       this.jobnum = jobNum;
95       if (!prepareInput(inSeqs, 2))
96       {
97         submitted = true;
98         subjobComplete = true;
99         returnInput = true;
100       }
101
102     }
103
104     Hashtable<String,Map> SeqNames = new Hashtable();
105
106     Vector<String[]> emptySeqs = new Vector();
107
108     /**
109      * prepare input sequences for MsaWS service
110      * 
111      * @param seqs
112      *          jalview sequences to be prepared
113      * @param minlen
114      *          minimum number of residues required for this MsaWS service
115      * @return true if seqs contains sequences to be submitted to service.
116      */
117     // TODO: return compbio.seqs list or nothing to indicate validity.
118     private boolean prepareInput(SequenceI[] seqs, int minlen)
119     {
120       int nseqs = 0;
121       if (minlen < 0)
122       {
123         throw new Error(
124                 "Implementation error: minlen must be zero or more.");
125       }
126       for (int i = 0; i < seqs.length; i++)
127       {
128         if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
129         {
130           nseqs++;
131         }
132       }
133       boolean valid = nseqs > 1; // need at least two seqs
134       compbio.data.sequence.FastaSequence seq;
135       for (int i = 0, n = 0; i < seqs.length; i++)
136       {
137
138         String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
139         // for
140         // any
141         // subjob
142         SeqNames.put(newname, jalview.analysis.SeqsetUtils
143                 .SeqCharacterHash(seqs[i]));
144         if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
145         {
146           // make new input sequence with or without gaps
147           seq = new compbio.data.sequence.FastaSequence(newname,
148                   (submitGaps) ? seqs[i].getSequenceAsString()
149                   : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
150                           seqs[i].getSequenceAsString()));
151           this.seqs.add(seq);
152         }
153         else
154         {
155           String empty = null;
156           if (seqs[i].getEnd() >= seqs[i].getStart())
157           {
158             empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
159                     .extractGaps(jalview.util.Comparison.GapChars, seqs[i]
160                             .getSequenceAsString());
161           }
162           emptySeqs.add(new String[]
163           { newname, empty });
164         }
165       }
166       return valid;
167     }
168
169     /**
170      * 
171      * @return true if getAlignment will return a valid alignment result.
172      */
173     public boolean hasResults()
174     {
175       if (subjobComplete && isFinished() && (alignment!=null || (emptySeqs!=null && emptySeqs.size()>0)))
176       {
177         return true;
178       }
179       return false;
180     }
181
182     /**
183      * 
184      * get the alignment including any empty sequences in the original order with original ids. Caller must access the alignment.getMetadata() object to annotate the final result passsed to the user.
185      * @return { SequenceI[], AlignmentOrder }
186      */
187     public Object[] getAlignment()
188     {
189       // is this a generic subjob or a Jws2 specific Object[] return signature
190       if (hasResults())
191       {
192         SequenceI[] alseqs = null;
193         char alseq_gapchar = '-';
194         int alseq_l = 0;
195         if (alignment.getSequences().size()>0)
196         {
197           alseqs = new SequenceI[alignment.getSequences().size()];
198           for (compbio.data.sequence.FastaSequence seq: alignment.getSequences())
199           {
200             alseqs[alseq_l++] = new Sequence(seq.getId(),seq.getSequence());
201           }
202           alseq_gapchar = alignment.getMetadata().getGapchar();
203           
204         }
205         // add in the empty seqs.
206         if (emptySeqs.size() > 0)
207         {
208           SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
209           // get width
210           int i, w = 0;
211           if (alseq_l > 0)
212           {
213             for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
214             {
215               if (w < alseqs[i].getLength())
216               {
217                 w = alseqs[i].getLength();
218               }
219               t_alseqs[i] = alseqs[i];
220               alseqs[i] = null;
221             }
222           }
223           // check that aligned width is at least as wide as emptySeqs width.
224           int ow = w, nw = w;
225           for (i = 0, w = emptySeqs.size(); i < w; i++)
226           {
227             String[] es = (String[]) emptySeqs.get(i);
228             if (es != null && es[1] != null)
229             {
230               int sw = es[1].length();
231               if (nw < sw)
232               {
233                 nw = sw;
234               }
235             }
236           }
237           // make a gapped string.
238           StringBuffer insbuff = new StringBuffer(w);
239           for (i = 0; i < nw; i++)
240           {
241             insbuff.append(alseq_gapchar);
242           }
243           if (ow < nw)
244           {
245             for (i = 0; i < alseq_l; i++)
246             {
247               int sw = t_alseqs[i].getLength();
248               if (nw > sw)
249               {
250                 // pad at end
251                 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
252                         + insbuff.substring(0, sw - nw));
253               }
254             }
255           }
256           for (i = 0, w = emptySeqs.size(); i < w; i++)
257           {
258             String[] es = (String[]) emptySeqs.get(i);
259             if (es[1] == null)
260             {
261               t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
262                       insbuff.toString(), 1, 0);
263             }
264             else
265             {
266               if (es[1].length() < nw)
267               {
268                 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
269                         es[0],
270                         es[1] + insbuff.substring(0, nw - es[1].length()),
271                         1, 1 + es[1].length());
272               }
273               else
274               {
275                 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
276                         es[0], es[1]);
277               }
278             }
279           }
280           alseqs = t_alseqs;
281         }
282         AlignmentOrder msaorder = new AlignmentOrder(alseqs);
283         // always recover the order - makes parseResult()'s life easier.
284         jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
285         // account for any missing sequences
286         jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
287         return new Object[]
288         { alseqs, msaorder };
289       }
290       return null;
291     }
292
293     /**
294      * mark subjob as cancelled and set result object appropriatly
295      */
296     void cancel()
297     {
298       cancelled = true;
299       subjobComplete = true;
300       alignment = null;
301     }
302
303     /**
304      * 
305      * @return boolean true if job can be submitted.
306      */
307     public boolean hasValidInput()
308     {
309       // TODO: get attributes for this MsaWS instance to check if it can do two sequence alignment.
310       if (seqs != null && seqs.size()>=2) // two or more sequences is valid ? 
311       {
312         return true;
313       }
314       return false;
315     }
316     StringBuffer jobProgress=new StringBuffer();
317     public void setStatus(String string)
318     {
319       jobProgress.setLength(0);
320       jobProgress.append(string);
321     }
322
323     @Override
324     public String getStatus()
325     {
326       return jobProgress.toString();
327     }
328
329     @Override
330     public boolean hasStatus()
331     {
332       return jobProgress!=null;
333     }
334
335     /**
336      * @return the lastChunk
337      */
338     public long getLastChunk()
339     {
340       return lastChunk;
341     }
342
343     /**
344      * @param lastChunk the lastChunk to set
345      */
346     public void setLastChunk(long lastChunk)
347     {
348       this.lastChunk = lastChunk;
349     }
350
351     String alignmentProgram=null;
352     public String getAlignmentProgram()
353     {
354       return alignmentProgram;
355     }
356     
357   }
358
359   String alTitle; // name which will be used to form new alignment window.
360
361   Alignment dataset; // dataset to which the new alignment will be
362
363   // associated.
364
365   @SuppressWarnings("unchecked")
366   MsaWS server = null;
367
368   /**
369    * set basic options for this (group) of Msa jobs
370    * 
371    * @param subgaps
372    *          boolean
373    * @param presorder
374    *          boolean
375    */
376   MsaWSThread(MsaWS server, String wsUrl,
377           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
378           AlignmentView alview, String wsname, boolean subgaps,
379           boolean presorder)
380   {
381     super(alFrame, wsinfo, alview, wsname, wsUrl);
382     this.server = server;
383     this.submitGaps = subgaps;
384     this.preserveOrder = presorder;
385   }
386
387   /**
388    * create one or more Msa jobs to align visible seuqences in _msa
389    * 
390    * @param title
391    *          String
392    * @param _msa
393    *          AlignmentView
394    * @param subgaps
395    *          boolean
396    * @param presorder
397    *          boolean
398    * @param seqset
399    *          Alignment
400    */
401   MsaWSThread(MsaWS server2, Preset preset, List<Argument> paramset, String wsUrl, 
402           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
403           String wsname, String title, AlignmentView _msa, boolean subgaps,
404           boolean presorder, Alignment seqset)
405   {
406     this(server2,wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
407     OutputHeader = wsInfo.getProgressText();
408     alTitle = title;
409     dataset = seqset;
410
411     SequenceI[][] conmsa = _msa.getVisibleContigs('-');
412     if (conmsa != null)
413     {
414       int njobs = conmsa.length;
415       jobs = new MsaWSJob[njobs];
416       for (int j = 0; j < njobs; j++)
417       {
418         if (j != 0)
419         {
420           jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
421         }
422         else
423         {
424           jobs[j] = new MsaWSJob(0, conmsa[j]);
425         }
426         ((MsaWSJob) jobs[j]).preset = preset;
427         ((MsaWSJob) jobs[j]).arguments = paramset;
428         ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
429         if (njobs > 0)
430         {
431           wsinfo
432                   .setProgressName("region " + jobs[j].getJobnum(),
433                           jobs[j].getJobnum());
434         }
435         wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
436       }
437     }
438   }
439
440   public boolean isCancellable()
441   {
442     return true;
443   }
444
445   public void cancelJob()
446   {
447     if (!jobComplete && jobs != null)
448     {
449       boolean cancelled = true;
450       for (int job = 0; job < jobs.length; job++)
451       {
452         if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
453         {
454           String cancelledMessage = "";
455           try
456           {
457             boolean cancelledJob = server
458                     .cancelJob(jobs[job].getJobId());
459             if (cancelledJob)
460             {
461               // CANCELLED_JOB
462               cancelledMessage = "Job cancelled.";
463               ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this ugliness - 
464               wsInfo.setStatus(jobs[job].getJobnum(),
465                       WebserviceInfo.STATE_CANCELLED_OK);
466             } 
467             else 
468             {
469               // VALID UNSTOPPABLE JOB
470               cancelledMessage += "Server cannot cancel this job. just close the window.\n";
471               cancelled = false;
472               // wsInfo.setStatus(jobs[job].jobnum,
473               // WebserviceInfo.STATE_RUNNING);
474             }
475           } catch (Exception exc)
476           {
477             cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
478                     + exc + "\n");
479             Cache.log.warn(
480                     "Exception whilst cancelling " + jobs[job].getJobId(), exc);
481           }
482           wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
483                   + cancelledMessage + "\n");
484         }
485       }
486       if (cancelled)
487       {
488         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
489         jobComplete = true;
490       }
491       this.interrupt(); // kick thread to update job states.
492     }
493     else
494     {
495       if (!jobComplete)
496       {
497         wsInfo
498                 .setProgressText(OutputHeader
499                         + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
500       }
501     }
502   }
503
504   public void pollJob(AWsJob job) throws Exception
505   {
506     // TODO: investigate if we still need to cast here in J1.6
507     MsaWSJob j=((MsaWSJob) job);
508     // this is standard code, but since the interface doesn't comprise of a basic one that implements (getJobStatus, pullExecStatistics) we have to repeat the code for all jw2s services.
509     j.setjobStatus(server.getJobStatus(job.getJobId()));
510     updateJobProgress(j);
511   }
512   protected void updateJobProgress(MsaWSJob j) throws Exception {
513     StringBuffer response = j.jobProgress;
514     long lastchunk = j.getLastChunk();
515     do {
516       j.setLastChunk(lastchunk);
517       ChunkHolder chunk = server.pullExecStatistics(j.getJobId(), lastchunk);
518       if (chunk!=null) {
519         response.append(chunk.getChunk());
520         lastchunk = chunk.getNextPosition();
521       };
522     } while (lastchunk>=0 && j.getLastChunk()!=lastchunk);
523   }
524
525   public void StartJob(AWsJob job)
526   {
527     // boiler plate template
528     if (!(job instanceof MsaWSJob))
529     {
530       throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
531               + job.getClass());
532     }
533     MsaWSJob j = (MsaWSJob) job;
534     if (j.isSubmitted())
535     {
536       if (Cache.log.isDebugEnabled())
537       {
538         Cache.log.debug("Tried to submit an already submitted job "
539                 + j.getJobId());
540       }
541       return;
542     }
543     // end boilerplate
544     
545     if (j.seqs == null || j.seqs.size()==0)
546     {
547       // special case - selection consisted entirely of empty sequences...
548       j.setjobStatus(JobStatus.FINISHED);
549       j.setStatus("Empty Alignment Job");
550     }
551     try
552     {
553       // TODO: get the parameters (if any) for this job and submit the job
554       if (j.arguments!=null && j.arguments.size()>0)
555       {
556         j.setJobId(server.customAlign(j.seqs, j.arguments));
557       } else
558         if (j.preset!=null)
559         {
560           j.setJobId(server.presetAlign(j.seqs, j.preset));
561         } else {
562                 j.setJobId(server.align(j.seqs));
563         }
564
565       if (j.getJobId()!= null)
566       {
567         j.setSubmitted(true);
568         j.setSubjobComplete(false);
569         // System.out.println(WsURL + " Job Id '" + jobId + "'");
570       }
571       else
572       {
573         throw new Exception(
574                   "Server at "
575                           + WsUrl
576                           + " returned null string for job id, it probably cannot be contacted. Try again later ?");
577         }
578     } catch (Exception e)
579     {
580       // Boilerplate code here
581       // TODO: JBPNote catch timeout or other fault types explicitly
582       // For unexpected errors
583       System.err
584               .println(WebServiceName
585                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
586                       + "When contacting Server:" + WsUrl + "\n");
587       e.printStackTrace(System.err);
588       j.setAllowedServerExceptions(0);
589       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
590       wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR);
591       wsInfo
592               .appendProgressText(
593                       j.getJobnum(),
594                       "Failed to submit sequences for alignment.\n"
595                               + "It is most likely that there is a problem with the server.\n"
596                               + "Just close the window\n");
597
598       // e.printStackTrace(); // TODO: JBPNote DEBUG
599     }
600   }
601
602
603   public void parseResult()
604   {
605     int results = 0; // number of result sets received
606     JobStateSummary finalState = new JobStateSummary();
607     try
608     {
609       for (int j = 0; j < jobs.length; j++)
610       {
611         MsaWSJob msjob = ((MsaWSJob) jobs[j]);
612         if (jobs[j].isFinished() && msjob.alignment==null)
613         {
614           try {
615             updateJobProgress(msjob);
616           } catch (Exception e)
617           {
618             Cache.log.warn("Exception when retrieving remaining Job progress data for job "+msjob.getJobId()+" on server "+WsUrl);
619             e.printStackTrace();
620           }
621           if (Cache.log.isDebugEnabled())
622           {
623             System.out.println("Job Execution file for job: "+msjob.getJobId()+" on server "+WsUrl);
624             System.out.println(msjob.getStatus());
625             System.out.println("*** End of status");
626             
627           }
628           try {
629             msjob.alignment = server.getResult(msjob.getJobId());
630           }
631           catch (Exception e)
632           {
633             Cache.log.error("Couldn't get Alignment for job.",e);
634           }
635         }
636         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
637         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
638                 && jobs[j].hasResults())
639         {
640           results++;
641           compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
642           if (alignment != null)
643           {
644             wsInfo.appendProgressText(jobs[j].getJobnum(),
645                     "\nAlignment Object Method Notes\n");
646             wsInfo.appendProgressText(jobs[j].getJobnum(), "Calculated with "+alignment.getMetadata().getProgram().toString());
647             // JBPNote The returned files from a webservice could be
648             // hidden behind icons in the monitor window that,
649             // when clicked, pop up their corresponding data
650           }
651         }
652       }
653     } catch (Exception ex)
654     {
655
656       Cache.log.error("Unexpected exception when processing results for "
657               + alTitle, ex);
658       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
659     }
660     if (results > 0)
661     {
662       wsInfo.showResultsNewFrame
663               .addActionListener(new java.awt.event.ActionListener()
664               {
665                 public void actionPerformed(java.awt.event.ActionEvent evt)
666                 {
667                   displayResults(true);
668                 }
669               });
670       wsInfo.mergeResults
671               .addActionListener(new java.awt.event.ActionListener()
672               {
673                 public void actionPerformed(java.awt.event.ActionEvent evt)
674                 {
675                   displayResults(false);
676                 }
677               });
678       wsInfo.setResultsReady();
679     }
680     else
681     {
682       wsInfo.setFinishedNoResults();
683     }
684   }
685
686   void displayResults(boolean newFrame)
687   {
688     // view input or result data for each block
689     Vector alorders = new Vector();
690     SequenceI[][] results = new SequenceI[jobs.length][];
691     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
692     String lastProgram = null;
693     MsaWSJob msjob;
694     for (int j = 0; j < jobs.length; j++)
695     {
696       if (jobs[j].hasResults())
697       {
698         msjob = (MsaWSJob)jobs[j];
699         Object[] res = msjob.getAlignment();
700         lastProgram = msjob.getAlignmentProgram();
701         alorders.add(res[1]);
702         results[j] = (SequenceI[]) res[0];
703         orders[j] = (AlignmentOrder) res[1];
704
705         // SequenceI[] alignment = input.getUpdated
706       }
707       else
708       {
709         results[j] = null;
710       }
711     }
712     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
713     // trash references to original result data
714     for (int j = 0; j < jobs.length; j++)
715     {
716       results[j] = null;
717       orders[j] = null;
718     }
719     SequenceI[] alignment = (SequenceI[]) newview[0];
720     ColumnSelection columnselection = (ColumnSelection) newview[1];
721     Alignment al = new Alignment(alignment);
722     // TODO: add 'provenance' property to alignment from the method notes
723     if (lastProgram!=null) {
724       al.setProperty("Alignment Program", lastProgram);
725     }
726     // accompanying each subjob
727     if (dataset != null)
728     {
729       al.setDataset(dataset);
730     }
731
732     propagateDatasetMappings(al);
733     // JBNote- TODO: warn user if a block is input rather than aligned data ?
734
735     if (newFrame)
736     {
737       AlignFrame af = new AlignFrame(al, columnselection,
738               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
739
740       // initialise with same renderer settings as in parent alignframe.
741       af.getFeatureRenderer().transferSettings(this.featureSettings);
742       // update orders
743       if (alorders.size() > 0)
744       {
745         if (alorders.size() == 1)
746         {
747           af.addSortByOrderMenuItem(WebServiceName + " Ordering",
748                   (AlignmentOrder) alorders.get(0));
749         }
750         else
751         {
752           // construct a non-redundant ordering set
753           Vector names = new Vector();
754           for (int i = 0, l = alorders.size(); i < l; i++)
755           {
756             String orderName = new String(" Region " + i);
757             int j = i + 1;
758
759             while (j < l)
760             {
761               if (((AlignmentOrder) alorders.get(i))
762                       .equals(((AlignmentOrder) alorders.get(j))))
763               {
764                 alorders.remove(j);
765                 l--;
766                 orderName += "," + j;
767               }
768               else
769               {
770                 j++;
771               }
772             }
773
774             if (i == 0 && j == 1)
775             {
776               names.add(new String(""));
777             }
778             else
779             {
780               names.add(orderName);
781             }
782           }
783           for (int i = 0, l = alorders.size(); i < l; i++)
784           {
785             af.addSortByOrderMenuItem(WebServiceName
786                     + ((String) names.get(i)) + " Ordering",
787                     (AlignmentOrder) alorders.get(i));
788           }
789         }
790       }
791
792       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
793               AlignFrame.DEFAULT_HEIGHT);
794
795     }
796     else
797     {
798       System.out.println("MERGE WITH OLD FRAME");
799       // TODO: modify alignment in original frame, replacing old for new
800       // alignment using the commands.EditCommand model to ensure the update can
801       // be undone
802     }
803   }
804
805   public boolean canMergeResults()
806   {
807     return false;
808   }
809 }