ed9e5518d1574ebbd78065b7f68d0a9852b0605f
[jalview.git] / src / jalview / commands / EditCommand.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
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 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  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.commands;
20
21 import java.util.*;
22
23 import jalview.datamodel.*;
24
25 /**
26  * 
27  * <p>
28  * Title: EditCommmand
29  * </p>
30  * 
31  * <p>
32  * Description: Essential information for performing undo and redo for cut/paste
33  * insert/delete gap which can be stored in the HistoryList
34  * </p>
35  * 
36  * <p>
37  * Copyright: Copyright (c) 2006
38  * </p>
39  * 
40  * <p>
41  * Company: Dundee University
42  * </p>
43  * 
44  * @author not attributable
45  * @version 1.0
46  */
47 public class EditCommand implements CommandI
48 {
49   public static final int INSERT_GAP = 0;
50
51   public static final int DELETE_GAP = 1;
52
53   public static final int CUT = 2;
54
55   public static final int PASTE = 3;
56
57   public static final int REPLACE = 4;
58
59   public static final int INSERT_NUC = 5;
60
61   Edit[] edits;
62
63   String description;
64
65   public EditCommand()
66   {
67   }
68
69   public EditCommand(String description)
70   {
71     this.description = description;
72   }
73
74   public EditCommand(String description, int command, SequenceI[] seqs,
75           int position, int number, AlignmentI al)
76   {
77     this.description = description;
78     if (command == CUT || command == PASTE)
79     {
80       edits = new Edit[]
81       { new Edit(command, seqs, position, number, al) };
82     }
83
84     performEdit(0, null);
85   }
86
87   public EditCommand(String description, int command, String replace,
88           SequenceI[] seqs, int position, int number, AlignmentI al)
89   {
90     this.description = description;
91     if (command == REPLACE)
92     {
93       edits = new Edit[]
94       { new Edit(command, seqs, position, number, al, replace) };
95     }
96
97     performEdit(0, null);
98   }
99
100   @Override
101   final public String getDescription()
102   {
103     return description;
104   }
105
106   @Override
107   public int getSize()
108   {
109     return edits == null ? 0 : edits.length;
110   }
111
112   final public AlignmentI getAlignment()
113   {
114     return edits[0].al;
115   }
116
117   /**
118    * append a new editCommand Note. this shouldn't be called if the edit is an
119    * operation affects more alignment objects than the one referenced in al (for
120    * example, cut or pasting whole sequences). Use the form with an additional
121    * AlignmentI[] views parameter.
122    * 
123    * @param command
124    * @param seqs
125    * @param position
126    * @param number
127    * @param al
128    * @param performEdit
129    */
130   final public void appendEdit(int command, SequenceI[] seqs, int position,
131           int number, AlignmentI al, boolean performEdit)
132   {
133     appendEdit(command, seqs, position, number, al, performEdit, null);
134   }
135
136   /**
137    * append a new edit command with a set of alignment views that may be
138    * operated on
139    * 
140    * @param command
141    * @param seqs
142    * @param position
143    * @param number
144    * @param al
145    * @param performEdit
146    * @param views
147    */
148   final public void appendEdit(int command, SequenceI[] seqs, int position,
149           int number, AlignmentI al, boolean performEdit, AlignmentI[] views)
150   {
151     Edit edit = new Edit(command, seqs, position, number,
152             al.getGapCharacter());
153     if (al.getHeight() == seqs.length)
154     {
155       edit.al = al;
156       edit.fullAlignmentHeight = true;
157     }
158
159     if (edits != null)
160     {
161       Edit[] temp = new Edit[edits.length + 1];
162       System.arraycopy(edits, 0, temp, 0, edits.length);
163       edits = temp;
164       edits[edits.length - 1] = edit;
165     }
166     else
167     {
168       edits = new Edit[]
169       { edit };
170     }
171
172     if (performEdit)
173     {
174       performEdit(edits.length - 1, views);
175     }
176   }
177
178   final void performEdit(int commandIndex, AlignmentI[] views)
179   {
180     int eSize = edits.length;
181     for (int e = commandIndex; e < eSize; e++)
182     {
183       switch (edits[e].command)
184       {
185       case INSERT_GAP:
186         insertGap(edits[e]);
187         break;
188       case DELETE_GAP:
189         deleteGap(edits[e]);
190         break;
191       case CUT:
192         cut(edits[e], views);
193         break;
194       case PASTE:
195         paste(edits[e], views);
196         break;
197       case REPLACE:
198         replace(edits[e]);
199         break;
200       // TODO:add deleteNuc for UNDO
201       // case INSERT_NUC:
202       // insertNuc(edits[e]);
203       // break;
204       }
205     }
206   }
207
208   @Override
209   final public void doCommand(AlignmentI[] views)
210   {
211     performEdit(0, views);
212   }
213
214   @Override
215   final public void undoCommand(AlignmentI[] views)
216   {
217     int e = 0, eSize = edits.length;
218     for (e = eSize - 1; e > -1; e--)
219     {
220       switch (edits[e].command)
221       {
222       case INSERT_GAP:
223         deleteGap(edits[e]);
224         break;
225       case DELETE_GAP:
226         insertGap(edits[e]);
227         break;
228       case CUT:
229         paste(edits[e], views);
230         break;
231       case PASTE:
232         cut(edits[e], views);
233         break;
234       case REPLACE:
235         replace(edits[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], 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 }