edit jws2 job parameters before running job
[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         StringBuffer pset = new StringBuffer();
557         for (Argument arg: j.arguments) {
558           pset.append(arg.getName()+" "+arg.getDefaultValue()+"\n");
559         }
560         j.setStatus("Custom Parameters:\n"+pset.toString()+"\nJob Output:\n");
561         j.setJobId(server.customAlign(j.seqs, j.arguments));
562       } else
563         if (j.preset!=null)
564         {
565           j.setJobId(server.presetAlign(j.seqs, j.preset));
566         } else {
567                 j.setJobId(server.align(j.seqs));
568         }
569
570       if (j.getJobId()!= null)
571       {
572         j.setSubmitted(true);
573         j.setSubjobComplete(false);
574         // System.out.println(WsURL + " Job Id '" + jobId + "'");
575       }
576       else
577       {
578         throw new Exception(
579                   "Server at "
580                           + WsUrl
581                           + " returned null string for job id, it probably cannot be contacted. Try again later ?");
582         }
583     } catch (Exception e)
584     {
585       // Boilerplate code here
586       // TODO: JBPNote catch timeout or other fault types explicitly
587       // For unexpected errors
588       System.err
589               .println(WebServiceName
590                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
591                       + "When contacting Server:" + WsUrl + "\n");
592       e.printStackTrace(System.err);
593       j.setAllowedServerExceptions(0);
594       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
595       wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR);
596       wsInfo
597               .appendProgressText(
598                       j.getJobnum(),
599                       "Failed to submit sequences for alignment.\n"
600                               + "It is most likely that there is a problem with the server.\n"
601                               + "Just close the window\n");
602
603       // e.printStackTrace(); // TODO: JBPNote DEBUG
604     }
605   }
606
607
608   public void parseResult()
609   {
610     int results = 0; // number of result sets received
611     JobStateSummary finalState = new JobStateSummary();
612     try
613     {
614       for (int j = 0; j < jobs.length; j++)
615       {
616         MsaWSJob msjob = ((MsaWSJob) jobs[j]);
617         if (jobs[j].isFinished() && msjob.alignment==null)
618         {
619           try {
620             updateJobProgress(msjob);
621           } catch (Exception e)
622           {
623             Cache.log.warn("Exception when retrieving remaining Job progress data for job "+msjob.getJobId()+" on server "+WsUrl);
624             e.printStackTrace();
625           }
626           if (Cache.log.isDebugEnabled())
627           {
628             System.out.println("Job Execution file for job: "+msjob.getJobId()+" on server "+WsUrl);
629             System.out.println(msjob.getStatus());
630             System.out.println("*** End of status");
631             
632           }
633           try {
634             msjob.alignment = server.getResult(msjob.getJobId());
635           }
636           catch (Exception e)
637           {
638             Cache.log.error("Couldn't get Alignment for job.",e);
639           }
640         }
641         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
642         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
643                 && jobs[j].hasResults())
644         {
645           results++;
646           compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
647           if (alignment != null)
648           {
649             wsInfo.appendProgressText(jobs[j].getJobnum(),
650                     "\nAlignment Object Method Notes\n");
651             wsInfo.appendProgressText(jobs[j].getJobnum(), "Calculated with "+alignment.getMetadata().getProgram().toString());
652             // JBPNote The returned files from a webservice could be
653             // hidden behind icons in the monitor window that,
654             // when clicked, pop up their corresponding data
655           }
656         }
657       }
658     } catch (Exception ex)
659     {
660
661       Cache.log.error("Unexpected exception when processing results for "
662               + alTitle, ex);
663       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
664     }
665     if (results > 0)
666     {
667       wsInfo.showResultsNewFrame
668               .addActionListener(new java.awt.event.ActionListener()
669               {
670                 public void actionPerformed(java.awt.event.ActionEvent evt)
671                 {
672                   displayResults(true);
673                 }
674               });
675       wsInfo.mergeResults
676               .addActionListener(new java.awt.event.ActionListener()
677               {
678                 public void actionPerformed(java.awt.event.ActionEvent evt)
679                 {
680                   displayResults(false);
681                 }
682               });
683       wsInfo.setResultsReady();
684     }
685     else
686     {
687       wsInfo.setFinishedNoResults();
688     }
689   }
690
691   void displayResults(boolean newFrame)
692   {
693     // view input or result data for each block
694     Vector alorders = new Vector();
695     SequenceI[][] results = new SequenceI[jobs.length][];
696     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
697     String lastProgram = null;
698     MsaWSJob msjob;
699     for (int j = 0; j < jobs.length; j++)
700     {
701       if (jobs[j].hasResults())
702       {
703         msjob = (MsaWSJob)jobs[j];
704         Object[] res = msjob.getAlignment();
705         lastProgram = msjob.getAlignmentProgram();
706         alorders.add(res[1]);
707         results[j] = (SequenceI[]) res[0];
708         orders[j] = (AlignmentOrder) res[1];
709
710         // SequenceI[] alignment = input.getUpdated
711       }
712       else
713       {
714         results[j] = null;
715       }
716     }
717     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
718     // trash references to original result data
719     for (int j = 0; j < jobs.length; j++)
720     {
721       results[j] = null;
722       orders[j] = null;
723     }
724     SequenceI[] alignment = (SequenceI[]) newview[0];
725     ColumnSelection columnselection = (ColumnSelection) newview[1];
726     Alignment al = new Alignment(alignment);
727     // TODO: add 'provenance' property to alignment from the method notes
728     if (lastProgram!=null) {
729       al.setProperty("Alignment Program", lastProgram);
730     }
731     // accompanying each subjob
732     if (dataset != null)
733     {
734       al.setDataset(dataset);
735     }
736
737     propagateDatasetMappings(al);
738     // JBNote- TODO: warn user if a block is input rather than aligned data ?
739
740     if (newFrame)
741     {
742       AlignFrame af = new AlignFrame(al, columnselection,
743               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
744
745       // initialise with same renderer settings as in parent alignframe.
746       af.getFeatureRenderer().transferSettings(this.featureSettings);
747       // update orders
748       if (alorders.size() > 0)
749       {
750         if (alorders.size() == 1)
751         {
752           af.addSortByOrderMenuItem(WebServiceName + " Ordering",
753                   (AlignmentOrder) alorders.get(0));
754         }
755         else
756         {
757           // construct a non-redundant ordering set
758           Vector names = new Vector();
759           for (int i = 0, l = alorders.size(); i < l; i++)
760           {
761             String orderName = new String(" Region " + i);
762             int j = i + 1;
763
764             while (j < l)
765             {
766               if (((AlignmentOrder) alorders.get(i))
767                       .equals(((AlignmentOrder) alorders.get(j))))
768               {
769                 alorders.remove(j);
770                 l--;
771                 orderName += "," + j;
772               }
773               else
774               {
775                 j++;
776               }
777             }
778
779             if (i == 0 && j == 1)
780             {
781               names.add(new String(""));
782             }
783             else
784             {
785               names.add(orderName);
786             }
787           }
788           for (int i = 0, l = alorders.size(); i < l; i++)
789           {
790             af.addSortByOrderMenuItem(WebServiceName
791                     + ((String) names.get(i)) + " Ordering",
792                     (AlignmentOrder) alorders.get(i));
793           }
794         }
795       }
796
797       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
798               AlignFrame.DEFAULT_HEIGHT);
799
800     }
801     else
802     {
803       System.out.println("MERGE WITH OLD FRAME");
804       // TODO: modify alignment in original frame, replacing old for new
805       // alignment using the commands.EditCommand model to ensure the update can
806       // be undone
807     }
808   }
809
810   public boolean canMergeResults()
811   {
812     return false;
813   }
814 }