5c8f0be7eb1b2c334c0aac326b9044ae413445a0
[jalview.git] / src / jalview / commands / EditCommand.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, 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   //
254   // final void insertNuc(Edit command)
255   // {
256   //
257   // for (int s = 0; s < command.seqs.length; s++)
258   // {
259   // System.out.println("pos: "+command.position+" number: "+command.number);
260   // command.seqs[s].insertCharAt(command.position, command.number,'A');
261   // }
262   //
263   // adjustAnnotations(command, true, false, null);
264   // }
265
266   final void deleteGap(Edit command)
267   {
268     for (int s = 0; s < command.seqs.length; s++)
269     {
270       command.seqs[s].deleteChars(command.position, command.position
271               + command.number);
272     }
273
274     adjustAnnotations(command, false, false, null);
275   }
276
277   void cut(Edit command, AlignmentI[] views)
278   {
279     boolean seqDeleted = false;
280     command.string = new char[command.seqs.length][];
281
282     for (int i = 0; i < command.seqs.length; i++)
283     {
284       if (command.seqs[i].getLength() > command.position)
285       {
286         command.string[i] = command.seqs[i].getSequence(command.position,
287                 command.position + command.number);
288         SequenceI oldds = command.seqs[i].getDatasetSequence();
289         if (command.oldds != null && command.oldds[i] != null)
290         {
291           // we are redoing an undone cut.
292           command.seqs[i].setDatasetSequence(null);
293         }
294         command.seqs[i].deleteChars(command.position, command.position
295                 + command.number);
296         if (command.oldds != null && command.oldds[i] != null)
297         {
298           // oldds entry contains the cut dataset sequence.
299           command.seqs[i].setDatasetSequence(command.oldds[i]);
300           command.oldds[i] = oldds;
301         }
302         else
303         {
304           // modify the oldds if necessary
305           if (oldds != command.seqs[i].getDatasetSequence()
306                   || command.seqs[i].getSequenceFeatures() != null)
307           {
308             if (command.oldds == null)
309             {
310               command.oldds = new SequenceI[command.seqs.length];
311             }
312             command.oldds[i] = oldds;
313             adjustFeatures(
314                     command,
315                     i,
316                     command.seqs[i].findPosition(command.position),
317                     command.seqs[i].findPosition(command.position
318                             + command.number), false);
319           }
320         }
321       }
322
323       if (command.seqs[i].getLength() < 1)
324       {
325         command.al.deleteSequence(command.seqs[i]);
326         seqDeleted = true;
327       }
328     }
329
330     adjustAnnotations(command, false, seqDeleted, views);
331   }
332
333   void paste(Edit command, AlignmentI[] views)
334   {
335     StringBuffer tmp;
336     boolean newDSNeeded;
337     boolean newDSWasNeeded;
338     int newstart, newend;
339     boolean seqWasDeleted = false;
340     int start = 0, end = 0;
341
342     for (int i = 0; i < command.seqs.length; i++)
343     {
344       newDSNeeded = false;
345       newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
346       if (command.seqs[i].getLength() < 1)
347       {
348         // ie this sequence was deleted, we need to
349         // read it to the alignment
350         if (command.alIndex[i] < command.al.getHeight())
351         {
352           List<SequenceI> sequences;
353           synchronized (sequences = command.al.getSequences())
354           {
355             sequences.add(command.alIndex[i], command.seqs[i]);
356           }
357         }
358         else
359         {
360           command.al.addSequence(command.seqs[i]);
361         }
362         seqWasDeleted = true;
363       }
364       newstart = command.seqs[i].getStart();
365       newend = command.seqs[i].getEnd();
366
367       tmp = new StringBuffer();
368       tmp.append(command.seqs[i].getSequence());
369       // Undo of a delete does not replace original dataset sequence on to
370       // alignment sequence.
371
372       if (command.string != null && command.string[i] != null)
373       {
374         if (command.position >= tmp.length())
375         {
376           // This occurs if padding is on, and residues
377           // are removed from end of alignment
378           int length = command.position - tmp.length();
379           while (length > 0)
380           {
381             tmp.append(command.gapChar);
382             length--;
383           }
384         }
385         tmp.insert(command.position, command.string[i]);
386         for (int s = 0; s < command.string[i].length; s++)
387         {
388           if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]] != 23)
389           {
390             if (!newDSNeeded)
391             {
392               newDSNeeded = true;
393               start = command.seqs[i].findPosition(command.position);
394               end = command.seqs[i].findPosition(command.position
395                       + command.number);
396             }
397             if (command.seqs[i].getStart() == start)
398               newstart--;
399             else
400               newend++;
401           }
402         }
403         command.string[i] = null;
404       }
405
406       command.seqs[i].setSequence(tmp.toString());
407       command.seqs[i].setStart(newstart);
408       command.seqs[i].setEnd(newend);
409       if (newDSNeeded)
410       {
411         if (command.seqs[i].getDatasetSequence() != null)
412         {
413           SequenceI ds;
414           if (newDSWasNeeded)
415           {
416             ds = command.oldds[i];
417           }
418           else
419           {
420             // make a new DS sequence
421             // use new ds mechanism here
422             ds = new Sequence(command.seqs[i].getName(),
423                     jalview.analysis.AlignSeq.extractGaps(
424                             jalview.util.Comparison.GapChars,
425                             command.seqs[i].getSequenceAsString()),
426                     command.seqs[i].getStart(), command.seqs[i].getEnd());
427             ds.setDescription(command.seqs[i].getDescription());
428           }
429           if (command.oldds == null)
430           {
431             command.oldds = new SequenceI[command.seqs.length];
432           }
433           command.oldds[i] = command.seqs[i].getDatasetSequence();
434           command.seqs[i].setDatasetSequence(ds);
435         }
436         adjustFeatures(command, i, start, end, true);
437       }
438     }
439     adjustAnnotations(command, true, seqWasDeleted, views);
440
441     command.string = null;
442   }
443
444   void replace(Edit command)
445   {
446     StringBuffer tmp;
447     String oldstring;
448     int start = command.position;
449     int end = command.number;
450     // TODO TUTORIAL - Fix for replacement with different length of sequence (or
451     // whole sequence)
452     // TODO Jalview 2.4 bugfix change to an aggregate command - original
453     // sequence string is cut, new string is pasted in.
454     command.number = start + command.string[0].length;
455     for (int i = 0; i < command.seqs.length; i++)
456     {
457       boolean newDSWasNeeded = command.oldds != null
458               && command.oldds[i] != null;
459
460       /**
461        * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
462        * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
463        * viewport.alignment));
464        * 
465        */
466       /**
467        * then addHistoryItem(new EditCommand( "Add sequences",
468        * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
469        * 
470        */
471       oldstring = command.seqs[i].getSequenceAsString();
472       tmp = new StringBuffer(oldstring.substring(0, start));
473       tmp.append(command.string[i]);
474       String nogaprep = jalview.analysis.AlignSeq.extractGaps(
475               jalview.util.Comparison.GapChars, new String(
476                       command.string[i]));
477       int ipos = command.seqs[i].findPosition(start)
478               - command.seqs[i].getStart();
479       tmp.append(oldstring.substring(end));
480       command.seqs[i].setSequence(tmp.toString());
481       command.string[i] = oldstring.substring(start, end).toCharArray();
482       String nogapold = jalview.analysis.AlignSeq.extractGaps(
483               jalview.util.Comparison.GapChars, new String(
484                       command.string[i]));
485       if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
486       {
487         if (newDSWasNeeded)
488         {
489           SequenceI oldds = command.seqs[i].getDatasetSequence();
490           command.seqs[i].setDatasetSequence(command.oldds[i]);
491           command.oldds[i] = oldds;
492         }
493         else
494         {
495           if (command.oldds == null)
496           {
497             command.oldds = new SequenceI[command.seqs.length];
498           }
499           command.oldds[i] = command.seqs[i].getDatasetSequence();
500           SequenceI newds = new Sequence(
501                   command.seqs[i].getDatasetSequence());
502           String fullseq, osp = newds.getSequenceAsString();
503           fullseq = osp.substring(0, ipos) + nogaprep
504                   + osp.substring(ipos + nogaprep.length());
505           newds.setSequence(fullseq.toUpperCase());
506           // TODO: JAL-1131 ensure newly created dataset sequence is added to
507           // the set of
508           // dataset sequences associated with the alignment.
509           // TODO: JAL-1131 fix up any annotation associated with new dataset
510           // sequence to ensure that original sequence/annotation relationships
511           // are preserved.
512           command.seqs[i].setDatasetSequence(newds);
513
514         }
515       }
516       tmp = null;
517       oldstring = null;
518     }
519   }
520
521   final void adjustAnnotations(Edit command, boolean insert,
522           boolean modifyVisibility, AlignmentI[] views)
523   {
524     AlignmentAnnotation[] annotations = null;
525
526     if (modifyVisibility && !insert)
527     {
528       // only occurs if a sequence was added or deleted.
529       command.deletedAnnotationRows = new Hashtable();
530     }
531     if (command.fullAlignmentHeight)
532     {
533       annotations = command.al.getAlignmentAnnotation();
534     }
535     else
536     {
537       int aSize = 0;
538       AlignmentAnnotation[] tmp;
539       for (int s = 0; s < command.seqs.length; s++)
540       {
541         if (modifyVisibility)
542         {
543           // Rows are only removed or added to sequence object.
544           if (!insert)
545           {
546             // remove rows
547             tmp = command.seqs[s].getAnnotation();
548             if (tmp != null)
549             {
550               int alen = tmp.length;
551               for (int aa = 0; aa < tmp.length; aa++)
552               {
553                 if (!command.al.deleteAnnotation(tmp[aa]))
554                 {
555                   // strip out annotation not in the current al (will be put
556                   // back on insert in all views)
557                   tmp[aa] = null;
558                   alen--;
559                 }
560               }
561               command.seqs[s].setAlignmentAnnotation(null);
562               if (alen != tmp.length)
563               {
564                 // save the non-null annotation references only
565                 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
566                 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
567                 {
568                   if (tmp[aa] != null)
569                   {
570                     saved[aapos++] = tmp[aa];
571                     tmp[aa] = null;
572                   }
573                 }
574                 tmp = saved;
575                 command.deletedAnnotationRows.put(command.seqs[s], saved);
576                 // and then remove any annotation in the other views
577                 for (int alview = 0; views != null && alview < views.length; alview++)
578                 {
579                   if (views[alview] != command.al)
580                   {
581                     AlignmentAnnotation[] toremove = views[alview]
582                             .getAlignmentAnnotation();
583                     if (toremove == null || toremove.length == 0)
584                     {
585                       continue;
586                     }
587                     // remove any alignment annotation on this sequence that's
588                     // on that alignment view.
589                     for (int aa = 0; aa < toremove.length; aa++)
590                     {
591                       if (toremove[aa].sequenceRef == command.seqs[s])
592                       {
593                         views[alview].deleteAnnotation(toremove[aa]);
594                       }
595                     }
596                   }
597                 }
598               }
599               else
600               {
601                 // save all the annotation
602                 command.deletedAnnotationRows.put(command.seqs[s], tmp);
603               }
604             }
605           }
606           else
607           {
608             // recover rows
609             if (command.deletedAnnotationRows != null
610                     && command.deletedAnnotationRows
611                             .containsKey(command.seqs[s]))
612             {
613               AlignmentAnnotation[] revealed = (AlignmentAnnotation[]) command.deletedAnnotationRows
614                       .get(command.seqs[s]);
615               command.seqs[s].setAlignmentAnnotation(revealed);
616               if (revealed != null)
617               {
618                 for (int aa = 0; aa < revealed.length; aa++)
619                 {
620                   // iterate through al adding original annotation
621                   command.al.addAnnotation(revealed[aa]);
622                 }
623                 for (int aa = 0; aa < revealed.length; aa++)
624                 {
625                   command.al.setAnnotationIndex(revealed[aa], aa);
626                 }
627                 // and then duplicate added annotation on every other alignment
628                 // view
629                 for (int vnum = 0; views != null && vnum < views.length; vnum++)
630                 {
631                   if (views[vnum] != command.al)
632                   {
633                     int avwidth = views[vnum].getWidth() + 1;
634                     // duplicate in this view
635                     for (int a = 0; a < revealed.length; a++)
636                     {
637                       AlignmentAnnotation newann = new AlignmentAnnotation(
638                               revealed[a]);
639                       command.seqs[s].addAlignmentAnnotation(newann);
640                       newann.padAnnotation(avwidth);
641                       views[vnum].addAnnotation(newann);
642                       views[vnum].setAnnotationIndex(newann, a);
643                     }
644                   }
645                 }
646               }
647             }
648           }
649           continue;
650         }
651
652         if (command.seqs[s].getAnnotation() == null)
653         {
654           continue;
655         }
656
657         if (aSize == 0)
658         {
659           annotations = command.seqs[s].getAnnotation();
660         }
661         else
662         {
663           tmp = new AlignmentAnnotation[aSize
664                   + command.seqs[s].getAnnotation().length];
665
666           System.arraycopy(annotations, 0, tmp, 0, aSize);
667
668           System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
669                   command.seqs[s].getAnnotation().length);
670
671           annotations = tmp;
672         }
673         aSize = annotations.length;
674       }
675     }
676
677     if (annotations == null)
678     {
679       return;
680     }
681
682     if (!insert)
683     {
684       command.deletedAnnotations = new Hashtable();
685     }
686
687     int aSize;
688     Annotation[] temp;
689     for (int a = 0; a < annotations.length; a++)
690     {
691       if (annotations[a].autoCalculated
692               || annotations[a].annotations == null)
693       {
694         continue;
695       }
696
697       int tSize = 0;
698
699       aSize = annotations[a].annotations.length;
700       if (insert)
701       {
702         temp = new Annotation[aSize + command.number];
703         if (annotations[a].padGaps)
704           for (int aa = 0; aa < temp.length; aa++)
705           {
706             temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
707           }
708       }
709       else
710       {
711         if (command.position < aSize)
712         {
713           if (command.position + command.number >= aSize)
714           {
715             tSize = aSize;
716           }
717           else
718           {
719             tSize = aSize - command.number;
720           }
721         }
722         else
723         {
724           tSize = aSize;
725         }
726
727         if (tSize < 0)
728         {
729           tSize = aSize;
730         }
731         temp = new Annotation[tSize];
732       }
733
734       if (insert)
735       {
736         if (command.position < annotations[a].annotations.length)
737         {
738           System.arraycopy(annotations[a].annotations, 0, temp, 0,
739                   command.position);
740
741           if (command.deletedAnnotations != null
742                   && command.deletedAnnotations
743                           .containsKey(annotations[a].annotationId))
744           {
745             Annotation[] restore = (Annotation[]) command.deletedAnnotations
746                     .get(annotations[a].annotationId);
747
748             System.arraycopy(restore, 0, temp, command.position,
749                     command.number);
750
751           }
752
753           System.arraycopy(annotations[a].annotations, command.position,
754                   temp, command.position + command.number, aSize
755                           - command.position);
756         }
757         else
758         {
759           if (command.deletedAnnotations != null
760                   && command.deletedAnnotations
761                           .containsKey(annotations[a].annotationId))
762           {
763             Annotation[] restore = (Annotation[]) command.deletedAnnotations
764                     .get(annotations[a].annotationId);
765
766             temp = new Annotation[annotations[a].annotations.length
767                     + restore.length];
768             System.arraycopy(annotations[a].annotations, 0, temp, 0,
769                     annotations[a].annotations.length);
770             System.arraycopy(restore, 0, temp,
771                     annotations[a].annotations.length, restore.length);
772           }
773           else
774           {
775             temp = annotations[a].annotations;
776           }
777         }
778       }
779       else
780       {
781         if (tSize != aSize || command.position < 2)
782         {
783           int copylen = Math.min(command.position,
784                   annotations[a].annotations.length);
785           if (copylen > 0)
786             System.arraycopy(annotations[a].annotations, 0, temp, 0,
787                     copylen); // command.position);
788
789           Annotation[] deleted = new Annotation[command.number];
790           if (copylen >= command.position)
791           {
792             copylen = Math.min(command.number,
793                     annotations[a].annotations.length - command.position);
794             if (copylen > 0)
795             {
796               System.arraycopy(annotations[a].annotations,
797                       command.position, deleted, 0, copylen); // command.number);
798             }
799           }
800
801           command.deletedAnnotations.put(annotations[a].annotationId,
802                   deleted);
803           if (annotations[a].annotations.length > command.position
804                   + command.number)
805           {
806             System.arraycopy(annotations[a].annotations, command.position
807                     + command.number, temp, command.position,
808                     annotations[a].annotations.length - command.position
809                             - command.number); // aSize
810           }
811         }
812         else
813         {
814           int dSize = aSize - command.position;
815
816           if (dSize > 0)
817           {
818             Annotation[] deleted = new Annotation[command.number];
819             System.arraycopy(annotations[a].annotations, command.position,
820                     deleted, 0, dSize);
821
822             command.deletedAnnotations.put(annotations[a].annotationId,
823                     deleted);
824
825             tSize = Math.min(annotations[a].annotations.length,
826                     command.position);
827             temp = new Annotation[tSize];
828             System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
829           }
830           else
831           {
832             temp = annotations[a].annotations;
833           }
834         }
835       }
836
837       annotations[a].annotations = temp;
838     }
839   }
840
841   final void adjustFeatures(Edit command, int index, int i, int j,
842           boolean insert)
843   {
844     SequenceI seq = command.seqs[index];
845     SequenceI sequence = seq.getDatasetSequence();
846     if (sequence == null)
847     {
848       sequence = seq;
849     }
850
851     if (insert)
852     {
853       if (command.editedFeatures != null
854               && command.editedFeatures.containsKey(seq))
855       {
856         sequence.setSequenceFeatures((SequenceFeature[]) command.editedFeatures
857                 .get(seq));
858       }
859
860       return;
861     }
862
863     SequenceFeature[] sf = sequence.getSequenceFeatures();
864
865     if (sf == null)
866     {
867       return;
868     }
869
870     SequenceFeature[] oldsf = new SequenceFeature[sf.length];
871
872     int cSize = j - i;
873
874     for (int s = 0; s < sf.length; s++)
875     {
876       SequenceFeature copy = new SequenceFeature(sf[s]);
877
878       oldsf[s] = copy;
879
880       if (sf[s].getEnd() < i)
881       {
882         continue;
883       }
884
885       if (sf[s].getBegin() > j)
886       {
887         sf[s].setBegin(copy.getBegin() - cSize);
888         sf[s].setEnd(copy.getEnd() - cSize);
889         continue;
890       }
891
892       if (sf[s].getBegin() >= i)
893       {
894         sf[s].setBegin(i);
895       }
896
897       if (sf[s].getEnd() < j)
898       {
899         sf[s].setEnd(j - 1);
900       }
901
902       sf[s].setEnd(sf[s].getEnd() - (cSize));
903
904       if (sf[s].getBegin() > sf[s].getEnd())
905       {
906         sequence.deleteFeature(sf[s]);
907       }
908     }
909
910     if (command.editedFeatures == null)
911     {
912       command.editedFeatures = new Hashtable();
913     }
914
915     command.editedFeatures.put(seq, oldsf);
916
917   }
918
919   class Edit
920   {
921     public SequenceI[] oldds;
922
923     boolean fullAlignmentHeight = false;
924
925     Hashtable deletedAnnotationRows;
926
927     Hashtable deletedAnnotations;
928
929     Hashtable editedFeatures;
930
931     AlignmentI al;
932
933     int command;
934
935     char[][] string;
936
937     SequenceI[] seqs;
938
939     int[] alIndex;
940
941     int position, number;
942
943     char gapChar;
944
945     Edit(int command, SequenceI[] seqs, int position, int number,
946             char gapChar)
947     {
948       this.command = command;
949       this.seqs = seqs;
950       this.position = position;
951       this.number = number;
952       this.gapChar = gapChar;
953     }
954
955     Edit(int command, SequenceI[] seqs, int position, int number,
956             AlignmentI al)
957     {
958       this.gapChar = al.getGapCharacter();
959       this.command = command;
960       this.seqs = seqs;
961       this.position = position;
962       this.number = number;
963       this.al = al;
964
965       alIndex = new int[seqs.length];
966       for (int i = 0; i < seqs.length; i++)
967       {
968         alIndex[i] = al.findIndex(seqs[i]);
969       }
970
971       fullAlignmentHeight = (al.getHeight() == seqs.length);
972     }
973
974     Edit(int command, SequenceI[] seqs, int position, int number,
975             AlignmentI al, String replace)
976     {
977       this.command = command;
978       this.seqs = seqs;
979       this.position = position;
980       this.number = number;
981       this.al = al;
982       this.gapChar = al.getGapCharacter();
983       string = new char[seqs.length][];
984       for (int i = 0; i < seqs.length; i++)
985       {
986         string[i] = replace.toCharArray();
987       }
988
989       fullAlignmentHeight = (al.getHeight() == seqs.length);
990     }
991   }
992 }