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