2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.commands;
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.util.Comparison;
31 import jalview.util.ReverseListIterator;
32 import jalview.util.StringUtils;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.Hashtable;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.ListIterator;
49 * Description: Essential information for performing undo and redo for cut/paste
50 * insert/delete gap which can be stored in the HistoryList
54 * Copyright: Copyright (c) 2006
58 * Company: Dundee University
61 * @author not attributable
64 public class EditCommand implements CommandI
71 public Action getUndoAction()
79 public Action getUndoAction()
87 public Action getUndoAction()
95 public Action getUndoAction()
103 public Action getUndoAction()
111 public Action getUndoAction()
116 public abstract Action getUndoAction();
119 private List<Edit> edits = new ArrayList<Edit>();
127 public EditCommand(String desc)
129 this.description = desc;
132 public EditCommand(String desc, Action command, SequenceI[] seqs,
133 int position, int number, AlignmentI al)
135 this.description = desc;
136 if (command == Action.CUT || command == Action.PASTE)
138 setEdit(new Edit(command, seqs, position, number, al));
141 performEdit(0, null);
144 public EditCommand(String desc, Action command, String replace,
145 SequenceI[] seqs, int position, int number, AlignmentI al)
147 this.description = desc;
148 if (command == Action.REPLACE)
150 setEdit(new Edit(command, seqs, position, number, al, replace));
153 performEdit(0, null);
157 * Set the list of edits to the specified item (only).
161 protected void setEdit(Edit e)
168 * Add the given edit command to the stored list of commands. If simply
169 * expanding the range of the last command added, then modify it instead of
170 * adding a new command.
174 public void addEdit(Edit e)
176 if (!expandEdit(edits, e))
183 * Returns true if the new edit is incorporated by updating (expanding the
184 * range of) the last edit on the list, else false. We can 'expand' the last
185 * edit if the new one is the same action, on the same sequences, and acts on
186 * a contiguous range. This is the case where a mouse drag generates a series
187 * of contiguous gap insertions or deletions.
193 protected static boolean expandEdit(List<Edit> edits, Edit e)
195 if (edits == null || edits.isEmpty())
199 Edit lastEdit = edits.get(edits.size() - 1);
200 Action action = e.command;
201 if (lastEdit.command != action)
207 * Both commands must act on the same sequences - compare the underlying
208 * dataset sequences, rather than the aligned sequences, which change as
211 if (lastEdit.seqs.length != e.seqs.length)
215 for (int i = 0; i < e.seqs.length; i++)
217 if (lastEdit.seqs[i].getDatasetSequence() != e.seqs[i]
218 .getDatasetSequence())
225 * Check a contiguous edit; either
227 * <li>a new Insert <n> positions to the right of the last <insert n>, or</li>
228 * <li>a new Delete <n> gaps which is <n> positions to the left of the last
232 boolean contiguous = (action == Action.INSERT_GAP && e.position == lastEdit.position
234 || (action == Action.DELETE_GAP && e.position + e.number == lastEdit.position);
238 * We are just expanding the range of the last edit. For delete gap, also
239 * moving the start position left.
241 lastEdit.number += e.number;
242 lastEdit.seqs = e.seqs;
243 if (action == Action.DELETE_GAP)
253 * Clear the list of stored edit commands.
256 protected void clearEdits()
262 * Returns the i'th stored Edit command.
267 protected Edit getEdit(int i)
269 if (i >= 0 && i < edits.size())
277 final public String getDescription()
289 * Return the alignment for the first edit (or null if no edit).
293 final public AlignmentI getAlignment()
295 return (edits.isEmpty() ? null : edits.get(0).al);
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.
311 final public void appendEdit(Action command, SequenceI[] seqs,
312 int position, int number, AlignmentI al, boolean performEdit)
314 appendEdit(command, seqs, position, number, al, performEdit, null);
318 * append a new edit command with a set of alignment views that may be
329 final public void appendEdit(Action command, SequenceI[] seqs,
330 int position, int number, AlignmentI al, boolean performEdit,
333 Edit edit = new Edit(command, seqs, position, number,
334 al.getGapCharacter());
335 if (al.getHeight() == seqs.length)
338 edit.fullAlignmentHeight = true;
345 performEdit(edit, views);
350 * Overloaded method that accepts an Edit object with additional parameters.
357 final public void appendEdit(Edit edit, AlignmentI al,
358 boolean performEdit, AlignmentI[] views)
360 if (al.getHeight() == edit.seqs.length)
363 edit.fullAlignmentHeight = true;
370 performEdit(edit, views);
375 * Execute all the edit commands, starting at the given commandIndex
377 * @param commandIndex
380 public final void performEdit(int commandIndex, AlignmentI[] views)
382 ListIterator<Edit> iterator = edits.listIterator(commandIndex);
383 while (iterator.hasNext())
385 Edit edit = iterator.next();
386 performEdit(edit, views);
391 * Execute one edit command in all the specified alignment views
396 protected static void performEdit(Edit edit, AlignmentI[] views)
398 switch (edit.command)
416 // TODO:add deleteNuc for UNDO
418 // insertNuc(edits[e]);
426 final public void doCommand(AlignmentI[] views)
428 performEdit(0, views);
432 * Undo the stored list of commands, in reverse order.
435 final public void undoCommand(AlignmentI[] views)
437 ListIterator<Edit> iterator = edits.listIterator(edits.size());
438 while (iterator.hasPrevious())
440 Edit e = iterator.previous();
468 * Insert gap(s) in sequences as specified by the command, and adjust
473 final private static void insertGap(Edit command)
476 for (int s = 0; s < command.seqs.length; s++)
478 command.seqs[s].insertCharAt(command.position, command.number,
480 // System.out.println("pos: "+command.position+" number: "+command.number);
483 adjustAnnotations(command, true, false, null);
487 // final void insertNuc(Edit command)
490 // for (int s = 0; s < command.seqs.length; s++)
492 // System.out.println("pos: "+command.position+" number: "+command.number);
493 // command.seqs[s].insertCharAt(command.position, command.number,'A');
496 // adjustAnnotations(command, true, false, null);
500 * Delete gap(s) in sequences as specified by the command, and adjust
505 final static private void deleteGap(Edit command)
507 for (int s = 0; s < command.seqs.length; s++)
509 command.seqs[s].deleteChars(command.position, command.position
513 adjustAnnotations(command, false, false, null);
517 * Carry out a Cut action. The cut characters are saved in case Undo is
523 static void cut(Edit command, AlignmentI[] views)
525 boolean seqDeleted = false;
526 command.string = new char[command.seqs.length][];
528 for (int i = 0; i < command.seqs.length; i++)
530 final SequenceI sequence = command.seqs[i];
531 if (sequence.getLength() > command.position)
533 command.string[i] = sequence.getSequence(command.position,
534 command.position + command.number);
535 SequenceI oldds = sequence.getDatasetSequence();
536 if (command.oldds != null && command.oldds[i] != null)
538 // we are redoing an undone cut.
539 sequence.setDatasetSequence(null);
541 sequence.deleteChars(command.position, command.position
543 if (command.oldds != null && command.oldds[i] != null)
545 // oldds entry contains the cut dataset sequence.
546 sequence.setDatasetSequence(command.oldds[i]);
547 command.oldds[i] = oldds;
551 // modify the oldds if necessary
552 if (oldds != sequence.getDatasetSequence()
553 || sequence.getFeatures().hasFeatures())
555 if (command.oldds == null)
557 command.oldds = new SequenceI[command.seqs.length];
559 command.oldds[i] = oldds;
560 // FIXME JAL-2541 JAL-2526 get correct positions if on a gap
561 List<SequenceFeature[]> amendedFeatures = sequence
562 .adjustFeatures(command.position, command.position
563 + command.number - 1);
564 if (command.editedFeatures == null)
566 command.editedFeatures = new HashMap<>();
568 command.editedFeatures.put(sequence, amendedFeatures);
573 // sequence.findPosition(command.position),
574 // sequence.findPosition(command.position + command.number),
580 if (sequence.getLength() < 1)
582 command.al.deleteSequence(sequence);
587 adjustAnnotations(command, false, seqDeleted, views);
591 * Perform the given Paste command. This may be to add cut or copied sequences
592 * to an alignment, or to undo a 'Cut' action on a region of the alignment.
597 static void paste(Edit command, AlignmentI[] views)
601 boolean newDSWasNeeded;
602 int newstart, newend;
603 boolean seqWasDeleted = false;
604 int start = 0, end = 0;
606 for (int i = 0; i < command.seqs.length; i++)
609 newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
610 SequenceI sequence = command.seqs[i];
611 if (sequence.getLength() < 1)
613 // ie this sequence was deleted, we need to
614 // readd it to the alignment
615 if (command.alIndex[i] < command.al.getHeight())
617 List<SequenceI> sequences;
618 synchronized (sequences = command.al.getSequences())
620 if (!(command.alIndex[i] < 0))
622 sequences.add(command.alIndex[i], sequence);
628 command.al.addSequence(sequence);
630 seqWasDeleted = true;
632 newstart = sequence.getStart();
633 newend = sequence.getEnd();
635 tmp = new StringBuffer();
636 tmp.append(sequence.getSequence());
637 // Undo of a delete does not replace original dataset sequence on to
638 // alignment sequence.
640 if (command.string != null && command.string[i] != null)
642 if (command.position >= tmp.length())
644 // This occurs if padding is on, and residues
645 // are removed from end of alignment
646 int length = command.position - tmp.length();
649 tmp.append(command.gapChar);
653 tmp.insert(command.position, command.string[i]);
654 for (int s = 0; s < command.string[i].length; s++)
656 // if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]]
658 if (!Comparison.isGap(command.string[i][s]))
663 start = sequence.findPosition(command.position);
664 end = sequence.findPosition(command.position
667 if (sequence.getStart() == start)
677 command.string[i] = null;
680 sequence.setSequence(tmp.toString());
681 sequence.setStart(newstart);
682 sequence.setEnd(newend);
685 if (sequence.getDatasetSequence() != null)
690 ds = command.oldds[i];
694 // make a new DS sequence
695 // use new ds mechanism here
696 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
697 sequence.getSequenceAsString());
698 ds = new Sequence(sequence.getName(), ungapped,
699 sequence.getStart(), sequence.getEnd());
700 ds.setDescription(sequence.getDescription());
702 if (command.oldds == null)
704 command.oldds = new SequenceI[command.seqs.length];
706 command.oldds[i] = sequence.getDatasetSequence();
707 sequence.setDatasetSequence(ds);
709 undoCutFeatures(command, i, start, end);
712 adjustAnnotations(command, true, seqWasDeleted, views);
714 command.string = null;
717 static void replace(Edit command)
721 int start = command.position;
722 int end = command.number;
723 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
725 // TODO Jalview 2.4 bugfix change to an aggregate command - original
726 // sequence string is cut, new string is pasted in.
727 command.number = start + command.string[0].length;
728 for (int i = 0; i < command.seqs.length; i++)
730 boolean newDSWasNeeded = command.oldds != null
731 && command.oldds[i] != null;
734 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
735 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
736 * viewport.alignment));
740 * then addHistoryItem(new EditCommand( "Add sequences",
741 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
744 oldstring = command.seqs[i].getSequenceAsString();
745 tmp = new StringBuffer(oldstring.substring(0, start));
746 tmp.append(command.string[i]);
747 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
748 jalview.util.Comparison.GapChars, new String(
750 int ipos = command.seqs[i].findPosition(start)
751 - command.seqs[i].getStart();
752 tmp.append(oldstring.substring(end));
753 command.seqs[i].setSequence(tmp.toString());
754 command.string[i] = oldstring.substring(start, end).toCharArray();
755 String nogapold = jalview.analysis.AlignSeq.extractGaps(
756 jalview.util.Comparison.GapChars, new String(
758 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
762 SequenceI oldds = command.seqs[i].getDatasetSequence();
763 command.seqs[i].setDatasetSequence(command.oldds[i]);
764 command.oldds[i] = oldds;
768 if (command.oldds == null)
770 command.oldds = new SequenceI[command.seqs.length];
772 command.oldds[i] = command.seqs[i].getDatasetSequence();
773 SequenceI newds = new Sequence(
774 command.seqs[i].getDatasetSequence());
775 String fullseq, osp = newds.getSequenceAsString();
776 fullseq = osp.substring(0, ipos) + nogaprep
777 + osp.substring(ipos + nogaprep.length());
778 newds.setSequence(fullseq.toUpperCase());
779 // TODO: JAL-1131 ensure newly created dataset sequence is added to
781 // dataset sequences associated with the alignment.
782 // TODO: JAL-1131 fix up any annotation associated with new dataset
783 // sequence to ensure that original sequence/annotation relationships
785 command.seqs[i].setDatasetSequence(newds);
794 final static void adjustAnnotations(Edit command, boolean insert,
795 boolean modifyVisibility, AlignmentI[] views)
797 AlignmentAnnotation[] annotations = null;
799 if (modifyVisibility && !insert)
801 // only occurs if a sequence was added or deleted.
802 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
804 if (command.fullAlignmentHeight)
806 annotations = command.al.getAlignmentAnnotation();
811 AlignmentAnnotation[] tmp;
812 for (int s = 0; s < command.seqs.length; s++)
814 command.seqs[s].sequenceChanged();
816 if (modifyVisibility)
818 // Rows are only removed or added to sequence object.
822 tmp = command.seqs[s].getAnnotation();
825 int alen = tmp.length;
826 for (int aa = 0; aa < tmp.length; aa++)
828 if (!command.al.deleteAnnotation(tmp[aa]))
830 // strip out annotation not in the current al (will be put
831 // back on insert in all views)
836 command.seqs[s].setAlignmentAnnotation(null);
837 if (alen != tmp.length)
839 // save the non-null annotation references only
840 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
841 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
845 saved[aapos++] = tmp[aa];
850 command.deletedAnnotationRows.put(command.seqs[s], saved);
851 // and then remove any annotation in the other views
852 for (int alview = 0; views != null && alview < views.length; alview++)
854 if (views[alview] != command.al)
856 AlignmentAnnotation[] toremove = views[alview]
857 .getAlignmentAnnotation();
858 if (toremove == null || toremove.length == 0)
862 // remove any alignment annotation on this sequence that's
863 // on that alignment view.
864 for (int aa = 0; aa < toremove.length; aa++)
866 if (toremove[aa].sequenceRef == command.seqs[s])
868 views[alview].deleteAnnotation(toremove[aa]);
876 // save all the annotation
877 command.deletedAnnotationRows.put(command.seqs[s], tmp);
884 if (command.deletedAnnotationRows != null
885 && command.deletedAnnotationRows
886 .containsKey(command.seqs[s]))
888 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
889 .get(command.seqs[s]);
890 command.seqs[s].setAlignmentAnnotation(revealed);
891 if (revealed != null)
893 for (int aa = 0; aa < revealed.length; aa++)
895 // iterate through al adding original annotation
896 command.al.addAnnotation(revealed[aa]);
898 for (int aa = 0; aa < revealed.length; aa++)
900 command.al.setAnnotationIndex(revealed[aa], aa);
902 // and then duplicate added annotation on every other alignment
904 for (int vnum = 0; views != null && vnum < views.length; vnum++)
906 if (views[vnum] != command.al)
908 int avwidth = views[vnum].getWidth() + 1;
909 // duplicate in this view
910 for (int a = 0; a < revealed.length; a++)
912 AlignmentAnnotation newann = new AlignmentAnnotation(
914 command.seqs[s].addAlignmentAnnotation(newann);
915 newann.padAnnotation(avwidth);
916 views[vnum].addAnnotation(newann);
917 views[vnum].setAnnotationIndex(newann, a);
927 if (command.seqs[s].getAnnotation() == null)
934 annotations = command.seqs[s].getAnnotation();
938 tmp = new AlignmentAnnotation[aSize
939 + command.seqs[s].getAnnotation().length];
941 System.arraycopy(annotations, 0, tmp, 0, aSize);
943 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
944 command.seqs[s].getAnnotation().length);
948 aSize = annotations.length;
952 if (annotations == null)
959 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
964 for (int a = 0; a < annotations.length; a++)
966 if (annotations[a].autoCalculated
967 || annotations[a].annotations == null)
974 aSize = annotations[a].annotations.length;
977 temp = new Annotation[aSize + command.number];
978 if (annotations[a].padGaps)
980 for (int aa = 0; aa < temp.length; aa++)
982 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
988 if (command.position < aSize)
990 if (command.position + command.number >= aSize)
996 tSize = aSize - command.number;
1008 temp = new Annotation[tSize];
1013 if (command.position < annotations[a].annotations.length)
1015 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1018 if (command.deletedAnnotations != null
1019 && command.deletedAnnotations
1020 .containsKey(annotations[a].annotationId))
1022 Annotation[] restore = command.deletedAnnotations
1023 .get(annotations[a].annotationId);
1025 System.arraycopy(restore, 0, temp, command.position,
1030 System.arraycopy(annotations[a].annotations, command.position,
1031 temp, command.position + command.number, aSize
1032 - command.position);
1036 if (command.deletedAnnotations != null
1037 && command.deletedAnnotations
1038 .containsKey(annotations[a].annotationId))
1040 Annotation[] restore = command.deletedAnnotations
1041 .get(annotations[a].annotationId);
1043 temp = new Annotation[annotations[a].annotations.length
1045 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1046 annotations[a].annotations.length);
1047 System.arraycopy(restore, 0, temp,
1048 annotations[a].annotations.length, restore.length);
1052 temp = annotations[a].annotations;
1058 if (tSize != aSize || command.position < 2)
1060 int copylen = Math.min(command.position,
1061 annotations[a].annotations.length);
1064 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1065 copylen); // command.position);
1068 Annotation[] deleted = new Annotation[command.number];
1069 if (copylen >= command.position)
1071 copylen = Math.min(command.number,
1072 annotations[a].annotations.length - command.position);
1075 System.arraycopy(annotations[a].annotations,
1076 command.position, deleted, 0, copylen); // command.number);
1080 command.deletedAnnotations.put(annotations[a].annotationId,
1082 if (annotations[a].annotations.length > command.position
1085 System.arraycopy(annotations[a].annotations, command.position
1086 + command.number, temp, command.position,
1087 annotations[a].annotations.length - command.position
1088 - command.number); // aSize
1093 int dSize = aSize - command.position;
1097 Annotation[] deleted = new Annotation[command.number];
1098 System.arraycopy(annotations[a].annotations, command.position,
1101 command.deletedAnnotations.put(annotations[a].annotationId,
1104 tSize = Math.min(annotations[a].annotations.length,
1106 temp = new Annotation[tSize];
1107 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1111 temp = annotations[a].annotations;
1116 annotations[a].annotations = temp;
1120 final static void undoCutFeatures(Edit command, int index, final int i,
1123 SequenceI seq = command.seqs[index];
1124 SequenceI sequence = seq.getDatasetSequence();
1125 if (sequence == null)
1131 * insert == true for an Undo of a Cut; restore the original features
1132 * and delete the amended ones
1136 // TODO shift right features that lie to the right of the restored cut
1137 // (add a start position parameter to SequenceFeatures.shift)
1139 if (command.editedFeatures != null
1140 && command.editedFeatures.containsKey(seq))
1142 for (SequenceFeature[] toRestore : command.editedFeatures.get(seq))
1144 sequence.addSequenceFeature(toRestore[0]);
1145 if (toRestore[1] != null)
1147 sequence.deleteFeature(toRestore[1]);
1154 // List<SequenceFeature> sf = sequence.getFeatures()
1155 // .getPositionalFeatures();
1157 // if (sf.isEmpty())
1162 // List<SequenceFeature> oldsf = new ArrayList<SequenceFeature>();
1164 // int cSize = j - i;
1166 // for (SequenceFeature feature : sf)
1168 // SequenceFeature copy = new SequenceFeature(feature);
1172 // if (feature.getEnd() < i)
1177 // if (feature.getBegin() > j)
1179 // int newBegin = copy.getBegin() - cSize;
1180 // int newEnd = copy.getEnd() - cSize;
1181 // SequenceFeature newSf = new SequenceFeature(feature, newBegin,
1182 // newEnd, feature.getFeatureGroup(), feature.getScore());
1183 // sequence.deleteFeature(feature);
1184 // sequence.addSequenceFeature(newSf);
1185 // // feature.setBegin(newBegin);
1186 // // feature.setEnd(newEnd);
1190 // int newBegin = feature.getBegin();
1191 // int newEnd = feature.getEnd();
1192 // if (newBegin >= i)
1195 // // feature.setBegin(i);
1201 // // feature.setEnd(j - 1);
1203 // newEnd = newEnd - cSize;
1204 // // feature.setEnd(feature.getEnd() - (cSize));
1206 // sequence.deleteFeature(feature);
1207 // if (newEnd >= newBegin)
1209 // sequence.addSequenceFeature(new SequenceFeature(feature, newBegin,
1210 // newEnd, feature.getFeatureGroup(), feature.getScore()));
1212 // // if (feature.getBegin() > feature.getEnd())
1214 // // sequence.deleteFeature(feature);
1218 // if (command.editedFeatures == null)
1220 // command.editedFeatures = new Hashtable<SequenceI,
1221 // List<SequenceFeature>>();
1224 // command.editedFeatures.put(seq, oldsf);
1229 * Returns the list of edit commands wrapped by this object.
1233 public List<Edit> getEdits()
1239 * Returns a map whose keys are the dataset sequences, and values their
1240 * aligned sequences before the command edit list was applied. The aligned
1241 * sequences are copies, which may be updated without affecting the originals.
1243 * The command holds references to the aligned sequences (after editing). If
1244 * the command is an 'undo',then the prior state is simply the aligned state.
1245 * Otherwise, we have to derive the prior state by working backwards through
1246 * the edit list to infer the aligned sequences before editing.
1248 * Note: an alternative solution would be to cache the 'before' state of each
1249 * edit, but this would be expensive in space in the common case that the
1250 * original is never needed (edits are not mirrored).
1253 * @throws IllegalStateException
1254 * on detecting an edit command of a type that can't be unwound
1256 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1258 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1259 if (getEdits() == null)
1265 for (Edit e : getEdits())
1267 for (SequenceI seq : e.getSequences())
1269 SequenceI ds = seq.getDatasetSequence();
1270 // SequenceI preEdit = result.get(ds);
1271 if (!result.containsKey(ds))
1274 * copy sequence including start/end (but don't use copy constructor
1275 * as we don't need annotations)
1277 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1278 seq.getStart(), seq.getEnd());
1279 preEdit.setDatasetSequence(ds);
1280 result.put(ds, preEdit);
1288 * Work backwards through the edit list, deriving the sequences before each
1289 * was applied. The final result is the sequence set before any edits.
1291 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1292 while (editList.hasNext())
1294 Edit oldEdit = editList.next();
1295 Action action = oldEdit.getAction();
1296 int position = oldEdit.getPosition();
1297 int number = oldEdit.getNumber();
1298 final char gap = oldEdit.getGapCharacter();
1299 for (SequenceI seq : oldEdit.getSequences())
1301 SequenceI ds = seq.getDatasetSequence();
1302 SequenceI preEdit = result.get(ds);
1303 if (preEdit == null)
1305 preEdit = new Sequence("", seq.getSequenceAsString(),
1306 seq.getStart(), seq.getEnd());
1307 preEdit.setDatasetSequence(ds);
1308 result.put(ds, preEdit);
1311 * 'Undo' this edit action on the sequence (updating the value in the
1316 if (action == Action.DELETE_GAP)
1318 preEdit.setSequence(new String(StringUtils.insertCharAt(
1319 preEdit.getSequence(), position, number, gap)));
1321 else if (action == Action.INSERT_GAP)
1323 preEdit.setSequence(new String(StringUtils.deleteChars(
1324 preEdit.getSequence(), position, position + number)));
1328 System.err.println("Can't undo edit action " + action);
1329 // throw new IllegalStateException("Can't undo edit action " +
1340 public SequenceI[] oldds;
1342 boolean fullAlignmentHeight = false;
1344 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1346 Map<String, Annotation[]> deletedAnnotations;
1348 Map<SequenceI, List<SequenceFeature[]>> editedFeatures;
1360 int position, number;
1364 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1369 this.position = pos;
1370 this.number = count;
1374 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1377 this.gapChar = align.getGapCharacter();
1380 this.position = pos;
1381 this.number = count;
1384 alIndex = new int[sqs.length];
1385 for (int i = 0; i < sqs.length; i++)
1387 alIndex[i] = align.findIndex(sqs[i]);
1390 fullAlignmentHeight = (align.getHeight() == sqs.length);
1393 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1394 AlignmentI align, String replace)
1398 this.position = pos;
1399 this.number = count;
1401 this.gapChar = align.getGapCharacter();
1402 string = new char[sqs.length][];
1403 for (int i = 0; i < sqs.length; i++)
1405 string[i] = replace.toCharArray();
1408 fullAlignmentHeight = (align.getHeight() == sqs.length);
1411 public SequenceI[] getSequences()
1416 public int getPosition()
1421 public Action getAction()
1426 public int getNumber()
1431 public char getGapCharacter()
1438 * Returns an iterator over the list of edit commands which traverses the list
1439 * either forwards or backwards.
1444 public Iterator<Edit> getEditIterator(boolean forwards)
1448 return getEdits().iterator();
1452 return new ReverseListIterator<Edit>(getEdits());