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