JAL-2418 source formatting
[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>,
226      * or</li>
227      * <li>a new Delete <n> gaps which is <n> positions to the left of the last
228      * delete.</li>
229      * </ul>
230      */
231     boolean contiguous = (action == Action.INSERT_GAP
232             && e.position == lastEdit.position + lastEdit.number)
233             || (action == Action.DELETE_GAP
234                     && e.position + e.number == lastEdit.position);
235     if (contiguous)
236     {
237       /*
238        * We are just expanding the range of the last edit. For delete gap, also
239        * moving the start position left.
240        */
241       lastEdit.number += e.number;
242       lastEdit.seqs = e.seqs;
243       if (action == Action.DELETE_GAP)
244       {
245         lastEdit.position--;
246       }
247       return true;
248     }
249     return false;
250   }
251
252   /**
253    * Clear the list of stored edit commands.
254    * 
255    */
256   protected void clearEdits()
257   {
258     edits.clear();
259   }
260
261   /**
262    * Returns the i'th stored Edit command.
263    * 
264    * @param i
265    * @return
266    */
267   protected Edit getEdit(int i)
268   {
269     if (i >= 0 && i < edits.size())
270     {
271       return edits.get(i);
272     }
273     return null;
274   }
275
276   @Override
277   final public String getDescription()
278   {
279     return description;
280   }
281
282   @Override
283   public int getSize()
284   {
285     return edits.size();
286   }
287
288   /**
289    * Return the alignment for the first edit (or null if no edit).
290    * 
291    * @return
292    */
293   final public AlignmentI getAlignment()
294   {
295     return (edits.isEmpty() ? null : edits.get(0).al);
296   }
297
298   /**
299    * append a new editCommand Note. this shouldn't be called if the edit is an
300    * operation affects more alignment objects than the one referenced in al (for
301    * example, cut or pasting whole sequences). Use the form with an additional
302    * AlignmentI[] views parameter.
303    * 
304    * @param command
305    * @param seqs
306    * @param position
307    * @param number
308    * @param al
309    * @param performEdit
310    */
311   final public void appendEdit(Action command, SequenceI[] seqs,
312           int position, int number, AlignmentI al, boolean performEdit)
313   {
314     appendEdit(command, seqs, position, number, al, performEdit, null);
315   }
316
317   /**
318    * append a new edit command with a set of alignment views that may be
319    * operated on
320    * 
321    * @param command
322    * @param seqs
323    * @param position
324    * @param number
325    * @param al
326    * @param performEdit
327    * @param views
328    */
329   final public void appendEdit(Action command, SequenceI[] seqs,
330           int position, int number, AlignmentI al, boolean performEdit,
331           AlignmentI[] views)
332   {
333     Edit edit = new Edit(command, seqs, position, number,
334             al.getGapCharacter());
335     if (al.getHeight() == seqs.length)
336     {
337       edit.al = al;
338       edit.fullAlignmentHeight = true;
339     }
340
341     addEdit(edit);
342
343     if (performEdit)
344     {
345       performEdit(edit, views);
346     }
347   }
348
349   /**
350    * Overloaded method that accepts an Edit object with additional parameters.
351    * 
352    * @param edit
353    * @param al
354    * @param performEdit
355    * @param views
356    */
357   final public void appendEdit(Edit edit, AlignmentI al,
358           boolean performEdit, AlignmentI[] views)
359   {
360     if (al.getHeight() == edit.seqs.length)
361     {
362       edit.al = al;
363       edit.fullAlignmentHeight = true;
364     }
365
366     addEdit(edit);
367
368     if (performEdit)
369     {
370       performEdit(edit, views);
371     }
372   }
373
374   /**
375    * Execute all the edit commands, starting at the given commandIndex
376    * 
377    * @param commandIndex
378    * @param views
379    */
380   public final void performEdit(int commandIndex, AlignmentI[] views)
381   {
382     ListIterator<Edit> iterator = edits.listIterator(commandIndex);
383     while (iterator.hasNext())
384     {
385       Edit edit = iterator.next();
386       performEdit(edit, views);
387     }
388   }
389
390   /**
391    * Execute one edit command in all the specified alignment views
392    * 
393    * @param edit
394    * @param views
395    */
396   protected static void performEdit(Edit edit, AlignmentI[] views)
397   {
398     switch (edit.command)
399     {
400     case INSERT_GAP:
401       insertGap(edit);
402       break;
403     case DELETE_GAP:
404       deleteGap(edit);
405       break;
406     case CUT:
407       cut(edit, views);
408       break;
409     case PASTE:
410       paste(edit, views);
411       break;
412     case REPLACE:
413       replace(edit);
414       break;
415     case INSERT_NUC:
416       // TODO:add deleteNuc for UNDO
417       // case INSERT_NUC:
418       // insertNuc(edits[e]);
419       break;
420     default:
421       break;
422     }
423   }
424
425   @Override
426   final public void doCommand(AlignmentI[] views)
427   {
428     performEdit(0, views);
429   }
430
431   /**
432    * Undo the stored list of commands, in reverse order.
433    */
434   @Override
435   final public void undoCommand(AlignmentI[] views)
436   {
437     ListIterator<Edit> iterator = edits.listIterator(edits.size());
438     while (iterator.hasPrevious())
439     {
440       Edit e = iterator.previous();
441       switch (e.command)
442       {
443       case INSERT_GAP:
444         deleteGap(e);
445         break;
446       case DELETE_GAP:
447         insertGap(e);
448         break;
449       case CUT:
450         paste(e, views);
451         break;
452       case PASTE:
453         cut(e, views);
454         break;
455       case REPLACE:
456         replace(e);
457         break;
458       case INSERT_NUC:
459         // not implemented
460         break;
461       default:
462         break;
463       }
464     }
465   }
466
467   /**
468    * Insert gap(s) in sequences as specified by the command, and adjust
469    * annotations.
470    * 
471    * @param command
472    */
473   final private static void insertGap(Edit command)
474   {
475
476     for (int s = 0; s < command.seqs.length; s++)
477     {
478       command.seqs[s].insertCharAt(command.position, command.number,
479               command.gapChar);
480       // System.out.println("pos: "+command.position+" number:
481       // "+command.number);
482     }
483
484     adjustAnnotations(command, true, false, null);
485   }
486
487   //
488   // final void insertNuc(Edit command)
489   // {
490   //
491   // for (int s = 0; s < command.seqs.length; s++)
492   // {
493   // System.out.println("pos: "+command.position+" number: "+command.number);
494   // command.seqs[s].insertCharAt(command.position, command.number,'A');
495   // }
496   //
497   // adjustAnnotations(command, true, false, null);
498   // }
499
500   /**
501    * Delete gap(s) in sequences as specified by the command, and adjust
502    * annotations.
503    * 
504    * @param command
505    */
506   final static private void deleteGap(Edit command)
507   {
508     for (int s = 0; s < command.seqs.length; s++)
509     {
510       command.seqs[s].deleteChars(command.position,
511               command.position + command.number);
512     }
513
514     adjustAnnotations(command, false, false, null);
515   }
516
517   /**
518    * Carry out a Cut action. The cut characters are saved in case Undo is
519    * requested.
520    * 
521    * @param command
522    * @param views
523    */
524   static void cut(Edit command, AlignmentI[] views)
525   {
526     boolean seqDeleted = false;
527     command.string = new char[command.seqs.length][];
528
529     for (int i = 0; i < command.seqs.length; i++)
530     {
531       final SequenceI sequence = command.seqs[i];
532       if (sequence.getLength() > command.position)
533       {
534         command.string[i] = sequence.getSequence(command.position,
535                 command.position + command.number);
536         SequenceI oldds = sequence.getDatasetSequence();
537         if (command.oldds != null && command.oldds[i] != null)
538         {
539           // we are redoing an undone cut.
540           sequence.setDatasetSequence(null);
541         }
542         sequence.deleteChars(command.position,
543                 command.position + command.number);
544         if (command.oldds != null && command.oldds[i] != null)
545         {
546           // oldds entry contains the cut dataset sequence.
547           sequence.setDatasetSequence(command.oldds[i]);
548           command.oldds[i] = oldds;
549         }
550         else
551         {
552           // modify the oldds if necessary
553           if (oldds != sequence.getDatasetSequence()
554                   || sequence.getSequenceFeatures() != null)
555           {
556             if (command.oldds == null)
557             {
558               command.oldds = new SequenceI[command.seqs.length];
559             }
560             command.oldds[i] = oldds;
561             adjustFeatures(command, i,
562                     sequence.findPosition(command.position),
563                     sequence.findPosition(
564                             command.position + command.number),
565                     false);
566           }
567         }
568       }
569
570       if (sequence.getLength() < 1)
571       {
572         command.al.deleteSequence(sequence);
573         seqDeleted = true;
574       }
575     }
576
577     adjustAnnotations(command, false, seqDeleted, views);
578   }
579
580   /**
581    * Perform the given Paste command. This may be to add cut or copied sequences
582    * to an alignment, or to undo a 'Cut' action on a region of the alignment.
583    * 
584    * @param command
585    * @param views
586    */
587   static void paste(Edit command, AlignmentI[] views)
588   {
589     StringBuffer tmp;
590     boolean newDSNeeded;
591     boolean newDSWasNeeded;
592     int newstart, newend;
593     boolean seqWasDeleted = false;
594     int start = 0, end = 0;
595
596     for (int i = 0; i < command.seqs.length; i++)
597     {
598       newDSNeeded = false;
599       newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
600       if (command.seqs[i].getLength() < 1)
601       {
602         // ie this sequence was deleted, we need to
603         // readd it to the alignment
604         if (command.alIndex[i] < command.al.getHeight())
605         {
606           List<SequenceI> sequences;
607           synchronized (sequences = command.al.getSequences())
608           {
609             if (!(command.alIndex[i] < 0))
610             {
611               sequences.add(command.alIndex[i], command.seqs[i]);
612             }
613           }
614         }
615         else
616         {
617           command.al.addSequence(command.seqs[i]);
618         }
619         seqWasDeleted = true;
620       }
621       newstart = command.seqs[i].getStart();
622       newend = command.seqs[i].getEnd();
623
624       tmp = new StringBuffer();
625       tmp.append(command.seqs[i].getSequence());
626       // Undo of a delete does not replace original dataset sequence on to
627       // alignment sequence.
628
629       if (command.string != null && command.string[i] != null)
630       {
631         if (command.position >= tmp.length())
632         {
633           // This occurs if padding is on, and residues
634           // are removed from end of alignment
635           int length = command.position - tmp.length();
636           while (length > 0)
637           {
638             tmp.append(command.gapChar);
639             length--;
640           }
641         }
642         tmp.insert(command.position, command.string[i]);
643         for (int s = 0; s < command.string[i].length; s++)
644         {
645           if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]] != 23)
646           {
647             if (!newDSNeeded)
648             {
649               newDSNeeded = true;
650               start = command.seqs[i].findPosition(command.position);
651               end = command.seqs[i]
652                       .findPosition(command.position + command.number);
653             }
654             if (command.seqs[i].getStart() == start)
655             {
656               newstart--;
657             }
658             else
659             {
660               newend++;
661             }
662           }
663         }
664         command.string[i] = null;
665       }
666
667       command.seqs[i].setSequence(tmp.toString());
668       command.seqs[i].setStart(newstart);
669       command.seqs[i].setEnd(newend);
670       if (newDSNeeded)
671       {
672         if (command.seqs[i].getDatasetSequence() != null)
673         {
674           SequenceI ds;
675           if (newDSWasNeeded)
676           {
677             ds = command.oldds[i];
678           }
679           else
680           {
681             // make a new DS sequence
682             // use new ds mechanism here
683             ds = new Sequence(command.seqs[i].getName(),
684                     jalview.analysis.AlignSeq.extractGaps(
685                             jalview.util.Comparison.GapChars,
686                             command.seqs[i].getSequenceAsString()),
687                     command.seqs[i].getStart(), command.seqs[i].getEnd());
688             ds.setDescription(command.seqs[i].getDescription());
689           }
690           if (command.oldds == null)
691           {
692             command.oldds = new SequenceI[command.seqs.length];
693           }
694           command.oldds[i] = command.seqs[i].getDatasetSequence();
695           command.seqs[i].setDatasetSequence(ds);
696         }
697         adjustFeatures(command, i, start, end, true);
698       }
699     }
700     adjustAnnotations(command, true, seqWasDeleted, views);
701
702     command.string = null;
703   }
704
705   static void replace(Edit command)
706   {
707     StringBuffer tmp;
708     String oldstring;
709     int start = command.position;
710     int end = command.number;
711     // TODO TUTORIAL - Fix for replacement with different length of sequence (or
712     // whole sequence)
713     // TODO Jalview 2.4 bugfix change to an aggregate command - original
714     // sequence string is cut, new string is pasted in.
715     command.number = start + command.string[0].length;
716     for (int i = 0; i < command.seqs.length; i++)
717     {
718       boolean newDSWasNeeded = command.oldds != null
719               && command.oldds[i] != null;
720
721       /**
722        * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
723        * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
724        * viewport.alignment));
725        * 
726        */
727       /**
728        * then addHistoryItem(new EditCommand( "Add sequences",
729        * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
730        * 
731        */
732       oldstring = command.seqs[i].getSequenceAsString();
733       tmp = new StringBuffer(oldstring.substring(0, start));
734       tmp.append(command.string[i]);
735       String nogaprep = jalview.analysis.AlignSeq.extractGaps(
736               jalview.util.Comparison.GapChars,
737               new String(command.string[i]));
738       int ipos = command.seqs[i].findPosition(start)
739               - command.seqs[i].getStart();
740       tmp.append(oldstring.substring(end));
741       command.seqs[i].setSequence(tmp.toString());
742       command.string[i] = oldstring.substring(start, end).toCharArray();
743       String nogapold = jalview.analysis.AlignSeq.extractGaps(
744               jalview.util.Comparison.GapChars,
745               new String(command.string[i]));
746       if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
747       {
748         if (newDSWasNeeded)
749         {
750           SequenceI oldds = command.seqs[i].getDatasetSequence();
751           command.seqs[i].setDatasetSequence(command.oldds[i]);
752           command.oldds[i] = oldds;
753         }
754         else
755         {
756           if (command.oldds == null)
757           {
758             command.oldds = new SequenceI[command.seqs.length];
759           }
760           command.oldds[i] = command.seqs[i].getDatasetSequence();
761           SequenceI newds = new Sequence(
762                   command.seqs[i].getDatasetSequence());
763           String fullseq, osp = newds.getSequenceAsString();
764           fullseq = osp.substring(0, ipos) + nogaprep
765                   + osp.substring(ipos + nogaprep.length());
766           newds.setSequence(fullseq.toUpperCase());
767           // TODO: JAL-1131 ensure newly created dataset sequence is added to
768           // the set of
769           // dataset sequences associated with the alignment.
770           // TODO: JAL-1131 fix up any annotation associated with new dataset
771           // sequence to ensure that original sequence/annotation relationships
772           // are preserved.
773           command.seqs[i].setDatasetSequence(newds);
774
775         }
776       }
777       tmp = null;
778       oldstring = null;
779     }
780   }
781
782   final static void adjustAnnotations(Edit command, boolean insert,
783           boolean modifyVisibility, AlignmentI[] views)
784   {
785     AlignmentAnnotation[] annotations = null;
786
787     if (modifyVisibility && !insert)
788     {
789       // only occurs if a sequence was added or deleted.
790       command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
791     }
792     if (command.fullAlignmentHeight)
793     {
794       annotations = command.al.getAlignmentAnnotation();
795     }
796     else
797     {
798       int aSize = 0;
799       AlignmentAnnotation[] tmp;
800       for (int s = 0; s < command.seqs.length; s++)
801       {
802         if (modifyVisibility)
803         {
804           // Rows are only removed or added to sequence object.
805           if (!insert)
806           {
807             // remove rows
808             tmp = command.seqs[s].getAnnotation();
809             if (tmp != null)
810             {
811               int alen = tmp.length;
812               for (int aa = 0; aa < tmp.length; aa++)
813               {
814                 if (!command.al.deleteAnnotation(tmp[aa]))
815                 {
816                   // strip out annotation not in the current al (will be put
817                   // back on insert in all views)
818                   tmp[aa] = null;
819                   alen--;
820                 }
821               }
822               command.seqs[s].setAlignmentAnnotation(null);
823               if (alen != tmp.length)
824               {
825                 // save the non-null annotation references only
826                 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
827                 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
828                 {
829                   if (tmp[aa] != null)
830                   {
831                     saved[aapos++] = tmp[aa];
832                     tmp[aa] = null;
833                   }
834                 }
835                 tmp = saved;
836                 command.deletedAnnotationRows.put(command.seqs[s], saved);
837                 // and then remove any annotation in the other views
838                 for (int alview = 0; views != null
839                         && alview < views.length; alview++)
840                 {
841                   if (views[alview] != command.al)
842                   {
843                     AlignmentAnnotation[] toremove = views[alview]
844                             .getAlignmentAnnotation();
845                     if (toremove == null || toremove.length == 0)
846                     {
847                       continue;
848                     }
849                     // remove any alignment annotation on this sequence that's
850                     // on that alignment view.
851                     for (int aa = 0; aa < toremove.length; aa++)
852                     {
853                       if (toremove[aa].sequenceRef == command.seqs[s])
854                       {
855                         views[alview].deleteAnnotation(toremove[aa]);
856                       }
857                     }
858                   }
859                 }
860               }
861               else
862               {
863                 // save all the annotation
864                 command.deletedAnnotationRows.put(command.seqs[s], tmp);
865               }
866             }
867           }
868           else
869           {
870             // recover rows
871             if (command.deletedAnnotationRows != null
872                     && command.deletedAnnotationRows
873                             .containsKey(command.seqs[s]))
874             {
875               AlignmentAnnotation[] revealed = command.deletedAnnotationRows
876                       .get(command.seqs[s]);
877               command.seqs[s].setAlignmentAnnotation(revealed);
878               if (revealed != null)
879               {
880                 for (int aa = 0; aa < revealed.length; aa++)
881                 {
882                   // iterate through al adding original annotation
883                   command.al.addAnnotation(revealed[aa]);
884                 }
885                 for (int aa = 0; aa < revealed.length; aa++)
886                 {
887                   command.al.setAnnotationIndex(revealed[aa], aa);
888                 }
889                 // and then duplicate added annotation on every other alignment
890                 // view
891                 for (int vnum = 0; views != null
892                         && 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,
1020                   aSize - 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, command.position,
1064                       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,
1074                     command.position + command.number, temp,
1075                     command.position, annotations[a].annotations.length
1076                             - command.position - 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.get(seq));
1124       }
1125
1126       return;
1127     }
1128
1129     SequenceFeature[] sf = sequence.getSequenceFeatures();
1130
1131     if (sf == null)
1132     {
1133       return;
1134     }
1135
1136     SequenceFeature[] oldsf = new SequenceFeature[sf.length];
1137
1138     int cSize = j - i;
1139
1140     for (int s = 0; s < sf.length; s++)
1141     {
1142       SequenceFeature copy = new SequenceFeature(sf[s]);
1143
1144       oldsf[s] = copy;
1145
1146       if (sf[s].getEnd() < i)
1147       {
1148         continue;
1149       }
1150
1151       if (sf[s].getBegin() > j)
1152       {
1153         sf[s].setBegin(copy.getBegin() - cSize);
1154         sf[s].setEnd(copy.getEnd() - cSize);
1155         continue;
1156       }
1157
1158       if (sf[s].getBegin() >= i)
1159       {
1160         sf[s].setBegin(i);
1161       }
1162
1163       if (sf[s].getEnd() < j)
1164       {
1165         sf[s].setEnd(j - 1);
1166       }
1167
1168       sf[s].setEnd(sf[s].getEnd() - (cSize));
1169
1170       if (sf[s].getBegin() > sf[s].getEnd())
1171       {
1172         sequence.deleteFeature(sf[s]);
1173       }
1174     }
1175
1176     if (command.editedFeatures == null)
1177     {
1178       command.editedFeatures = new Hashtable<SequenceI, SequenceFeature[]>();
1179     }
1180
1181     command.editedFeatures.put(seq, oldsf);
1182
1183   }
1184
1185   /**
1186    * Returns the list of edit commands wrapped by this object.
1187    * 
1188    * @return
1189    */
1190   public List<Edit> getEdits()
1191   {
1192     return this.edits;
1193   }
1194
1195   /**
1196    * Returns a map whose keys are the dataset sequences, and values their
1197    * aligned sequences before the command edit list was applied. The aligned
1198    * sequences are copies, which may be updated without affecting the originals.
1199    * 
1200    * The command holds references to the aligned sequences (after editing). If
1201    * the command is an 'undo',then the prior state is simply the aligned state.
1202    * Otherwise, we have to derive the prior state by working backwards through
1203    * the edit list to infer the aligned sequences before editing.
1204    * 
1205    * Note: an alternative solution would be to cache the 'before' state of each
1206    * edit, but this would be expensive in space in the common case that the
1207    * original is never needed (edits are not mirrored).
1208    * 
1209    * @return
1210    * @throws IllegalStateException
1211    *           on detecting an edit command of a type that can't be unwound
1212    */
1213   public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1214   {
1215     Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1216     if (getEdits() == null)
1217     {
1218       return result;
1219     }
1220     if (forUndo)
1221     {
1222       for (Edit e : getEdits())
1223       {
1224         for (SequenceI seq : e.getSequences())
1225         {
1226           SequenceI ds = seq.getDatasetSequence();
1227           // SequenceI preEdit = result.get(ds);
1228           if (!result.containsKey(ds))
1229           {
1230             /*
1231              * copy sequence including start/end (but don't use copy constructor
1232              * as we don't need annotations)
1233              */
1234             SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1235                     seq.getStart(), seq.getEnd());
1236             preEdit.setDatasetSequence(ds);
1237             result.put(ds, preEdit);
1238           }
1239         }
1240       }
1241       return result;
1242     }
1243
1244     /*
1245      * Work backwards through the edit list, deriving the sequences before each
1246      * was applied. The final result is the sequence set before any edits.
1247      */
1248     Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1249     while (editList.hasNext())
1250     {
1251       Edit oldEdit = editList.next();
1252       Action action = oldEdit.getAction();
1253       int position = oldEdit.getPosition();
1254       int number = oldEdit.getNumber();
1255       final char gap = oldEdit.getGapCharacter();
1256       for (SequenceI seq : oldEdit.getSequences())
1257       {
1258         SequenceI ds = seq.getDatasetSequence();
1259         SequenceI preEdit = result.get(ds);
1260         if (preEdit == null)
1261         {
1262           preEdit = new Sequence("", seq.getSequenceAsString(),
1263                   seq.getStart(), seq.getEnd());
1264           preEdit.setDatasetSequence(ds);
1265           result.put(ds, preEdit);
1266         }
1267         /*
1268          * 'Undo' this edit action on the sequence (updating the value in the
1269          * map).
1270          */
1271         if (ds != null)
1272         {
1273           if (action == Action.DELETE_GAP)
1274           {
1275             preEdit.setSequence(new String(StringUtils.insertCharAt(
1276                     preEdit.getSequence(), position, number, gap)));
1277           }
1278           else if (action == Action.INSERT_GAP)
1279           {
1280             preEdit.setSequence(new String(StringUtils.deleteChars(
1281                     preEdit.getSequence(), position, position + number)));
1282           }
1283           else
1284           {
1285             System.err.println("Can't undo edit action " + action);
1286             // throw new IllegalStateException("Can't undo edit action " +
1287             // action);
1288           }
1289         }
1290       }
1291     }
1292     return result;
1293   }
1294
1295   public class Edit
1296   {
1297     public SequenceI[] oldds;
1298
1299     boolean fullAlignmentHeight = false;
1300
1301     Hashtable<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1302
1303     Hashtable<String, Annotation[]> deletedAnnotations;
1304
1305     Hashtable<SequenceI, SequenceFeature[]> editedFeatures;
1306
1307     AlignmentI al;
1308
1309     Action command;
1310
1311     char[][] string;
1312
1313     SequenceI[] seqs;
1314
1315     int[] alIndex;
1316
1317     int position, number;
1318
1319     char gapChar;
1320
1321     public Edit(Action command, SequenceI[] seqs, int position, int number,
1322             char gapChar)
1323     {
1324       this.command = command;
1325       this.seqs = seqs;
1326       this.position = position;
1327       this.number = number;
1328       this.gapChar = gapChar;
1329     }
1330
1331     Edit(Action command, SequenceI[] seqs, int position, int number,
1332             AlignmentI al)
1333     {
1334       this.gapChar = al.getGapCharacter();
1335       this.command = command;
1336       this.seqs = seqs;
1337       this.position = position;
1338       this.number = number;
1339       this.al = al;
1340
1341       alIndex = new int[seqs.length];
1342       for (int i = 0; i < seqs.length; i++)
1343       {
1344         alIndex[i] = al.findIndex(seqs[i]);
1345       }
1346
1347       fullAlignmentHeight = (al.getHeight() == seqs.length);
1348     }
1349
1350     Edit(Action command, SequenceI[] seqs, int position, int number,
1351             AlignmentI al, String replace)
1352     {
1353       this.command = command;
1354       this.seqs = seqs;
1355       this.position = position;
1356       this.number = number;
1357       this.al = al;
1358       this.gapChar = al.getGapCharacter();
1359       string = new char[seqs.length][];
1360       for (int i = 0; i < seqs.length; i++)
1361       {
1362         string[i] = replace.toCharArray();
1363       }
1364
1365       fullAlignmentHeight = (al.getHeight() == seqs.length);
1366     }
1367
1368     public SequenceI[] getSequences()
1369     {
1370       return seqs;
1371     }
1372
1373     public int getPosition()
1374     {
1375       return position;
1376     }
1377
1378     public Action getAction()
1379     {
1380       return command;
1381     }
1382
1383     public int getNumber()
1384     {
1385       return number;
1386     }
1387
1388     public char getGapCharacter()
1389     {
1390       return gapChar;
1391     }
1392   }
1393
1394   /**
1395    * Returns an iterator over the list of edit commands which traverses the list
1396    * either forwards or backwards.
1397    * 
1398    * @param forwards
1399    * @return
1400    */
1401   public Iterator<Edit> getEditIterator(boolean forwards)
1402   {
1403     if (forwards)
1404     {
1405       return getEdits().iterator();
1406     }
1407     else
1408     {
1409       return new ReverseListIterator<Edit>(getEdits());
1410     }
1411   }
1412 }