JAL-353 undo for successive removal of redundant sequence - bugfix
[jalview.git] / src / jalview / commands / EditCommand.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.commands;
22
23 import java.util.*;
24
25 import jalview.datamodel.*;
26
27 /**
28  * 
29  * <p>
30  * Title: EditCommmand
31  * </p>
32  * 
33  * <p>
34  * Description: Essential information for performing undo and redo for cut/paste
35  * insert/delete gap which can be stored in the HistoryList
36  * </p>
37  * 
38  * <p>
39  * Copyright: Copyright (c) 2006
40  * </p>
41  * 
42  * <p>
43  * Company: Dundee University
44  * </p>
45  * 
46  * @author not attributable
47  * @version 1.0
48  */
49 public class EditCommand implements CommandI
50 {
51   public static final int INSERT_GAP = 0;
52
53   public static final int DELETE_GAP = 1;
54
55   public static final int CUT = 2;
56
57   public static final int PASTE = 3;
58
59   public static final int REPLACE = 4;
60
61   public static final int INSERT_NUC = 5;
62
63   Edit[] edits;
64
65   String description;
66
67   public EditCommand()
68   {
69   }
70
71   public EditCommand(String description)
72   {
73     this.description = description;
74   }
75
76   public EditCommand(String description, int command, SequenceI[] seqs,
77           int position, int number, AlignmentI al)
78   {
79     this.description = description;
80     if (command == CUT || command == PASTE)
81     {
82       edits = new Edit[]
83       { new Edit(command, seqs, position, number, al) };
84     }
85
86     performEdit(0, null);
87   }
88
89   public EditCommand(String description, int command, String replace,
90           SequenceI[] seqs, int position, int number, AlignmentI al)
91   {
92     this.description = description;
93     if (command == REPLACE)
94     {
95       edits = new Edit[]
96       { new Edit(command, seqs, position, number, al, replace) };
97     }
98
99     performEdit(0, null);
100   }
101
102   @Override
103   final public String getDescription()
104   {
105     return description;
106   }
107
108   @Override
109   public int getSize()
110   {
111     return edits == null ? 0 : edits.length;
112   }
113
114   final public AlignmentI getAlignment()
115   {
116     return edits[0].al;
117   }
118
119   /**
120    * append a new editCommand Note. this shouldn't be called if the edit is an
121    * operation affects more alignment objects than the one referenced in al (for
122    * example, cut or pasting whole sequences). Use the form with an additional
123    * AlignmentI[] views parameter.
124    * 
125    * @param command
126    * @param seqs
127    * @param position
128    * @param number
129    * @param al
130    * @param performEdit
131    */
132   final public void appendEdit(int command, SequenceI[] seqs, int position,
133           int number, AlignmentI al, boolean performEdit)
134   {
135     appendEdit(command, seqs, position, number, al, performEdit, null);
136   }
137
138   /**
139    * append a new edit command with a set of alignment views that may be
140    * operated on
141    * 
142    * @param command
143    * @param seqs
144    * @param position
145    * @param number
146    * @param al
147    * @param performEdit
148    * @param views
149    */
150   final public void appendEdit(int command, SequenceI[] seqs, int position,
151           int number, AlignmentI al, boolean performEdit, AlignmentI[] views)
152   {
153     Edit edit = new Edit(command, seqs, position, number,
154             al.getGapCharacter());
155     if (al.getHeight() == seqs.length)
156     {
157       edit.al = al;
158       edit.fullAlignmentHeight = true;
159     }
160
161     if (edits != null)
162     {
163       Edit[] temp = new Edit[edits.length + 1];
164       System.arraycopy(edits, 0, temp, 0, edits.length);
165       edits = temp;
166       edits[edits.length - 1] = edit;
167     }
168     else
169     {
170       edits = new Edit[]
171       { edit };
172     }
173
174     if (performEdit)
175     {
176       performEdit(edits.length - 1, views);
177     }
178   }
179
180   final void performEdit(int commandIndex, AlignmentI[] views)
181   {
182     int eSize = edits.length;
183     for (int e = commandIndex; e < eSize; e++)
184     {
185       switch (edits[e].command)
186       {
187       case INSERT_GAP:
188         insertGap(edits[e]);
189         break;
190       case DELETE_GAP:
191         deleteGap(edits[e]);
192         break;
193       case CUT:
194         cut(edits[e], views);
195         break;
196       case PASTE:
197         paste(edits[e], views);
198         break;
199       case REPLACE:
200         replace(edits[e]);
201         break;
202       // TODO:add deleteNuc for UNDO
203       // case INSERT_NUC:
204       // insertNuc(edits[e]);
205       // break;
206       }
207     }
208   }
209
210   @Override
211   final public void doCommand(AlignmentI[] views)
212   {
213     performEdit(0, views);
214   }
215
216   @Override
217   final public void undoCommand(AlignmentI[] views)
218   { 
219     for(Edit e : edits){
220         switch (e.command)
221         {
222         case INSERT_GAP:
223           deleteGap(e);
224           break;
225         case DELETE_GAP:
226           insertGap(e);
227           break;
228         case CUT:
229           paste(e, views);
230           break;
231         case PASTE:
232           cut(e, views);
233           break;
234         case REPLACE:
235           replace(e);
236           break;
237         }
238     }
239   }
240
241   final void insertGap(Edit command)
242   {
243
244     for (int s = 0; s < command.seqs.length; s++)
245     {
246       command.seqs[s].insertCharAt(command.position, command.number,
247               command.gapChar);
248       // System.out.println("pos: "+command.position+" number: "+command.number);
249     }
250
251     adjustAnnotations(command, true, false, null);
252   }
253
254   //
255   // final void insertNuc(Edit command)
256   // {
257   //
258   // for (int s = 0; s < command.seqs.length; s++)
259   // {
260   // System.out.println("pos: "+command.position+" number: "+command.number);
261   // command.seqs[s].insertCharAt(command.position, command.number,'A');
262   // }
263   //
264   // adjustAnnotations(command, true, false, null);
265   // }
266
267   final void deleteGap(Edit command)
268   {
269     for (int s = 0; s < command.seqs.length; s++)
270     {
271       command.seqs[s].deleteChars(command.position, command.position
272               + command.number);
273     }
274
275     adjustAnnotations(command, false, false, null);
276   }
277
278   void cut(Edit command, AlignmentI[] views)
279   {
280     boolean seqDeleted = false;
281     command.string = new char[command.seqs.length][];
282
283     for (int i = 0; i < command.seqs.length; i++)
284     {
285       if (command.seqs[i].getLength() > command.position)
286       {
287         command.string[i] = command.seqs[i].getSequence(command.position,
288                 command.position + command.number);
289         SequenceI oldds = command.seqs[i].getDatasetSequence();
290         if (command.oldds != null && command.oldds[i] != null)
291         {
292           // we are redoing an undone cut.
293           command.seqs[i].setDatasetSequence(null);
294         }
295         command.seqs[i].deleteChars(command.position, command.position
296                 + command.number);
297         if (command.oldds != null && command.oldds[i] != null)
298         {
299           // oldds entry contains the cut dataset sequence.
300           command.seqs[i].setDatasetSequence(command.oldds[i]);
301           command.oldds[i] = oldds;
302         }
303         else
304         {
305           // modify the oldds if necessary
306           if (oldds != command.seqs[i].getDatasetSequence()
307                   || command.seqs[i].getSequenceFeatures() != null)
308           {
309             if (command.oldds == null)
310             {
311               command.oldds = new SequenceI[command.seqs.length];
312             }
313             command.oldds[i] = oldds;
314             adjustFeatures(
315                     command,
316                     i,
317                     command.seqs[i].findPosition(command.position),
318                     command.seqs[i].findPosition(command.position
319                             + command.number), false);
320           }
321         }
322       }
323
324       if (command.seqs[i].getLength() < 1)
325       {
326         command.al.deleteSequence(command.seqs[i]);
327         seqDeleted = true;
328       }
329     }
330
331     adjustAnnotations(command, false, seqDeleted, views);
332   }
333
334   void paste(Edit command, AlignmentI[] views)
335   {
336     StringBuffer tmp;
337     boolean newDSNeeded;
338     boolean newDSWasNeeded;
339     int newstart, newend;
340     boolean seqWasDeleted = false;
341     int start = 0, end = 0;
342
343     for (int i = 0; i < command.seqs.length; i++)
344     {
345       newDSNeeded = false;
346       newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
347       if (command.seqs[i].getLength() < 1)
348       {
349         // ie this sequence was deleted, we need to
350         // read it to the alignment
351         if (command.alIndex[i] < command.al.getHeight())
352         {
353           List<SequenceI> sequences;
354           synchronized (sequences = command.al.getSequences())
355           {
356             sequences.add(command.alIndex[i] < 0 ? 0 : command.alIndex[i], command.seqs[i]);
357           }
358         }
359         else
360         {
361           command.al.addSequence(command.seqs[i]);
362         }
363         seqWasDeleted = true;
364       }
365       newstart = command.seqs[i].getStart();
366       newend = command.seqs[i].getEnd();
367
368       tmp = new StringBuffer();
369       tmp.append(command.seqs[i].getSequence());
370       // Undo of a delete does not replace original dataset sequence on to
371       // alignment sequence.
372
373       if (command.string != null && command.string[i] != null)
374       {
375         if (command.position >= tmp.length())
376         {
377           // This occurs if padding is on, and residues
378           // are removed from end of alignment
379           int length = command.position - tmp.length();
380           while (length > 0)
381           {
382             tmp.append(command.gapChar);
383             length--;
384           }
385         }
386         tmp.insert(command.position, command.string[i]);
387         for (int s = 0; s < command.string[i].length; s++)
388         {
389           if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]] != 23)
390           {
391             if (!newDSNeeded)
392             {
393               newDSNeeded = true;
394               start = command.seqs[i].findPosition(command.position);
395               end = command.seqs[i].findPosition(command.position
396                       + command.number);
397             }
398             if (command.seqs[i].getStart() == start)
399               newstart--;
400             else
401               newend++;
402           }
403         }
404         command.string[i] = null;
405       }
406
407       command.seqs[i].setSequence(tmp.toString());
408       command.seqs[i].setStart(newstart);
409       command.seqs[i].setEnd(newend);
410       if (newDSNeeded)
411       {
412         if (command.seqs[i].getDatasetSequence() != null)
413         {
414           SequenceI ds;
415           if (newDSWasNeeded)
416           {
417             ds = command.oldds[i];
418           }
419           else
420           {
421             // make a new DS sequence
422             // use new ds mechanism here
423             ds = new Sequence(command.seqs[i].getName(),
424                     jalview.analysis.AlignSeq.extractGaps(
425                             jalview.util.Comparison.GapChars,
426                             command.seqs[i].getSequenceAsString()),
427                     command.seqs[i].getStart(), command.seqs[i].getEnd());
428             ds.setDescription(command.seqs[i].getDescription());
429           }
430           if (command.oldds == null)
431           {
432             command.oldds = new SequenceI[command.seqs.length];
433           }
434           command.oldds[i] = command.seqs[i].getDatasetSequence();
435           command.seqs[i].setDatasetSequence(ds);
436         }
437         adjustFeatures(command, i, start, end, true);
438       }
439     }
440     adjustAnnotations(command, true, seqWasDeleted, views);
441
442     command.string = null;
443   }
444
445   void replace(Edit command)
446   {
447     StringBuffer tmp;
448     String oldstring;
449     int start = command.position;
450     int end = command.number;
451     // TODO TUTORIAL - Fix for replacement with different length of sequence (or
452     // whole sequence)
453     // TODO Jalview 2.4 bugfix change to an aggregate command - original
454     // sequence string is cut, new string is pasted in.
455     command.number = start + command.string[0].length;
456     for (int i = 0; i < command.seqs.length; i++)
457     {
458       boolean newDSWasNeeded = command.oldds != null
459               && command.oldds[i] != null;
460
461       /**
462        * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
463        * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
464        * viewport.alignment));
465        * 
466        */
467       /**
468        * then addHistoryItem(new EditCommand( "Add sequences",
469        * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
470        * 
471        */
472       oldstring = command.seqs[i].getSequenceAsString();
473       tmp = new StringBuffer(oldstring.substring(0, start));
474       tmp.append(command.string[i]);
475       String nogaprep = jalview.analysis.AlignSeq.extractGaps(
476               jalview.util.Comparison.GapChars, new String(
477                       command.string[i]));
478       int ipos = command.seqs[i].findPosition(start)
479               - command.seqs[i].getStart();
480       tmp.append(oldstring.substring(end));
481       command.seqs[i].setSequence(tmp.toString());
482       command.string[i] = oldstring.substring(start, end).toCharArray();
483       String nogapold = jalview.analysis.AlignSeq.extractGaps(
484               jalview.util.Comparison.GapChars, new String(
485                       command.string[i]));
486       if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
487       {
488         if (newDSWasNeeded)
489         {
490           SequenceI oldds = command.seqs[i].getDatasetSequence();
491           command.seqs[i].setDatasetSequence(command.oldds[i]);
492           command.oldds[i] = oldds;
493         }
494         else
495         {
496           if (command.oldds == null)
497           {
498             command.oldds = new SequenceI[command.seqs.length];
499           }
500           command.oldds[i] = command.seqs[i].getDatasetSequence();
501           SequenceI newds = new Sequence(
502                   command.seqs[i].getDatasetSequence());
503           String fullseq, osp = newds.getSequenceAsString();
504           fullseq = osp.substring(0, ipos) + nogaprep
505                   + osp.substring(ipos + nogaprep.length());
506           newds.setSequence(fullseq.toUpperCase());
507           // TODO: JAL-1131 ensure newly created dataset sequence is added to
508           // the set of
509           // dataset sequences associated with the alignment.
510           // TODO: JAL-1131 fix up any annotation associated with new dataset
511           // sequence to ensure that original sequence/annotation relationships
512           // are preserved.
513           command.seqs[i].setDatasetSequence(newds);
514
515         }
516       }
517       tmp = null;
518       oldstring = null;
519     }
520   }
521
522   final void adjustAnnotations(Edit command, boolean insert,
523           boolean modifyVisibility, AlignmentI[] views)
524   {
525     AlignmentAnnotation[] annotations = null;
526
527     if (modifyVisibility && !insert)
528     {
529       // only occurs if a sequence was added or deleted.
530       command.deletedAnnotationRows = new Hashtable();
531     }
532     if (command.fullAlignmentHeight)
533     {
534       annotations = command.al.getAlignmentAnnotation();
535     }
536     else
537     {
538       int aSize = 0;
539       AlignmentAnnotation[] tmp;
540       for (int s = 0; s < command.seqs.length; s++)
541       {
542         if (modifyVisibility)
543         {
544           // Rows are only removed or added to sequence object.
545           if (!insert)
546           {
547             // remove rows
548             tmp = command.seqs[s].getAnnotation();
549             if (tmp != null)
550             {
551               int alen = tmp.length;
552               for (int aa = 0; aa < tmp.length; aa++)
553               {
554                 if (!command.al.deleteAnnotation(tmp[aa]))
555                 {
556                   // strip out annotation not in the current al (will be put
557                   // back on insert in all views)
558                   tmp[aa] = null;
559                   alen--;
560                 }
561               }
562               command.seqs[s].setAlignmentAnnotation(null);
563               if (alen != tmp.length)
564               {
565                 // save the non-null annotation references only
566                 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
567                 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
568                 {
569                   if (tmp[aa] != null)
570                   {
571                     saved[aapos++] = tmp[aa];
572                     tmp[aa] = null;
573                   }
574                 }
575                 tmp = saved;
576                 command.deletedAnnotationRows.put(command.seqs[s], saved);
577                 // and then remove any annotation in the other views
578                 for (int alview = 0; views != null && alview < views.length; alview++)
579                 {
580                   if (views[alview] != command.al)
581                   {
582                     AlignmentAnnotation[] toremove = views[alview]
583                             .getAlignmentAnnotation();
584                     if (toremove == null || toremove.length == 0)
585                     {
586                       continue;
587                     }
588                     // remove any alignment annotation on this sequence that's
589                     // on that alignment view.
590                     for (int aa = 0; aa < toremove.length; aa++)
591                     {
592                       if (toremove[aa].sequenceRef == command.seqs[s])
593                       {
594                         views[alview].deleteAnnotation(toremove[aa]);
595                       }
596                     }
597                   }
598                 }
599               }
600               else
601               {
602                 // save all the annotation
603                 command.deletedAnnotationRows.put(command.seqs[s], tmp);
604               }
605             }
606           }
607           else
608           {
609             // recover rows
610             if (command.deletedAnnotationRows != null
611                     && command.deletedAnnotationRows
612                             .containsKey(command.seqs[s]))
613             {
614               AlignmentAnnotation[] revealed = (AlignmentAnnotation[]) command.deletedAnnotationRows
615                       .get(command.seqs[s]);
616               command.seqs[s].setAlignmentAnnotation(revealed);
617               if (revealed != null)
618               {
619                 for (int aa = 0; aa < revealed.length; aa++)
620                 {
621                   // iterate through al adding original annotation
622                   command.al.addAnnotation(revealed[aa]);
623                 }
624                 for (int aa = 0; aa < revealed.length; aa++)
625                 {
626                   command.al.setAnnotationIndex(revealed[aa], aa);
627                 }
628                 // and then duplicate added annotation on every other alignment
629                 // view
630                 for (int vnum = 0; views != null && vnum < views.length; vnum++)
631                 {
632                   if (views[vnum] != command.al)
633                   {
634                     int avwidth = views[vnum].getWidth() + 1;
635                     // duplicate in this view
636                     for (int a = 0; a < revealed.length; a++)
637                     {
638                       AlignmentAnnotation newann = new AlignmentAnnotation(
639                               revealed[a]);
640                       command.seqs[s].addAlignmentAnnotation(newann);
641                       newann.padAnnotation(avwidth);
642                       views[vnum].addAnnotation(newann);
643                       views[vnum].setAnnotationIndex(newann, a);
644                     }
645                   }
646                 }
647               }
648             }
649           }
650           continue;
651         }
652
653         if (command.seqs[s].getAnnotation() == null)
654         {
655           continue;
656         }
657
658         if (aSize == 0)
659         {
660           annotations = command.seqs[s].getAnnotation();
661         }
662         else
663         {
664           tmp = new AlignmentAnnotation[aSize
665                   + command.seqs[s].getAnnotation().length];
666
667           System.arraycopy(annotations, 0, tmp, 0, aSize);
668
669           System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
670                   command.seqs[s].getAnnotation().length);
671
672           annotations = tmp;
673         }
674         aSize = annotations.length;
675       }
676     }
677
678     if (annotations == null)
679     {
680       return;
681     }
682
683     if (!insert)
684     {
685       command.deletedAnnotations = new Hashtable();
686     }
687
688     int aSize;
689     Annotation[] temp;
690     for (int a = 0; a < annotations.length; a++)
691     {
692       if (annotations[a].autoCalculated
693               || annotations[a].annotations == null)
694       {
695         continue;
696       }
697
698       int tSize = 0;
699
700       aSize = annotations[a].annotations.length;
701       if (insert)
702       {
703         temp = new Annotation[aSize + command.number];
704         if (annotations[a].padGaps)
705           for (int aa = 0; aa < temp.length; aa++)
706           {
707             temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
708           }
709       }
710       else
711       {
712         if (command.position < aSize)
713         {
714           if (command.position + command.number >= aSize)
715           {
716             tSize = aSize;
717           }
718           else
719           {
720             tSize = aSize - command.number;
721           }
722         }
723         else
724         {
725           tSize = aSize;
726         }
727
728         if (tSize < 0)
729         {
730           tSize = aSize;
731         }
732         temp = new Annotation[tSize];
733       }
734
735       if (insert)
736       {
737         if (command.position < annotations[a].annotations.length)
738         {
739           System.arraycopy(annotations[a].annotations, 0, temp, 0,
740                   command.position);
741
742           if (command.deletedAnnotations != null
743                   && command.deletedAnnotations
744                           .containsKey(annotations[a].annotationId))
745           {
746             Annotation[] restore = (Annotation[]) command.deletedAnnotations
747                     .get(annotations[a].annotationId);
748
749             System.arraycopy(restore, 0, temp, command.position,
750                     command.number);
751
752           }
753
754           System.arraycopy(annotations[a].annotations, command.position,
755                   temp, command.position + command.number, aSize
756                           - command.position);
757         }
758         else
759         {
760           if (command.deletedAnnotations != null
761                   && command.deletedAnnotations
762                           .containsKey(annotations[a].annotationId))
763           {
764             Annotation[] restore = (Annotation[]) command.deletedAnnotations
765                     .get(annotations[a].annotationId);
766
767             temp = new Annotation[annotations[a].annotations.length
768                     + restore.length];
769             System.arraycopy(annotations[a].annotations, 0, temp, 0,
770                     annotations[a].annotations.length);
771             System.arraycopy(restore, 0, temp,
772                     annotations[a].annotations.length, restore.length);
773           }
774           else
775           {
776             temp = annotations[a].annotations;
777           }
778         }
779       }
780       else
781       {
782         if (tSize != aSize || command.position < 2)
783         {
784           int copylen = Math.min(command.position,
785                   annotations[a].annotations.length);
786           if (copylen > 0)
787             System.arraycopy(annotations[a].annotations, 0, temp, 0,
788                     copylen); // command.position);
789
790           Annotation[] deleted = new Annotation[command.number];
791           if (copylen >= command.position)
792           {
793             copylen = Math.min(command.number,
794                     annotations[a].annotations.length - command.position);
795             if (copylen > 0)
796             {
797               System.arraycopy(annotations[a].annotations,
798                       command.position, deleted, 0, copylen); // command.number);
799             }
800           }
801
802           command.deletedAnnotations.put(annotations[a].annotationId,
803                   deleted);
804           if (annotations[a].annotations.length > command.position
805                   + command.number)
806           {
807             System.arraycopy(annotations[a].annotations, command.position
808                     + command.number, temp, command.position,
809                     annotations[a].annotations.length - command.position
810                             - command.number); // aSize
811           }
812         }
813         else
814         {
815           int dSize = aSize - command.position;
816
817           if (dSize > 0)
818           {
819             Annotation[] deleted = new Annotation[command.number];
820             System.arraycopy(annotations[a].annotations, command.position,
821                     deleted, 0, dSize);
822
823             command.deletedAnnotations.put(annotations[a].annotationId,
824                     deleted);
825
826             tSize = Math.min(annotations[a].annotations.length,
827                     command.position);
828             temp = new Annotation[tSize];
829             System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
830           }
831           else
832           {
833             temp = annotations[a].annotations;
834           }
835         }
836       }
837
838       annotations[a].annotations = temp;
839     }
840   }
841
842   final void adjustFeatures(Edit command, int index, int i, int j,
843           boolean insert)
844   {
845     SequenceI seq = command.seqs[index];
846     SequenceI sequence = seq.getDatasetSequence();
847     if (sequence == null)
848     {
849       sequence = seq;
850     }
851
852     if (insert)
853     {
854       if (command.editedFeatures != null
855               && command.editedFeatures.containsKey(seq))
856       {
857         sequence.setSequenceFeatures((SequenceFeature[]) command.editedFeatures
858                 .get(seq));
859       }
860
861       return;
862     }
863
864     SequenceFeature[] sf = sequence.getSequenceFeatures();
865
866     if (sf == null)
867     {
868       return;
869     }
870
871     SequenceFeature[] oldsf = new SequenceFeature[sf.length];
872
873     int cSize = j - i;
874
875     for (int s = 0; s < sf.length; s++)
876     {
877       SequenceFeature copy = new SequenceFeature(sf[s]);
878
879       oldsf[s] = copy;
880
881       if (sf[s].getEnd() < i)
882       {
883         continue;
884       }
885
886       if (sf[s].getBegin() > j)
887       {
888         sf[s].setBegin(copy.getBegin() - cSize);
889         sf[s].setEnd(copy.getEnd() - cSize);
890         continue;
891       }
892
893       if (sf[s].getBegin() >= i)
894       {
895         sf[s].setBegin(i);
896       }
897
898       if (sf[s].getEnd() < j)
899       {
900         sf[s].setEnd(j - 1);
901       }
902
903       sf[s].setEnd(sf[s].getEnd() - (cSize));
904
905       if (sf[s].getBegin() > sf[s].getEnd())
906       {
907         sequence.deleteFeature(sf[s]);
908       }
909     }
910
911     if (command.editedFeatures == null)
912     {
913       command.editedFeatures = new Hashtable();
914     }
915
916     command.editedFeatures.put(seq, oldsf);
917
918   }
919
920   class Edit
921   {
922     public SequenceI[] oldds;
923
924     boolean fullAlignmentHeight = false;
925
926     Hashtable deletedAnnotationRows;
927
928     Hashtable deletedAnnotations;
929
930     Hashtable editedFeatures;
931
932     AlignmentI al;
933
934     int command;
935
936     char[][] string;
937
938     SequenceI[] seqs;
939
940     int[] alIndex;
941
942     int position, number;
943
944     char gapChar;
945
946     Edit(int command, SequenceI[] seqs, int position, int number,
947             char gapChar)
948     {
949       this.command = command;
950       this.seqs = seqs;
951       this.position = position;
952       this.number = number;
953       this.gapChar = gapChar;
954     }
955
956     Edit(int command, SequenceI[] seqs, int position, int number,
957             AlignmentI al)
958     {
959       this.gapChar = al.getGapCharacter();
960       this.command = command;
961       this.seqs = seqs;
962       this.position = position;
963       this.number = number;
964       this.al = al;
965
966       alIndex = new int[seqs.length];
967       for (int i = 0; i < seqs.length; i++)
968       {
969         alIndex[i] = al.findIndex(seqs[i]);
970       }
971
972       fullAlignmentHeight = (al.getHeight() == seqs.length);
973     }
974
975     Edit(int command, SequenceI[] seqs, int position, int number,
976             AlignmentI al, String replace)
977     {
978       this.command = command;
979       this.seqs = seqs;
980       this.position = position;
981       this.number = number;
982       this.al = al;
983       this.gapChar = al.getGapCharacter();
984       string = new char[seqs.length][];
985       for (int i = 0; i < seqs.length; i++)
986       {
987         string[i] = replace.toCharArray();
988       }
989
990       fullAlignmentHeight = (al.getHeight() == seqs.length);
991     }
992   }
993 }