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