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