abstracted GUI to use jalview.ws.params objects (JAL-591, JAL-633). User defined...
[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 || true)
460             {
461               // CANCELLED_JOB
462               // if the Jaba server indicates the job can't be cancelled, its because its running on the server's local execution engine
463               // so we just close the window anyway.
464               cancelledMessage = "Job cancelled.";
465               ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this ugliness - 
466               wsInfo.setStatus(jobs[job].getJobnum(),
467                       WebserviceInfo.STATE_CANCELLED_OK);
468             } 
469             else 
470             {
471               // VALID UNSTOPPABLE JOB
472               cancelledMessage += "Server cannot cancel this job. just close the window.\n";
473               cancelled = false;
474               // wsInfo.setStatus(jobs[job].jobnum,
475               // WebserviceInfo.STATE_RUNNING);
476             }
477           } catch (Exception exc)
478           {
479             cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
480                     + exc + "\n");
481             Cache.log.warn(
482                     "Exception whilst cancelling " + jobs[job].getJobId(), exc);
483           }
484           wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
485                   + cancelledMessage + "\n");
486         }
487       }
488       if (cancelled)
489       {
490         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
491         jobComplete = true;
492       }
493       this.interrupt(); // kick thread to update job states.
494     }
495     else
496     {
497       if (!jobComplete)
498       {
499         wsInfo
500                 .setProgressText(OutputHeader
501                         + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
502       }
503     }
504   }
505
506   public void pollJob(AWsJob job) throws Exception
507   {
508     // TODO: investigate if we still need to cast here in J1.6
509     MsaWSJob j=((MsaWSJob) job);
510     // 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.
511     j.setjobStatus(server.getJobStatus(job.getJobId()));
512     updateJobProgress(j);
513   }
514   protected void updateJobProgress(MsaWSJob j) throws Exception {
515     StringBuffer response = j.jobProgress;
516     long lastchunk = j.getLastChunk();
517     do {
518       j.setLastChunk(lastchunk);
519       ChunkHolder chunk = server.pullExecStatistics(j.getJobId(), lastchunk);
520       if (chunk!=null) {
521         response.append(chunk.getChunk());
522         lastchunk = chunk.getNextPosition();
523       };
524     } while (lastchunk>=0 && j.getLastChunk()!=lastchunk);
525   }
526
527   public void StartJob(AWsJob job)
528   {
529     // boiler plate template
530     if (!(job instanceof MsaWSJob))
531     {
532       throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
533               + job.getClass());
534     }
535     MsaWSJob j = (MsaWSJob) job;
536     if (j.isSubmitted())
537     {
538       if (Cache.log.isDebugEnabled())
539       {
540         Cache.log.debug("Tried to submit an already submitted job "
541                 + j.getJobId());
542       }
543       return;
544     }
545     // end boilerplate
546     
547     if (j.seqs == null || j.seqs.size()==0)
548     {
549       // special case - selection consisted entirely of empty sequences...
550       j.setjobStatus(JobStatus.FINISHED);
551       j.setStatus("Empty Alignment Job");
552     }
553     try
554     {
555       // TODO: get the parameters (if any) for this job and submit the job
556       if (j.arguments!=null && j.arguments.size()>0)
557       {
558         StringBuffer pset = new StringBuffer();
559         for (Argument arg: j.arguments) {
560           pset.append(arg.getName()+" "+arg.getDefaultValue()+"\n");
561         }
562         j.setStatus("Custom Parameters:\n"+pset.toString()+"\nJob Output:\n");
563         j.setJobId(server.customAlign(j.seqs, j.arguments));
564       } else
565         if (j.preset!=null)
566         {
567           j.setJobId(server.presetAlign(j.seqs, j.preset));
568         } else {
569                 j.setJobId(server.align(j.seqs));
570         }
571
572       if (j.getJobId()!= null)
573       {
574         j.setSubmitted(true);
575         j.setSubjobComplete(false);
576         // System.out.println(WsURL + " Job Id '" + jobId + "'");
577       }
578       else
579       {
580         throw new Exception(
581                   "Server at "
582                           + WsUrl
583                           + " returned null string for job id, it probably cannot be contacted. Try again later ?");
584         }
585     } catch (Exception e)
586     {
587       // Boilerplate code here
588       // TODO: JBPNote catch timeout or other fault types explicitly
589       // For unexpected errors
590       System.err
591               .println(WebServiceName
592                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
593                       + "When contacting Server:" + WsUrl + "\n");
594       e.printStackTrace(System.err);
595       j.setAllowedServerExceptions(0);
596       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
597       wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR);
598       wsInfo
599               .appendProgressText(
600                       j.getJobnum(),
601                       "Failed to submit sequences for alignment.\n"
602                               + "It is most likely that there is a problem with the server.\n"
603                               + "Just close the window\n");
604
605       // e.printStackTrace(); // TODO: JBPNote DEBUG
606     }
607   }
608
609
610   public void parseResult()
611   {
612     int results = 0; // number of result sets received
613     JobStateSummary finalState = new JobStateSummary();
614     try
615     {
616       for (int j = 0; j < jobs.length; j++)
617       {
618         MsaWSJob msjob = ((MsaWSJob) jobs[j]);
619         if (jobs[j].isFinished() && msjob.alignment==null)
620         {
621           try {
622             updateJobProgress(msjob);
623           } catch (Exception e)
624           {
625             Cache.log.warn("Exception when retrieving remaining Job progress data for job "+msjob.getJobId()+" on server "+WsUrl);
626             e.printStackTrace();
627           }
628           if (Cache.log.isDebugEnabled())
629           {
630             System.out.println("Job Execution file for job: "+msjob.getJobId()+" on server "+WsUrl);
631             System.out.println(msjob.getStatus());
632             System.out.println("*** End of status");
633             
634           }
635           try {
636             msjob.alignment = server.getResult(msjob.getJobId());
637           }
638           catch (Exception e)
639           {
640             Cache.log.error("Couldn't get Alignment for job.",e);
641           }
642         }
643         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
644         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
645                 && jobs[j].hasResults())
646         {
647           results++;
648           compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
649           if (alignment != null)
650           {
651             // server.close(jobs[j].getJobnum());
652 //            wsInfo.appendProgressText(jobs[j].getJobnum(),
653 //                    "\nAlignment Object Method Notes\n");
654 //            wsInfo.appendProgressText(jobs[j].getJobnum(), "Calculated with "+alignment.getMetadata().getProgram().toString());
655             // JBPNote The returned files from a webservice could be
656             // hidden behind icons in the monitor window that,
657             // when clicked, pop up their corresponding data
658           }
659         }
660       }
661     } catch (Exception ex)
662     {
663
664       Cache.log.error("Unexpected exception when processing results for "
665               + alTitle, ex);
666       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
667     }
668     if (results > 0)
669     {
670       wsInfo.showResultsNewFrame
671               .addActionListener(new java.awt.event.ActionListener()
672               {
673                 public void actionPerformed(java.awt.event.ActionEvent evt)
674                 {
675                   displayResults(true);
676                 }
677               });
678       wsInfo.mergeResults
679               .addActionListener(new java.awt.event.ActionListener()
680               {
681                 public void actionPerformed(java.awt.event.ActionEvent evt)
682                 {
683                   displayResults(false);
684                 }
685               });
686       wsInfo.setResultsReady();
687     }
688     else
689     {
690       wsInfo.setFinishedNoResults();
691     }
692   }
693
694   void displayResults(boolean newFrame)
695   {
696     // view input or result data for each block
697     Vector alorders = new Vector();
698     SequenceI[][] results = new SequenceI[jobs.length][];
699     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
700     String lastProgram = null;
701     MsaWSJob msjob;
702     for (int j = 0; j < jobs.length; j++)
703     {
704       if (jobs[j].hasResults())
705       {
706         msjob = (MsaWSJob)jobs[j];
707         Object[] res = msjob.getAlignment();
708         lastProgram = msjob.getAlignmentProgram();
709         alorders.add(res[1]);
710         results[j] = (SequenceI[]) res[0];
711         orders[j] = (AlignmentOrder) res[1];
712
713         // SequenceI[] alignment = input.getUpdated
714       }
715       else
716       {
717         results[j] = null;
718       }
719     }
720     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
721     // trash references to original result data
722     for (int j = 0; j < jobs.length; j++)
723     {
724       results[j] = null;
725       orders[j] = null;
726     }
727     SequenceI[] alignment = (SequenceI[]) newview[0];
728     ColumnSelection columnselection = (ColumnSelection) newview[1];
729     Alignment al = new Alignment(alignment);
730     // TODO: add 'provenance' property to alignment from the method notes
731     if (lastProgram!=null) {
732       al.setProperty("Alignment Program", lastProgram);
733     }
734     // accompanying each subjob
735     if (dataset != null)
736     {
737       al.setDataset(dataset);
738     }
739
740     propagateDatasetMappings(al);
741     // JBNote- TODO: warn user if a block is input rather than aligned data ?
742
743     if (newFrame)
744     {
745       AlignFrame af = new AlignFrame(al, columnselection,
746               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
747
748       // initialise with same renderer settings as in parent alignframe.
749       af.getFeatureRenderer().transferSettings(this.featureSettings);
750       // update orders
751       if (alorders.size() > 0)
752       {
753         if (alorders.size() == 1)
754         {
755           af.addSortByOrderMenuItem(WebServiceName + " Ordering",
756                   (AlignmentOrder) alorders.get(0));
757         }
758         else
759         {
760           // construct a non-redundant ordering set
761           Vector names = new Vector();
762           for (int i = 0, l = alorders.size(); i < l; i++)
763           {
764             String orderName = new String(" Region " + i);
765             int j = i + 1;
766
767             while (j < l)
768             {
769               if (((AlignmentOrder) alorders.get(i))
770                       .equals(((AlignmentOrder) alorders.get(j))))
771               {
772                 alorders.remove(j);
773                 l--;
774                 orderName += "," + j;
775               }
776               else
777               {
778                 j++;
779               }
780             }
781
782             if (i == 0 && j == 1)
783             {
784               names.add(new String(""));
785             }
786             else
787             {
788               names.add(orderName);
789             }
790           }
791           for (int i = 0, l = alorders.size(); i < l; i++)
792           {
793             af.addSortByOrderMenuItem(WebServiceName
794                     + ((String) names.get(i)) + " Ordering",
795                     (AlignmentOrder) alorders.get(i));
796           }
797         }
798       }
799
800       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
801               AlignFrame.DEFAULT_HEIGHT);
802
803     }
804     else
805     {
806       System.out.println("MERGE WITH OLD FRAME");
807       // TODO: modify alignment in original frame, replacing old for new
808       // alignment using the commands.EditCommand model to ensure the update can
809       // be undone
810     }
811   }
812
813   public boolean canMergeResults()
814   {
815     return false;
816   }
817 }