X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2FSequence.java;h=7ecb07a9f4edf237e4728c875ea0aa8f3c58bd0f;hb=0e65ec37e2c956e0ad94feda6fd07b7c3a9003ee;hp=dd7c24aeea1ec8b7a6853157d6c64b4a7e68f0f5;hpb=c63940e40edf030d8bb76d41fbd6b56331046427;p=jalview.git diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index dd7c24a..7ecb07a 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -89,6 +89,21 @@ public class Sequence extends ASequence implements SequenceI private SequenceFeatures sequenceFeatureStore; + /* + * A cursor holding the approximate current view position to the sequence, + * as determined by findIndex or findPosition or findPositions. + * Using a cursor as a hint allows these methods to be more performant for + * large sequences. + */ + private SequenceCursor cursor; + + /* + * A number that should be incremented whenever the sequence is edited. + * If the value matches the cursor token, then we can trust the cursor, + * if not then it should be recomputed. + */ + private int changeCount; + /** * Creates a new Sequence object. * @@ -656,7 +671,15 @@ public class Sequence extends ASequence implements SequenceI @Override public int findIndex(int pos) { - // returns the alignment position for a residue + /* + * use a valid nearby cursor if available + */ + if (cursor != null && cursor.sequence == this + && cursor.token == changeCount) + { + return findIndex(pos, cursor); + } + int j = start; int i = 0; // Rely on end being at least as long as the length of the sequence. @@ -666,7 +689,6 @@ public class Sequence extends ASequence implements SequenceI { j++; } - i++; } @@ -676,13 +698,80 @@ public class Sequence extends ASequence implements SequenceI } else { + updateCursor(pos, i); return i; } } + protected void updateCursor(int residuePos, int column) + { + // TODO probably want to synchronize this on something + cursor = new SequenceCursor(this, residuePos, column, this.changeCount); + } + + /** + * Answers the aligned column position (1..) for the given residue position + * (start..) given a 'hint' of a residue/column location in the neighbourhood. + * The hint may be left of, at, or to the right of the required position. + * + * @param pos + * @param curs + * @return + */ + protected int findIndex(int pos, SequenceCursor curs) + { + if (curs.sequence != this || curs.token != changeCount) + { + /* + * wrong or invalidated cursor, compute de novo + */ + return findIndex(pos); + } + + if (curs.residuePosition == pos) + { + return curs.columnPosition; + } + + /* + * move left or right to find pos from hint.position + */ + int col = curs.columnPosition - 1; // convert from base 1 to 0-based array + // index + int newPos = curs.residuePosition; + int delta = newPos > pos ? -1 : 1; + + while (newPos != pos) + { + col += delta; // shift one column left or right + if (col < 0 || col == sequence.length) + { + break; + } + if (!Comparison.isGap(sequence[col])) + { + newPos += delta; + } + } + + col++; // convert back to base 1 + updateCursor(pos, col); + + return col; + } + @Override - public int findPosition(int i) + public int findPosition(final int i) { + /* + * use a valid nearby cursor if available + */ + if (cursor != null && cursor.sequence == this + && cursor.token == changeCount) + { + return findPosition(i + 1, cursor); + } + int j = 0; int pos = start; int seqlen = sequence.length; @@ -696,15 +785,84 @@ public class Sequence extends ASequence implements SequenceI j++; } + if (j == i && !Comparison.isGap(sequence[i])) + { + updateCursor(pos, i + 1); + } + return pos; } /** + * Answers the sequence position (start..) for the given aligned column + * position (1..), given a hint of a cursor in the neighbourhood. The cursor + * may lie left of, at, or to the right of the column position. + * + * @param col + * @param curs + * @return + */ + protected int findPosition(final int col, SequenceCursor curs) + { + if (curs.sequence != this || curs.token != changeCount) + { + /* + * wrong or invalidated cursor, compute de novo + */ + return findPosition(col - 1);// ugh back to base 0 + } + + if (curs.columnPosition == col) + { + return curs.residuePosition; // easy case :-) + } + + /* + * move left or right to find pos from cursor position + */ + int column = curs.columnPosition - 1; // to base 0 + int newPos = curs.residuePosition; + int delta = curs.columnPosition > col ? -1 : 1; + boolean gapped = false; + + while (column != col - 1) + { + column += delta; // shift one column left or right + if (column < 0 || column == sequence.length) + { + break; + } + gapped = Comparison.isGap(sequence[column]); + if (!gapped) + { + newPos += delta; + } + } + + /* + * hack to give position to the right if on a gap + * pending resolution of JAL-2562 + */ + if (delta > 0 && gapped) + { + newPos++; + } + + return newPos; + } + + /** * {@inheritDoc} */ @Override public Range findPositions(int fromCol, int toCol) { + if (cursor != null && cursor.sequence == this + && cursor.token == changeCount) + { + return findPositions(fromCol, toCol, cursor); + } + /* * count residues before fromCol */ @@ -725,7 +883,6 @@ public class Sequence extends ASequence implements SequenceI */ int firstPos = 0; int lastPos = 0; - int firstPosCol = 0; boolean foundFirst = false; while (j <= toCol && j < seqlen) @@ -736,7 +893,6 @@ public class Sequence extends ASequence implements SequenceI if (!foundFirst) { firstPos = count; - firstPosCol = j; foundFirst = true; } lastPos = count; @@ -762,6 +918,104 @@ public class Sequence extends ASequence implements SequenceI } /** + * Returns the range of sequence positions included in the given alignment + * position range. If no positions are included (the range is entirely gaps), + * then returns null. The cursor parameter may provide a starting position in + * the neighbourhood of the search (which may be left of, right of, or + * overlapping the search region). + * + * @param fromCol + * start column of region (0..) + * @param toCol + * end column of region (0..) + * @param curs + * @return + */ + protected Range findPositions(int fromCol, int toCol, SequenceCursor curs) + { + if (curs.sequence != this || curs.token != changeCount) + { + /* + * wrong or invalidated cursor, compute de novo + */ + return findPositions(fromCol, toCol); + } + + /* + * keep this simple...first step from cursor to fromCol... + */ + final int seqlen = sequence.length; + int resNo = curs.residuePosition; + int col = curs.columnPosition - 1; // from base 1 to base 0 + if (col != fromCol) + { + int delta = col > fromCol ? -1 : 1; + while (col != fromCol && col >= 0 && col < seqlen) + { + if (!Comparison.isGap(sequence[col])) + { + resNo += delta; + } + col += delta; + } + } + + if (col < fromCol || col == seqlen) + { + /* + * sequence lies to the left of the target region + */ + return null; + } + + /* + * resNo is now the residue at fromCol (if not gapped), else the one + * before it (if delta == 1), else the one after (if delta == -1); + * we want the residue before fromCol + */ + if (!Comparison.isGap(sequence[fromCol])) + { + resNo--; + } + else if (curs.columnPosition > fromCol) + { + resNo -= 2; + } + + /* + * now first and last residues between fromCol and toCol + */ + int firstPos = 0; + int lastPos = 0; + boolean foundFirst = false; + + while (col <= toCol && col < seqlen) + { + if (!Comparison.isGap(sequence[col])) + { + resNo++; + if (!foundFirst) + { + firstPos = resNo; + foundFirst = true; + } + lastPos = resNo; + } + col++; + } + + if (firstPos == 0) + { + /* + * no residues in this range + */ + return null; + } + + return new Range(firstPos, lastPos); + } + + /** * Returns an int array where indices correspond to each residue in the * sequence and the element value gives its position in the alignment * @@ -1514,4 +1768,14 @@ public class Sequence extends ASequence implements SequenceI } return sequenceFeatureStore.findFeatures(from, to, types); } + + /** + * Invalidates any stale cursors (forcing recalculation) by incrementing the + * token that has to match the one presented by the cursor + */ + @Override + public void zapCursor() + { + changeCount++; + } }