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