+ * 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()) + { + seqToSearch = ""; + return false; + } + SequenceI seq = alignment.getSequenceAt(sequenceIndex); + SequenceGroup selection = viewport.getSelectionGroup(); + if (selection != null && !selection.contains(seq)) + { + if (!nextSequence(ignoreHidden)) + { + return false; + } + seq = alignment.getSequenceAt(sequenceIndex); + } - while (!found && (seqIndex < end)) + String seqString = null; + if (ignoreHidden) + { + seqString = getVisibleSequence(seq); + this.searchedSequenceStartPosition = 1; + } + 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; + + return true; + } + + /** + * 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;
+
+ return getSequence(ignoreHidden);
+ }
+
+ /**
+ * 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))
+ {
+ return true;
+ }
+ }
+
+ /*
+ * search for next match in sequence string
+ */
+ int end = seqToSearch.length();
+ while (residueIndex < end)
+ {
+ boolean matched = searchPattern.searchFrom(seqToSearch, residueIndex);
+ if (matched)
+ {
+ if (recordMatch(searchPattern, ignoreHidden))
{
- seq = (Sequence) alignment.getSequenceAt(seqIndex);
-
- if ((selection != null) && !selection.getSequences(false).contains(seq))
- {
- seqIndex++;
- resIndex = 0;
-
- continue;
- }
-
- item = seq.getSequenceAsString();
- // JBPNote - check if this toUpper which is present in the application implementation makes a difference
- //if(!caseSensitive)
- // item = item.toUpperCase();
-
- if ((selection != null) &&
- (selection.getEndRes() < alignment.getWidth()-1))
- {
- item = item.substring(0, selection.getEndRes() + 1);
- }
-
- ///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++)
- {
- if (!jalview.util.Comparison.isGap(item.charAt(j)))
- {
- noGapsSB.append(item.charAt(j));
- spaces.add(new Integer(insertCount));
- }
- else
- {
- insertCount++;
- }
- }
-
- String noGaps = noGapsSB.toString();
-
- for (int r = resIndex; r < noGaps.length(); r++)
- {
-
- if (regex.searchFrom(noGaps, r))
- {
- resIndex = regex.matchedFrom();
-
- if ((selection != null) &&
- ((resIndex +
- Integer.parseInt(spaces.get(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)
- {
- // thats enough, break and display the result
- found = true;
- resIndex++;
-
- break;
- }
-
- r = resIndex;
- }
- else
- {
- break;
- }
- }
-
- if (!found)
- {
- seqIndex++;
- resIndex = 0;
- }
+ return true;
}
-
- for (int id = 0; id < alignment.getHeight(); id++)
+ }
+ else
+ {
+ if (matchFeatureDesc)
{
- if (regex.search(alignment.getSequenceAt(id).getName()))
- {
- idMatch.add(alignment.getSequenceAt(id));
- hasResults=true;
- }
+ // TODO - record last matched
+ matched = searchSequenceFeatures(residueIndex, searchPattern);
}
- return hasResults;
+ residueIndex = Integer.MAX_VALUE;
+ }
}
- /**
- * @return the alignment
+
+ nextSequence(ignoreHidden);
+ return false;
+ }
+
+ /**
+ * Adds the match held in the
+ * Matches that lie entirely within hidden regions of the alignment are not
+ * added.
+ *
+ * @param searchPattern
+ * @param ignoreHidden
+ * @return
+ */
+ protected boolean recordMatch(Regex searchPattern, boolean ignoreHidden)
+ {
+ SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
+
+ /*
+ * convert start/end of the match to sequence coordinates
*/
- public AlignmentI getAlignment() {
- return alignment;
- }
- /**
- * @param alignment the alignment to set
+ 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)
*/
- public void setAlignment(AlignmentI alignment) {
- this.alignment = alignment;
- }
- /**
- * @return the caseSensitive
+ residueIndex = searchPattern.matchedFrom() + 1;
+
+ /*
+ * return false if the match is entirely in a hidden region
*/
- public boolean isCaseSensitive() {
- return caseSensitive;
+ if (allHidden(seq, matchStartPosition, matchEndPosition))
+ {
+ return false;
}
- /**
- * @param caseSensitive the caseSensitive to set
+
+ /*
+ * check that this match is not a subset of the previous one (JAL-2302)
*/
- public void setCaseSensitive(boolean caseSensitive) {
- this.caseSensitive = caseSensitive;
+ 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
+ */
+ protected boolean doNonMotifSearches(String searchString,
+ Regex searchPattern, boolean includeDescription)
+ {
+ SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
+
+ /*
+ * position sequence search to start of sequence
*/
- public void setSelection(jalview.datamodel.SequenceGroup selection) {
- this.selection = selection;
+ residueIndex = 0;
+ try
+ {
+ int res = Integer.parseInt(searchString);
+ return searchForResidueNumber(seq, res);
+ } catch (NumberFormatException ex)
+ {
+ // search pattern is not a number
}
- /**
- * @return the idMatch
- */
- public Vector getIdMatch() {
- return idMatch;
+
+ if (searchSequenceName(seq, searchPattern))
+ {
+ return true;
}
- /**
- * @return the regex
- */
- public com.stevesoft.pat.Regex getRegex() {
- return regex;
+ if (includeDescription && searchSequenceDescription(seq, searchPattern))
+ {
+ return true;
}
- /**
- * @return the searchResults
- */
- public SearchResults getSearchResults() {
- return searchResults;
+ return false;
+ }
+
+ /**
+ * 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.
+ *
+ * TODO: allow incremental searching (ie next feature matched after last)
+ *
+ * @param seq
+ * @param searchPattern
+ * @return
+ */
+ protected boolean searchSequenceFeatures(int from, Regex searchPattern)
+ {
+ boolean matched = false;
+ SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
+
+ SequenceFeaturesI sf = seq.getFeatures();
+ for (SequenceFeature feature : sf.getAllFeatures(null))
+ {
+ if (searchPattern.search(feature.type) || (feature.description != null
+ && searchPattern.search(feature.description)))
+ {
+ searchResults.addResult(seq, feature.getBegin(), feature.getEnd());
+ matched = true;
+ }
}
+ return matched;
+ }
- /**
- * @return the resIndex
- */
- public int getResIndex() {
- return resIndex;
+ /**
+ * Searches for a match with the sequence description, 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
+ */
+ protected boolean searchSequenceDescription(SequenceI seq,
+ Regex searchPattern)
+ {
+ String desc = seq.getDescription();
+ if (desc != null && searchPattern.search(desc)
+ && !idMatches.contains(seq))
+ {
+ idMatches.add(seq);
+ return true;
}
+ return false;
+ }
- /**
- * @param resIndex the resIndex to set
- */
- public void setResIndex(int resIndex) {
- this.resIndex = resIndex;
+ /**
+ * Searches for a match with the sequence name, 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
+ */
+ protected boolean searchSequenceName(SequenceI seq, Regex searchPattern)
+ {
+ if (searchPattern.search(seq.getName()) && !idMatches.contains(seq))
+ {
+ idMatches.add(seq);
+ return true;
}
+ return false;
+ }
- /**
- * @return the seqIndex
- */
- public int getSeqIndex() {
- return seqIndex;
+ /**
+ * If the residue position is valid for the sequence, and in a visible column,
+ * adds the position to the search results and returns true, else answers
+ * false.
+ *
+ * @param seq
+ * @param resNo
+ * @return
+ */
+ protected boolean searchForResidueNumber(SequenceI seq, int resNo)
+ {
+ if (seq.getStart() <= resNo && seq.getEnd() >= resNo)
+ {
+ if (isVisible(seq, resNo))
+ {
+ searchResults.addResult(seq, resNo, resNo);
+ return true;
+ }
}
+ return false;
+ }
- /**
- * @param seqIndex the seqIndex to set
- */
- public void setSeqIndex(int seqIndex) {
- this.seqIndex = seqIndex;
+ /**
+ * Returns true if the residue is in a visible column, else false
+ *
+ * @param seq
+ * @param res
+ * @return
+ */
+ private boolean isVisible(SequenceI seq, int res)
+ {
+ if (!viewport.hasHiddenColumns())
+ {
+ return true;
}
+ int col = seq.findIndex(res); // base 1
+ return viewport.getAlignment().getHiddenColumns().isVisible(col - 1); // base
+ // 0
+ }
+
+ @Override
+ public List