+ * If there are hidden columns, and option {@ignoreHidden} is selected, then + * only visible positions of the sequence are included, and a mapping is also + * constructed from the returned string positions to the true sequence + * positions. + *
+ * Note we have to do this each time {@code findNext} or {@code findAll} is + * called, in case the alignment, selection group or hidden columns have + * changed. In particular, if the sequence at offset {@code sequenceIndex} in + * the alignment is (no longer) in the selection group, search is advanced to + * the next sequence that is. + *
+ * Sets sequence string to the empty string if there are no more sequences (in + * selection group if any) at or after {@code sequenceIndex}. + *
+ * Returns true if a sequence could be found, false if end of alignment was + * reached + * + * @param ignoreHidden + * @return + */ + private boolean getSequence(boolean ignoreHidden) + { + AlignmentI alignment = viewport.getAlignment(); + if (sequenceIndex >= alignment.getHeight()) { - int res = Integer.parseInt(searchString); - found = true; - if (selection == null || selection.getSize() < 1) - { - seq = (Sequence) alignment.getSequenceAt(0); - } - else + seqToSearch = ""; + return false; + } + SequenceI seq = alignment.getSequenceAt(sequenceIndex); + SequenceGroup selection = viewport.getSelectionGroup(); + if (selection != null && !selection.contains(seq)) + { + if (!nextSequence(ignoreHidden)) { - seq = (Sequence) (selection.getSequenceAt(0)); + return false; } + seq = alignment.getSequenceAt(sequenceIndex); + } - searchResults.addResult(seq, res, res); - hasResults = true; + String seqString = null; + if (ignoreHidden) + { + seqString = getVisibleSequence(seq); + this.searchedSequenceStartPosition = 1; } - catch (NumberFormatException ex) + else { + int startCol = 0; + int endCol = seq.getLength() - 1; + this.searchedSequenceStartPosition = seq.getStart(); + if (selection != null) + { + startCol = selection.getStartRes(); + endCol = Math.min(endCol, selection.getEndRes()); + this.searchedSequenceStartPosition = seq.findPosition(startCol); + } + seqString = seq.getSequenceAsString(startCol, endCol + 1); } - /////////////////////////////////////////////// + /* + * remove gaps; note that even if this leaves an empty string, we 'search' + * the sequence anyway (for possible match on name or description) + */ + String ungapped = AlignSeq.extractGaps(Comparison.GapChars, seqString); + this.seqToSearch = ungapped; - int end = alignment.getHeight(); + return true; + } - if (selection != null) + /** + * Returns a string consisting of only the visible residues of {@code seq} + * from alignment column {@ fromColumn}, restricted to the current selection + * region if there is one. + *
+ * As a side-effect, also computes the mapping from the true sequence
+ * positions to the positions (1, 2, ...) of the returned sequence. This is to
+ * allow search matches in the visible sequence to be converted to sequence
+ * positions.
+ *
+ * @param seq
+ * @return
+ */
+ private String getVisibleSequence(SequenceI seq)
+ {
+ /*
+ * get start / end columns of sequence and convert to base 0
+ * (so as to match the visible column ranges)
+ */
+ int seqStartCol = seq.findIndex(seq.getStart()) - 1;
+ int seqEndCol = seq.findIndex(seq.getStart() + seq.getLength() - 1) - 1;
+ Iterator
+ * Returns true if a sequence could be found, false if end of alignment was
+ * reached
+ *
+ * @param ignoreHidden
+ */
+ private boolean nextSequence(boolean ignoreHidden)
+ {
+ sequenceIndex++;
+ residueIndex = -1;
- item = seq.getSequenceAsString();
- if(!caseSensitive)
- item = item.toUpperCase();
+ return getSequence(ignoreHidden);
+ }
- if ( (selection != null) &&
- (selection.getEndRes() < alignment.getWidth() - 1))
+ /**
+ * Finds the next match in the given sequence, starting at offset
+ * {@code residueIndex}. Answers true if a match is found, else false.
+ *
+ * If a match is found, {@code residueIndex} is advanced to the position after
+ * the start of the matched region, ready for the next search.
+ *
+ * If no match is found, {@code sequenceIndex} is advanced ready to search the
+ * next sequence.
+ *
+ * @param seqToSearch
+ * @param searchString
+ * @param searchPattern
+ * @param matchDescription
+ * @param ignoreHidden
+ * @return
+ */
+ protected boolean findNextMatch(String searchString, Regex searchPattern,
+ boolean matchDescription, boolean matchFeatureDesc,
+ boolean ignoreHidden)
+ {
+ if (residueIndex < 0)
+ {
+ /*
+ * at start of sequence; try find by residue number, in sequence id,
+ * or (optionally) in sequence description
+ */
+ if (doNonMotifSearches(searchString, searchPattern, matchDescription))
{
- item = item.substring(0, selection.getEndRes() + 1);
+ return true;
}
+ }
- ///Shall we ignore gaps???? - JBPNote: Add Flag for forcing this or not
- StringBuffer noGapsSB = new StringBuffer();
- int insertCount = 0;
- Vector spaces = new Vector();
-
- for (int j = 0; j < item.length(); j++)
+ /*
+ * search for next match in sequence string
+ */
+ int end = seqToSearch.length();
+ while (residueIndex < end)
+ {
+ boolean matched = searchPattern.searchFrom(seqToSearch, residueIndex);
+ if (matched)
{
- if (!jalview.util.Comparison.isGap(item.charAt(j)))
- {
- noGapsSB.append(item.charAt(j));
- spaces.addElement(new Integer(insertCount));
- }
- else
+ if (recordMatch(searchPattern, ignoreHidden))
{
- insertCount++;
+ return true;
}
}
-
- String noGaps = noGapsSB.toString();
-
- for (int r = resIndex; r < noGaps.length(); r++)
+ else
{
-
- if (regex.searchFrom(noGaps, r))
+ if (matchFeatureDesc)
{
- resIndex = regex.matchedFrom();
-
- if ( (selection != null) &&
- ( (resIndex +
- Integer.parseInt(spaces.elementAt(resIndex).toString())) <
- selection.getStartRes()))
- {
- continue;
- }
-
- int sres = seq.findPosition(resIndex +
- Integer.parseInt(spaces.elementAt(
- resIndex)
- .toString()));
- int eres = seq.findPosition(regex.matchedTo() - 1 +
- Integer.parseInt(spaces.elementAt(regex.
- matchedTo() -
- 1).toString()));
-
- searchResults.addResult(seq, sres, eres);
- hasResults = true;
- if (!findAll)
+ matched = searchSequenceFeatures(residueIndex, searchPattern);
+ if (matched)
{
- // thats enough, break and display the result
- found = true;
- resIndex++;
-
- break;
+ return true;
}
-
- r = resIndex;
- }
- else
- {
- break;
+ lastFeature = null;
}
- }
-
- if (!found)
- {
- seqIndex++;
- resIndex = 0;
+ residueIndex = Integer.MAX_VALUE;
}
}
- for (int id = 0; id < alignment.getHeight(); id++)
- {
- if (regex.search(alignment.getSequenceAt(id).getName()))
- {
- idMatch.addElement(alignment.getSequenceAt(id));
- hasResults = true;
- }
- }
- return hasResults;
+ nextSequence(ignoreHidden);
+ return false;
}
/**
- * @return the alignment
+ * Adds the match held in the
+ * Matches that lie entirely within hidden regions of the alignment are not
+ * added.
+ *
+ * @param searchPattern
+ * @param ignoreHidden
+ * @return
*/
- public AlignmentI getAlignment()
+ protected boolean recordMatch(Regex searchPattern, boolean ignoreHidden)
{
- return alignment;
- }
+ SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
+
+ /*
+ * convert start/end of the match to sequence coordinates
+ */
+ int offset = searchPattern.matchedFrom();
+ int matchStartPosition = this.searchedSequenceStartPosition + offset;
+ int matchEndPosition = matchStartPosition + searchPattern.charsMatched()
+ - 1;
+
+ /*
+ * update residueIndex to next position after the start of the match
+ * (findIndex returns a value base 1, columnIndex is held base 0)
+ */
+ residueIndex = searchPattern.matchedFrom() + 1;
+
+ /*
+ * return false if the match is entirely in a hidden region
+ */
+ if (allHidden(seq, matchStartPosition, matchEndPosition))
+ {
+ return false;
+ }
- /**
- * @param alignment the alignment to set
- */
- public void setAlignment(AlignmentI alignment)
- {
- this.alignment = alignment;
- }
+ /*
+ * check that this match is not a subset of the previous one (JAL-2302)
+ */
+ ListsearchPattern
Regex to the
+ * searchResults
, unless it is a subregion of the last match
+ * recorded. residueIndex
is advanced to the position after the
+ * start of the matched region, ready for the next search. Answers true if a
+ * match was added, else false.
+ *
+ *
+ * Answers true if a match is found, else false.
+ *
+ * @param searchString
+ * @param searchPattern
+ * @param includeDescription
+ * @return
*/
- public jalview.datamodel.SequenceGroup getSelection()
+ protected boolean doNonMotifSearches(String searchString,
+ Regex searchPattern, boolean includeDescription)
{
- return selection;
+ SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
+
+ /*
+ * position sequence search to start of sequence
+ */
+ residueIndex = 0;
+ try
+ {
+ int res = Integer.parseInt(searchString);
+ return searchForResidueNumber(seq, res);
+ } catch (NumberFormatException ex)
+ {
+ // search pattern is not a number
+ }
+
+ if (searchSequenceName(seq, searchPattern))
+ {
+ return true;
+ }
+ if (includeDescription && searchSequenceDescription(seq, searchPattern))
+ {
+ return true;
+ }
+ return false;
}
/**
- * @param selection the selection to set
+ * Searches for a match with the sequence features, and if found, adds the
+ * sequence to the list of match ids, (but not as a duplicate). Answers true
+ * if a match was added, else false.
+ *
+ * @param seq
+ * @param searchPattern
+ * @return
*/
- public void setSelection(jalview.datamodel.SequenceGroup selection)
+ protected boolean searchSequenceFeatures(int from, Regex searchPattern)
{
- this.selection = selection;
+ if (lastFeatureSequenceIndex != sequenceIndex)
+ {
+ lastFeatureSequenceIndex = sequenceIndex;
+ lastFeature = null;
+ }
+ SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
+ SequenceFeaturesI sf = seq.getFeatures();
+
+ // TODO - stash feature list and search incrementally
+ List