*/
package jalview.analysis;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import com.stevesoft.pat.Regex;
+
import jalview.api.AlignViewportI;
+import jalview.api.FeatureRenderer;
import jalview.api.FinderI;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SearchResultMatchI;
import jalview.datamodel.SearchResults;
import jalview.datamodel.SearchResultsI;
+import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeaturesI;
import jalview.util.Comparison;
import jalview.util.MapList;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-import com.stevesoft.pat.Regex;
-
/**
* Implements the search algorithm for the Find dialog
*/
private AlignViewportI viewport;
/*
+ * feature renderer model - if available
+ */
+ FeatureRenderer frm = null;
+
+ /*
* sequence index in alignment to search from
*/
private int sequenceIndex;
private int residueIndex;
/*
+ * last feature matched when incrementally searching sequence features
+ */
+ private SequenceFeature lastFeature;
+
+ /*
+ * last sequenceIndex used when lastFeature was discovered
+ */
+ private int lastFeatureSequenceIndex;
+
+ /*
* the true sequence position of the start of the
* last sequence searched (when 'ignore hidden regions' does not apply)
*/
@Override
public void findAll(String theSearchString, boolean matchCase,
- boolean searchDescription, boolean ignoreHidden)
+ boolean searchDescription, boolean searchFeatureDesc,
+ boolean ignoreHidden)
{
/*
* search from the start
*/
+ lastFeature = null;
+ lastFeatureSequenceIndex = 0;
sequenceIndex = 0;
residueIndex = -1;
- doFind(theSearchString, matchCase, searchDescription, true,
- ignoreHidden);
+ doFind(theSearchString, matchCase, searchDescription, searchFeatureDesc,
+ true, ignoreHidden);
/*
* reset to start for next search
*/
sequenceIndex = 0;
residueIndex = -1;
+ lastFeature = null;
+ lastFeatureSequenceIndex = 0;
}
@Override
public void findNext(String theSearchString, boolean matchCase,
- boolean searchDescription, boolean ignoreHidden)
+ boolean searchDescription, boolean searchFeatureDesc,
+ boolean ignoreHidden)
{
- doFind(theSearchString, matchCase, searchDescription, false,
- ignoreHidden);
-
+ doFind(theSearchString, matchCase, searchDescription, searchFeatureDesc,
+ false, ignoreHidden);
+
if (searchResults.isEmpty() && idMatches.isEmpty())
{
/*
*/
sequenceIndex = 0;
residueIndex = -1;
+ lastFeature = null;
+ lastFeatureSequenceIndex = 0;
}
}
* @param ignoreHidden
*/
protected void doFind(String theSearchString, boolean matchCase,
- boolean searchDescription, boolean findAll, boolean ignoreHidden)
+ boolean searchDescription, boolean searchFeatureDesc,
+ boolean findAll, boolean ignoreHidden)
{
searchResults = new SearchResults();
idMatches = new ArrayList<>();
String searchString = matchCase ? theSearchString
- : theSearchString.toUpperCase();
+ : theSearchString.toUpperCase(Locale.ROOT);
Regex searchPattern = new Regex(searchString);
searchPattern.setIgnoreCase(!matchCase);
getSequence(ignoreHidden);
boolean found = false;
- while (!found || findAll)
+ while ((!found || findAll) && sequenceIndex < end)
{
found = findNextMatch(searchString, searchPattern, searchDescription,
- ignoreHidden);
- if (sequenceIndex >= end)
- {
- break;
- }
+ searchFeatureDesc, ignoreHidden);
}
}
/**
- * Calculates and saves the sequence string to search. The string is restricted
- * to the current selection region if there is one, and is saved with all gaps
- * removed.
+ * Calculates and saves the sequence string to search. The string is
+ * restricted to the current selection region if there is one, and is saved
+ * with all gaps removed.
* <p>
* If there are hidden columns, and option {@ignoreHidden} is selected, then
* only visible positions of the sequence are included, and a mapping is also
if (ignoreHidden)
{
seqString = getVisibleSequence(seq);
+ this.searchedSequenceStartPosition = 1;
}
else
{
}
/**
- * 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.
+ * 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.
* <p>
- * 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.
+ * 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)
{
- int seqStartCol = seq.findIndex(seq.getStart());
- int seqEndCol = seq.findIndex(seq.getStart() + seq.getLength() - 1);
+ /*
+ * 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<int[]> visibleColumns = viewport.getViewAsVisibleContigs(true);
StringBuilder visibleSeq = new StringBuilder(seqEndCol - seqStartCol);
List<int[]> fromRanges = new ArrayList<>();
* so add the range to the mapping being constructed
*/
int seqResFrom = seq.findPosition(range[0]);
- int seqResTo = seq.findPosition(range[1]);
+ int seqResTo = seqResFrom + ungapped.length() - 1;
fromRanges.add(new int[] { seqResFrom, seqResTo });
}
}
/**
* Advances the search to the next sequence in the alignment. Sequences not in
- * the current selection group (if there is one) are skipped. The (sub-)sequence
- * to be searched is extracted, gaps removed, and saved, or set to null if there
- * are no more sequences to search.
+ * the current selection group (if there is one) are skipped. The
+ * (sub-)sequence to be searched is extracted, gaps removed, and saved, or set
+ * to null if there are no more sequences to search.
* <p>
* Returns true if a sequence could be found, false if end of alignment was
* reached
* @param ignoreHidden
* @return
*/
- protected boolean findNextMatch(String searchString,
- Regex searchPattern, boolean matchDescription,
+ 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))
+ if (doNonMotifSearches(searchString, searchPattern, matchDescription))
{
return true;
}
}
else
{
+ if (matchFeatureDesc)
+ {
+ matched = searchSequenceFeatures(residueIndex, searchPattern);
+ if (matched)
+ {
+ return true;
+ }
+ lastFeature = null;
+ }
residueIndex = Integer.MAX_VALUE;
}
}
*/
int offset = searchPattern.matchedFrom();
int matchStartPosition = this.searchedSequenceStartPosition + offset;
- int matchEndPosition = matchStartPosition
- + searchPattern.charsMatched() - 1;
+ 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 += offset + 1;
+ residueIndex = searchPattern.matchedFrom() + 1;
/*
* return false if the match is entirely in a hidden region
}
/**
- * Adds one match to the stored list. If hidden residues are being skipped, then
- * the match may need to be split into contiguous positions of the sequence (so
- * it does not include skipped residues).
+ * Adds one match to the stored list. If hidden residues are being skipped,
+ * then the match may need to be split into contiguous positions of the
+ * sequence (so it does not include skipped residues).
*
* @param seq
* @param matchStartPosition
*/
int[] truePositions = searchedSequenceMap
.locateInFrom(matchStartPosition, matchEndPosition);
- for (int i = 0; i < truePositions.length - 1; i += 2)
- {
- searchResults.addResult(seq, truePositions[i], truePositions[i + 1]);
- }
+ searchResults.addResult(seq, truePositions);
}
/**
}
/**
+ * 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
+ */
+ protected boolean searchSequenceFeatures(int from, Regex searchPattern)
+ {
+ 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<SequenceFeature> allFeatures = null;
+ if (frm != null)
+ {
+ allFeatures = frm.findFeaturesAtResidue(seq, seq.getStart(),
+ seq.getEnd());
+ }
+ else
+ {
+ //allFeatures = sf.getAllFeatures(null);
+ allFeatures = sf.getAllFeatures();
+ }
+ // so we can check we are advancing when debugging
+ long fpos = 0;
+
+ for (SequenceFeature feature : allFeatures)
+ {
+ fpos++;
+ if (lastFeature != null)
+ {
+ // iterate till we find last feature matched
+ if (lastFeature != feature)
+ {
+ continue;
+ }
+ else
+ {
+ lastFeature = null;
+ continue;
+ }
+ }
+
+ if (searchPattern.search(feature.type) || (feature.description != null
+ && searchPattern.search(feature.description)))
+ {
+ searchResults.addResult(seq, feature.getBegin(), feature.getEnd());
+ lastFeature = feature;
+ return true;
+ }
+ }
+ residueIndex = Integer.MAX_VALUE;
+ lastFeature = null;
+ return false;
+ }
+
+ /**
* 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 searchPattern
* @return
*/
- protected boolean searchSequenceDescription(SequenceI seq, Regex searchPattern)
+ protected boolean searchSequenceDescription(SequenceI seq,
+ Regex searchPattern)
{
String desc = seq.getDescription();
- if (desc != null && searchPattern.search(desc) && !idMatches.contains(seq))
+ if (desc != null && searchPattern.search(desc)
+ && !idMatches.contains(seq))
{
idMatches.add(seq);
return true;
/**
* 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.
+ * adds the position to the search results and returns true, else answers
+ * false.
*
* @param seq
* @param resNo
return true;
}
int col = seq.findIndex(res); // base 1
- return viewport.getAlignment().getHiddenColumns().isVisible(col - 1); // base 0
+ return viewport.getAlignment().getHiddenColumns().isVisible(col - 1); // base
+ // 0
}
@Override
{
return searchResults;
}
+
+ @Override
+ public void setFeatureRenderer(FeatureRenderer featureRenderer)
+ {
+ frm = featureRenderer;
+ }
}