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>,
229 * <li>a new Delete <n> gaps which is <n> positions to the left of the last
233 boolean contiguous = (action == Action.INSERT_GAP
234 && e.position == lastEdit.position + lastEdit.number)
235 || (action == Action.DELETE_GAP
236 && e.position + e.number == lastEdit.position);
240 * We are just expanding the range of the last edit. For delete gap, also
241 * moving the start position left.
243 lastEdit.number += e.number;
244 lastEdit.seqs = e.seqs;
245 if (action == Action.DELETE_GAP)
255 * Clear the list of stored edit commands.
258 protected void clearEdits()
264 * Returns the i'th stored Edit command.
269 protected Edit getEdit(int i)
271 if (i >= 0 && i < edits.size())
279 final public String getDescription()
291 * Return the alignment for the first edit (or null if no edit).
295 final public AlignmentI getAlignment()
297 return (edits.isEmpty() ? null : edits.get(0).al);
301 * append a new editCommand Note. this shouldn't be called if the edit is an
302 * operation affects more alignment objects than the one referenced in al (for
303 * example, cut or pasting whole sequences). Use the form with an additional
304 * AlignmentI[] views parameter.
313 final public void appendEdit(Action command, SequenceI[] seqs,
314 int position, int number, AlignmentI al, boolean performEdit)
316 appendEdit(command, seqs, position, number, al, performEdit, null);
320 * append a new edit command with a set of alignment views that may be
331 final public void appendEdit(Action command, SequenceI[] seqs,
332 int position, int number, AlignmentI al, boolean performEdit,
335 Edit edit = new Edit(command, seqs, position, number,
336 al.getGapCharacter());
337 if (al.getHeight() == seqs.length)
340 edit.fullAlignmentHeight = true;
347 performEdit(edit, views);
352 * Overloaded method that accepts an Edit object with additional parameters.
359 final public void appendEdit(Edit edit, AlignmentI al,
360 boolean performEdit, AlignmentI[] views)
362 if (al.getHeight() == edit.seqs.length)
365 edit.fullAlignmentHeight = true;
372 performEdit(edit, views);
377 * Execute all the edit commands, starting at the given commandIndex
379 * @param commandIndex
382 public final void performEdit(int commandIndex, AlignmentI[] views)
384 ListIterator<Edit> iterator = edits.listIterator(commandIndex);
385 while (iterator.hasNext())
387 Edit edit = iterator.next();
388 performEdit(edit, views);
393 * Execute one edit command in all the specified alignment views
398 protected static void performEdit(Edit edit, AlignmentI[] views)
400 switch (edit.command)
418 // TODO:add deleteNuc for UNDO
420 // insertNuc(edits[e]);
428 final public void doCommand(AlignmentI[] views)
430 performEdit(0, views);
434 * Undo the stored list of commands, in reverse order.
437 final public void undoCommand(AlignmentI[] views)
439 ListIterator<Edit> iterator = edits.listIterator(edits.size());
440 while (iterator.hasPrevious())
442 Edit e = iterator.previous();
470 * Insert gap(s) in sequences as specified by the command, and adjust
475 final private static void insertGap(Edit command)
478 for (int s = 0; s < command.seqs.length; s++)
480 command.seqs[s].insertCharAt(command.position, command.number,
482 // System.out.println("pos: "+command.position+" number:
483 // "+command.number);
486 adjustAnnotations(command, true, false, null);
490 // final void insertNuc(Edit command)
493 // for (int s = 0; s < command.seqs.length; s++)
495 // System.out.println("pos: "+command.position+" number: "+command.number);
496 // command.seqs[s].insertCharAt(command.position, command.number,'A');
499 // adjustAnnotations(command, true, false, null);
503 * Delete gap(s) in sequences as specified by the command, and adjust
508 final static private void deleteGap(Edit command)
510 for (int s = 0; s < command.seqs.length; s++)
512 command.seqs[s].deleteChars(command.position,
513 command.position + command.number);
516 adjustAnnotations(command, false, false, null);
520 * Carry out a Cut action. The cut characters are saved in case Undo is
526 static void cut(Edit command, AlignmentI[] views)
528 boolean seqDeleted = false;
529 command.string = new char[command.seqs.length][];
531 for (int i = 0; i < command.seqs.length; i++)
533 final SequenceI sequence = command.seqs[i];
534 if (sequence.getLength() > command.position)
536 command.string[i] = sequence.getSequence(command.position,
537 command.position + command.number);
538 SequenceI oldds = sequence.getDatasetSequence();
539 if (command.oldds != null && command.oldds[i] != null)
541 // we are redoing an undone cut.
542 sequence.setDatasetSequence(null);
544 sequence.deleteChars(command.position,
545 command.position + command.number);
546 if (command.oldds != null && command.oldds[i] != null)
548 // oldds entry contains the cut dataset sequence.
549 sequence.setDatasetSequence(command.oldds[i]);
550 command.oldds[i] = oldds;
554 // modify the oldds if necessary
555 if (oldds != sequence.getDatasetSequence()
556 || sequence.getFeatures().hasFeatures())
558 if (command.oldds == null)
560 command.oldds = new SequenceI[command.seqs.length];
562 command.oldds[i] = oldds;
563 // FIXME JAL-2541 JAL-2526 get correct positions if on a gap
564 List<SequenceFeature[]> amendedFeatures = sequence
565 .adjustFeatures(command.position, command.position
566 + command.number - 1);
567 if (command.editedFeatures == null)
569 command.editedFeatures = new HashMap<>();
571 command.editedFeatures.put(sequence, amendedFeatures);
576 // sequence.findPosition(command.position),
577 // sequence.findPosition(command.position + command.number),
583 if (sequence.getLength() < 1)
585 command.al.deleteSequence(sequence);
590 adjustAnnotations(command, false, seqDeleted, views);
594 * Perform the given Paste command. This may be to add cut or copied sequences
595 * to an alignment, or to undo a 'Cut' action on a region of the alignment.
600 static void paste(Edit command, AlignmentI[] views)
604 boolean newDSWasNeeded;
605 int newstart, newend;
606 boolean seqWasDeleted = false;
607 int start = 0, end = 0;
609 for (int i = 0; i < command.seqs.length; i++)
612 newDSWasNeeded = command.oldds != null && command.oldds[i] != null;
613 SequenceI sequence = command.seqs[i];
614 if (sequence.getLength() < 1)
616 // ie this sequence was deleted, we need to
617 // readd it to the alignment
618 if (command.alIndex[i] < command.al.getHeight())
620 List<SequenceI> sequences;
621 synchronized (sequences = command.al.getSequences())
623 if (!(command.alIndex[i] < 0))
625 sequences.add(command.alIndex[i], sequence);
631 command.al.addSequence(sequence);
633 seqWasDeleted = true;
635 newstart = sequence.getStart();
636 newend = sequence.getEnd();
638 tmp = new StringBuffer();
639 tmp.append(sequence.getSequence());
640 // Undo of a delete does not replace original dataset sequence on to
641 // alignment sequence.
643 if (command.string != null && command.string[i] != null)
645 if (command.position >= tmp.length())
647 // This occurs if padding is on, and residues
648 // are removed from end of alignment
649 int length = command.position - tmp.length();
652 tmp.append(command.gapChar);
656 tmp.insert(command.position, command.string[i]);
657 for (int s = 0; s < command.string[i].length; s++)
659 // if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]]
661 if (!Comparison.isGap(command.string[i][s]))
666 start = sequence.findPosition(command.position);
667 end = sequence.findPosition(command.position
670 if (sequence.getStart() == start)
680 command.string[i] = null;
683 sequence.setSequence(tmp.toString());
684 sequence.setStart(newstart);
685 sequence.setEnd(newend);
688 if (sequence.getDatasetSequence() != null)
693 ds = command.oldds[i];
697 // make a new DS sequence
698 // use new ds mechanism here
699 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
700 sequence.getSequenceAsString());
701 ds = new Sequence(sequence.getName(), ungapped,
702 sequence.getStart(), sequence.getEnd());
703 ds.setDescription(sequence.getDescription());
705 if (command.oldds == null)
707 command.oldds = new SequenceI[command.seqs.length];
709 command.oldds[i] = sequence.getDatasetSequence();
710 sequence.setDatasetSequence(ds);
712 undoCutFeatures(command, i, start, end);
715 adjustAnnotations(command, true, seqWasDeleted, views);
717 command.string = null;
720 static void replace(Edit command)
724 int start = command.position;
725 int end = command.number;
726 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
728 // TODO Jalview 2.4 bugfix change to an aggregate command - original
729 // sequence string is cut, new string is pasted in.
730 command.number = start + command.string[0].length;
731 for (int i = 0; i < command.seqs.length; i++)
733 boolean newDSWasNeeded = command.oldds != null
734 && command.oldds[i] != null;
737 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
738 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
739 * viewport.alignment));
743 * then addHistoryItem(new EditCommand( "Add sequences",
744 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
747 oldstring = command.seqs[i].getSequenceAsString();
748 tmp = new StringBuffer(oldstring.substring(0, start));
749 tmp.append(command.string[i]);
750 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
751 jalview.util.Comparison.GapChars,
752 new String(command.string[i]));
753 int ipos = command.seqs[i].findPosition(start)
754 - command.seqs[i].getStart();
755 tmp.append(oldstring.substring(end));
756 command.seqs[i].setSequence(tmp.toString());
757 command.string[i] = oldstring.substring(start, end).toCharArray();
758 String nogapold = jalview.analysis.AlignSeq.extractGaps(
759 jalview.util.Comparison.GapChars,
760 new String(command.string[i]));
761 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
765 SequenceI oldds = command.seqs[i].getDatasetSequence();
766 command.seqs[i].setDatasetSequence(command.oldds[i]);
767 command.oldds[i] = oldds;
771 if (command.oldds == null)
773 command.oldds = new SequenceI[command.seqs.length];
775 command.oldds[i] = command.seqs[i].getDatasetSequence();
776 SequenceI newds = new Sequence(
777 command.seqs[i].getDatasetSequence());
778 String fullseq, osp = newds.getSequenceAsString();
779 fullseq = osp.substring(0, ipos) + nogaprep
780 + osp.substring(ipos + nogaprep.length());
781 newds.setSequence(fullseq.toUpperCase());
782 // TODO: JAL-1131 ensure newly created dataset sequence is added to
784 // dataset sequences associated with the alignment.
785 // TODO: JAL-1131 fix up any annotation associated with new dataset
786 // sequence to ensure that original sequence/annotation relationships
788 command.seqs[i].setDatasetSequence(newds);
797 final static void adjustAnnotations(Edit command, boolean insert,
798 boolean modifyVisibility, AlignmentI[] views)
800 AlignmentAnnotation[] annotations = null;
802 if (modifyVisibility && !insert)
804 // only occurs if a sequence was added or deleted.
805 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
807 if (command.fullAlignmentHeight)
809 annotations = command.al.getAlignmentAnnotation();
814 AlignmentAnnotation[] tmp;
815 for (int s = 0; s < command.seqs.length; s++)
817 command.seqs[s].sequenceChanged();
819 if (modifyVisibility)
821 // Rows are only removed or added to sequence object.
825 tmp = command.seqs[s].getAnnotation();
828 int alen = tmp.length;
829 for (int aa = 0; aa < tmp.length; aa++)
831 if (!command.al.deleteAnnotation(tmp[aa]))
833 // strip out annotation not in the current al (will be put
834 // back on insert in all views)
839 command.seqs[s].setAlignmentAnnotation(null);
840 if (alen != tmp.length)
842 // save the non-null annotation references only
843 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
844 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
848 saved[aapos++] = tmp[aa];
853 command.deletedAnnotationRows.put(command.seqs[s], saved);
854 // and then remove any annotation in the other views
855 for (int alview = 0; views != null
856 && alview < views.length; alview++)
858 if (views[alview] != command.al)
860 AlignmentAnnotation[] toremove = views[alview]
861 .getAlignmentAnnotation();
862 if (toremove == null || toremove.length == 0)
866 // remove any alignment annotation on this sequence that's
867 // on that alignment view.
868 for (int aa = 0; aa < toremove.length; aa++)
870 if (toremove[aa].sequenceRef == command.seqs[s])
872 views[alview].deleteAnnotation(toremove[aa]);
880 // save all the annotation
881 command.deletedAnnotationRows.put(command.seqs[s], tmp);
888 if (command.deletedAnnotationRows != null
889 && command.deletedAnnotationRows
890 .containsKey(command.seqs[s]))
892 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
893 .get(command.seqs[s]);
894 command.seqs[s].setAlignmentAnnotation(revealed);
895 if (revealed != null)
897 for (int aa = 0; aa < revealed.length; aa++)
899 // iterate through al adding original annotation
900 command.al.addAnnotation(revealed[aa]);
902 for (int aa = 0; aa < revealed.length; aa++)
904 command.al.setAnnotationIndex(revealed[aa], aa);
906 // and then duplicate added annotation on every other alignment
908 for (int vnum = 0; views != null
909 && vnum < views.length; vnum++)
911 if (views[vnum] != command.al)
913 int avwidth = views[vnum].getWidth() + 1;
914 // duplicate in this view
915 for (int a = 0; a < revealed.length; a++)
917 AlignmentAnnotation newann = new AlignmentAnnotation(
919 command.seqs[s].addAlignmentAnnotation(newann);
920 newann.padAnnotation(avwidth);
921 views[vnum].addAnnotation(newann);
922 views[vnum].setAnnotationIndex(newann, a);
932 if (command.seqs[s].getAnnotation() == null)
939 annotations = command.seqs[s].getAnnotation();
943 tmp = new AlignmentAnnotation[aSize
944 + command.seqs[s].getAnnotation().length];
946 System.arraycopy(annotations, 0, tmp, 0, aSize);
948 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
949 command.seqs[s].getAnnotation().length);
953 aSize = annotations.length;
957 if (annotations == null)
964 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
969 for (int a = 0; a < annotations.length; a++)
971 if (annotations[a].autoCalculated
972 || annotations[a].annotations == null)
979 aSize = annotations[a].annotations.length;
982 temp = new Annotation[aSize + command.number];
983 if (annotations[a].padGaps)
985 for (int aa = 0; aa < temp.length; aa++)
987 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
993 if (command.position < aSize)
995 if (command.position + command.number >= aSize)
1001 tSize = aSize - command.number;
1013 temp = new Annotation[tSize];
1018 if (command.position < annotations[a].annotations.length)
1020 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1023 if (command.deletedAnnotations != null
1024 && command.deletedAnnotations
1025 .containsKey(annotations[a].annotationId))
1027 Annotation[] restore = command.deletedAnnotations
1028 .get(annotations[a].annotationId);
1030 System.arraycopy(restore, 0, temp, command.position,
1035 System.arraycopy(annotations[a].annotations, command.position,
1036 temp, command.position + command.number,
1037 aSize - command.position);
1041 if (command.deletedAnnotations != null
1042 && command.deletedAnnotations
1043 .containsKey(annotations[a].annotationId))
1045 Annotation[] restore = command.deletedAnnotations
1046 .get(annotations[a].annotationId);
1048 temp = new Annotation[annotations[a].annotations.length
1050 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1051 annotations[a].annotations.length);
1052 System.arraycopy(restore, 0, temp,
1053 annotations[a].annotations.length, restore.length);
1057 temp = annotations[a].annotations;
1063 if (tSize != aSize || command.position < 2)
1065 int copylen = Math.min(command.position,
1066 annotations[a].annotations.length);
1069 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1070 copylen); // command.position);
1073 Annotation[] deleted = new Annotation[command.number];
1074 if (copylen >= command.position)
1076 copylen = Math.min(command.number,
1077 annotations[a].annotations.length - command.position);
1080 System.arraycopy(annotations[a].annotations, command.position,
1081 deleted, 0, copylen); // command.number);
1085 command.deletedAnnotations.put(annotations[a].annotationId,
1087 if (annotations[a].annotations.length > command.position
1090 System.arraycopy(annotations[a].annotations,
1091 command.position + command.number, temp,
1092 command.position, annotations[a].annotations.length
1093 - command.position - command.number); // aSize
1098 int dSize = aSize - command.position;
1102 Annotation[] deleted = new Annotation[command.number];
1103 System.arraycopy(annotations[a].annotations, command.position,
1106 command.deletedAnnotations.put(annotations[a].annotationId,
1109 tSize = Math.min(annotations[a].annotations.length,
1111 temp = new Annotation[tSize];
1112 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1116 temp = annotations[a].annotations;
1121 annotations[a].annotations = temp;
1125 final static void undoCutFeatures(Edit command, int index, final int i,
1128 SequenceI seq = command.seqs[index];
1129 SequenceI sequence = seq.getDatasetSequence();
1130 if (sequence == null)
1136 * insert == true for an Undo of a Cut; restore the original features
1137 * and delete the amended ones
1141 // TODO shift right features that lie to the right of the restored cut
1142 // (add a start position parameter to SequenceFeatures.shift)
1144 if (command.editedFeatures != null
1145 && command.editedFeatures.containsKey(seq))
1147 for (SequenceFeature[] toRestore : command.editedFeatures.get(seq))
1149 sequence.addSequenceFeature(toRestore[0]);
1150 if (toRestore[1] != null)
1152 sequence.deleteFeature(toRestore[1]);
1159 // List<SequenceFeature> sf = sequence.getFeatures()
1160 // .getPositionalFeatures();
1162 // if (sf.isEmpty())
1167 // List<SequenceFeature> oldsf = new ArrayList<SequenceFeature>();
1169 // int cSize = j - i;
1171 // for (SequenceFeature feature : sf)
1173 // SequenceFeature copy = new SequenceFeature(feature);
1177 // if (feature.getEnd() < i)
1182 // if (feature.getBegin() > j)
1184 // int newBegin = copy.getBegin() - cSize;
1185 // int newEnd = copy.getEnd() - cSize;
1186 // SequenceFeature newSf = new SequenceFeature(feature, newBegin,
1187 // newEnd, feature.getFeatureGroup(), feature.getScore());
1188 // sequence.deleteFeature(feature);
1189 // sequence.addSequenceFeature(newSf);
1190 // // feature.setBegin(newBegin);
1191 // // feature.setEnd(newEnd);
1195 // int newBegin = feature.getBegin();
1196 // int newEnd = feature.getEnd();
1197 // if (newBegin >= i)
1200 // // feature.setBegin(i);
1206 // // feature.setEnd(j - 1);
1208 // newEnd = newEnd - cSize;
1209 // // feature.setEnd(feature.getEnd() - (cSize));
1211 // sequence.deleteFeature(feature);
1212 // if (newEnd >= newBegin)
1214 // sequence.addSequenceFeature(new SequenceFeature(feature, newBegin,
1215 // newEnd, feature.getFeatureGroup(), feature.getScore()));
1217 // // if (feature.getBegin() > feature.getEnd())
1219 // // sequence.deleteFeature(feature);
1223 // if (command.editedFeatures == null)
1225 // command.editedFeatures = new Hashtable<SequenceI,
1226 // List<SequenceFeature>>();
1229 // command.editedFeatures.put(seq, oldsf);
1234 * Returns the list of edit commands wrapped by this object.
1238 public List<Edit> getEdits()
1244 * Returns a map whose keys are the dataset sequences, and values their
1245 * aligned sequences before the command edit list was applied. The aligned
1246 * sequences are copies, which may be updated without affecting the originals.
1248 * The command holds references to the aligned sequences (after editing). If
1249 * the command is an 'undo',then the prior state is simply the aligned state.
1250 * Otherwise, we have to derive the prior state by working backwards through
1251 * the edit list to infer the aligned sequences before editing.
1253 * Note: an alternative solution would be to cache the 'before' state of each
1254 * edit, but this would be expensive in space in the common case that the
1255 * original is never needed (edits are not mirrored).
1258 * @throws IllegalStateException
1259 * on detecting an edit command of a type that can't be unwound
1261 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1263 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1264 if (getEdits() == null)
1270 for (Edit e : getEdits())
1272 for (SequenceI seq : e.getSequences())
1274 SequenceI ds = seq.getDatasetSequence();
1275 // SequenceI preEdit = result.get(ds);
1276 if (!result.containsKey(ds))
1279 * copy sequence including start/end (but don't use copy constructor
1280 * as we don't need annotations)
1282 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1283 seq.getStart(), seq.getEnd());
1284 preEdit.setDatasetSequence(ds);
1285 result.put(ds, preEdit);
1293 * Work backwards through the edit list, deriving the sequences before each
1294 * was applied. The final result is the sequence set before any edits.
1296 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1297 while (editList.hasNext())
1299 Edit oldEdit = editList.next();
1300 Action action = oldEdit.getAction();
1301 int position = oldEdit.getPosition();
1302 int number = oldEdit.getNumber();
1303 final char gap = oldEdit.getGapCharacter();
1304 for (SequenceI seq : oldEdit.getSequences())
1306 SequenceI ds = seq.getDatasetSequence();
1307 SequenceI preEdit = result.get(ds);
1308 if (preEdit == null)
1310 preEdit = new Sequence("", seq.getSequenceAsString(),
1311 seq.getStart(), seq.getEnd());
1312 preEdit.setDatasetSequence(ds);
1313 result.put(ds, preEdit);
1316 * 'Undo' this edit action on the sequence (updating the value in the
1321 if (action == Action.DELETE_GAP)
1323 preEdit.setSequence(new String(StringUtils.insertCharAt(
1324 preEdit.getSequence(), position, number, gap)));
1326 else if (action == Action.INSERT_GAP)
1328 preEdit.setSequence(new String(StringUtils.deleteChars(
1329 preEdit.getSequence(), position, position + number)));
1333 System.err.println("Can't undo edit action " + action);
1334 // throw new IllegalStateException("Can't undo edit action " +
1345 public SequenceI[] oldds;
1347 boolean fullAlignmentHeight = false;
1349 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1351 Map<String, Annotation[]> deletedAnnotations;
1353 Map<SequenceI, List<SequenceFeature[]>> editedFeatures;
1365 int position, number;
1369 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1374 this.position = pos;
1375 this.number = count;
1379 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1382 this.gapChar = align.getGapCharacter();
1385 this.position = pos;
1386 this.number = count;
1389 alIndex = new int[sqs.length];
1390 for (int i = 0; i < sqs.length; i++)
1392 alIndex[i] = align.findIndex(sqs[i]);
1395 fullAlignmentHeight = (align.getHeight() == sqs.length);
1398 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1399 AlignmentI align, String replace)
1403 this.position = pos;
1404 this.number = count;
1406 this.gapChar = align.getGapCharacter();
1407 string = new char[sqs.length][];
1408 for (int i = 0; i < sqs.length; i++)
1410 string[i] = replace.toCharArray();
1413 fullAlignmentHeight = (align.getHeight() == sqs.length);
1416 public SequenceI[] getSequences()
1421 public int getPosition()
1426 public Action getAction()
1431 public int getNumber()
1436 public char getGapCharacter()
1443 * Returns an iterator over the list of edit commands which traverses the list
1444 * either forwards or backwards.
1449 public Iterator<Edit> getEditIterator(boolean forwards)
1453 return getEdits().iterator();
1457 return new ReverseListIterator<Edit>(getEdits());