*/
package jalview.commands;
+import java.util.Locale;
+
import jalview.analysis.AlignSeq;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.Annotation;
+import jalview.datamodel.ContiguousI;
import jalview.datamodel.Range;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
return null;
}
};
+
public abstract Action getUndoAction();
};
- private List<Edit> edits = new ArrayList<Edit>();
+ private List<Edit> edits = new ArrayList<>();
String description;
command.string[i] = sequence.getSequence(command.position,
command.position + command.number);
SequenceI oldds = sequence.getDatasetSequence();
- if (command.oldds != null && command.oldds[i] != null)
- {
- // we are redoing an undone cut.
- sequence.setDatasetSequence(null);
- }
- Range cutPositions = sequence.findPositions(command.position + 1,
- command.position + command.number);
+ ContiguousI cutPositions = sequence.findPositions(
+ command.position + 1, command.position + command.number);
boolean cutIsInternal = cutPositions != null
- && sequence.getStart() != cutPositions
- .getBegin() && sequence.getEnd() != cutPositions.getEnd();
- sequence.deleteChars(command.position, command.position
- + command.number);
+ && sequence.getStart() != cutPositions.getBegin()
+ && sequence.getEnd() != cutPositions.getEnd();
+
+ /*
+ * perform the cut; if this results in a new dataset sequence, add
+ * that to the alignment dataset
+ */
+ SequenceI ds = sequence.getDatasetSequence();
+ sequence.deleteChars(command.position,
+ command.position + command.number);
if (command.oldds != null && command.oldds[i] != null)
{
- // Undoing previous Paste - so
- // oldds entry contains the cut dataset sequence,
- // with sequence features in expected place.
+ /*
+ * we are Redoing a Cut, or Undoing a Paste - so
+ * oldds entry contains the cut dataset sequence,
+ * with sequence features in expected place
+ */
sequence.setDatasetSequence(command.oldds[i]);
command.oldds[i] = oldds;
}
else
{
- // New cut operation
- // We always keep track of the dataset sequence so we can safely
- // restore it during the Undo
+ /*
+ * new cut operation: save the dataset sequence
+ * so it can be restored in an Undo
+ */
if (command.oldds == null)
{
command.oldds = new SequenceI[command.seqs.length];
}
- command.oldds[i] = oldds;
+ command.oldds[i] = oldds;// todo not if !cutIsInternal?
// do we need to edit sequence features for new sequence ?
- if (oldds != sequence.getDatasetSequence()
- || (cutIsInternal
- && sequence.getFeatures().hasFeatures()))
+ if (oldds != sequence.getDatasetSequence() || (cutIsInternal
+ && sequence.getFeatures().hasFeatures()))
+ // todo or just test cutIsInternal && cutPositions != null ?
{
if (cutPositions != null)
{
cutFeatures(command, sequence, cutPositions.getBegin(),
- cutPositions.getEnd(), cutIsInternal);
+ cutPositions.getEnd(), cutIsInternal);
}
}
}
+ SequenceI newDs = sequence.getDatasetSequence();
+ if (newDs != ds && command.al != null
+ && command.al.getDataset() != null
+ && !command.al.getDataset().getSequences().contains(newDs))
+ {
+ command.al.getDataset().addSequence(newDs);
+ }
}
if (sequence.getLength() < 1)
*/
if (command.alIndex[i] < command.al.getHeight())
{
- List<SequenceI> sequences;
- synchronized (sequences = command.al.getSequences())
+ List<SequenceI> sequences = command.al.getSequences();
+ synchronized (sequences)
{
if (!(command.alIndex[i] < 0))
{
command.oldds[i] = sequence.getDatasetSequence();
sameDatasetSequence = ds == sequence.getDatasetSequence();
ds.setSequenceFeatures(sequence.getSequenceFeatures());
+ if (!sameDatasetSequence && command.al.getDataset() != null)
+ {
+ // delete 'undone' sequence from alignment dataset
+ command.al.getDataset()
+ .deleteSequence(sequence.getDatasetSequence());
+ }
sequence.setDatasetSequence(ds);
}
undoCutFeatures(command, command.seqs[i], start, length,
static void replace(Edit command)
{
- StringBuffer tmp;
+ StringBuilder tmp;
String oldstring;
int start = command.position;
int end = command.number;
{
boolean newDSWasNeeded = command.oldds != null
&& command.oldds[i] != null;
+ boolean newStartEndWasNeeded = command.oldStartEnd != null
+ && command.oldStartEnd[i] != null;
/**
* cut addHistoryItem(new EditCommand("Cut Sequences", EditCommand.CUT,
* EditCommand.PASTE, sequences, 0, alignment.getWidth(), alignment) );
*
*/
+ ContiguousI beforeEditedPositions = command.seqs[i].findPositions(1,
+ start);
+ ContiguousI afterEditedPositions = command.seqs[i]
+ .findPositions(end + 1, command.seqs[i].getLength());
+
oldstring = command.seqs[i].getSequenceAsString();
- tmp = new StringBuffer(oldstring.substring(0, start));
+ tmp = new StringBuilder(oldstring.substring(0, start));
tmp.append(command.string[i]);
- String nogaprep = jalview.analysis.AlignSeq.extractGaps(
- jalview.util.Comparison.GapChars,
+ String nogaprep = AlignSeq.extractGaps(Comparison.GapChars,
new String(command.string[i]));
- int ipos = command.seqs[i].findPosition(start)
- - command.seqs[i].getStart();
- tmp.append(oldstring.substring(end));
+ if (end < oldstring.length())
+ {
+ tmp.append(oldstring.substring(end));
+ }
+ // stash end prior to updating the sequence object so we can save it if
+ // need be.
+ Range oldstartend = new Range(command.seqs[i].getStart(),
+ command.seqs[i].getEnd());
command.seqs[i].setSequence(tmp.toString());
- command.string[i] = oldstring.substring(start, end).toCharArray();
+ command.string[i] = oldstring
+ .substring(start, Math.min(end, oldstring.length()))
+ .toCharArray();
String nogapold = AlignSeq.extractGaps(Comparison.GapChars,
new String(command.string[i]));
- if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
+
+ if (!nogaprep.toLowerCase(Locale.ROOT)
+ .equals(nogapold.toLowerCase(Locale.ROOT)))
{
- if (newDSWasNeeded)
+ // we may already have dataset and limits stashed...
+ if (newDSWasNeeded || newStartEndWasNeeded)
{
- SequenceI oldds = command.seqs[i].getDatasetSequence();
- command.seqs[i].setDatasetSequence(command.oldds[i]);
- command.oldds[i] = oldds;
+ if (newDSWasNeeded)
+ {
+ // then just switch the dataset sequence
+ SequenceI oldds = command.seqs[i].getDatasetSequence();
+ command.seqs[i].setDatasetSequence(command.oldds[i]);
+ command.oldds[i] = oldds;
+ }
+ if (newStartEndWasNeeded)
+ {
+ Range newStart = command.oldStartEnd[i];
+ command.oldStartEnd[i] = oldstartend;
+ command.seqs[i].setStart(newStart.getBegin());
+ command.seqs[i].setEnd(newStart.getEnd());
+ }
}
else
{
- if (command.oldds == null)
+ // decide if we need a new dataset sequence or modify start/end
+ // first edit the original dataset sequence string
+ SequenceI oldds = command.seqs[i].getDatasetSequence();
+ String osp = oldds.getSequenceAsString();
+ int beforeStartOfEdit = -oldds.getStart() + 1
+ + (beforeEditedPositions == null
+ ? ((afterEditedPositions != null)
+ ? afterEditedPositions.getBegin() - 1
+ : oldstartend.getBegin()
+ + nogapold.length())
+ : beforeEditedPositions.getEnd());
+ int afterEndOfEdit = -oldds.getStart() + 1
+ + ((afterEditedPositions == null) ? oldstartend.getEnd()
+ : afterEditedPositions.getBegin() - 1);
+ String fullseq = osp.substring(0, beforeStartOfEdit) + nogaprep
+ + osp.substring(afterEndOfEdit);
+
+ // and check if new sequence data is different..
+ if (!fullseq.equalsIgnoreCase(osp))
{
- command.oldds = new SequenceI[command.seqs.length];
- }
- command.oldds[i] = command.seqs[i].getDatasetSequence();
- SequenceI newds = new Sequence(
- command.seqs[i].getDatasetSequence());
- String fullseq, osp = newds.getSequenceAsString();
- fullseq = osp.substring(0, ipos) + nogaprep
- + osp.substring(ipos + nogaprep.length());
- newds.setSequence(fullseq.toUpperCase());
- // TODO: JAL-1131 ensure newly created dataset sequence is added to
- // the set of
- // dataset sequences associated with the alignment.
- // TODO: JAL-1131 fix up any annotation associated with new dataset
- // sequence to ensure that original sequence/annotation relationships
- // are preserved.
- command.seqs[i].setDatasetSequence(newds);
+ // old ds and edited ds are different, so
+ // create the new dataset sequence
+ SequenceI newds = new Sequence(oldds);
+ newds.setSequence(fullseq.toUpperCase(Locale.ROOT));
+
+ if (command.oldds == null)
+ {
+ command.oldds = new SequenceI[command.seqs.length];
+ }
+ command.oldds[i] = command.seqs[i].getDatasetSequence();
+
+ // And preserve start/end for good-measure
+ if (command.oldStartEnd == null)
+ {
+ command.oldStartEnd = new Range[command.seqs.length];
+ }
+ command.oldStartEnd[i] = oldstartend;
+ // TODO: JAL-1131 ensure newly created dataset sequence is added to
+ // the set of
+ // dataset sequences associated with the alignment.
+ // TODO: JAL-1131 fix up any annotation associated with new dataset
+ // sequence to ensure that original sequence/annotation
+ // relationships
+ // are preserved.
+ command.seqs[i].setDatasetSequence(newds);
+ }
+ else
+ {
+ if (command.oldStartEnd == null)
+ {
+ command.oldStartEnd = new Range[command.seqs.length];
+ }
+ command.oldStartEnd[i] = new Range(command.seqs[i].getStart(),
+ command.seqs[i].getEnd());
+ if (beforeEditedPositions != null
+ && afterEditedPositions == null)
+ {
+ // modification at end
+ command.seqs[i].setEnd(beforeEditedPositions.getEnd()
+ + nogaprep.length() - nogapold.length());
+ }
+ else if (afterEditedPositions != null
+ && beforeEditedPositions == null)
+ {
+ // modification at start
+ command.seqs[i].setStart(
+ afterEditedPositions.getBegin() - nogaprep.length());
+ }
+ else
+ {
+ // edit covered both start and end. Here we can only guess the
+ // new
+ // start/end
+ String nogapalseq = AlignSeq.extractGaps(Comparison.GapChars,
+ command.seqs[i].getSequenceAsString()
+ .toUpperCase(Locale.ROOT));
+ int newStart = command.seqs[i].getDatasetSequence()
+ .getSequenceAsString().indexOf(nogapalseq);
+ if (newStart == -1)
+ {
+ throw new Error(
+ "Implementation Error: could not locate start/end "
+ + "in dataset sequence after an edit of the sequence string");
+ }
+ int newEnd = newStart + nogapalseq.length() - 1;
+ command.seqs[i].setStart(newStart);
+ command.seqs[i].setEnd(newEnd);
+ }
+ }
}
}
tmp = null;
if (modifyVisibility && !insert)
{
// only occurs if a sequence was added or deleted.
- command.deletedAnnotationRows = new Hashtable<SequenceI, AlignmentAnnotation[]>();
+ command.deletedAnnotationRows = new Hashtable<>();
}
if (command.fullAlignmentHeight)
{
}
// and then duplicate added annotation on every other alignment
// view
- for (int vnum = 0; views != null && vnum < views.length; vnum++)
+ for (int vnum = 0; views != null
+ && vnum < views.length; vnum++)
{
if (views[vnum] != command.al)
{
if (!insert)
{
- command.deletedAnnotations = new Hashtable<String, Annotation[]>();
+ command.deletedAnnotations = new Hashtable<>();
}
int aSize;
* feature was shifted left to cut position (not truncated),
* so shift it back right
*/
- SequenceFeature shifted = new SequenceFeature(sf, sf.getBegin()
- + length, sf.getEnd() + length, sf.getFeatureGroup(),
- sf.getScore());
+ SequenceFeature shifted = new SequenceFeature(sf,
+ sf.getBegin() + length, sf.getEnd() + length,
+ sf.getFeatureGroup(), sf.getScore());
seq.addSequenceFeature(shifted);
seq.deleteFeature(sf);
}
*/
public Map<SequenceI, SequenceI> priorState(boolean forUndo)
{
- Map<SequenceI, SequenceI> result = new HashMap<SequenceI, SequenceI>();
+ Map<SequenceI, SequenceI> result = new HashMap<>();
if (getEdits() == null)
{
return result;
* Work backwards through the edit list, deriving the sequences before each
* was applied. The final result is the sequence set before any edits.
*/
- Iterator<Edit> editList = new ReverseListIterator<Edit>(getEdits());
+ Iterator<Edit> editList = new ReverseListIterator<>(getEdits());
while (editList.hasNext())
{
Edit oldEdit = editList.next();
public class Edit
{
- public SequenceI[] oldds;
+ SequenceI[] oldds;
+
+ /**
+ * start and end of sequence prior to edit
+ */
+ Range[] oldStartEnd;
boolean fullAlignmentHeight = false;
AlignmentI al;
- Action command;
+ final Action command;
char[][] string;
int[] alIndex;
- int position, number;
+ int position;
+
+ int number;
char gapChar;
- public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
- char gap)
+ /*
+ * flag that identifies edits inserted to balance
+ * user edits in a 'locked editing' region
+ */
+ private boolean systemGenerated;
+
+ public Edit(Action cmd, SequenceI[] sqs, int pos, int count, char gap)
{
this.command = cmd;
this.seqs = sqs;
this.gapChar = gap;
}
- Edit(Action cmd, SequenceI[] sqs, int pos, int count,
- AlignmentI align)
+ Edit(Action cmd, SequenceI[] sqs, int pos, int count, AlignmentI align)
{
this(cmd, sqs, pos, count, align.getGapCharacter());
fullAlignmentHeight = (align.getHeight() == sqs.length);
}
- Edit(Action cmd, SequenceI[] sqs, int pos, int count,
- AlignmentI align, String replace)
+ /**
+ * Constructor given a REPLACE command and the replacement string
+ *
+ * @param cmd
+ * @param sqs
+ * @param pos
+ * @param count
+ * @param align
+ * @param replace
+ */
+ Edit(Action cmd, SequenceI[] sqs, int pos, int count, AlignmentI align,
+ String replace)
{
this(cmd, sqs, pos, count, align);
{
return gapChar;
}
+
+ public void setSystemGenerated(boolean b)
+ {
+ systemGenerated = b;
+ }
+
+ public boolean isSystemGenerated()
+ {
+ return systemGenerated;
+ }
}
/**
}
else
{
- return new ReverseListIterator<Edit>(getEdits());
+ return new ReverseListIterator<>(getEdits());
}
}
* <li>features right of the cut are shifted left</li>
* <li>features internal to the cut region are deleted</li>
* <li>features that overlap or span the cut are shortened</li>
- * <li>the originals of any deleted or shorted features are saved, to re-add
+ * <li>the originals of any deleted or shortened features are saved, to re-add
* on Undo</li>
* <li>any added (shortened) features are saved, to delete on Undo</li>
* </ul>
protected static void cutFeatures(Edit command, SequenceI seq,
int fromPosition, int toPosition, boolean cutIsInternal)
{
+ /*
+ * if the cut is at start or end of sequence
+ * then we don't modify the sequence feature store
+ */
if (!cutIsInternal)
{
return;
}
List<SequenceFeature> added = new ArrayList<>();
List<SequenceFeature> removed = new ArrayList<>();
-
+
SequenceFeaturesI featureStore = seq.getFeatures();
if (toPosition < fromPosition || featureStore == null)
{
return;
}
-
+
int cutStartPos = fromPosition;
int cutEndPos = toPosition;
int cutWidth = cutEndPos - cutStartPos + 1;
-
+
synchronized (featureStore)
{
/*
* get features that overlap the cut region
*/
- List<SequenceFeature> toAmend = featureStore.findFeatures(
- cutStartPos, cutEndPos);
-
+ List<SequenceFeature> toAmend = featureStore.findFeatures(cutStartPos,
+ cutEndPos);
+
/*
* add any contact features that span the cut region
* (not returned by findFeatures)
int newEnd = sfEnd;
boolean toDelete = false;
boolean follows = false;
-
+
if (sfBegin >= cutStartPos && sfEnd <= cutEndPos)
{
/*
toDelete = true;
}
}
-
+
seq.deleteFeature(sf);
if (!follows)
{
}
}
}
-
+
/*
* and left shift any features lying to the right of the cut region
- * (but not if the cut is at start or end of sequence)
*/
- if (cutIsInternal)
- {
- featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
- }
+
+ featureStore.shiftFeatures(cutEndPos + 1, -cutWidth);
}
/*