From: gmungoc Date: Fri, 9 Mar 2018 16:05:54 +0000 (+0000) Subject: JAL-2839 Finder refactoring prior to fixing the defect X-Git-Tag: Release_2_11_0~17^2~67^2~8 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=dfc18d64376ae95cfdbf3c112825587a8891179c;p=jalview.git JAL-2839 Finder refactoring prior to fixing the defect --- diff --git a/src/jalview/analysis/Finder.java b/src/jalview/analysis/Finder.java index 191f6e8..d7bf0a2 100644 --- a/src/jalview/analysis/Finder.java +++ b/src/jalview/analysis/Finder.java @@ -28,301 +28,340 @@ import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.util.Comparison; -import java.util.ArrayList; -import java.util.List; import java.util.Vector; import com.stevesoft.pat.Regex; +/** + * Implements the search algorithm for the Find dialog + */ public class Finder { - /** - * Implements the search algorithms for the Find dialog box. + /* + * match residue locations */ - SearchResultsI searchResults; + private SearchResultsI searchResults; - AlignmentI alignment; + /* + * sequences matched by id or description + */ + private Vector idMatch; - SequenceGroup selection = null; + /* + * the alignment to search over + */ + private AlignmentI alignment; - Vector idMatch = null; + /* + * (optional) selection to restrict search to + */ + private SequenceGroup selection; - boolean caseSensitive = false; + /* + * set true for case-sensitive search (default is false) + */ + private boolean caseSensitive; - private boolean includeDescription = false; + /* + * set true to search sequence description (default is false) + */ + private boolean includeDescription; - boolean findAll = false; + /* + * set true to return all matches (default is next match only) + */ + private boolean findAll; - Regex regex = null; + /* + * sequence index in alignment to search from + */ + private int seqIndex; - /** - * holds last-searched position between calls to find(false) + /* + * residue position in sequence to search from, base 1 + * (position of last match for a repeat search) */ - int seqIndex = 0, resIndex = -1; + private int resIndex; - public Finder(AlignmentI alignment, SequenceGroup selection) + /** + * Constructor to start searching an alignment, optionally restricting results + * to a selection + * + * @param al + * @param sel + */ + public Finder(AlignmentI al, SequenceGroup sel) { - this.alignment = alignment; - this.selection = selection; + this(al, sel, 0, -1); } /** - * restart search at given sequence and residue on alignment and (optionally) - * contained in selection + * Constructor to resume search at given sequence and residue on alignment and + * (optionally) restricted to a selection * - * @param alignment - * @param selectionGroup - * @param seqIndex - * @param resIndex + * @param al + * @param sel + * @param seqindex + * @param resindex */ - public Finder(AlignmentI alignment, SequenceGroup selectionGroup, - int seqIndex, int resIndex) + public Finder(AlignmentI al, SequenceGroup sel, int seqindex, + int resindex) { - this(alignment, selectionGroup); - this.seqIndex = seqIndex; - this.resIndex = resIndex; + this.alignment = al; + this.selection = sel; + this.seqIndex = seqindex; + this.resIndex = resindex; } - public boolean find(String searchString) + /** + * Performs a find for the given search string. By default the next match is + * found, but if setFindAll(true) has been called, then all matches are found. + * Sequences matched by id or description can be retrieved by getIdMatch(), + * and matched residue patterns by getSearchResults(). + * + * @param theSearchString + * @return + */ + public void find(String theSearchString) { - boolean hasResults = false; - if (!caseSensitive) - { - searchString = searchString.toUpperCase(); - } - regex = new Regex(searchString); + String searchString = caseSensitive ? theSearchString.toUpperCase() + : theSearchString; + Regex regex = new Regex(searchString); regex.setIgnoreCase(!caseSensitive); searchResults = new SearchResults(); - idMatch = new Vector(); - String item = null; - boolean found = false; - int end = alignment.getHeight(); - - // ///////////////////////////////////////////// + idMatch = new Vector<>(); - if (selection != null) + if (selection != null && selection.getSize() < 1) { - if ((selection.getSize() < 1) - || ((selection.getEndRes() - selection.getStartRes()) < 2)) - { - selection = null; - } + selection = null; // ? ignore column-only selection } - SearchResultMatchI lastm = null; - while (!found && (seqIndex < end)) + boolean finished = false; + int end = alignment.getHeight(); + + while (!finished && (seqIndex < end)) { SequenceI seq = alignment.getSequenceAt(seqIndex); - if ((selection != null && selection.getSize() > 0) - && !selection.getSequences(null).contains(seq)) + if ((selection != null) && !selection.contains(seq)) { + // this sequence is not in the selection - skip to next sequence seqIndex++; resIndex = -1; - continue; } + if (resIndex < 0) { + /* + * at start of sequence; try find by residue number, in sequence id, + * or (optionally) in sequence description + */ resIndex = 0; - // test for one off matches - sequence position and sequence ID - // //// is the searchString a residue number? - try - { - int res = Integer.parseInt(searchString); - // possibly a residue number - check if valid for seq - if (seq.getEnd() >= res) - { - searchResults.addResult(seq, res, res); - hasResults = true; - // resIndex=seq.getLength(); - // seqIndex++; - if (!findAll) - { - found = true; - break; - } - } - } catch (NumberFormatException ex) + if (doNonMotifSearches(seq, searchString, regex)) { + return; } + } - if (regex.search(seq.getName()) && !idMatch.contains(seq)) - { - idMatch.addElement(seq); - hasResults = true; - if (!findAll) - { - // stop and return the match - found = true; - break; - } - } + finished = searchSequenceString(seq, regex) && !findAll; - if (isIncludeDescription() && seq.getDescription() != null - && regex.search(seq.getDescription()) - && !idMatch.contains(seq)) - { - idMatch.addElement(seq); - hasResults = true; - if (!findAll) - { - // stop and return the match - found = true; - break; - } - } + if (!finished) + { + seqIndex++; + resIndex = -1; } - item = seq.getSequenceAsString(); + } + } - if ((selection != null) - && (selection.getEndRes() < alignment.getWidth() - 1)) + /** + * Searches the sequence, starting from resIndex (base 1), and + * adds matches to searchResults. The search is restricted to the + * selection region if there is one. Answers true if any match is + * added, else false. + * + * @param seq + * @param regex + * @return + */ + protected boolean searchSequenceString(SequenceI seq, Regex regex) + { + /* + * Restrict search to selected region if there is one + */ + int seqColStart = 0; + int seqColEnd = seq.getLength() - 1; + int residueOffset = 0; + if (selection != null) + { + int selColEnd = selection.getEndRes(); + int selColStart = selection.getStartRes(); + if (selColStart > seqColEnd) { - item = item.substring(0, selection.getEndRes() + 1); + return false; // sequence doesn't reach selection region } + seqColStart = selColStart; + seqColEnd = Math.min(seqColEnd, selColEnd); + residueOffset = seq.findPosition(selection.getStartRes()) + - seq.getStart(); + } + String seqString = seq.getSequenceAsString(seqColStart, seqColEnd + 1); - // /Shall we ignore gaps???? - JBPNote: Add Flag for forcing this or not - StringBuilder noGapsSB = new StringBuilder(); - int insertCount = 0; - List spaces = new ArrayList(); + String noGaps = AlignSeq.extractGaps(Comparison.GapChars, seqString); - for (int j = 0; j < item.length(); j++) - { - if (!Comparison.isGap(item.charAt(j))) - { - noGapsSB.append(item.charAt(j)); - spaces.add(Integer.valueOf(insertCount)); - } - else - { - insertCount++; - } - } + SearchResultMatchI lastMatch = null; + boolean found = false; - String noGaps = noGapsSB.toString(); - for (int r = resIndex; r < noGaps.length(); r++) + for (int r = resIndex; r < noGaps.length(); r++) + { + /* + * searchFrom position is base 0, r is base 1, + * so search is from the position after the r'th residue + */ + if (regex.searchFrom(noGaps, r)) { - - if (regex.searchFrom(noGaps, r)) + resIndex = regex.matchedFrom(); + resIndex += residueOffset; // add back #residues before selection region + int matchStartPosition = resIndex + seq.getStart(); + int matchEndPosition = matchStartPosition + regex.charsMatched() + - 1; + if (lastMatch == null || !lastMatch.contains(seq, + matchStartPosition, matchEndPosition)) { - resIndex = regex.matchedFrom(); - - if ((selection != null && selection.getSize() > 0) && (resIndex - + spaces.get(resIndex) < selection.getStartRes())) - { - continue; - } - // if invalid string used, then regex has no matched to/from - int sres = seq.findPosition(resIndex + spaces.get(resIndex)); - int eres = seq.findPosition(regex.matchedTo() - 1 - + (spaces.get(regex.matchedTo() - 1))); - // only add result if not contained in previous result - if (lastm == null || (lastm.getSequence() != seq - || (!(lastm.getStart() <= sres - && lastm.getEnd() >= eres)))) - { - lastm = searchResults.addResult(seq, sres, eres); - } - hasResults = true; - if (!findAll) - { - // thats enough, break and display the result - found = true; - resIndex++; - - break; - } - - r = resIndex; + lastMatch = searchResults.addResult(seq, matchStartPosition, + matchEndPosition); + found = true; } - else + if (!findAll) { - break; + resIndex++; + return true; } + r = resIndex; } - - if (!found) + else { - seqIndex++; - resIndex = -1; + break; } } - - /** - * We now search the Id string in the main search loop. for (int id = 0; id - * < alignment.getHeight(); id++) { if - * (regex.search(alignment.getSequenceAt(id).getName())) { - * idMatch.addElement(alignment.getSequenceAt(id)); hasResults = true; } } - */ - return hasResults; - } - - /** - * @return the alignment - */ - public AlignmentI getAlignment() - { - return alignment; - } - - /** - * @param alignment - * the alignment to set - */ - public void setAlignment(AlignmentI alignment) - { - this.alignment = alignment; + return found; } /** - * @return the caseSensitive + * Does searches other than for residue patterns. Currently this includes + *
    + *
  • find residue by position (if search string is a number)
  • + *
  • match search string to sequence id
  • + *
  • match search string to sequence description (optional)
  • + *
+ * Answers true if a match is found and we are not doing 'find all' (so this + * search action is complete), else false. + * + * @param seq + * @param searchString + * @param regex + * @return */ - public boolean isCaseSensitive() + protected boolean doNonMotifSearches(SequenceI seq, String searchString, + Regex regex) { - return caseSensitive; + if (searchForResidueNumber(seq, searchString) && !findAll) + { + return true; + } + if (searchSequenceName(seq, regex) && !findAll) + { + return true; + } + if (searchSequenceDescription(seq, regex) && !findAll) + { + return true; + } + return false; } /** - * @param caseSensitive - * the caseSensitive to set + * Searches for a match with the sequence description, if that option was + * requested, 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 regex + * @return */ - public void setCaseSensitive(boolean caseSensitive) + protected boolean searchSequenceDescription(SequenceI seq, Regex regex) { - this.caseSensitive = caseSensitive; + if (!includeDescription) + { + return false; + } + String desc = seq.getDescription(); + if (desc != null && regex.search(desc) && !idMatch.contains(seq)) + { + idMatch.addElement(seq); + return true; + } + return false; } /** - * @return the findAll + * 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 regex + * @return */ - public boolean isFindAll() + protected boolean searchSequenceName(SequenceI seq, Regex regex) { - return findAll; + if (regex.search(seq.getName()) && !idMatch.contains(seq)) + { + idMatch.addElement(seq); + return true; + } + return false; } /** - * @param findAll - * the findAll to set + * Tries to interpret the search string as a residue position, and if valid, + * adds the position to the search results */ - public void setFindAll(boolean findAll) + protected boolean searchForResidueNumber(SequenceI seq, String searchString) { - this.findAll = findAll; + try + { + int res = Integer.parseInt(searchString); + if (seq.getStart() <= res && seq.getEnd() >= res) + { + searchResults.addResult(seq, res, res); + return true; + } + } catch (NumberFormatException ex) + { + } + return false; } /** - * @return the selection + * Sets whether the search is case sensitive (default is no) + * + * @param value */ - public jalview.datamodel.SequenceGroup getSelection() + public void setCaseSensitive(boolean value) { - return selection; + this.caseSensitive = value; } /** - * @param selection - * the selection to set + * Sets whether search returns all matches. Default is to return the next + * match only. + * + * @param value */ - public void setSelection(jalview.datamodel.SequenceGroup selection) + public void setFindAll(boolean value) { - this.selection = selection; + this.findAll = value; } /** @@ -337,14 +376,6 @@ public class Finder } /** - * @return the regex - */ - public com.stevesoft.pat.Regex getRegex() - { - return regex; - } - - /** * @return the searchResults */ public SearchResultsI getSearchResults() @@ -361,15 +392,6 @@ public class Finder } /** - * @param resIndex - * the resIndex to set - */ - public void setResIndex(int resIndex) - { - this.resIndex = resIndex; - } - - /** * @return the seqIndex */ public int getSeqIndex() @@ -378,21 +400,13 @@ public class Finder } /** - * @param seqIndex - * the seqIndex to set + * Sets whether search also searches in sequence description text (default is + * no) + * + * @param value */ - public void setSeqIndex(int seqIndex) - { - this.seqIndex = seqIndex; - } - - public boolean isIncludeDescription() - { - return includeDescription; - } - - public void setIncludeDescription(boolean includeDescription) + public void setIncludeDescription(boolean value) { - this.includeDescription = includeDescription; + this.includeDescription = value; } } diff --git a/src/jalview/appletgui/Finder.java b/src/jalview/appletgui/Finder.java index 675b862..ee0d4a0 100644 --- a/src/jalview/appletgui/Finder.java +++ b/src/jalview/appletgui/Finder.java @@ -115,8 +115,8 @@ public class Finder extends Panel implements ActionListener public void createNewGroup_actionPerformed() { - List seqs = new ArrayList(); - List features = new ArrayList(); + List seqs = new ArrayList<>(); + List features = new ArrayList<>(); String searchString = textfield.getText().trim(); for (SearchResultMatchI match : searchResults.getResults()) @@ -156,27 +156,15 @@ public class Finder extends Panel implements ActionListener resIndex = finder.getResIndex(); searchResults = finder.getSearchResults(); Vector idMatch = finder.getIdMatch(); - boolean haveResults = false; - // set or reset the GUI - if ((idMatch.size() > 0)) - { - haveResults = true; - ap.idPanel.highlightSearchResults(idMatch); - } - else - { - ap.idPanel.highlightSearchResults(null); - } + ap.idPanel.highlightSearchResults(idMatch); - if (searchResults.getSize() > 0) + if (searchResults.isEmpty()) { - haveResults = true; - createNewGroup.setEnabled(true); - + searchResults = null; } else { - searchResults = null; + createNewGroup.setEnabled(true); } // if allResults is null, this effectively switches displaySearch flag in @@ -184,7 +172,7 @@ public class Finder extends Panel implements ActionListener ap.highlightSearchResults(searchResults); // TODO: add enablers for 'SelectSequences' or 'SelectColumns' or // 'SelectRegion' selection - if (!haveResults) + if (idMatch.isEmpty() && searchResults == null) { ap.alignFrame.statusBar.setText( MessageManager.getString("label.finished_searching")); diff --git a/src/jalview/datamodel/SearchResultMatchI.java b/src/jalview/datamodel/SearchResultMatchI.java index a47ca8b..661ad6c 100644 --- a/src/jalview/datamodel/SearchResultMatchI.java +++ b/src/jalview/datamodel/SearchResultMatchI.java @@ -47,4 +47,14 @@ public interface SearchResultMatchI */ int getEnd(); + /** + * Answers true if this match is for the given sequence and includes (matches + * or encloses) the given start-end range + * + * @param seq + * @param start + * @param end + * @return + */ + boolean contains(SequenceI seq, int start, int end); } \ No newline at end of file diff --git a/src/jalview/datamodel/SearchResults.java b/src/jalview/datamodel/SearchResults.java index cde50e5..63a87a8 100755 --- a/src/jalview/datamodel/SearchResults.java +++ b/src/jalview/datamodel/SearchResults.java @@ -91,27 +91,18 @@ public class SearchResults implements SearchResultsI } } - /* (non-Javadoc) - * @see jalview.datamodel.SearchResultMatchI#getSequence() - */ @Override public SequenceI getSequence() { return sequence; } - /* (non-Javadoc) - * @see jalview.datamodel.SearchResultMatchI#getStart() - */ @Override public int getStart() { return start; } - /* (non-Javadoc) - * @see jalview.datamodel.SearchResultMatchI#getEnd() - */ @Override public int getEnd() { @@ -162,6 +153,12 @@ public class SearchResults implements SearchResultsI return (sequence == m.getSequence() && start == m.getStart() && end == m.getEnd()); } + + @Override + public boolean contains(SequenceI seq, int from, int to) + { + return (sequence == seq && start <= from && end >= to); + } } /* (non-Javadoc) diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index 8dce31e..57e4525 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -112,9 +112,11 @@ public interface SequenceI extends ASequenceI * get a range on the sequence as a string * * @param start - * position relative to start of sequence including gaps (from 0) + * (inclusive) position relative to start of sequence including gaps + * (from 0) * @param end - * position relative to start of sequence including gaps (from 0) + * (exclusive) position relative to start of sequence including gaps + * (from 0) * * @return String containing all gap and symbols in specified range */ diff --git a/src/jalview/gui/Finder.java b/src/jalview/gui/Finder.java index 84540f4..a75c6ff 100755 --- a/src/jalview/gui/Finder.java +++ b/src/jalview/gui/Finder.java @@ -210,8 +210,8 @@ public class Finder extends GFinder @Override public void createFeatures_actionPerformed() { - List seqs = new ArrayList(); - List features = new ArrayList(); + List seqs = new ArrayList<>(); + List features = new ArrayList<>(); String searchString = searchBox.getEditor().getItem().toString().trim(); String desc = "Search Results"; @@ -268,42 +268,28 @@ public class Finder extends GFinder finder.setFindAll(doFindAll); - finder.find(searchString); // returns true if anything was actually found + finder.find(searchString); seqIndex = finder.getSeqIndex(); resIndex = finder.getResIndex(); - searchResults = finder.getSearchResults(); // find(regex, - // caseSensitive.isSelected(), ) + searchResults = finder.getSearchResults(); Vector idMatch = finder.getIdMatch(); - boolean haveResults = false; - // set or reset the GUI - if ((idMatch.size() > 0)) - { - haveResults = true; - ap.getIdPanel().highlightSearchResults(idMatch); - } - else - { - ap.getIdPanel().highlightSearchResults(null); - } + ap.getIdPanel().highlightSearchResults(idMatch); - if (searchResults.getSize() > 0) + if (searchResults.isEmpty()) { - haveResults = true; - createFeatures.setEnabled(true); + searchResults = null; } else { - searchResults = null; + createFeatures.setEnabled(true); } - // if allResults is null, this effectively switches displaySearch flag in - // seqCanvas ap.highlightSearchResults(searchResults); // TODO: add enablers for 'SelectSequences' or 'SelectColumns' or // 'SelectRegion' selection - if (!haveResults) + if (idMatch.isEmpty() && searchResults == null) { JvOptionPane.showInternalMessageDialog(this, MessageManager.getString("label.finished_searching"), null, diff --git a/src/jalview/gui/IdPanel.java b/src/jalview/gui/IdPanel.java index a183144..baac012 100755 --- a/src/jalview/gui/IdPanel.java +++ b/src/jalview/gui/IdPanel.java @@ -432,7 +432,7 @@ public class IdPanel extends JPanel { getIdCanvas().setHighlighted(list); - if (list == null) + if (list == null || list.isEmpty()) { return; } diff --git a/test/jalview/analysis/FinderTest.java b/test/jalview/analysis/FinderTest.java index d7a509f..771712d 100644 --- a/test/jalview/analysis/FinderTest.java +++ b/test/jalview/analysis/FinderTest.java @@ -24,11 +24,13 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; +import jalview.bin.Cache; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.SearchResultMatchI; import jalview.datamodel.SearchResultsI; import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceGroup; import jalview.gui.AlignFrame; import jalview.gui.JvOptionPane; import jalview.io.DataSourceType; @@ -55,7 +57,11 @@ public class FinderTest @BeforeClass(groups = "Functional") public void setUp() { - String seqData = "seq1 ABCD--EF-GHI\n" + "seq2 A--BCDefHI\n" + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + Cache.applicationProperties.setProperty("PAD_GAPS", + Boolean.FALSE.toString()); + + String seqData = "seq1seq1/8-16 ABCD--EF-GHI\n" + "seq2 A--BCDefHI\n" + "seq3 --bcdEFH\n" + "seq4 aa---aMMMMMaaa\n"; af = new FileLoader().LoadFileWaitTillLoaded(seqData, DataSourceType.PASTE); @@ -63,19 +69,31 @@ public class FinderTest } /** - * Test for find all matches of a regular expression + * Test for find matches of a regular expression */ @Test(groups = "Functional") - public void testFindAll_regex() + public void testFind_regex() { + /* + * find next match only + */ Finder f = new Finder(al, null); - f.setFindAll(true); f.find("E.H"); // 'E, any character, H' + // should match seq2 efH only + SearchResultsI sr = f.getSearchResults(); + assertEquals(sr.getSize(), 1); + List matches = sr.getResults(); + assertSame(al.getSequenceAt(1), matches.get(0).getSequence()); + assertEquals(matches.get(0).getStart(), 5); + assertEquals(matches.get(0).getEnd(), 7); + f = new Finder(al, null); + f.setFindAll(true); + f.find("E.H"); // 'E, any character, H' // should match seq2 efH and seq3 EFH - SearchResultsI sr = f.getSearchResults(); + sr = f.getSearchResults(); assertEquals(sr.getSize(), 2); - List matches = sr.getResults(); + matches = sr.getResults(); assertSame(al.getSequenceAt(1), matches.get(0).getSequence()); assertSame(al.getSequenceAt(2), matches.get(1).getSequence()); assertEquals(matches.get(0).getStart(), 5); @@ -91,19 +109,40 @@ public class FinderTest public void testFind_residueNumber() { Finder f = new Finder(al, null); - f.setFindAll(true); - f.find("9"); - // seq1 and seq4 have 9 residues; no match in other sequences + /* + * find first match should return seq1 residue 9 + */ + f.find("9"); SearchResultsI sr = f.getSearchResults(); - assertEquals(sr.getSize(), 2); + assertEquals(sr.getSize(), 1); List matches = sr.getResults(); assertSame(al.getSequenceAt(0), matches.get(0).getSequence()); + assertEquals(matches.get(0).getStart(), 9); + assertEquals(matches.get(0).getEnd(), 9); + + /* + * find all matches should return seq1 and seq4 (others are too short) + */ + f = new Finder(al, null); + f.setFindAll(true); + f.find("9"); + sr = f.getSearchResults(); + assertEquals(sr.getSize(), 2); + matches = sr.getResults(); + assertSame(al.getSequenceAt(0), matches.get(0).getSequence()); assertSame(al.getSequenceAt(3), matches.get(1).getSequence()); assertEquals(matches.get(0).getStart(), 9); assertEquals(matches.get(0).getEnd(), 9); assertEquals(matches.get(1).getStart(), 9); assertEquals(matches.get(1).getEnd(), 9); + + /* + * parsing of search string as integer is strict + */ + f = new Finder(al, null); + f.find(" 9"); + assertTrue(f.getSearchResults().isEmpty()); } /** @@ -150,15 +189,28 @@ public class FinderTest * Test for matching within sequence descriptions */ @Test(groups = "Functional") - public void testFindAll_inDescription() + public void testFind_inDescription() { AlignmentI al2 = new Alignment(al); al2.getSequenceAt(0).setDescription("BRAF"); al2.getSequenceAt(1).setDescription("braf"); + + /* + * find first match only + */ Finder f = new Finder(al2, null); - f.setFindAll(true); f.setIncludeDescription(true); + f.find("rAF"); + assertEquals(f.getIdMatch().size(), 1); + assertSame(f.getIdMatch().get(0), al2.getSequenceAt(0)); + assertTrue(f.getSearchResults().isEmpty()); + /* + * find all matches + */ + f = new Finder(al2, null); + f.setFindAll(true); + f.setIncludeDescription(true); f.find("rAF"); assertEquals(f.getIdMatch().size(), 2); assertSame(f.getIdMatch().get(0), al2.getSequenceAt(0)); @@ -217,7 +269,8 @@ public class FinderTest f.setFindAll(true); /* - * case insensitive + * case insensitive; seq1 occurs twice in sequence id but + * only one match should be returned */ f.find("SEQ1"); assertEquals(f.getIdMatch().size(), 1); @@ -255,17 +308,47 @@ public class FinderTest } /** - * Test finding all matches of a sequence pattern in an alignment + * Test finding next match of a sequence pattern in an alignment + */ + @Test(groups = "Functional") + public void testFind() + { + Finder f = new Finder(al, null); + f.find("EfH"); + SearchResultsI searchResults = f.getSearchResults(); + assertEquals(searchResults.getSize(), 1); + SearchResultMatchI match = searchResults.getResults().get(0); + assertSame(al.getSequenceAt(1), match.getSequence()); + assertEquals(5, match.getStart()); + assertEquals(7, match.getEnd()); + } + + /** + * Test for JAL-2302 to verify that sub-matches are not included in a find all + * result */ @Test(groups = "Functional") - public void testFindAll_simpleMatch() + public void testFind_maximalResultOnly() { Finder f = new Finder(al, null); f.setFindAll(true); + f.find("M+"); + SearchResultsI searchResults = f.getSearchResults(); + assertEquals(searchResults.getSize(), 1); + SearchResultMatchI match = searchResults.getResults().get(0); + assertSame(al.getSequenceAt(3), match.getSequence()); + assertEquals(4, match.getStart()); // dataset sequence positions + assertEquals(8, match.getEnd()); // base 1 + } - /* - * case insensitive first - */ + /** + * Test finding all matches of a sequence pattern in an alignment + */ + @Test(groups = "Functional") + public void testFind_findAll() + { + Finder f = new Finder(al, null); + f.setFindAll(true); f.find("EfH"); SearchResultsI searchResults = f.getSearchResults(); assertEquals(searchResults.getSize(), 2); @@ -277,41 +360,163 @@ public class FinderTest assertSame(al.getSequenceAt(2), match.getSequence()); assertEquals(4, match.getStart()); assertEquals(6, match.getEnd()); + } - /* - * case sensitive - */ - f = new Finder(al, null); - f.setFindAll(true); + /** + * Test finding all matches, case-sensitive + */ + @Test(groups = "Functional") + public void testFind_findAllCaseSensitive() + { + Finder f = new Finder(al, null); f.setCaseSensitive(true); + f.setFindAll(true); f.find("BC"); - searchResults = f.getSearchResults(); + SearchResultsI searchResults = f.getSearchResults(); assertEquals(searchResults.getSize(), 2); - match = searchResults.getResults().get(0); + SearchResultMatchI match = searchResults.getResults().get(0); assertSame(al.getSequenceAt(0), match.getSequence()); - assertEquals(2, match.getStart()); - assertEquals(3, match.getEnd()); + assertEquals(match.getStart(), 9); + assertEquals(match.getEnd(), 10); match = searchResults.getResults().get(1); assertSame(al.getSequenceAt(1), match.getSequence()); + assertEquals(match.getStart(), 2); + assertEquals(match.getEnd(), 3); + } + + /** + * Test finding next match of a sequence pattern in a selection group + */ + @Test(groups = "Functional") + public void testFind_inSelection() + { + /* + * select sequences 2 and 3, columns 4-6 which contains + * BCD + * cdE + */ + SequenceGroup sg = new SequenceGroup(); + sg.setStartRes(3); + sg.setEndRes(5); + sg.addSequence(al.getSequenceAt(1), false); + sg.addSequence(al.getSequenceAt(2), false); + + Finder f = new Finder(al, sg); + f.find("b"); + assertTrue(f.getIdMatch().isEmpty()); + SearchResultsI searchResults = f.getSearchResults(); + assertEquals(searchResults.getSize(), 1); + SearchResultMatchI match = searchResults.getResults().get(0); + assertSame(al.getSequenceAt(1), match.getSequence()); assertEquals(2, match.getStart()); + assertEquals(2, match.getEnd()); + + /* + * a second Find should not return the 'b' in seq3 as outside the selection + */ + f.find("b"); + assertTrue(f.getSearchResults().isEmpty()); + assertTrue(f.getIdMatch().isEmpty()); + + f = new Finder(al, sg); + f.find("d"); + assertTrue(f.getIdMatch().isEmpty()); + searchResults = f.getSearchResults(); + assertEquals(searchResults.getSize(), 1); + match = searchResults.getResults().get(0); + assertSame(al.getSequenceAt(1), match.getSequence()); + assertEquals(4, match.getStart()); + assertEquals(4, match.getEnd()); + f.find("d"); + assertTrue(f.getIdMatch().isEmpty()); + searchResults = f.getSearchResults(); + assertEquals(searchResults.getSize(), 1); + match = searchResults.getResults().get(0); + assertSame(al.getSequenceAt(2), match.getSequence()); + assertEquals(3, match.getStart()); assertEquals(3, match.getEnd()); } /** - * Test for JAL-2302 to verify that sub-matches are not included in a find all - * result + * Test finding all matches of a search pattern in a selection group */ @Test(groups = "Functional") - public void testFind_maximalResultOnly() + public void testFind_findAllInSelection() { - Finder f = new Finder(al, null); + /* + * select sequences 2 and 3, columns 4-6 which contains + * BCD + * cdE + */ + SequenceGroup sg = new SequenceGroup(); + sg.setStartRes(3); + sg.setEndRes(5); + sg.addSequence(al.getSequenceAt(1), false); + sg.addSequence(al.getSequenceAt(2), false); + + /* + * search for 'e' should match two sequence ids and one residue + */ + Finder f = new Finder(al, sg); f.setFindAll(true); - f.find("M+"); + f.find("e"); + assertEquals(f.getIdMatch().size(), 2); + assertSame(f.getIdMatch().get(0), al.getSequenceAt(1)); + assertSame(f.getIdMatch().get(1), al.getSequenceAt(2)); SearchResultsI searchResults = f.getSearchResults(); assertEquals(searchResults.getSize(), 1); SearchResultMatchI match = searchResults.getResults().get(0); - assertSame(al.getSequenceAt(3), match.getSequence()); - assertEquals(4, match.getStart()); // dataset sequence positions - assertEquals(8, match.getEnd()); // base 1 + assertSame(al.getSequenceAt(2), match.getSequence()); + assertEquals(4, match.getStart()); + assertEquals(4, match.getEnd()); + + /* + * search for 'Q' should match two sequence ids only + */ + f = new Finder(al, sg); + f.setFindAll(true); + f.find("Q"); + assertEquals(f.getIdMatch().size(), 2); + assertSame(f.getIdMatch().get(0), al.getSequenceAt(1)); + assertSame(f.getIdMatch().get(1), al.getSequenceAt(2)); + assertTrue(f.getSearchResults().isEmpty()); + } + + /** + * Test finding in selection with a sequence too short to reach it + */ + @Test(groups = "Functional") + public void testFind_findAllInSelectionWithShortSequence() + { + /* + * select all sequences, columns 10-12 + * BCD + * cdE + */ + SequenceGroup sg = new SequenceGroup(); + sg.setStartRes(9); + sg.setEndRes(11); + sg.addSequence(al.getSequenceAt(0), false); + sg.addSequence(al.getSequenceAt(1), false); + sg.addSequence(al.getSequenceAt(2), false); + sg.addSequence(al.getSequenceAt(3), false); + + /* + * search for 'I' should match two sequence positions + */ + Finder f = new Finder(al, sg); + f.setFindAll(true); + f.find("I"); + assertTrue(f.getIdMatch().isEmpty()); + SearchResultsI searchResults = f.getSearchResults(); + assertEquals(searchResults.getSize(), 2); + SearchResultMatchI match = searchResults.getResults().get(0); + assertSame(al.getSequenceAt(0), match.getSequence()); + assertEquals(16, match.getStart()); + assertEquals(16, match.getEnd()); + match = searchResults.getResults().get(1); + assertSame(al.getSequenceAt(1), match.getSequence()); + assertEquals(8, match.getStart()); + assertEquals(8, match.getEnd()); } } diff --git a/test/jalview/datamodel/SearchResultsTest.java b/test/jalview/datamodel/SearchResultsTest.java index f1a6e20..1168af2 100644 --- a/test/jalview/datamodel/SearchResultsTest.java +++ b/test/jalview/datamodel/SearchResultsTest.java @@ -186,6 +186,25 @@ public class SearchResultsTest assertEquals(5, m.getEnd()); } + @Test(groups = { "Functional" }) + public void testMatchContains() + { + SequenceI seq1 = new Sequence("", "abcdefghijklm"); + SequenceI seq2 = new Sequence("", "abcdefghijklm"); + SearchResultMatchI m = new SearchResults().new Match(seq1, 2, 5); + + assertTrue(m.contains(seq1, 2, 5)); + assertTrue(m.contains(seq1, 3, 5)); + assertTrue(m.contains(seq1, 2, 4)); + assertTrue(m.contains(seq1, 3, 3)); + + assertFalse(m.contains(seq1, 2, 6)); + assertFalse(m.contains(seq1, 1, 5)); + assertFalse(m.contains(seq1, 1, 8)); + assertFalse(m.contains(seq2, 3, 3)); + assertFalse(m.contains(null, 3, 3)); + } + /** * test markColumns for creating column selections */