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.Range;
28 import jalview.datamodel.Sequence;
29 import jalview.datamodel.SequenceFeature;
30 import jalview.datamodel.SequenceI;
31 import jalview.datamodel.features.SequenceFeaturesI;
32 import jalview.util.Comparison;
33 import jalview.util.ReverseListIterator;
34 import jalview.util.StringUtils;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.Hashtable;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.ListIterator;
51 * Description: Essential information for performing undo and redo for cut/paste
52 * insert/delete gap which can be stored in the HistoryList
56 * Copyright: Copyright (c) 2006
60 * Company: Dundee University
63 * @author not attributable
66 public class EditCommand implements CommandI
73 public Action getUndoAction()
81 public Action getUndoAction()
89 public Action getUndoAction()
97 public Action getUndoAction()
105 public Action getUndoAction()
113 public Action getUndoAction()
118 public abstract Action getUndoAction();
121 private List<Edit> edits = new ArrayList<Edit>();
129 public EditCommand(String desc)
131 this.description = desc;
134 public EditCommand(String desc, Action command, SequenceI[] seqs,
135 int position, int number, AlignmentI al)
137 this.description = desc;
138 if (command == Action.CUT || command == Action.PASTE)
140 setEdit(new Edit(command, seqs, position, number, al));
143 performEdit(0, null);
146 public EditCommand(String desc, Action command, String replace,
147 SequenceI[] seqs, int position, int number, AlignmentI al)
149 this.description = desc;
150 if (command == Action.REPLACE)
152 setEdit(new Edit(command, seqs, position, number, al, replace));
155 performEdit(0, null);
159 * Set the list of edits to the specified item (only).
163 protected void setEdit(Edit e)
170 * Add the given edit command to the stored list of commands. If simply
171 * expanding the range of the last command added, then modify it instead of
172 * adding a new command.
176 public void addEdit(Edit e)
178 if (!expandEdit(edits, e))
185 * Returns true if the new edit is incorporated by updating (expanding the
186 * range of) the last edit on the list, else false. We can 'expand' the last
187 * edit if the new one is the same action, on the same sequences, and acts on
188 * a contiguous range. This is the case where a mouse drag generates a series
189 * of contiguous gap insertions or deletions.
195 protected static boolean expandEdit(List<Edit> edits, Edit e)
197 if (edits == null || edits.isEmpty())
201 Edit lastEdit = edits.get(edits.size() - 1);
202 Action action = e.command;
203 if (lastEdit.command != action)
209 * Both commands must act on the same sequences - compare the underlying
210 * dataset sequences, rather than the aligned sequences, which change as
213 if (lastEdit.seqs.length != e.seqs.length)
217 for (int i = 0; i < e.seqs.length; i++)
219 if (lastEdit.seqs[i].getDatasetSequence() != e.seqs[i]
220 .getDatasetSequence())
227 * Check a contiguous edit; either
229 * <li>a new Insert <n> positions to the right of the last <insert n>,
231 * <li>a new Delete <n> gaps which is <n> positions to the left of the last
235 boolean contiguous = (action == Action.INSERT_GAP
236 && e.position == lastEdit.position + lastEdit.number)
237 || (action == Action.DELETE_GAP
238 && e.position + e.number == lastEdit.position);
242 * We are just expanding the range of the last edit. For delete gap, also
243 * moving the start position left.
245 lastEdit.number += e.number;
246 lastEdit.seqs = e.seqs;
247 if (action == Action.DELETE_GAP)
257 * Clear the list of stored edit commands.
260 protected void clearEdits()
266 * Returns the i'th stored Edit command.
271 protected Edit getEdit(int i)
273 if (i >= 0 && i < edits.size())
281 final public String getDescription()
293 * Return the alignment for the first edit (or null if no edit).
297 final public AlignmentI getAlignment()
299 return (edits.isEmpty() ? null : edits.get(0).al);
303 * append a new editCommand Note. this shouldn't be called if the edit is an
304 * operation affects more alignment objects than the one referenced in al (for
305 * example, cut or pasting whole sequences). Use the form with an additional
306 * AlignmentI[] views parameter.
315 final public void appendEdit(Action command, SequenceI[] seqs,
316 int position, int number, AlignmentI al, boolean performEdit)
318 appendEdit(command, seqs, position, number, al, performEdit, null);
322 * append a new edit command with a set of alignment views that may be
333 final public void appendEdit(Action command, SequenceI[] seqs,
334 int position, int number, AlignmentI al, boolean performEdit,
337 Edit edit = new Edit(command, seqs, position, number, al);
338 appendEdit(edit, al, performEdit, views);
342 * Overloaded method that accepts an Edit object with additional parameters.
349 final public void appendEdit(Edit edit, AlignmentI al,
350 boolean performEdit, AlignmentI[] views)
352 if (al.getHeight() == edit.seqs.length)
355 edit.fullAlignmentHeight = true;
362 performEdit(edit, views);
367 * Execute all the edit commands, starting at the given commandIndex
369 * @param commandIndex
372 public final void performEdit(int commandIndex, AlignmentI[] views)
374 ListIterator<Edit> iterator = edits.listIterator(commandIndex);
375 while (iterator.hasNext())
377 Edit edit = iterator.next();
378 performEdit(edit, views);
383 * Execute one edit command in all the specified alignment views
388 protected static void performEdit(Edit edit, AlignmentI[] views)
390 switch (edit.command)
408 // TODO:add deleteNuc for UNDO
410 // insertNuc(edits[e]);
418 final public void doCommand(AlignmentI[] views)
420 performEdit(0, views);
424 * Undo the stored list of commands, in reverse order.
427 final public void undoCommand(AlignmentI[] views)
429 ListIterator<Edit> iterator = edits.listIterator(edits.size());
430 while (iterator.hasPrevious())
432 Edit e = iterator.previous();
460 * Insert gap(s) in sequences as specified by the command, and adjust
465 final private static void insertGap(Edit command)
468 for (int s = 0; s < command.seqs.length; s++)
470 command.seqs[s].insertCharAt(command.position, command.number,
472 // System.out.println("pos: "+command.position+" number:
473 // "+command.number);
476 adjustAnnotations(command, true, false, null);
480 // final void insertNuc(Edit command)
483 // for (int s = 0; s < command.seqs.length; s++)
485 // System.out.println("pos: "+command.position+" number: "+command.number);
486 // command.seqs[s].insertCharAt(command.position, command.number,'A');
489 // adjustAnnotations(command, true, false, null);
493 * Delete gap(s) in sequences as specified by the command, and adjust
498 final static private void deleteGap(Edit command)
500 for (int s = 0; s < command.seqs.length; s++)
502 command.seqs[s].deleteChars(command.position,
503 command.position + command.number);
506 adjustAnnotations(command, false, false, null);
510 * Carry out a Cut action. The cut characters are saved in case Undo is
516 static void cut(Edit command, AlignmentI[] views)
518 boolean seqDeleted = false;
519 command.string = new char[command.seqs.length][];
521 for (int i = 0; i < command.seqs.length; i++)
523 final SequenceI sequence = command.seqs[i];
524 if (sequence.getLength() > command.position)
526 command.string[i] = sequence.getSequence(command.position,
527 command.position + command.number);
528 SequenceI oldds = sequence.getDatasetSequence();
529 if (command.oldds != null && command.oldds[i] != null)
531 // we are redoing an undone cut.
532 sequence.setDatasetSequence(null);
534 Range cutPositions = sequence.findPositions(command.position + 1,
535 command.position + command.number);
536 boolean cutIsInternal = cutPositions != null
537 && sequence.getStart() != cutPositions
538 .getBegin() && sequence.getEnd() != cutPositions.getEnd();
539 sequence.deleteChars(command.position, command.position
542 if (command.oldds != null && command.oldds[i] != null)
544 // oldds entry contains the cut dataset sequence.
545 sequence.setDatasetSequence(command.oldds[i]);
546 command.oldds[i] = oldds;
550 // modify the oldds if necessary
551 if (oldds != sequence.getDatasetSequence()
552 || sequence.getFeatures().hasFeatures())
554 if (command.oldds == null)
556 command.oldds = new SequenceI[command.seqs.length];
558 command.oldds[i] = oldds;
560 if (cutPositions != null)
562 cutFeatures(command, sequence, cutPositions.getBegin(),
563 cutPositions.getEnd(), cutIsInternal);
569 if (sequence.getLength() < 1)
571 command.al.deleteSequence(sequence);
576 adjustAnnotations(command, false, seqDeleted, views);
580 * Perform the given Paste command. This may be to add cut or copied sequences
581 * to an alignment, or to undo a 'Cut' action on a region of the alignment.
586 static void paste(Edit command, AlignmentI[] views)
588 boolean seqWasDeleted = false;
590 for (int i = 0; i < command.seqs.length; i++)
592 boolean newDSNeeded = false;
593 boolean newDSWasNeeded = command.oldds != null
594 && command.oldds[i] != null;
595 SequenceI sequence = command.seqs[i];
596 if (sequence.getLength() < 1)
599 * sequence was deleted; re-add it to the alignment
601 if (command.alIndex[i] < command.al.getHeight())
603 List<SequenceI> sequences;
604 synchronized (sequences = command.al.getSequences())
606 if (!(command.alIndex[i] < 0))
608 sequences.add(command.alIndex[i], sequence);
614 command.al.addSequence(sequence);
616 seqWasDeleted = true;
618 int newStart = sequence.getStart();
619 int newEnd = sequence.getEnd();
621 StringBuilder tmp = new StringBuilder();
622 tmp.append(sequence.getSequence());
623 // Undo of a delete does not replace original dataset sequence on to
624 // alignment sequence.
629 if (command.string != null && command.string[i] != null)
631 if (command.position >= tmp.length())
633 // This occurs if padding is on, and residues
634 // are removed from end of alignment
635 int len = command.position - tmp.length();
638 tmp.append(command.gapChar);
642 tmp.insert(command.position, command.string[i]);
643 for (int s = 0; s < command.string[i].length; s++)
645 if (!Comparison.isGap(command.string[i][s]))
651 start = sequence.findPosition(command.position);
653 // .findPosition(command.position + command.number);
655 if (sequence.getStart() == start)
665 command.string[i] = null;
668 sequence.setSequence(tmp.toString());
669 sequence.setStart(newStart);
670 sequence.setEnd(newEnd);
673 * command and Undo share the same dataset sequence if cut was
674 * at start or end of sequence
676 boolean sameDatasetSequence = false;
679 if (sequence.getDatasetSequence() != null)
684 ds = command.oldds[i];
688 // make a new DS sequence
689 // use new ds mechanism here
690 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
691 sequence.getSequenceAsString());
692 ds = new Sequence(sequence.getName(), ungapped,
693 sequence.getStart(), sequence.getEnd());
694 ds.setDescription(sequence.getDescription());
696 if (command.oldds == null)
698 command.oldds = new SequenceI[command.seqs.length];
700 command.oldds[i] = sequence.getDatasetSequence();
701 sameDatasetSequence = ds == sequence.getDatasetSequence();
702 ds.setSequenceFeatures(sequence.getSequenceFeatures());
703 sequence.setDatasetSequence(ds);
705 undoCutFeatures(command, command.seqs[i], start, length,
706 sameDatasetSequence);
709 adjustAnnotations(command, true, seqWasDeleted, views);
711 command.string = null;
714 static void replace(Edit command)
718 int start = command.position;
719 int end = command.number;
720 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
722 // TODO Jalview 2.4 bugfix change to an aggregate command - original
723 // sequence string is cut, new string is pasted in.
724 command.number = start + command.string[0].length;
725 for (int i = 0; i < command.seqs.length; i++)
727 boolean newDSWasNeeded = command.oldds != null
728 && command.oldds[i] != null;
731 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
732 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
733 * viewport.alignment));
737 * then addHistoryItem(new EditCommand( "Add sequences",
738 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
741 oldstring = command.seqs[i].getSequenceAsString();
742 tmp = new StringBuffer(oldstring.substring(0, start));
743 tmp.append(command.string[i]);
744 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
745 jalview.util.Comparison.GapChars,
746 new String(command.string[i]));
747 int ipos = command.seqs[i].findPosition(start)
748 - command.seqs[i].getStart();
749 tmp.append(oldstring.substring(end));
750 command.seqs[i].setSequence(tmp.toString());
751 command.string[i] = oldstring.substring(start, end).toCharArray();
752 String nogapold = AlignSeq.extractGaps(Comparison.GapChars,
753 new String(command.string[i]));
754 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
758 SequenceI oldds = command.seqs[i].getDatasetSequence();
759 command.seqs[i].setDatasetSequence(command.oldds[i]);
760 command.oldds[i] = oldds;
764 if (command.oldds == null)
766 command.oldds = new SequenceI[command.seqs.length];
768 command.oldds[i] = command.seqs[i].getDatasetSequence();
769 SequenceI newds = new Sequence(
770 command.seqs[i].getDatasetSequence());
771 String fullseq, osp = newds.getSequenceAsString();
772 fullseq = osp.substring(0, ipos) + nogaprep
773 + osp.substring(ipos + nogaprep.length());
774 newds.setSequence(fullseq.toUpperCase());
775 // TODO: JAL-1131 ensure newly created dataset sequence is added to
777 // dataset sequences associated with the alignment.
778 // TODO: JAL-1131 fix up any annotation associated with new dataset
779 // sequence to ensure that original sequence/annotation relationships
781 command.seqs[i].setDatasetSequence(newds);
790 final static void adjustAnnotations(Edit command, boolean insert,
791 boolean modifyVisibility, AlignmentI[] views)
793 AlignmentAnnotation[] annotations = null;
795 if (modifyVisibility && !insert)
797 // only occurs if a sequence was added or deleted.
798 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
800 if (command.fullAlignmentHeight)
802 annotations = command.al.getAlignmentAnnotation();
807 AlignmentAnnotation[] tmp;
808 for (int s = 0; s < command.seqs.length; s++)
810 command.seqs[s].sequenceChanged();
812 if (modifyVisibility)
814 // Rows are only removed or added to sequence object.
818 tmp = command.seqs[s].getAnnotation();
821 int alen = tmp.length;
822 for (int aa = 0; aa < tmp.length; aa++)
824 if (!command.al.deleteAnnotation(tmp[aa]))
826 // strip out annotation not in the current al (will be put
827 // back on insert in all views)
832 command.seqs[s].setAlignmentAnnotation(null);
833 if (alen != tmp.length)
835 // save the non-null annotation references only
836 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
837 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
841 saved[aapos++] = tmp[aa];
846 command.deletedAnnotationRows.put(command.seqs[s], saved);
847 // and then remove any annotation in the other views
848 for (int alview = 0; views != null
849 && alview < views.length; alview++)
851 if (views[alview] != command.al)
853 AlignmentAnnotation[] toremove = views[alview]
854 .getAlignmentAnnotation();
855 if (toremove == null || toremove.length == 0)
859 // remove any alignment annotation on this sequence that's
860 // on that alignment view.
861 for (int aa = 0; aa < toremove.length; aa++)
863 if (toremove[aa].sequenceRef == command.seqs[s])
865 views[alview].deleteAnnotation(toremove[aa]);
873 // save all the annotation
874 command.deletedAnnotationRows.put(command.seqs[s], tmp);
881 if (command.deletedAnnotationRows != null
882 && command.deletedAnnotationRows
883 .containsKey(command.seqs[s]))
885 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
886 .get(command.seqs[s]);
887 command.seqs[s].setAlignmentAnnotation(revealed);
888 if (revealed != null)
890 for (int aa = 0; aa < revealed.length; aa++)
892 // iterate through al adding original annotation
893 command.al.addAnnotation(revealed[aa]);
895 for (int aa = 0; aa < revealed.length; aa++)
897 command.al.setAnnotationIndex(revealed[aa], aa);
899 // and then duplicate added annotation on every other alignment
901 for (int vnum = 0; views != null && vnum < views.length; vnum++)
903 if (views[vnum] != command.al)
905 int avwidth = views[vnum].getWidth() + 1;
906 // duplicate in this view
907 for (int a = 0; a < revealed.length; a++)
909 AlignmentAnnotation newann = new AlignmentAnnotation(
911 command.seqs[s].addAlignmentAnnotation(newann);
912 newann.padAnnotation(avwidth);
913 views[vnum].addAnnotation(newann);
914 views[vnum].setAnnotationIndex(newann, a);
924 if (command.seqs[s].getAnnotation() == null)
931 annotations = command.seqs[s].getAnnotation();
935 tmp = new AlignmentAnnotation[aSize
936 + command.seqs[s].getAnnotation().length];
938 System.arraycopy(annotations, 0, tmp, 0, aSize);
940 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
941 command.seqs[s].getAnnotation().length);
945 aSize = annotations.length;
949 if (annotations == null)
956 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
961 for (int a = 0; a < annotations.length; a++)
963 if (annotations[a].autoCalculated
964 || annotations[a].annotations == null)
971 aSize = annotations[a].annotations.length;
974 temp = new Annotation[aSize + command.number];
975 if (annotations[a].padGaps)
977 for (int aa = 0; aa < temp.length; aa++)
979 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
985 if (command.position < aSize)
987 if (command.position + command.number >= aSize)
993 tSize = aSize - command.number;
1005 temp = new Annotation[tSize];
1010 if (command.position < annotations[a].annotations.length)
1012 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1015 if (command.deletedAnnotations != null
1016 && command.deletedAnnotations
1017 .containsKey(annotations[a].annotationId))
1019 Annotation[] restore = command.deletedAnnotations
1020 .get(annotations[a].annotationId);
1022 System.arraycopy(restore, 0, temp, command.position,
1027 System.arraycopy(annotations[a].annotations, command.position,
1028 temp, command.position + command.number,
1029 aSize - command.position);
1033 if (command.deletedAnnotations != null
1034 && command.deletedAnnotations
1035 .containsKey(annotations[a].annotationId))
1037 Annotation[] restore = command.deletedAnnotations
1038 .get(annotations[a].annotationId);
1040 temp = new Annotation[annotations[a].annotations.length
1042 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1043 annotations[a].annotations.length);
1044 System.arraycopy(restore, 0, temp,
1045 annotations[a].annotations.length, restore.length);
1049 temp = annotations[a].annotations;
1055 if (tSize != aSize || command.position < 2)
1057 int copylen = Math.min(command.position,
1058 annotations[a].annotations.length);
1061 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1062 copylen); // command.position);
1065 Annotation[] deleted = new Annotation[command.number];
1066 if (copylen >= command.position)
1068 copylen = Math.min(command.number,
1069 annotations[a].annotations.length - command.position);
1072 System.arraycopy(annotations[a].annotations, command.position,
1073 deleted, 0, copylen); // command.number);
1077 command.deletedAnnotations.put(annotations[a].annotationId,
1079 if (annotations[a].annotations.length > command.position
1082 System.arraycopy(annotations[a].annotations,
1083 command.position + command.number, temp,
1084 command.position, annotations[a].annotations.length
1085 - command.position - command.number); // aSize
1090 int dSize = aSize - command.position;
1094 Annotation[] deleted = new Annotation[command.number];
1095 System.arraycopy(annotations[a].annotations, command.position,
1098 command.deletedAnnotations.put(annotations[a].annotationId,
1101 tSize = Math.min(annotations[a].annotations.length,
1103 temp = new Annotation[tSize];
1104 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1108 temp = annotations[a].annotations;
1113 annotations[a].annotations = temp;
1118 * Restores features to the state before a Cut.
1120 * <li>re-add any features deleted by the cut</li>
1121 * <li>remove any truncated features created by the cut</li>
1122 * <li>shift right any features to the right of the cut</li>
1128 * the sequence the Cut applied to
1130 * the start residue position of the cut
1132 * the number of residues cut
1133 * @param sameDatasetSequence
1134 * true if dataset sequence and frame of reference were left
1135 * unchanged by the Cut
1137 final static void undoCutFeatures(Edit command, SequenceI seq,
1138 final int start, final int length, boolean sameDatasetSequence)
1140 SequenceI sequence = seq.getDatasetSequence();
1141 if (sequence == null)
1147 * shift right features that lie to the right of the restored cut (but not
1148 * if dataset sequence unchanged - so coordinates were changed by Cut)
1150 if (!sameDatasetSequence)
1153 * shift right all features right of and not
1154 * contiguous with the cut position
1156 seq.getFeatures().shiftFeatures(start + 1, length);
1159 * shift right any features that start at the cut position,
1160 * unless they were truncated
1162 List<SequenceFeature> sfs = seq.getFeatures().findFeatures(start,
1164 for (SequenceFeature sf : sfs)
1166 if (sf.getBegin() == start)
1168 if (!command.truncatedFeatures.containsKey(seq)
1169 || !command.truncatedFeatures.get(seq).contains(sf))
1172 * feature was shifted left to cut position (not truncated),
1173 * so shift it back right
1175 SequenceFeature shifted = new SequenceFeature(sf, sf.getBegin()
1176 + length, sf.getEnd() + length, sf.getFeatureGroup(),
1178 seq.addSequenceFeature(shifted);
1179 seq.deleteFeature(sf);
1186 * restore any features that were deleted or truncated
1188 if (command.deletedFeatures != null
1189 && command.deletedFeatures.containsKey(seq))
1191 for (SequenceFeature deleted : command.deletedFeatures.get(seq))
1193 sequence.addSequenceFeature(deleted);
1198 * delete any truncated features
1200 if (command.truncatedFeatures != null
1201 && command.truncatedFeatures.containsKey(seq))
1203 for (SequenceFeature amended : command.truncatedFeatures.get(seq))
1205 sequence.deleteFeature(amended);
1211 * Returns the list of edit commands wrapped by this object.
1215 public List<Edit> getEdits()
1221 * Returns a map whose keys are the dataset sequences, and values their
1222 * aligned sequences before the command edit list was applied. The aligned
1223 * sequences are copies, which may be updated without affecting the originals.
1225 * The command holds references to the aligned sequences (after editing). If
1226 * the command is an 'undo',then the prior state is simply the aligned state.
1227 * Otherwise, we have to derive the prior state by working backwards through
1228 * the edit list to infer the aligned sequences before editing.
1230 * Note: an alternative solution would be to cache the 'before' state of each
1231 * edit, but this would be expensive in space in the common case that the
1232 * original is never needed (edits are not mirrored).
1235 * @throws IllegalStateException
1236 * on detecting an edit command of a type that can't be unwound
1238 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1240 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1241 if (getEdits() == null)
1247 for (Edit e : getEdits())
1249 for (SequenceI seq : e.getSequences())
1251 SequenceI ds = seq.getDatasetSequence();
1252 // SequenceI preEdit = result.get(ds);
1253 if (!result.containsKey(ds))
1256 * copy sequence including start/end (but don't use copy constructor
1257 * as we don't need annotations)
1259 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1260 seq.getStart(), seq.getEnd());
1261 preEdit.setDatasetSequence(ds);
1262 result.put(ds, preEdit);
1270 * Work backwards through the edit list, deriving the sequences before each
1271 * was applied. The final result is the sequence set before any edits.
1273 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1274 while (editList.hasNext())
1276 Edit oldEdit = editList.next();
1277 Action action = oldEdit.getAction();
1278 int position = oldEdit.getPosition();
1279 int number = oldEdit.getNumber();
1280 final char gap = oldEdit.getGapCharacter();
1281 for (SequenceI seq : oldEdit.getSequences())
1283 SequenceI ds = seq.getDatasetSequence();
1284 SequenceI preEdit = result.get(ds);
1285 if (preEdit == null)
1287 preEdit = new Sequence("", seq.getSequenceAsString(),
1288 seq.getStart(), seq.getEnd());
1289 preEdit.setDatasetSequence(ds);
1290 result.put(ds, preEdit);
1293 * 'Undo' this edit action on the sequence (updating the value in the
1298 if (action == Action.DELETE_GAP)
1300 preEdit.setSequence(new String(StringUtils.insertCharAt(
1301 preEdit.getSequence(), position, number, gap)));
1303 else if (action == Action.INSERT_GAP)
1305 preEdit.setSequence(new String(StringUtils.deleteChars(
1306 preEdit.getSequence(), position, position + number)));
1310 System.err.println("Can't undo edit action " + action);
1311 // throw new IllegalStateException("Can't undo edit action " +
1322 public SequenceI[] oldds;
1324 boolean fullAlignmentHeight = false;
1326 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1328 Map<String, Annotation[]> deletedAnnotations;
1331 * features deleted by the cut (re-add on Undo)
1332 * (including the original of any shortened features)
1334 Map<SequenceI, List<SequenceFeature>> deletedFeatures;
1337 * shortened features added by the cut (delete on Undo)
1339 Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
1351 int position, number;
1355 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1360 this.position = pos;
1361 this.number = count;
1365 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1368 this(cmd, sqs, pos, count, align.getGapCharacter());
1372 alIndex = new int[sqs.length];
1373 for (int i = 0; i < sqs.length; i++)
1375 alIndex[i] = align.findIndex(sqs[i]);
1378 fullAlignmentHeight = (align.getHeight() == sqs.length);
1381 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1382 AlignmentI align, String replace)
1384 this(cmd, sqs, pos, count, align);
1386 string = new char[sqs.length][];
1387 for (int i = 0; i < sqs.length; i++)
1389 string[i] = replace.toCharArray();
1393 public SequenceI[] getSequences()
1398 public int getPosition()
1403 public Action getAction()
1408 public int getNumber()
1413 public char getGapCharacter()
1420 * Returns an iterator over the list of edit commands which traverses the list
1421 * either forwards or backwards.
1426 public Iterator<Edit> getEditIterator(boolean forwards)
1430 return getEdits().iterator();
1434 return new ReverseListIterator<Edit>(getEdits());
1439 * Adjusts features for Cut, and saves details of changes made to allow Undo
1441 * <li>features left of the cut are unchanged</li>
1442 * <li>features right of the cut are shifted left</li>
1443 * <li>features internal to the cut region are deleted</li>
1444 * <li>features that overlap or span the cut are shortened</li>
1445 * <li>the originals of any deleted or shorted features are saved, to re-add
1447 * <li>any added (shortened) features are saved, to delete on Undo</li>
1452 * @param fromPosition
1454 * @param cutIsInternal
1456 protected static void cutFeatures(Edit command, SequenceI seq,
1457 int fromPosition, int toPosition, boolean cutIsInternal)
1463 List<SequenceFeature> added = new ArrayList<>();
1464 List<SequenceFeature> removed = new ArrayList<>();
1466 SequenceFeaturesI featureStore = seq.getFeatures();
1467 if (toPosition < fromPosition || featureStore == null)
1472 int cutStartPos = fromPosition;
1473 int cutEndPos = toPosition;
1474 int cutWidth = cutEndPos - cutStartPos + 1;
1476 synchronized (featureStore)
1479 * get features that overlap the cut region
1481 List<SequenceFeature> toAmend = featureStore.findFeatures(
1482 cutStartPos, cutEndPos);
1485 * add any contact features that span the cut region
1486 * (not returned by findFeatures)
1488 for (SequenceFeature contact : featureStore.getContactFeatures())
1490 if (contact.getBegin() < cutStartPos
1491 && contact.getEnd() > cutEndPos)
1493 toAmend.add(contact);
1498 * adjust start-end of overlapping features;
1499 * delete features enclosed by the cut;
1500 * delete partially overlapping contact features
1502 for (SequenceFeature sf : toAmend)
1504 int sfBegin = sf.getBegin();
1505 int sfEnd = sf.getEnd();
1506 int newBegin = sfBegin;
1508 boolean toDelete = false;
1509 boolean follows = false;
1511 if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
1514 * feature lies within cut region - delete it
1518 else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
1521 * feature spans cut region - left-shift the end
1525 else if (sfEnd <= cutEndPos)
1528 * feature overlaps left of cut region - truncate right
1530 newEnd = cutStartPos - 1;
1531 if (sf.isContactFeature())
1536 else if (sfBegin >= cutStartPos)
1539 * remaining case - feature overlaps right
1540 * truncate left, adjust end of feature
1542 newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
1543 newEnd = newBegin + sfEnd - cutEndPos - 1;
1544 if (sf.isContactFeature())
1550 seq.deleteFeature(sf);
1557 SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
1558 sf.getFeatureGroup(), sf.getScore());
1559 seq.addSequenceFeature(copy);
1568 * and left shift any features lying to the right of the cut region
1569 * (but not if the cut is at start or end of sequence)
1573 featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
1578 * save deleted and amended features, so that Undo can
1579 * re-add or delete them respectively
1581 if (command.deletedFeatures == null)
1583 command.deletedFeatures = new HashMap<>();
1585 if (command.truncatedFeatures == null)
1587 command.truncatedFeatures = new HashMap<>();
1589 command.deletedFeatures.put(seq, removed);
1590 command.truncatedFeatures.put(seq, added);