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;
559 if (oldds != sequence.getDatasetSequence())
561 oldds.getFeatures().deleteAll();
564 if (cutPositions != null)
566 cutFeatures(command, sequence, cutPositions.getBegin(),
567 cutPositions.getEnd(), cutIsInternal);
573 if (sequence.getLength() < 1)
575 command.al.deleteSequence(sequence);
580 adjustAnnotations(command, false, seqDeleted, views);
584 * Perform the given Paste command. This may be to add cut or copied sequences
585 * to an alignment, or to undo a 'Cut' action on a region of the alignment.
590 static void paste(Edit command, AlignmentI[] views)
592 boolean seqWasDeleted = false;
594 for (int i = 0; i < command.seqs.length; i++)
596 boolean newDSNeeded = false;
597 boolean newDSWasNeeded = command.oldds != null
598 && command.oldds[i] != null;
599 SequenceI sequence = command.seqs[i];
600 if (sequence.getLength() < 1)
603 * sequence was deleted; re-add it to the alignment
605 if (command.alIndex[i] < command.al.getHeight())
607 List<SequenceI> sequences;
608 synchronized (sequences = command.al.getSequences())
610 if (!(command.alIndex[i] < 0))
612 sequences.add(command.alIndex[i], sequence);
618 command.al.addSequence(sequence);
620 seqWasDeleted = true;
622 int newStart = sequence.getStart();
623 int newEnd = sequence.getEnd();
625 StringBuilder tmp = new StringBuilder();
626 tmp.append(sequence.getSequence());
627 // Undo of a delete does not replace original dataset sequence on to
628 // alignment sequence.
633 if (command.string != null && command.string[i] != null)
635 if (command.position >= tmp.length())
637 // This occurs if padding is on, and residues
638 // are removed from end of alignment
639 int len = command.position - tmp.length();
642 tmp.append(command.gapChar);
646 tmp.insert(command.position, command.string[i]);
647 for (int s = 0; s < command.string[i].length; s++)
649 if (!Comparison.isGap(command.string[i][s]))
655 start = sequence.findPosition(command.position);
657 // .findPosition(command.position + command.number);
659 if (sequence.getStart() == start)
669 command.string[i] = null;
672 sequence.setSequence(tmp.toString());
673 sequence.setStart(newStart);
674 sequence.setEnd(newEnd);
677 * command and Undo share the same dataset sequence if cut was
678 * at start or end of sequence
680 boolean sameDatasetSequence = false;
683 if (sequence.getDatasetSequence() != null)
688 ds = command.oldds[i];
692 // make a new DS sequence
693 // use new ds mechanism here
694 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
695 sequence.getSequenceAsString());
696 ds = new Sequence(sequence.getName(), ungapped,
697 sequence.getStart(), sequence.getEnd());
698 ds.setDescription(sequence.getDescription());
700 if (command.oldds == null)
702 command.oldds = new SequenceI[command.seqs.length];
704 command.oldds[i] = sequence.getDatasetSequence();
705 sameDatasetSequence = ds == sequence.getDatasetSequence();
706 ds.setSequenceFeatures(sequence.getSequenceFeatures());
707 sequence.setDatasetSequence(ds);
709 undoCutFeatures(command, command.seqs[i], start, length,
710 sameDatasetSequence);
713 adjustAnnotations(command, true, seqWasDeleted, views);
715 command.string = null;
718 static void replace(Edit command)
722 int start = command.position;
723 int end = command.number;
724 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
726 // TODO Jalview 2.4 bugfix change to an aggregate command - original
727 // sequence string is cut, new string is pasted in.
728 command.number = start + command.string[0].length;
729 for (int i = 0; i < command.seqs.length; i++)
731 boolean newDSWasNeeded = command.oldds != null
732 && command.oldds[i] != null;
735 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
736 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
737 * viewport.alignment));
741 * then addHistoryItem(new EditCommand( "Add sequences",
742 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
745 oldstring = command.seqs[i].getSequenceAsString();
746 tmp = new StringBuffer(oldstring.substring(0, start));
747 tmp.append(command.string[i]);
748 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
749 jalview.util.Comparison.GapChars,
750 new String(command.string[i]));
751 int ipos = command.seqs[i].findPosition(start)
752 - command.seqs[i].getStart();
753 tmp.append(oldstring.substring(end));
754 command.seqs[i].setSequence(tmp.toString());
755 command.string[i] = oldstring.substring(start, end).toCharArray();
756 String nogapold = AlignSeq.extractGaps(Comparison.GapChars,
757 new String(command.string[i]));
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
853 && alview < views.length; alview++)
855 if (views[alview] != command.al)
857 AlignmentAnnotation[] toremove = views[alview]
858 .getAlignmentAnnotation();
859 if (toremove == null || toremove.length == 0)
863 // remove any alignment annotation on this sequence that's
864 // on that alignment view.
865 for (int aa = 0; aa < toremove.length; aa++)
867 if (toremove[aa].sequenceRef == command.seqs[s])
869 views[alview].deleteAnnotation(toremove[aa]);
877 // save all the annotation
878 command.deletedAnnotationRows.put(command.seqs[s], tmp);
885 if (command.deletedAnnotationRows != null
886 && command.deletedAnnotationRows
887 .containsKey(command.seqs[s]))
889 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
890 .get(command.seqs[s]);
891 command.seqs[s].setAlignmentAnnotation(revealed);
892 if (revealed != null)
894 for (int aa = 0; aa < revealed.length; aa++)
896 // iterate through al adding original annotation
897 command.al.addAnnotation(revealed[aa]);
899 for (int aa = 0; aa < revealed.length; aa++)
901 command.al.setAnnotationIndex(revealed[aa], aa);
903 // and then duplicate added annotation on every other alignment
905 for (int vnum = 0; views != null && vnum < views.length; vnum++)
907 if (views[vnum] != command.al)
909 int avwidth = views[vnum].getWidth() + 1;
910 // duplicate in this view
911 for (int a = 0; a < revealed.length; a++)
913 AlignmentAnnotation newann = new AlignmentAnnotation(
915 command.seqs[s].addAlignmentAnnotation(newann);
916 newann.padAnnotation(avwidth);
917 views[vnum].addAnnotation(newann);
918 views[vnum].setAnnotationIndex(newann, a);
928 if (command.seqs[s].getAnnotation() == null)
935 annotations = command.seqs[s].getAnnotation();
939 tmp = new AlignmentAnnotation[aSize
940 + command.seqs[s].getAnnotation().length];
942 System.arraycopy(annotations, 0, tmp, 0, aSize);
944 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
945 command.seqs[s].getAnnotation().length);
949 aSize = annotations.length;
953 if (annotations == null)
960 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
965 for (int a = 0; a < annotations.length; a++)
967 if (annotations[a].autoCalculated
968 || annotations[a].annotations == null)
975 aSize = annotations[a].annotations.length;
978 temp = new Annotation[aSize + command.number];
979 if (annotations[a].padGaps)
981 for (int aa = 0; aa < temp.length; aa++)
983 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
989 if (command.position < aSize)
991 if (command.position + command.number >= aSize)
997 tSize = aSize - command.number;
1009 temp = new Annotation[tSize];
1014 if (command.position < annotations[a].annotations.length)
1016 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1019 if (command.deletedAnnotations != null
1020 && command.deletedAnnotations
1021 .containsKey(annotations[a].annotationId))
1023 Annotation[] restore = command.deletedAnnotations
1024 .get(annotations[a].annotationId);
1026 System.arraycopy(restore, 0, temp, command.position,
1031 System.arraycopy(annotations[a].annotations, command.position,
1032 temp, command.position + command.number,
1033 aSize - command.position);
1037 if (command.deletedAnnotations != null
1038 && command.deletedAnnotations
1039 .containsKey(annotations[a].annotationId))
1041 Annotation[] restore = command.deletedAnnotations
1042 .get(annotations[a].annotationId);
1044 temp = new Annotation[annotations[a].annotations.length
1046 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1047 annotations[a].annotations.length);
1048 System.arraycopy(restore, 0, temp,
1049 annotations[a].annotations.length, restore.length);
1053 temp = annotations[a].annotations;
1059 if (tSize != aSize || command.position < 2)
1061 int copylen = Math.min(command.position,
1062 annotations[a].annotations.length);
1065 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1066 copylen); // command.position);
1069 Annotation[] deleted = new Annotation[command.number];
1070 if (copylen >= command.position)
1072 copylen = Math.min(command.number,
1073 annotations[a].annotations.length - command.position);
1076 System.arraycopy(annotations[a].annotations, command.position,
1077 deleted, 0, copylen); // command.number);
1081 command.deletedAnnotations.put(annotations[a].annotationId,
1083 if (annotations[a].annotations.length > command.position
1086 System.arraycopy(annotations[a].annotations,
1087 command.position + command.number, temp,
1088 command.position, annotations[a].annotations.length
1089 - command.position - command.number); // aSize
1094 int dSize = aSize - command.position;
1098 Annotation[] deleted = new Annotation[command.number];
1099 System.arraycopy(annotations[a].annotations, command.position,
1102 command.deletedAnnotations.put(annotations[a].annotationId,
1105 tSize = Math.min(annotations[a].annotations.length,
1107 temp = new Annotation[tSize];
1108 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1112 temp = annotations[a].annotations;
1117 annotations[a].annotations = temp;
1122 * Restores features to the state before a Cut.
1124 * <li>re-add any features deleted by the cut</li>
1125 * <li>remove any truncated features created by the cut</li>
1126 * <li>shift right any features to the right of the cut</li>
1132 * the sequence the Cut applied to
1134 * the start residue position of the cut
1136 * the number of residues cut
1137 * @param sameDatasetSequence
1138 * true if dataset sequence and frame of reference were left
1139 * unchanged by the Cut
1141 final static void undoCutFeatures(Edit command, SequenceI seq,
1142 final int start, final int length, boolean sameDatasetSequence)
1144 SequenceI sequence = seq.getDatasetSequence();
1145 if (sequence == null)
1151 * shift right features that lie to the right of the restored cut (but not
1152 * if dataset sequence unchanged - so coordinates were changed by Cut)
1154 if (!sameDatasetSequence)
1157 * shift right all features right of and not
1158 * contiguous with the cut position
1160 seq.getFeatures().shiftFeatures(start + 1, length);
1163 * shift right any features that start at the cut position,
1164 * unless they were truncated
1166 List<SequenceFeature> sfs = seq.getFeatures().findFeatures(start,
1168 for (SequenceFeature sf : sfs)
1170 if (sf.getBegin() == start)
1172 if (!command.truncatedFeatures.containsKey(seq)
1173 || !command.truncatedFeatures.get(seq).contains(sf))
1176 * feature was shifted left to cut position (not truncated),
1177 * so shift it back right
1179 SequenceFeature shifted = new SequenceFeature(sf, sf.getBegin()
1180 + length, sf.getEnd() + length, sf.getFeatureGroup(),
1182 seq.addSequenceFeature(shifted);
1183 seq.deleteFeature(sf);
1190 * restore any features that were deleted or truncated
1192 if (command.deletedFeatures != null
1193 && command.deletedFeatures.containsKey(seq))
1195 for (SequenceFeature deleted : command.deletedFeatures.get(seq))
1197 sequence.addSequenceFeature(deleted);
1202 * delete any truncated features
1204 if (command.truncatedFeatures != null
1205 && command.truncatedFeatures.containsKey(seq))
1207 for (SequenceFeature amended : command.truncatedFeatures.get(seq))
1209 sequence.deleteFeature(amended);
1215 * Returns the list of edit commands wrapped by this object.
1219 public List<Edit> getEdits()
1225 * Returns a map whose keys are the dataset sequences, and values their
1226 * aligned sequences before the command edit list was applied. The aligned
1227 * sequences are copies, which may be updated without affecting the originals.
1229 * The command holds references to the aligned sequences (after editing). If
1230 * the command is an 'undo',then the prior state is simply the aligned state.
1231 * Otherwise, we have to derive the prior state by working backwards through
1232 * the edit list to infer the aligned sequences before editing.
1234 * Note: an alternative solution would be to cache the 'before' state of each
1235 * edit, but this would be expensive in space in the common case that the
1236 * original is never needed (edits are not mirrored).
1239 * @throws IllegalStateException
1240 * on detecting an edit command of a type that can't be unwound
1242 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1244 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1245 if (getEdits() == null)
1251 for (Edit e : getEdits())
1253 for (SequenceI seq : e.getSequences())
1255 SequenceI ds = seq.getDatasetSequence();
1256 // SequenceI preEdit = result.get(ds);
1257 if (!result.containsKey(ds))
1260 * copy sequence including start/end (but don't use copy constructor
1261 * as we don't need annotations)
1263 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1264 seq.getStart(), seq.getEnd());
1265 preEdit.setDatasetSequence(ds);
1266 result.put(ds, preEdit);
1274 * Work backwards through the edit list, deriving the sequences before each
1275 * was applied. The final result is the sequence set before any edits.
1277 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1278 while (editList.hasNext())
1280 Edit oldEdit = editList.next();
1281 Action action = oldEdit.getAction();
1282 int position = oldEdit.getPosition();
1283 int number = oldEdit.getNumber();
1284 final char gap = oldEdit.getGapCharacter();
1285 for (SequenceI seq : oldEdit.getSequences())
1287 SequenceI ds = seq.getDatasetSequence();
1288 SequenceI preEdit = result.get(ds);
1289 if (preEdit == null)
1291 preEdit = new Sequence("", seq.getSequenceAsString(),
1292 seq.getStart(), seq.getEnd());
1293 preEdit.setDatasetSequence(ds);
1294 result.put(ds, preEdit);
1297 * 'Undo' this edit action on the sequence (updating the value in the
1302 if (action == Action.DELETE_GAP)
1304 preEdit.setSequence(new String(StringUtils.insertCharAt(
1305 preEdit.getSequence(), position, number, gap)));
1307 else if (action == Action.INSERT_GAP)
1309 preEdit.setSequence(new String(StringUtils.deleteChars(
1310 preEdit.getSequence(), position, position + number)));
1314 System.err.println("Can't undo edit action " + action);
1315 // throw new IllegalStateException("Can't undo edit action " +
1326 public SequenceI[] oldds;
1328 boolean fullAlignmentHeight = false;
1330 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1332 Map<String, Annotation[]> deletedAnnotations;
1335 * features deleted by the cut (re-add on Undo)
1336 * (including the original of any shortened features)
1338 Map<SequenceI, List<SequenceFeature>> deletedFeatures;
1341 * shortened features added by the cut (delete on Undo)
1343 Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
1355 int position, number;
1359 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1364 this.position = pos;
1365 this.number = count;
1369 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1372 this(cmd, sqs, pos, count, align.getGapCharacter());
1376 alIndex = new int[sqs.length];
1377 for (int i = 0; i < sqs.length; i++)
1379 alIndex[i] = align.findIndex(sqs[i]);
1382 fullAlignmentHeight = (align.getHeight() == sqs.length);
1385 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1386 AlignmentI align, String replace)
1388 this(cmd, sqs, pos, count, align);
1390 string = new char[sqs.length][];
1391 for (int i = 0; i < sqs.length; i++)
1393 string[i] = replace.toCharArray();
1397 public SequenceI[] getSequences()
1402 public int getPosition()
1407 public Action getAction()
1412 public int getNumber()
1417 public char getGapCharacter()
1424 * Returns an iterator over the list of edit commands which traverses the list
1425 * either forwards or backwards.
1430 public Iterator<Edit> getEditIterator(boolean forwards)
1434 return getEdits().iterator();
1438 return new ReverseListIterator<Edit>(getEdits());
1443 * Adjusts features for Cut, and saves details of changes made to allow Undo
1445 * <li>features left of the cut are unchanged</li>
1446 * <li>features right of the cut are shifted left</li>
1447 * <li>features internal to the cut region are deleted</li>
1448 * <li>features that overlap or span the cut are shortened</li>
1449 * <li>the originals of any deleted or shorted features are saved, to re-add
1451 * <li>any added (shortened) features are saved, to delete on Undo</li>
1456 * @param fromPosition
1458 * @param cutIsInternal
1460 protected static void cutFeatures(Edit command, SequenceI seq,
1461 int fromPosition, int toPosition, boolean cutIsInternal)
1467 List<SequenceFeature> added = new ArrayList<>();
1468 List<SequenceFeature> removed = new ArrayList<>();
1470 SequenceFeaturesI featureStore = seq.getFeatures();
1471 if (toPosition < fromPosition || featureStore == null)
1476 int cutStartPos = fromPosition;
1477 int cutEndPos = toPosition;
1478 int cutWidth = cutEndPos - cutStartPos + 1;
1480 synchronized (featureStore)
1483 * get features that overlap the cut region
1485 List<SequenceFeature> toAmend = featureStore.findFeatures(
1486 cutStartPos, cutEndPos);
1489 * add any contact features that span the cut region
1490 * (not returned by findFeatures)
1492 for (SequenceFeature contact : featureStore.getContactFeatures())
1494 if (contact.getBegin() < cutStartPos
1495 && contact.getEnd() > cutEndPos)
1497 toAmend.add(contact);
1502 * adjust start-end of overlapping features;
1503 * delete features enclosed by the cut;
1504 * delete partially overlapping contact features
1506 for (SequenceFeature sf : toAmend)
1508 int sfBegin = sf.getBegin();
1509 int sfEnd = sf.getEnd();
1510 int newBegin = sfBegin;
1512 boolean toDelete = false;
1513 boolean follows = false;
1515 if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
1518 * feature lies within cut region - delete it
1522 else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
1525 * feature spans cut region - left-shift the end
1529 else if (sfEnd <= cutEndPos)
1532 * feature overlaps left of cut region - truncate right
1534 newEnd = cutStartPos - 1;
1535 if (sf.isContactFeature())
1540 else if (sfBegin >= cutStartPos)
1543 * remaining case - feature overlaps right
1544 * truncate left, adjust end of feature
1546 newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
1547 newEnd = newBegin + sfEnd - cutEndPos - 1;
1548 if (sf.isContactFeature())
1554 seq.deleteFeature(sf);
1561 SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
1562 sf.getFeatureGroup(), sf.getScore());
1563 seq.addSequenceFeature(copy);
1572 * and left shift any features lying to the right of the cut region
1573 * (but not if the cut is at start or end of sequence)
1577 featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
1582 * save deleted and amended features, so that Undo can
1583 * re-add or delete them respectively
1585 if (command.deletedFeatures == null)
1587 command.deletedFeatures = new HashMap<>();
1589 if (command.truncatedFeatures == null)
1591 command.truncatedFeatures = new HashMap<>();
1593 command.deletedFeatures.put(seq, removed);
1594 command.truncatedFeatures.put(seq, added);