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