JAL-966 JAL-1738 JAL-345 interfaces for SearchResults and SearchResults.Match
[jalview.git] / src / jalview / datamodel / SearchResults.java
index e62e58a..2f62991 100755 (executable)
@@ -28,15 +28,19 @@ import java.util.List;
  * Holds a list of search result matches, where each match is a contiguous
  * stretch of a single sequence.
  * 
- * @author gmcarstairs
+ * @author gmcarstairs amwaterhouse
  *
  */
-public class SearchResults
+public class SearchResults implements SearchResultsI
 {
 
   private List<Match> matches = new ArrayList<Match>();
 
-  public class Match
+  /**
+   * One match consists of a sequence reference, start and end positions.
+   * Discontiguous ranges in a sequence require two or more Match objects.
+   */
+  public class Match implements SearchResultMatchI
   {
     SequenceI sequence;
 
@@ -63,63 +67,124 @@ public class SearchResults
     public Match(SequenceI seq, int start, int end)
     {
       sequence = seq;
-      this.start = start;
-      this.end = end;
+
+      /*
+       * always hold in forwards order, even if given in reverse order
+       * (such as from a mapping to a reverse strand); this avoids
+       * trouble for routines that highlight search results etc
+       */
+      if (start <= end)
+      {
+        this.start = start;
+        this.end = end;
+      }
+      else
+      {
+        this.start = end;
+        this.end = start;
+      }
     }
 
+    /* (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()
     {
       return end;
     }
 
     /**
-     * Returns the string of characters in the matched region.
+     * Returns the string of characters in the matched region, prefixed by the
+     * start position, e.g. "12CGT" or "208K"
      */
     @Override
     public String toString()
     {
+      final int from = Math.max(start - 1, 0);
+      String startPosition = String.valueOf(from);
+      return startPosition + getCharacters();
+    }
+
+    /* (non-Javadoc)
+     * @see jalview.datamodel.SearchResultMatchI#getCharacters()
+     */
+    @Override
+    public String getCharacters()
+    {
       char[] chars = sequence.getSequence();
       // convert start/end to base 0 (with bounds check)
       final int from = Math.max(start - 1, 0);
       final int to = Math.min(end, chars.length + 1);
       return String.valueOf(Arrays.copyOfRange(chars, from, to));
     }
+
+    public void setSequence(SequenceI seq)
+    {
+      this.sequence = seq;
+    }
+
+    /**
+     * Hashcode is the hashcode of the matched sequence plus a hash of start and
+     * end positions. Match objects that pass the test for equals are guaranteed
+     * to have the same hashcode.
+     */
+    @Override
+    public int hashCode()
+    {
+      int hash = sequence == null ? 0 : sequence.hashCode();
+      hash += 31 * start;
+      hash += 67 * end;
+      return hash;
+    }
+
+    /**
+     * Two Match objects are equal if they are for the same sequence, start and
+     * end positions
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+      if (obj == null || !(obj instanceof Match))
+      {
+        return false;
+      }
+      Match m = (Match) obj;
+      return (this.sequence == m.sequence && this.start == m.start && this.end == m.end);
+    }
   }
 
-  /**
-   * This method replaces the old search results which merely held an alignment
-   * index of search matches. This broke when sequences were moved around the
-   * alignment
-   * 
-   * @param seq
-   *          Sequence
-   * @param start
-   *          int
-   * @param end
-   *          int
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#addResult(jalview.datamodel.SequenceI, int, int)
    */
+  @Override
   public void addResult(SequenceI seq, int start, int end)
   {
     matches.add(new Match(seq, start, end));
   }
 
-  /**
-   * Quickly check if the given sequence is referred to in the search results
-   * 
-   * @param sequence
-   *          (specific alignment sequence or a dataset sequence)
-   * @return true if the results involve sequence
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#involvesSequence(jalview.datamodel.SequenceI)
    */
+  @Override
   public boolean involvesSequence(SequenceI sequence)
   {
     SequenceI ds = sequence.getDatasetSequence();
@@ -134,11 +199,10 @@ public class SearchResults
     return false;
   }
 
-  /**
-   * This Method returns the search matches which lie between the start and end
-   * points of the sequence in question. It is optimised for returning objects
-   * for drawing on SequenceCanvas
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#getResults(jalview.datamodel.SequenceI, int, int)
    */
+  @Override
   public int[] getResults(SequenceI sequence, int start, int end)
   {
     if (matches.isEmpty())
@@ -183,8 +247,7 @@ public class SearchResults
 
           if (result == null)
           {
-            result = new int[]
-            { matchStart, matchEnd };
+            result = new int[] { matchStart, matchEnd };
           }
           else
           {
@@ -207,62 +270,64 @@ public class SearchResults
     return result;
   }
 
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#getSize()
+   */
+  @Override
   public int getSize()
   {
     return matches.size();
   }
 
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#getResultSequence(int)
+   */
+  @Override
   public SequenceI getResultSequence(int index)
   {
     return matches.get(index).sequence;
   }
 
-  /**
-   * Returns the start position of the i'th match in the search results.
-   * 
-   * @param i
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#getResultStart(int)
    */
+  @Override
   public int getResultStart(int i)
   {
     return matches.get(i).start;
   }
 
-  /**
-   * Returns the end position of the i'th match in the search results.
-   * 
-   * @param i
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#getResultEnd(int)
    */
+  @Override
   public int getResultEnd(int i)
   {
     return matches.get(i).end;
   }
 
-  /**
-   * Returns true if no search result matches are held.
-   * 
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#isEmpty()
    */
+  @Override
   public boolean isEmpty()
   {
     return matches.isEmpty();
   }
 
-  /**
-   * Returns the list of matches.
-   * 
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.SearchResultsI#getResults()
    */
+  @Override
   public List<Match> getResults()
   {
     return matches;
   }
 
   /**
-   * Return the results as a string of characters. Meant for use when the
-   * context ensures that all matches are to regions of the same sequence
-   * (otherwise the result is meaningless).
+   * Return the results as a string of characters (bases) prefixed by start
+   * position(s). Meant for use when the context ensures that all matches are to
+   * regions of the same sequence (otherwise the result is meaningless).
    * 
    * @return
    */
@@ -270,10 +335,53 @@ public class SearchResults
   public String toString()
   {
     StringBuilder result = new StringBuilder(256);
-    for (Match m : matches)
+    for (SearchResultMatchI m : matches)
     {
       result.append(m.toString());
     }
     return result.toString();
   }
+
+  /**
+   * Return the results as a string of characters (bases). Meant for use when
+   * the context ensures that all matches are to regions of the same sequence
+   * (otherwise the result is meaningless).
+   * 
+   * @return
+   */
+  public String getCharacters()
+  {
+    StringBuilder result = new StringBuilder(256);
+    for (SearchResultMatchI m : matches)
+    {
+      result.append(m.getCharacters());
+    }
+    return result.toString();
+  }
+
+  /**
+   * Hashcode is has derived from the list of matches. This ensures that when
+   * two SearchResults objects satisfy the test for equals(), then they have the
+   * same hashcode.
+   */
+  @Override
+  public int hashCode()
+  {
+    return matches.hashCode();
+  }
+
+  /**
+   * Two SearchResults are considered equal if they contain the same matches in
+   * the same order.
+   */
+  @Override
+  public boolean equals(Object obj)
+  {
+    if (obj == null || !(obj instanceof SearchResults))
+    {
+      return false;
+    }
+    SearchResults sr = (SearchResults) obj;
+    return ((ArrayList<Match>) this.matches).equals(sr.matches);
+  }
 }