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 // Undoing previous Paste - so
545 // oldds entry contains the cut dataset sequence,
546 // with sequence features in expected place.
547 sequence.setDatasetSequence(command.oldds[i]);
548 command.oldds[i] = oldds;
553 // We always keep track of the dataset sequence so we can safely
554 // restore it during the Undo
555 if (command.oldds == null)
557 command.oldds = new SequenceI[command.seqs.length];
559 command.oldds[i] = oldds;
561 if (cutPositions != null)
563 cutFeatures(command, sequence, cutPositions.getBegin(),
564 cutPositions.getEnd(), cutIsInternal);
570 if (sequence.getLength() < 1)
572 command.al.deleteSequence(sequence);
577 adjustAnnotations(command, false, seqDeleted, views);
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.
587 static void paste(Edit command, AlignmentI[] views)
589 boolean seqWasDeleted = false;
591 for (int i = 0; i < command.seqs.length; i++)
593 boolean newDSNeeded = false;
594 boolean newDSWasNeeded = command.oldds != null
595 && command.oldds[i] != null;
596 SequenceI sequence = command.seqs[i];
597 if (sequence.getLength() < 1)
600 * sequence was deleted; re-add it to the alignment
602 if (command.alIndex[i] < command.al.getHeight())
604 List<SequenceI> sequences;
605 synchronized (sequences = command.al.getSequences())
607 if (!(command.alIndex[i] < 0))
609 sequences.add(command.alIndex[i], sequence);
615 command.al.addSequence(sequence);
617 seqWasDeleted = true;
619 int newStart = sequence.getStart();
620 int newEnd = sequence.getEnd();
622 StringBuilder tmp = new StringBuilder();
623 tmp.append(sequence.getSequence());
624 // Undo of a delete does not replace original dataset sequence on to
625 // alignment sequence.
630 if (command.string != null && command.string[i] != null)
632 if (command.position >= tmp.length())
634 // This occurs if padding is on, and residues
635 // are removed from end of alignment
636 int len = command.position - tmp.length();
639 tmp.append(command.gapChar);
643 tmp.insert(command.position, command.string[i]);
644 for (int s = 0; s < command.string[i].length; s++)
646 if (!Comparison.isGap(command.string[i][s]))
652 start = sequence.findPosition(command.position);
654 // .findPosition(command.position + command.number);
656 if (sequence.getStart() == start)
666 command.string[i] = null;
669 sequence.setSequence(tmp.toString());
670 sequence.setStart(newStart);
671 sequence.setEnd(newEnd);
674 * command and Undo share the same dataset sequence if cut was
675 * at start or end of sequence
677 boolean sameDatasetSequence = false;
680 if (sequence.getDatasetSequence() != null)
685 ds = command.oldds[i];
689 // make a new DS sequence
690 // use new ds mechanism here
691 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
692 sequence.getSequenceAsString());
693 ds = new Sequence(sequence.getName(), ungapped,
694 sequence.getStart(), sequence.getEnd());
695 ds.setDescription(sequence.getDescription());
697 if (command.oldds == null)
699 command.oldds = new SequenceI[command.seqs.length];
701 command.oldds[i] = sequence.getDatasetSequence();
702 sameDatasetSequence = ds == sequence.getDatasetSequence();
703 ds.setSequenceFeatures(sequence.getSequenceFeatures());
704 sequence.setDatasetSequence(ds);
706 undoCutFeatures(command, command.seqs[i], start, length,
707 sameDatasetSequence);
710 adjustAnnotations(command, true, seqWasDeleted, views);
712 command.string = null;
715 static void replace(Edit command)
719 int start = command.position;
720 int end = command.number;
721 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
723 // TODO Jalview 2.4 bugfix change to an aggregate command - original
724 // sequence string is cut, new string is pasted in.
725 command.number = start + command.string[0].length;
726 for (int i = 0; i < command.seqs.length; i++)
728 boolean newDSWasNeeded = command.oldds != null
729 && command.oldds[i] != null;
732 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
733 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
734 * viewport.alignment));
738 * then addHistoryItem(new EditCommand( "Add sequences",
739 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
742 oldstring = command.seqs[i].getSequenceAsString();
743 tmp = new StringBuffer(oldstring.substring(0, start));
744 tmp.append(command.string[i]);
745 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
746 jalview.util.Comparison.GapChars,
747 new String(command.string[i]));
748 int ipos = command.seqs[i].findPosition(start)
749 - command.seqs[i].getStart();
750 tmp.append(oldstring.substring(end));
751 command.seqs[i].setSequence(tmp.toString());
752 command.string[i] = oldstring.substring(start, end).toCharArray();
753 String nogapold = AlignSeq.extractGaps(Comparison.GapChars,
754 new String(command.string[i]));
755 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
759 SequenceI oldds = command.seqs[i].getDatasetSequence();
760 command.seqs[i].setDatasetSequence(command.oldds[i]);
761 command.oldds[i] = oldds;
765 if (command.oldds == null)
767 command.oldds = new SequenceI[command.seqs.length];
769 command.oldds[i] = command.seqs[i].getDatasetSequence();
770 SequenceI newds = new Sequence(
771 command.seqs[i].getDatasetSequence());
772 String fullseq, osp = newds.getSequenceAsString();
773 fullseq = osp.substring(0, ipos) + nogaprep
774 + osp.substring(ipos + nogaprep.length());
775 newds.setSequence(fullseq.toUpperCase());
776 // TODO: JAL-1131 ensure newly created dataset sequence is added to
778 // dataset sequences associated with the alignment.
779 // TODO: JAL-1131 fix up any annotation associated with new dataset
780 // sequence to ensure that original sequence/annotation relationships
782 command.seqs[i].setDatasetSequence(newds);
791 final static void adjustAnnotations(Edit command, boolean insert,
792 boolean modifyVisibility, AlignmentI[] views)
794 AlignmentAnnotation[] annotations = null;
796 if (modifyVisibility && !insert)
798 // only occurs if a sequence was added or deleted.
799 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
801 if (command.fullAlignmentHeight)
803 annotations = command.al.getAlignmentAnnotation();
808 AlignmentAnnotation[] tmp;
809 for (int s = 0; s < command.seqs.length; s++)
811 command.seqs[s].sequenceChanged();
813 if (modifyVisibility)
815 // Rows are only removed or added to sequence object.
819 tmp = command.seqs[s].getAnnotation();
822 int alen = tmp.length;
823 for (int aa = 0; aa < tmp.length; aa++)
825 if (!command.al.deleteAnnotation(tmp[aa]))
827 // strip out annotation not in the current al (will be put
828 // back on insert in all views)
833 command.seqs[s].setAlignmentAnnotation(null);
834 if (alen != tmp.length)
836 // save the non-null annotation references only
837 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
838 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
842 saved[aapos++] = tmp[aa];
847 command.deletedAnnotationRows.put(command.seqs[s], saved);
848 // and then remove any annotation in the other views
849 for (int alview = 0; views != null
850 && alview < views.length; alview++)
852 if (views[alview] != command.al)
854 AlignmentAnnotation[] toremove = views[alview]
855 .getAlignmentAnnotation();
856 if (toremove == null || toremove.length == 0)
860 // remove any alignment annotation on this sequence that's
861 // on that alignment view.
862 for (int aa = 0; aa < toremove.length; aa++)
864 if (toremove[aa].sequenceRef == command.seqs[s])
866 views[alview].deleteAnnotation(toremove[aa]);
874 // save all the annotation
875 command.deletedAnnotationRows.put(command.seqs[s], tmp);
882 if (command.deletedAnnotationRows != null
883 && command.deletedAnnotationRows
884 .containsKey(command.seqs[s]))
886 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
887 .get(command.seqs[s]);
888 command.seqs[s].setAlignmentAnnotation(revealed);
889 if (revealed != null)
891 for (int aa = 0; aa < revealed.length; aa++)
893 // iterate through al adding original annotation
894 command.al.addAnnotation(revealed[aa]);
896 for (int aa = 0; aa < revealed.length; aa++)
898 command.al.setAnnotationIndex(revealed[aa], aa);
900 // and then duplicate added annotation on every other alignment
902 for (int vnum = 0; views != null && vnum < views.length; vnum++)
904 if (views[vnum] != command.al)
906 int avwidth = views[vnum].getWidth() + 1;
907 // duplicate in this view
908 for (int a = 0; a < revealed.length; a++)
910 AlignmentAnnotation newann = new AlignmentAnnotation(
912 command.seqs[s].addAlignmentAnnotation(newann);
913 newann.padAnnotation(avwidth);
914 views[vnum].addAnnotation(newann);
915 views[vnum].setAnnotationIndex(newann, a);
925 if (command.seqs[s].getAnnotation() == null)
932 annotations = command.seqs[s].getAnnotation();
936 tmp = new AlignmentAnnotation[aSize
937 + command.seqs[s].getAnnotation().length];
939 System.arraycopy(annotations, 0, tmp, 0, aSize);
941 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
942 command.seqs[s].getAnnotation().length);
946 aSize = annotations.length;
950 if (annotations == null)
957 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
962 for (int a = 0; a < annotations.length; a++)
964 if (annotations[a].autoCalculated
965 || annotations[a].annotations == null)
972 aSize = annotations[a].annotations.length;
975 temp = new Annotation[aSize + command.number];
976 if (annotations[a].padGaps)
978 for (int aa = 0; aa < temp.length; aa++)
980 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
986 if (command.position < aSize)
988 if (command.position + command.number >= aSize)
994 tSize = aSize - command.number;
1006 temp = new Annotation[tSize];
1011 if (command.position < annotations[a].annotations.length)
1013 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1016 if (command.deletedAnnotations != null
1017 && command.deletedAnnotations
1018 .containsKey(annotations[a].annotationId))
1020 Annotation[] restore = command.deletedAnnotations
1021 .get(annotations[a].annotationId);
1023 System.arraycopy(restore, 0, temp, command.position,
1028 System.arraycopy(annotations[a].annotations, command.position,
1029 temp, command.position + command.number,
1030 aSize - command.position);
1034 if (command.deletedAnnotations != null
1035 && command.deletedAnnotations
1036 .containsKey(annotations[a].annotationId))
1038 Annotation[] restore = command.deletedAnnotations
1039 .get(annotations[a].annotationId);
1041 temp = new Annotation[annotations[a].annotations.length
1043 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1044 annotations[a].annotations.length);
1045 System.arraycopy(restore, 0, temp,
1046 annotations[a].annotations.length, restore.length);
1050 temp = annotations[a].annotations;
1056 if (tSize != aSize || command.position < 2)
1058 int copylen = Math.min(command.position,
1059 annotations[a].annotations.length);
1062 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1063 copylen); // command.position);
1066 Annotation[] deleted = new Annotation[command.number];
1067 if (copylen >= command.position)
1069 copylen = Math.min(command.number,
1070 annotations[a].annotations.length - command.position);
1073 System.arraycopy(annotations[a].annotations, command.position,
1074 deleted, 0, copylen); // command.number);
1078 command.deletedAnnotations.put(annotations[a].annotationId,
1080 if (annotations[a].annotations.length > command.position
1083 System.arraycopy(annotations[a].annotations,
1084 command.position + command.number, temp,
1085 command.position, annotations[a].annotations.length
1086 - command.position - command.number); // aSize
1091 int dSize = aSize - command.position;
1095 Annotation[] deleted = new Annotation[command.number];
1096 System.arraycopy(annotations[a].annotations, command.position,
1099 command.deletedAnnotations.put(annotations[a].annotationId,
1102 tSize = Math.min(annotations[a].annotations.length,
1104 temp = new Annotation[tSize];
1105 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1109 temp = annotations[a].annotations;
1114 annotations[a].annotations = temp;
1119 * Restores features to the state before a Cut.
1121 * <li>re-add any features deleted by the cut</li>
1122 * <li>remove any truncated features created by the cut</li>
1123 * <li>shift right any features to the right of the cut</li>
1129 * the sequence the Cut applied to
1131 * the start residue position of the cut
1133 * the number of residues cut
1134 * @param sameDatasetSequence
1135 * true if dataset sequence and frame of reference were left
1136 * unchanged by the Cut
1138 final static void undoCutFeatures(Edit command, SequenceI seq,
1139 final int start, final int length, boolean sameDatasetSequence)
1141 SequenceI sequence = seq.getDatasetSequence();
1142 if (sequence == null)
1148 * shift right features that lie to the right of the restored cut (but not
1149 * if dataset sequence unchanged - so coordinates were changed by Cut)
1151 if (!sameDatasetSequence)
1154 * shift right all features right of and not
1155 * contiguous with the cut position
1157 seq.getFeatures().shiftFeatures(start + 1, length);
1160 * shift right any features that start at the cut position,
1161 * unless they were truncated
1163 List<SequenceFeature> sfs = seq.getFeatures().findFeatures(start,
1165 for (SequenceFeature sf : sfs)
1167 if (sf.getBegin() == start)
1169 if (!command.truncatedFeatures.containsKey(seq)
1170 || !command.truncatedFeatures.get(seq).contains(sf))
1173 * feature was shifted left to cut position (not truncated),
1174 * so shift it back right
1176 SequenceFeature shifted = new SequenceFeature(sf, sf.getBegin()
1177 + length, sf.getEnd() + length, sf.getFeatureGroup(),
1179 seq.addSequenceFeature(shifted);
1180 seq.deleteFeature(sf);
1187 * restore any features that were deleted or truncated
1189 if (command.deletedFeatures != null
1190 && command.deletedFeatures.containsKey(seq))
1192 for (SequenceFeature deleted : command.deletedFeatures.get(seq))
1194 sequence.addSequenceFeature(deleted);
1199 * delete any truncated features
1201 if (command.truncatedFeatures != null
1202 && command.truncatedFeatures.containsKey(seq))
1204 for (SequenceFeature amended : command.truncatedFeatures.get(seq))
1206 sequence.deleteFeature(amended);
1212 * Returns the list of edit commands wrapped by this object.
1216 public List<Edit> getEdits()
1222 * Returns a map whose keys are the dataset sequences, and values their
1223 * aligned sequences before the command edit list was applied. The aligned
1224 * sequences are copies, which may be updated without affecting the originals.
1226 * The command holds references to the aligned sequences (after editing). If
1227 * the command is an 'undo',then the prior state is simply the aligned state.
1228 * Otherwise, we have to derive the prior state by working backwards through
1229 * the edit list to infer the aligned sequences before editing.
1231 * Note: an alternative solution would be to cache the 'before' state of each
1232 * edit, but this would be expensive in space in the common case that the
1233 * original is never needed (edits are not mirrored).
1236 * @throws IllegalStateException
1237 * on detecting an edit command of a type that can't be unwound
1239 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1241 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1242 if (getEdits() == null)
1248 for (Edit e : getEdits())
1250 for (SequenceI seq : e.getSequences())
1252 SequenceI ds = seq.getDatasetSequence();
1253 // SequenceI preEdit = result.get(ds);
1254 if (!result.containsKey(ds))
1257 * copy sequence including start/end (but don't use copy constructor
1258 * as we don't need annotations)
1260 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1261 seq.getStart(), seq.getEnd());
1262 preEdit.setDatasetSequence(ds);
1263 result.put(ds, preEdit);
1271 * Work backwards through the edit list, deriving the sequences before each
1272 * was applied. The final result is the sequence set before any edits.
1274 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1275 while (editList.hasNext())
1277 Edit oldEdit = editList.next();
1278 Action action = oldEdit.getAction();
1279 int position = oldEdit.getPosition();
1280 int number = oldEdit.getNumber();
1281 final char gap = oldEdit.getGapCharacter();
1282 for (SequenceI seq : oldEdit.getSequences())
1284 SequenceI ds = seq.getDatasetSequence();
1285 SequenceI preEdit = result.get(ds);
1286 if (preEdit == null)
1288 preEdit = new Sequence("", seq.getSequenceAsString(),
1289 seq.getStart(), seq.getEnd());
1290 preEdit.setDatasetSequence(ds);
1291 result.put(ds, preEdit);
1294 * 'Undo' this edit action on the sequence (updating the value in the
1299 if (action == Action.DELETE_GAP)
1301 preEdit.setSequence(new String(StringUtils.insertCharAt(
1302 preEdit.getSequence(), position, number, gap)));
1304 else if (action == Action.INSERT_GAP)
1306 preEdit.setSequence(new String(StringUtils.deleteChars(
1307 preEdit.getSequence(), position, position + number)));
1311 System.err.println("Can't undo edit action " + action);
1312 // throw new IllegalStateException("Can't undo edit action " +
1323 public SequenceI[] oldds;
1325 boolean fullAlignmentHeight = false;
1327 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1329 Map<String, Annotation[]> deletedAnnotations;
1332 * features deleted by the cut (re-add on Undo)
1333 * (including the original of any shortened features)
1335 Map<SequenceI, List<SequenceFeature>> deletedFeatures;
1338 * shortened features added by the cut (delete on Undo)
1340 Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
1352 int position, number;
1356 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1361 this.position = pos;
1362 this.number = count;
1366 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1369 this(cmd, sqs, pos, count, align.getGapCharacter());
1373 alIndex = new int[sqs.length];
1374 for (int i = 0; i < sqs.length; i++)
1376 alIndex[i] = align.findIndex(sqs[i]);
1379 fullAlignmentHeight = (align.getHeight() == sqs.length);
1382 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1383 AlignmentI align, String replace)
1385 this(cmd, sqs, pos, count, align);
1387 string = new char[sqs.length][];
1388 for (int i = 0; i < sqs.length; i++)
1390 string[i] = replace.toCharArray();
1394 public SequenceI[] getSequences()
1399 public int getPosition()
1404 public Action getAction()
1409 public int getNumber()
1414 public char getGapCharacter()
1421 * Returns an iterator over the list of edit commands which traverses the list
1422 * either forwards or backwards.
1427 public Iterator<Edit> getEditIterator(boolean forwards)
1431 return getEdits().iterator();
1435 return new ReverseListIterator<Edit>(getEdits());
1440 * Adjusts features for Cut, and saves details of changes made to allow Undo
1442 * <li>features left of the cut are unchanged</li>
1443 * <li>features right of the cut are shifted left</li>
1444 * <li>features internal to the cut region are deleted</li>
1445 * <li>features that overlap or span the cut are shortened</li>
1446 * <li>the originals of any deleted or shorted features are saved, to re-add
1448 * <li>any added (shortened) features are saved, to delete on Undo</li>
1453 * @param fromPosition
1455 * @param cutIsInternal
1457 protected static void cutFeatures(Edit command, SequenceI seq,
1458 int fromPosition, int toPosition, boolean cutIsInternal)
1464 List<SequenceFeature> added = new ArrayList<>();
1465 List<SequenceFeature> removed = new ArrayList<>();
1467 SequenceFeaturesI featureStore = seq.getFeatures();
1468 if (toPosition < fromPosition || featureStore == null)
1473 int cutStartPos = fromPosition;
1474 int cutEndPos = toPosition;
1475 int cutWidth = cutEndPos - cutStartPos + 1;
1477 synchronized (featureStore)
1480 * get features that overlap the cut region
1482 List<SequenceFeature> toAmend = featureStore.findFeatures(
1483 cutStartPos, cutEndPos);
1486 * add any contact features that span the cut region
1487 * (not returned by findFeatures)
1489 for (SequenceFeature contact : featureStore.getContactFeatures())
1491 if (contact.getBegin() < cutStartPos
1492 && contact.getEnd() > cutEndPos)
1494 toAmend.add(contact);
1499 * adjust start-end of overlapping features;
1500 * delete features enclosed by the cut;
1501 * delete partially overlapping contact features
1503 for (SequenceFeature sf : toAmend)
1505 int sfBegin = sf.getBegin();
1506 int sfEnd = sf.getEnd();
1507 int newBegin = sfBegin;
1509 boolean toDelete = false;
1510 boolean follows = false;
1512 if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
1515 * feature lies within cut region - delete it
1519 else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
1522 * feature spans cut region - left-shift the end
1526 else if (sfEnd <= cutEndPos)
1529 * feature overlaps left of cut region - truncate right
1531 newEnd = cutStartPos - 1;
1532 if (sf.isContactFeature())
1537 else if (sfBegin >= cutStartPos)
1540 * remaining case - feature overlaps right
1541 * truncate left, adjust end of feature
1543 newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
1544 newEnd = newBegin + sfEnd - cutEndPos - 1;
1545 if (sf.isContactFeature())
1551 seq.deleteFeature(sf);
1558 SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
1559 sf.getFeatureGroup(), sf.getScore());
1560 seq.addSequenceFeature(copy);
1569 * and left shift any features lying to the right of the cut region
1570 * (but not if the cut is at start or end of sequence)
1574 featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
1579 * save deleted and amended features, so that Undo can
1580 * re-add or delete them respectively
1582 if (command.deletedFeatures == null)
1584 command.deletedFeatures = new HashMap<>();
1586 if (command.truncatedFeatures == null)
1588 command.truncatedFeatures = new HashMap<>();
1590 command.deletedFeatures.put(seq, removed);
1591 command.truncatedFeatures.put(seq, added);