JAL-2839 Finder refactoring prior to fixing the defect
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 9 Mar 2018 16:05:54 +0000 (16:05 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 9 Mar 2018 16:05:54 +0000 (16:05 +0000)
src/jalview/analysis/Finder.java
src/jalview/appletgui/Finder.java
src/jalview/datamodel/SearchResultMatchI.java
src/jalview/datamodel/SearchResults.java
src/jalview/datamodel/SequenceI.java
src/jalview/gui/Finder.java
src/jalview/gui/IdPanel.java
test/jalview/analysis/FinderTest.java
test/jalview/datamodel/SearchResultsTest.java

index 191f6e8..d7bf0a2 100644 (file)
@@ -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<SequenceI> idMatch;
 
-  SequenceGroup selection = null;
+  /*
+   * the alignment to search over
+   */
+  private AlignmentI alignment;
 
-  Vector<SequenceI> 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<SequenceI>();
-    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 <code>resIndex</code> (base 1), and
+   * adds matches to <code>searchResults</code>. The search is restricted to the
+   * <code>selection</code> 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<Integer> spaces = new ArrayList<Integer>();
+    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
+   * <ul>
+   * <li>find residue by position (if search string is a number)</li>
+   * <li>match search string to sequence id</li>
+   * <li>match search string to sequence description (optional)</li>
+   * </ul>
+   * 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;
   }
 }
index 675b862..ee0d4a0 100644 (file)
@@ -115,8 +115,8 @@ public class Finder extends Panel implements ActionListener
 
   public void createNewGroup_actionPerformed()
   {
-    List<SequenceI> seqs = new ArrayList<SequenceI>();
-    List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+    List<SequenceI> seqs = new ArrayList<>();
+    List<SequenceFeature> 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<SequenceI> 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"));
index a47ca8b..661ad6c 100644 (file)
@@ -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
index cde50e5..63a87a8 100755 (executable)
@@ -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)
index 8dce31e..57e4525 100755 (executable)
@@ -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
    */
index 84540f4..a75c6ff 100755 (executable)
@@ -210,8 +210,8 @@ public class Finder extends GFinder
   @Override
   public void createFeatures_actionPerformed()
   {
-    List<SequenceI> seqs = new ArrayList<SequenceI>();
-    List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+    List<SequenceI> seqs = new ArrayList<>();
+    List<SequenceFeature> 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<SequenceI> 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,
index a183144..baac012 100755 (executable)
@@ -432,7 +432,7 @@ public class IdPanel extends JPanel
   {
     getIdCanvas().setHighlighted(list);
 
-    if (list == null)
+    if (list == null || list.isEmpty())
     {
       return;
     }
index d7a509f..771712d 100644 (file)
@@ -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<SearchResultMatchI> 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<SearchResultMatchI> 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<SearchResultMatchI> 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());
   }
 }
index f1a6e20..1168af2 100644 (file)
@@ -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
    */