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 // do we need to edit sequence features for new sequence ?
562 if (oldds != sequence.getDatasetSequence()
564 && sequence.getFeatures().hasFeatures()))
566 if (cutPositions != null)
568 cutFeatures(command, sequence, cutPositions.getBegin(),
569 cutPositions.getEnd(), cutIsInternal);
575 if (sequence.getLength() < 1)
577 command.al.deleteSequence(sequence);
582 adjustAnnotations(command, false, seqDeleted, views);
586 * Perform the given Paste command. This may be to add cut or copied sequences
587 * to an alignment, or to undo a 'Cut' action on a region of the alignment.
592 static void paste(Edit command, AlignmentI[] views)
594 boolean seqWasDeleted = false;
596 for (int i = 0; i < command.seqs.length; i++)
598 boolean newDSNeeded = false;
599 boolean newDSWasNeeded = command.oldds != null
600 && command.oldds[i] != null;
601 SequenceI sequence = command.seqs[i];
602 if (sequence.getLength() < 1)
605 * sequence was deleted; re-add it to the alignment
607 if (command.alIndex[i] < command.al.getHeight())
609 List<SequenceI> sequences;
610 synchronized (sequences = command.al.getSequences())
612 if (!(command.alIndex[i] < 0))
614 sequences.add(command.alIndex[i], sequence);
620 command.al.addSequence(sequence);
622 seqWasDeleted = true;
624 int newStart = sequence.getStart();
625 int newEnd = sequence.getEnd();
627 StringBuilder tmp = new StringBuilder();
628 tmp.append(sequence.getSequence());
629 // Undo of a delete does not replace original dataset sequence on to
630 // alignment sequence.
635 if (command.string != null && command.string[i] != null)
637 if (command.position >= tmp.length())
639 // This occurs if padding is on, and residues
640 // are removed from end of alignment
641 int len = command.position - tmp.length();
644 tmp.append(command.gapChar);
648 tmp.insert(command.position, command.string[i]);
649 for (int s = 0; s < command.string[i].length; s++)
651 if (!Comparison.isGap(command.string[i][s]))
657 start = sequence.findPosition(command.position);
659 // .findPosition(command.position + command.number);
661 if (sequence.getStart() == start)
671 command.string[i] = null;
674 sequence.setSequence(tmp.toString());
675 sequence.setStart(newStart);
676 sequence.setEnd(newEnd);
679 * command and Undo share the same dataset sequence if cut was
680 * at start or end of sequence
682 boolean sameDatasetSequence = false;
685 if (sequence.getDatasetSequence() != null)
690 ds = command.oldds[i];
694 // make a new DS sequence
695 // use new ds mechanism here
696 String ungapped = AlignSeq.extractGaps(Comparison.GapChars,
697 sequence.getSequenceAsString());
698 ds = new Sequence(sequence.getName(), ungapped,
699 sequence.getStart(), sequence.getEnd());
700 ds.setDescription(sequence.getDescription());
702 if (command.oldds == null)
704 command.oldds = new SequenceI[command.seqs.length];
706 command.oldds[i] = sequence.getDatasetSequence();
707 sameDatasetSequence = ds == sequence.getDatasetSequence();
708 ds.setSequenceFeatures(sequence.getSequenceFeatures());
709 sequence.setDatasetSequence(ds);
711 undoCutFeatures(command, command.seqs[i], start, length,
712 sameDatasetSequence);
715 adjustAnnotations(command, true, seqWasDeleted, views);
717 command.string = null;
720 static void replace(Edit command)
724 int start = command.position;
725 int end = command.number;
726 // TODO TUTORIAL - Fix for replacement with different length of sequence (or
728 // TODO Jalview 2.4 bugfix change to an aggregate command - original
729 // sequence string is cut, new string is pasted in.
730 command.number = start + command.string[0].length;
731 for (int i = 0; i < command.seqs.length; i++)
733 boolean newDSWasNeeded = command.oldds != null
734 && command.oldds[i] != null;
735 boolean newStartEndWasNeeded = command.oldStartEnd!=null && command.oldStartEnd[i]!=null;
738 * cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
739 * cut, sg.getStartRes(), sg.getEndRes()-sg.getStartRes()+1,
740 * viewport.alignment));
744 * then addHistoryItem(new EditCommand( "Add sequences",
745 * EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
749 Range beforeEditedPositions = command.seqs[i].findPositions(1, start);
750 Range afterEditedPositions = command.seqs[i]
751 .findPositions(start + end + 1, command.seqs[i].getLength());
753 oldstring = command.seqs[i].getSequenceAsString();
754 tmp = new StringBuffer(oldstring.substring(0, start));
755 tmp.append(command.string[i]);
756 String nogaprep = jalview.analysis.AlignSeq.extractGaps(
757 jalview.util.Comparison.GapChars,
758 new String(command.string[i]));
759 int ipos = command.seqs[i].findPosition(start)
760 - command.seqs[i].getStart();
761 if (end < oldstring.length())
763 tmp.append(oldstring.substring(end));
765 command.seqs[i].setSequence(tmp.toString());
766 command.string[i] = oldstring
767 .substring(start, Math.min(end, oldstring.length()))
769 String nogapold = AlignSeq.extractGaps(Comparison.GapChars,
770 new String(command.string[i]));
772 if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
774 // probably need a new dataset sequence
777 // then just switch the dataset sequence
778 SequenceI oldds = command.seqs[i].getDatasetSequence();
779 command.seqs[i].setDatasetSequence(command.oldds[i]);
780 command.oldds[i] = oldds;
783 if (newStartEndWasNeeded)
785 Range newStart = command.oldStartEnd[i];
786 command.oldStartEnd[i] = new Range(command.seqs[i].getStart(),
787 command.seqs[i].getEnd());
788 command.seqs[i].setStart(newStart.getBegin());
789 command.seqs[i].setEnd(newStart.getEnd());
793 // first edit the original dataset sequence string
794 SequenceI oldds = command.seqs[i].getDatasetSequence();
795 String fullseq, osp = oldds.getSequenceAsString();
797 fullseq = osp.substring(0, ipos) + nogaprep
798 + osp.substring(ipos + nogaprep.length());
800 // and check if new sequence data is different..
801 if (!fullseq.equalsIgnoreCase(osp))
803 // old ds and edited ds are different, so
804 // create the new dataset sequence
805 SequenceI newds = new Sequence(oldds);
806 newds.setSequence(fullseq.toUpperCase());
808 if (command.oldds == null)
810 command.oldds = new SequenceI[command.seqs.length];
812 command.oldds[i] = command.seqs[i].getDatasetSequence();
813 // TODO: JAL-1131 ensure newly created dataset sequence is added to
815 // dataset sequences associated with the alignment.
816 // TODO: JAL-1131 fix up any annotation associated with new dataset
817 // sequence to ensure that original sequence/annotation
820 command.seqs[i].setDatasetSequence(newds);
824 if (command.oldStartEnd == null)
826 command.oldStartEnd = new Range[command.seqs.length];
828 command.oldStartEnd[i] = new Range(command.seqs[i].getStart(),
829 command.seqs[i].getEnd());
830 if (beforeEditedPositions != null
831 && afterEditedPositions == null)
833 // modification at end
834 command.seqs[i].setEnd(
835 beforeEditedPositions.getEnd() + nogaprep.length());
837 else if (afterEditedPositions != null
838 && beforeEditedPositions == null)
840 // modification at start
841 command.seqs[i].setStart(
842 afterEditedPositions.getBegin() - nogaprep.length());
846 // edit covered both start and end. Here we can only guess the
849 String nogapalseq = jalview.analysis.AlignSeq.extractGaps(
850 jalview.util.Comparison.GapChars,
851 command.seqs[i].getSequenceAsString().toUpperCase());
852 int newStart = command.seqs[i].getDatasetSequence()
853 .getSequenceAsString().indexOf(nogapalseq);
857 "Implementation Error: could not locate start/end "
858 + "in dataset sequence after an edit of the sequence string");
860 int newEnd = newStart + nogapalseq.length() - 1;
861 command.seqs[i].setStart(newStart);
862 command.seqs[i].setEnd(newEnd);
872 final static void adjustAnnotations(Edit command, boolean insert,
873 boolean modifyVisibility, AlignmentI[] views)
875 AlignmentAnnotation[] annotations = null;
877 if (modifyVisibility && !insert)
879 // only occurs if a sequence was added or deleted.
880 command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
882 if (command.fullAlignmentHeight)
884 annotations = command.al.getAlignmentAnnotation();
889 AlignmentAnnotation[] tmp;
890 for (int s = 0; s < command.seqs.length; s++)
892 command.seqs[s].sequenceChanged();
894 if (modifyVisibility)
896 // Rows are only removed or added to sequence object.
900 tmp = command.seqs[s].getAnnotation();
903 int alen = tmp.length;
904 for (int aa = 0; aa < tmp.length; aa++)
906 if (!command.al.deleteAnnotation(tmp[aa]))
908 // strip out annotation not in the current al (will be put
909 // back on insert in all views)
914 command.seqs[s].setAlignmentAnnotation(null);
915 if (alen != tmp.length)
917 // save the non-null annotation references only
918 AlignmentAnnotation[] saved = new AlignmentAnnotation[alen];
919 for (int aa = 0, aapos = 0; aa < tmp.length; aa++)
923 saved[aapos++] = tmp[aa];
928 command.deletedAnnotationRows.put(command.seqs[s], saved);
929 // and then remove any annotation in the other views
930 for (int alview = 0; views != null
931 && alview < views.length; alview++)
933 if (views[alview] != command.al)
935 AlignmentAnnotation[] toremove = views[alview]
936 .getAlignmentAnnotation();
937 if (toremove == null || toremove.length == 0)
941 // remove any alignment annotation on this sequence that's
942 // on that alignment view.
943 for (int aa = 0; aa < toremove.length; aa++)
945 if (toremove[aa].sequenceRef == command.seqs[s])
947 views[alview].deleteAnnotation(toremove[aa]);
955 // save all the annotation
956 command.deletedAnnotationRows.put(command.seqs[s], tmp);
963 if (command.deletedAnnotationRows != null
964 && command.deletedAnnotationRows
965 .containsKey(command.seqs[s]))
967 AlignmentAnnotation[] revealed = command.deletedAnnotationRows
968 .get(command.seqs[s]);
969 command.seqs[s].setAlignmentAnnotation(revealed);
970 if (revealed != null)
972 for (int aa = 0; aa < revealed.length; aa++)
974 // iterate through al adding original annotation
975 command.al.addAnnotation(revealed[aa]);
977 for (int aa = 0; aa < revealed.length; aa++)
979 command.al.setAnnotationIndex(revealed[aa], aa);
981 // and then duplicate added annotation on every other alignment
983 for (int vnum = 0; views != null && vnum < views.length; vnum++)
985 if (views[vnum] != command.al)
987 int avwidth = views[vnum].getWidth() + 1;
988 // duplicate in this view
989 for (int a = 0; a < revealed.length; a++)
991 AlignmentAnnotation newann = new AlignmentAnnotation(
993 command.seqs[s].addAlignmentAnnotation(newann);
994 newann.padAnnotation(avwidth);
995 views[vnum].addAnnotation(newann);
996 views[vnum].setAnnotationIndex(newann, a);
1006 if (command.seqs[s].getAnnotation() == null)
1013 annotations = command.seqs[s].getAnnotation();
1017 tmp = new AlignmentAnnotation[aSize
1018 + command.seqs[s].getAnnotation().length];
1020 System.arraycopy(annotations, 0, tmp, 0, aSize);
1022 System.arraycopy(command.seqs[s].getAnnotation(), 0, tmp, aSize,
1023 command.seqs[s].getAnnotation().length);
1027 aSize = annotations.length;
1031 if (annotations == null)
1038 command.deletedAnnotations = new Hashtable<String, Annotation[]>();
1043 for (int a = 0; a < annotations.length; a++)
1045 if (annotations[a].autoCalculated
1046 || annotations[a].annotations == null)
1053 aSize = annotations[a].annotations.length;
1056 temp = new Annotation[aSize + command.number];
1057 if (annotations[a].padGaps)
1059 for (int aa = 0; aa < temp.length; aa++)
1061 temp[aa] = new Annotation(command.gapChar + "", null, ' ', 0);
1067 if (command.position < aSize)
1069 if (command.position + command.number >= aSize)
1075 tSize = aSize - command.number;
1087 temp = new Annotation[tSize];
1092 if (command.position < annotations[a].annotations.length)
1094 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1097 if (command.deletedAnnotations != null
1098 && command.deletedAnnotations
1099 .containsKey(annotations[a].annotationId))
1101 Annotation[] restore = command.deletedAnnotations
1102 .get(annotations[a].annotationId);
1104 System.arraycopy(restore, 0, temp, command.position,
1109 System.arraycopy(annotations[a].annotations, command.position,
1110 temp, command.position + command.number,
1111 aSize - command.position);
1115 if (command.deletedAnnotations != null
1116 && command.deletedAnnotations
1117 .containsKey(annotations[a].annotationId))
1119 Annotation[] restore = command.deletedAnnotations
1120 .get(annotations[a].annotationId);
1122 temp = new Annotation[annotations[a].annotations.length
1124 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1125 annotations[a].annotations.length);
1126 System.arraycopy(restore, 0, temp,
1127 annotations[a].annotations.length, restore.length);
1131 temp = annotations[a].annotations;
1137 if (tSize != aSize || command.position < 2)
1139 int copylen = Math.min(command.position,
1140 annotations[a].annotations.length);
1143 System.arraycopy(annotations[a].annotations, 0, temp, 0,
1144 copylen); // command.position);
1147 Annotation[] deleted = new Annotation[command.number];
1148 if (copylen >= command.position)
1150 copylen = Math.min(command.number,
1151 annotations[a].annotations.length - command.position);
1154 System.arraycopy(annotations[a].annotations, command.position,
1155 deleted, 0, copylen); // command.number);
1159 command.deletedAnnotations.put(annotations[a].annotationId,
1161 if (annotations[a].annotations.length > command.position
1164 System.arraycopy(annotations[a].annotations,
1165 command.position + command.number, temp,
1166 command.position, annotations[a].annotations.length
1167 - command.position - command.number); // aSize
1172 int dSize = aSize - command.position;
1176 Annotation[] deleted = new Annotation[command.number];
1177 System.arraycopy(annotations[a].annotations, command.position,
1180 command.deletedAnnotations.put(annotations[a].annotationId,
1183 tSize = Math.min(annotations[a].annotations.length,
1185 temp = new Annotation[tSize];
1186 System.arraycopy(annotations[a].annotations, 0, temp, 0, tSize);
1190 temp = annotations[a].annotations;
1195 annotations[a].annotations = temp;
1200 * Restores features to the state before a Cut.
1202 * <li>re-add any features deleted by the cut</li>
1203 * <li>remove any truncated features created by the cut</li>
1204 * <li>shift right any features to the right of the cut</li>
1210 * the sequence the Cut applied to
1212 * the start residue position of the cut
1214 * the number of residues cut
1215 * @param sameDatasetSequence
1216 * true if dataset sequence and frame of reference were left
1217 * unchanged by the Cut
1219 final static void undoCutFeatures(Edit command, SequenceI seq,
1220 final int start, final int length, boolean sameDatasetSequence)
1222 SequenceI sequence = seq.getDatasetSequence();
1223 if (sequence == null)
1229 * shift right features that lie to the right of the restored cut (but not
1230 * if dataset sequence unchanged - so coordinates were changed by Cut)
1232 if (!sameDatasetSequence)
1235 * shift right all features right of and not
1236 * contiguous with the cut position
1238 seq.getFeatures().shiftFeatures(start + 1, length);
1241 * shift right any features that start at the cut position,
1242 * unless they were truncated
1244 List<SequenceFeature> sfs = seq.getFeatures().findFeatures(start,
1246 for (SequenceFeature sf : sfs)
1248 if (sf.getBegin() == start)
1250 if (!command.truncatedFeatures.containsKey(seq)
1251 || !command.truncatedFeatures.get(seq).contains(sf))
1254 * feature was shifted left to cut position (not truncated),
1255 * so shift it back right
1257 SequenceFeature shifted = new SequenceFeature(sf, sf.getBegin()
1258 + length, sf.getEnd() + length, sf.getFeatureGroup(),
1260 seq.addSequenceFeature(shifted);
1261 seq.deleteFeature(sf);
1268 * restore any features that were deleted or truncated
1270 if (command.deletedFeatures != null
1271 && command.deletedFeatures.containsKey(seq))
1273 for (SequenceFeature deleted : command.deletedFeatures.get(seq))
1275 sequence.addSequenceFeature(deleted);
1280 * delete any truncated features
1282 if (command.truncatedFeatures != null
1283 && command.truncatedFeatures.containsKey(seq))
1285 for (SequenceFeature amended : command.truncatedFeatures.get(seq))
1287 sequence.deleteFeature(amended);
1293 * Returns the list of edit commands wrapped by this object.
1297 public List<Edit> getEdits()
1303 * Returns a map whose keys are the dataset sequences, and values their
1304 * aligned sequences before the command edit list was applied. The aligned
1305 * sequences are copies, which may be updated without affecting the originals.
1307 * The command holds references to the aligned sequences (after editing). If
1308 * the command is an 'undo',then the prior state is simply the aligned state.
1309 * Otherwise, we have to derive the prior state by working backwards through
1310 * the edit list to infer the aligned sequences before editing.
1312 * Note: an alternative solution would be to cache the 'before' state of each
1313 * edit, but this would be expensive in space in the common case that the
1314 * original is never needed (edits are not mirrored).
1317 * @throws IllegalStateException
1318 * on detecting an edit command of a type that can't be unwound
1320 public Map<SequenceI, SequenceI> priorState(boolean forUndo)
1322 Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
1323 if (getEdits() == null)
1329 for (Edit e : getEdits())
1331 for (SequenceI seq : e.getSequences())
1333 SequenceI ds = seq.getDatasetSequence();
1334 // SequenceI preEdit = result.get(ds);
1335 if (!result.containsKey(ds))
1338 * copy sequence including start/end (but don't use copy constructor
1339 * as we don't need annotations)
1341 SequenceI preEdit = new Sequence("", seq.getSequenceAsString(),
1342 seq.getStart(), seq.getEnd());
1343 preEdit.setDatasetSequence(ds);
1344 result.put(ds, preEdit);
1352 * Work backwards through the edit list, deriving the sequences before each
1353 * was applied. The final result is the sequence set before any edits.
1355 Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
1356 while (editList.hasNext())
1358 Edit oldEdit = editList.next();
1359 Action action = oldEdit.getAction();
1360 int position = oldEdit.getPosition();
1361 int number = oldEdit.getNumber();
1362 final char gap = oldEdit.getGapCharacter();
1363 for (SequenceI seq : oldEdit.getSequences())
1365 SequenceI ds = seq.getDatasetSequence();
1366 SequenceI preEdit = result.get(ds);
1367 if (preEdit == null)
1369 preEdit = new Sequence("", seq.getSequenceAsString(),
1370 seq.getStart(), seq.getEnd());
1371 preEdit.setDatasetSequence(ds);
1372 result.put(ds, preEdit);
1375 * 'Undo' this edit action on the sequence (updating the value in the
1380 if (action == Action.DELETE_GAP)
1382 preEdit.setSequence(new String(StringUtils.insertCharAt(
1383 preEdit.getSequence(), position, number, gap)));
1385 else if (action == Action.INSERT_GAP)
1387 preEdit.setSequence(new String(StringUtils.deleteChars(
1388 preEdit.getSequence(), position, position + number)));
1392 System.err.println("Can't undo edit action " + action);
1393 // throw new IllegalStateException("Can't undo edit action " +
1404 public SequenceI[] oldds;
1407 * start and end of sequence prior to edit
1409 public Range[] oldStartEnd;
1411 boolean fullAlignmentHeight = false;
1413 Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
1415 Map<String, Annotation[]> deletedAnnotations;
1418 * features deleted by the cut (re-add on Undo)
1419 * (including the original of any shortened features)
1421 Map<SequenceI, List<SequenceFeature>> deletedFeatures;
1424 * shortened features added by the cut (delete on Undo)
1426 Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
1438 int position, number;
1442 public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1447 this.position = pos;
1448 this.number = count;
1452 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1455 this(cmd, sqs, pos, count, align.getGapCharacter());
1459 alIndex = new int[sqs.length];
1460 for (int i = 0; i < sqs.length; i++)
1462 alIndex[i] = align.findIndex(sqs[i]);
1465 fullAlignmentHeight = (align.getHeight() == sqs.length);
1468 Edit(Action cmd, SequenceI[] sqs, int pos, int count,
1469 AlignmentI align, String replace)
1471 this(cmd, sqs, pos, count, align);
1473 string = new char[sqs.length][];
1474 for (int i = 0; i < sqs.length; i++)
1476 string[i] = replace.toCharArray();
1480 public SequenceI[] getSequences()
1485 public int getPosition()
1490 public Action getAction()
1495 public int getNumber()
1500 public char getGapCharacter()
1507 * Returns an iterator over the list of edit commands which traverses the list
1508 * either forwards or backwards.
1513 public Iterator<Edit> getEditIterator(boolean forwards)
1517 return getEdits().iterator();
1521 return new ReverseListIterator<Edit>(getEdits());
1526 * Adjusts features for Cut, and saves details of changes made to allow Undo
1528 * <li>features left of the cut are unchanged</li>
1529 * <li>features right of the cut are shifted left</li>
1530 * <li>features internal to the cut region are deleted</li>
1531 * <li>features that overlap or span the cut are shortened</li>
1532 * <li>the originals of any deleted or shorted features are saved, to re-add
1534 * <li>any added (shortened) features are saved, to delete on Undo</li>
1539 * @param fromPosition
1541 * @param cutIsInternal
1543 protected static void cutFeatures(Edit command, SequenceI seq,
1544 int fromPosition, int toPosition, boolean cutIsInternal)
1546 /* if the cut is at start or end of sequence
1547 * then we don't modify the seuqence feature store
1553 List<SequenceFeature> added = new ArrayList<>();
1554 List<SequenceFeature> removed = new ArrayList<>();
1556 SequenceFeaturesI featureStore = seq.getFeatures();
1557 if (toPosition < fromPosition || featureStore == null)
1562 int cutStartPos = fromPosition;
1563 int cutEndPos = toPosition;
1564 int cutWidth = cutEndPos - cutStartPos + 1;
1566 synchronized (featureStore)
1569 * get features that overlap the cut region
1571 List<SequenceFeature> toAmend = featureStore.findFeatures(
1572 cutStartPos, cutEndPos);
1575 * add any contact features that span the cut region
1576 * (not returned by findFeatures)
1578 for (SequenceFeature contact : featureStore.getContactFeatures())
1580 if (contact.getBegin() < cutStartPos
1581 && contact.getEnd() > cutEndPos)
1583 toAmend.add(contact);
1588 * adjust start-end of overlapping features;
1589 * delete features enclosed by the cut;
1590 * delete partially overlapping contact features
1592 for (SequenceFeature sf : toAmend)
1594 int sfBegin = sf.getBegin();
1595 int sfEnd = sf.getEnd();
1596 int newBegin = sfBegin;
1598 boolean toDelete = false;
1599 boolean follows = false;
1601 if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
1604 * feature lies within cut region - delete it
1608 else if (sfBegin < cutStartPos && sfEnd > cutEndPos)
1611 * feature spans cut region - left-shift the end
1615 else if (sfEnd <= cutEndPos)
1618 * feature overlaps left of cut region - truncate right
1620 newEnd = cutStartPos - 1;
1621 if (sf.isContactFeature())
1626 else if (sfBegin >= cutStartPos)
1629 * remaining case - feature overlaps right
1630 * truncate left, adjust end of feature
1632 newBegin = cutIsInternal ? cutStartPos : cutEndPos + 1;
1633 newEnd = newBegin + sfEnd - cutEndPos - 1;
1634 if (sf.isContactFeature())
1640 seq.deleteFeature(sf);
1647 SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
1648 sf.getFeatureGroup(), sf.getScore());
1649 seq.addSequenceFeature(copy);
1658 * and left shift any features lying to the right of the cut region
1661 featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
1665 * save deleted and amended features, so that Undo can
1666 * re-add or delete them respectively
1668 if (command.deletedFeatures == null)
1670 command.deletedFeatures = new HashMap<>();
1672 if (command.truncatedFeatures == null)
1674 command.truncatedFeatures = new HashMap<>();
1676 command.deletedFeatures.put(seq, removed);
1677 command.truncatedFeatures.put(seq, added);