preset and argument support
[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   }
352
353   String alTitle; // name which will be used to form new alignment window.
354
355   Alignment dataset; // dataset to which the new alignment will be
356
357   // associated.
358
359   @SuppressWarnings("unchecked")
360   MsaWS server = null;
361
362   /**
363    * set basic options for this (group) of Msa jobs
364    * 
365    * @param subgaps
366    *          boolean
367    * @param presorder
368    *          boolean
369    */
370   MsaWSThread(MsaWS server, String wsUrl,
371           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
372           AlignmentView alview, String wsname, boolean subgaps,
373           boolean presorder)
374   {
375     super(alFrame, wsinfo, alview, wsname, wsUrl);
376     this.server = server;
377     this.submitGaps = subgaps;
378     this.preserveOrder = presorder;
379   }
380
381   /**
382    * create one or more Msa jobs to align visible seuqences in _msa
383    * 
384    * @param title
385    *          String
386    * @param _msa
387    *          AlignmentView
388    * @param subgaps
389    *          boolean
390    * @param presorder
391    *          boolean
392    * @param seqset
393    *          Alignment
394    */
395   MsaWSThread(MsaWS server2, Preset preset, List<Argument> paramset, String wsUrl, 
396           WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
397           String wsname, String title, AlignmentView _msa, boolean subgaps,
398           boolean presorder, Alignment seqset)
399   {
400     this(server2,wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
401     OutputHeader = wsInfo.getProgressText();
402     alTitle = title;
403     dataset = seqset;
404
405     SequenceI[][] conmsa = _msa.getVisibleContigs('-');
406     if (conmsa != null)
407     {
408       int njobs = conmsa.length;
409       jobs = new MsaWSJob[njobs];
410       for (int j = 0; j < njobs; j++)
411       {
412         if (j != 0)
413         {
414           jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
415         }
416         else
417         {
418           jobs[j] = new MsaWSJob(0, conmsa[j]);
419         }
420         ((MsaWSJob) jobs[j]).preset = preset;
421         ((MsaWSJob) jobs[j]).arguments = paramset;
422         if (njobs > 0)
423         {
424           wsinfo
425                   .setProgressName("region " + jobs[j].getJobnum(),
426                           jobs[j].getJobnum());
427         }
428         wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
429       }
430     }
431   }
432
433   public boolean isCancellable()
434   {
435     return true;
436   }
437
438   public void cancelJob()
439   {
440     if (!jobComplete && jobs != null)
441     {
442       boolean cancelled = true;
443       for (int job = 0; job < jobs.length; job++)
444       {
445         if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
446         {
447           String cancelledMessage = "";
448           try
449           {
450             boolean cancelledJob = server
451                     .cancelJob(jobs[job].getJobId());
452             if (cancelledJob)
453             {
454               // CANCELLED_JOB
455               cancelledMessage = "Job cancelled.";
456               ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this ugliness - 
457               wsInfo.setStatus(jobs[job].getJobnum(),
458                       WebserviceInfo.STATE_CANCELLED_OK);
459             } 
460             else 
461             {
462               // VALID UNSTOPPABLE JOB
463               cancelledMessage += "Server cannot cancel this job. just close the window.\n";
464               cancelled = false;
465               // wsInfo.setStatus(jobs[job].jobnum,
466               // WebserviceInfo.STATE_RUNNING);
467             }
468           } catch (Exception exc)
469           {
470             cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
471                     + exc + "\n");
472             Cache.log.warn(
473                     "Exception whilst cancelling " + jobs[job].getJobId(), exc);
474           }
475           wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
476                   + cancelledMessage + "\n");
477         }
478       }
479       if (cancelled)
480       {
481         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
482         jobComplete = true;
483       }
484       this.interrupt(); // kick thread to update job states.
485     }
486     else
487     {
488       if (!jobComplete)
489       {
490         wsInfo
491                 .setProgressText(OutputHeader
492                         + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
493       }
494     }
495   }
496
497   public void pollJob(AWsJob job) throws Exception
498   {
499     // TODO: investigate if we still need to cast here in J1.6
500     MsaWSJob j=((MsaWSJob) job);
501     // 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.
502     j.setjobStatus(server.getJobStatus(job.getJobId()));
503     updateJobProgress(j);
504   }
505   protected void updateJobProgress(MsaWSJob j) throws Exception {
506     StringBuffer response = j.jobProgress;
507     long lastchunk = j.getLastChunk();
508     do {
509       j.setLastChunk(lastchunk);
510       ChunkHolder chunk = server.pullExecStatistics(j.getJobId(), lastchunk);
511       if (chunk!=null) {
512         response.append(chunk.getChunk());
513         lastchunk = chunk.getNextPosition();
514       };
515     } while (lastchunk>=0 && j.getLastChunk()!=lastchunk);
516   }
517
518   public void StartJob(AWsJob job)
519   {
520     // boiler plate template
521     if (!(job instanceof MsaWSJob))
522     {
523       throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
524               + job.getClass());
525     }
526     MsaWSJob j = (MsaWSJob) job;
527     if (j.isSubmitted())
528     {
529       if (Cache.log.isDebugEnabled())
530       {
531         Cache.log.debug("Tried to submit an already submitted job "
532                 + j.getJobId());
533       }
534       return;
535     }
536     // end boilerplate
537     
538     if (j.seqs == null || j.seqs.size()==0)
539     {
540       // special case - selection consisted entirely of empty sequences...
541       j.setjobStatus(JobStatus.FINISHED);
542       j.setStatus("Empty Alignment Job");
543     }
544     try
545     {
546       // TODO: get the parameters (if any) for this job and submit the job
547       if (j.arguments!=null && j.arguments.size()>0)
548       {
549         j.setJobId(server.customAlign(j.seqs, j.arguments));
550       } else
551         if (j.preset!=null)
552         {
553           j.setJobId(server.presetAlign(j.seqs, j.preset));
554         } else {
555                 j.setJobId(server.align(j.seqs));
556         }
557
558       if (j.getJobId()!= null)
559       {
560         j.setSubmitted(true);
561         j.setSubjobComplete(false);
562         // System.out.println(WsURL + " Job Id '" + jobId + "'");
563       }
564       else
565       {
566         throw new Exception(
567                   "Server at "
568                           + WsUrl
569                           + " returned null string for job id, it probably cannot be contacted. Try again later ?");
570         }
571     } catch (Exception e)
572     {
573       // Boilerplate code here
574       // TODO: JBPNote catch timeout or other fault types explicitly
575       // For unexpected errors
576       System.err
577               .println(WebServiceName
578                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
579                       + "When contacting Server:" + WsUrl + "\n");
580       e.printStackTrace(System.err);
581       j.setAllowedServerExceptions(0);
582       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
583       wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR);
584       wsInfo
585               .appendProgressText(
586                       j.getJobnum(),
587                       "Failed to submit sequences for alignment.\n"
588                               + "It is most likely that there is a problem with the server.\n"
589                               + "Just close the window\n");
590
591       // e.printStackTrace(); // TODO: JBPNote DEBUG
592     }
593   }
594
595
596   public void parseResult()
597   {
598     int results = 0; // number of result sets received
599     JobStateSummary finalState = new JobStateSummary();
600     try
601     {
602       for (int j = 0; j < jobs.length; j++)
603       {
604         MsaWSJob msjob = ((MsaWSJob) jobs[j]);
605         if (jobs[j].isFinished() && msjob.alignment==null)
606         {
607           try {
608             updateJobProgress(msjob);
609           } catch (Exception e)
610           {
611             Cache.log.warn("Exception when retrieving remaining Job progress data for job "+msjob.getJobId()+" on server "+WsUrl);
612             e.printStackTrace();
613           }
614           if (Cache.log.isDebugEnabled())
615           {
616             System.out.println("Job Execution file for job: "+msjob.getJobId()+" on server "+WsUrl);
617             System.out.println(msjob.getStatus());
618             System.out.println("*** End of status");
619             
620           }
621           try {
622             msjob.alignment = server.getResult(msjob.getJobId());
623           }
624           catch (Exception e)
625           {
626             Cache.log.error("Couldn't get Alignment for job.",e);
627           }
628         }
629         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
630         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
631                 && jobs[j].hasResults())
632         {
633           results++;
634           compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
635           if (alignment != null)
636           {
637             wsInfo.appendProgressText(jobs[j].getJobnum(),
638                     "\nAlignment Object Method Notes\n");
639             wsInfo.appendProgressText(jobs[j].getJobnum(), "Calculated with "+alignment.getMetadata().getProgram().toString());
640             // JBPNote The returned files from a webservice could be
641             // hidden behind icons in the monitor window that,
642             // when clicked, pop up their corresponding data
643           }
644         }
645       }
646     } catch (Exception ex)
647     {
648
649       Cache.log.error("Unexpected exception when processing results for "
650               + alTitle, ex);
651       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
652     }
653     if (results > 0)
654     {
655       wsInfo.showResultsNewFrame
656               .addActionListener(new java.awt.event.ActionListener()
657               {
658                 public void actionPerformed(java.awt.event.ActionEvent evt)
659                 {
660                   displayResults(true);
661                 }
662               });
663       wsInfo.mergeResults
664               .addActionListener(new java.awt.event.ActionListener()
665               {
666                 public void actionPerformed(java.awt.event.ActionEvent evt)
667                 {
668                   displayResults(false);
669                 }
670               });
671       wsInfo.setResultsReady();
672     }
673     else
674     {
675       wsInfo.setFinishedNoResults();
676     }
677   }
678
679   void displayResults(boolean newFrame)
680   {
681     // view input or result data for each block
682     Vector alorders = new Vector();
683     SequenceI[][] results = new SequenceI[jobs.length][];
684     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
685     String lastProgram = null;
686     MsaWSJob msjob;
687     for (int j = 0; j < jobs.length; j++)
688     {
689       if (jobs[j].hasResults())
690       {
691         msjob = (MsaWSJob)jobs[j];
692         Object[] res = msjob.getAlignment();
693         lastProgram = msjob.alignment.getMetadata().getProgram().name();
694         alorders.add(res[1]);
695         results[j] = (SequenceI[]) res[0];
696         orders[j] = (AlignmentOrder) res[1];
697
698         // SequenceI[] alignment = input.getUpdated
699       }
700       else
701       {
702         results[j] = null;
703       }
704     }
705     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
706     // trash references to original result data
707     for (int j = 0; j < jobs.length; j++)
708     {
709       results[j] = null;
710       orders[j] = null;
711     }
712     SequenceI[] alignment = (SequenceI[]) newview[0];
713     ColumnSelection columnselection = (ColumnSelection) newview[1];
714     Alignment al = new Alignment(alignment);
715     // TODO: add 'provenance' property to alignment from the method notes
716     if (lastProgram!=null) {
717       al.setProperty("Alignment Program", lastProgram);
718     }
719     // accompanying each subjob
720     if (dataset != null)
721     {
722       al.setDataset(dataset);
723     }
724
725     propagateDatasetMappings(al);
726     // JBNote- TODO: warn user if a block is input rather than aligned data ?
727
728     if (newFrame)
729     {
730       AlignFrame af = new AlignFrame(al, columnselection,
731               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
732
733       // initialise with same renderer settings as in parent alignframe.
734       af.getFeatureRenderer().transferSettings(this.featureSettings);
735       // update orders
736       if (alorders.size() > 0)
737       {
738         if (alorders.size() == 1)
739         {
740           af.addSortByOrderMenuItem(WebServiceName + " Ordering",
741                   (AlignmentOrder) alorders.get(0));
742         }
743         else
744         {
745           // construct a non-redundant ordering set
746           Vector names = new Vector();
747           for (int i = 0, l = alorders.size(); i < l; i++)
748           {
749             String orderName = new String(" Region " + i);
750             int j = i + 1;
751
752             while (j < l)
753             {
754               if (((AlignmentOrder) alorders.get(i))
755                       .equals(((AlignmentOrder) alorders.get(j))))
756               {
757                 alorders.remove(j);
758                 l--;
759                 orderName += "," + j;
760               }
761               else
762               {
763                 j++;
764               }
765             }
766
767             if (i == 0 && j == 1)
768             {
769               names.add(new String(""));
770             }
771             else
772             {
773               names.add(orderName);
774             }
775           }
776           for (int i = 0, l = alorders.size(); i < l; i++)
777           {
778             af.addSortByOrderMenuItem(WebServiceName
779                     + ((String) names.get(i)) + " Ordering",
780                     (AlignmentOrder) alorders.get(i));
781           }
782         }
783       }
784
785       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
786               AlignFrame.DEFAULT_HEIGHT);
787
788     }
789     else
790     {
791       System.out.println("MERGE WITH OLD FRAME");
792       // TODO: modify alignment in original frame, replacing old for new
793       // alignment using the commands.EditCommand model to ensure the update can
794       // be undone
795     }
796   }
797
798   public boolean canMergeResults()
799   {
800     return false;
801   }
802 }