001package org.biojava.bio.program.ssaha;
002
003import java.io.PrintStream;
004
005/**
006 * The interface used to inform interested parties that some sequence has
007 * been searched and something found.
008 * <p>
009 * The callbacks will always be called in the order startSearch, hit,
010 * endSearch, during which time there may be multiple hit calls. The seqID
011 * of startSearch and endSearch will match. After this, a new startSearch
012 * may begin. These events will usually originate from the search method of
013 * DataStore.
014 *
015 * @author Matthew Pocock
016 */
017public interface SearchListener {
018  /**
019   * Indicates that a sequence is about to be searched against a DataStore.
020   *
021   * @param seqID  the id of the sequence to be searched
022   */
023  public void startSearch(String seqID);
024
025  /**
026   * Indicates that a sequence has been searched against a DataStore.
027   *
028   * @param seqID  the id of the sequence to be searched
029   */
030  public void endSearch(String seqID);
031  
032  /**
033   * There has been a hit between the query sequence and a database
034   * sequence.
035   *
036   * @param hitID  the number of the sequence hit; resolvable by
037   *               String id = DataStore.seqNameForID(hitID)
038   * @param queryOffset the offset into the query sequence
039   * @param hitOffset the offset into the sequence hit in the database
040   * @param hitLength the number of symbols hit
041   */
042  public void hit(
043    int hitID,
044    int queryOffset,
045    int hitOffset,
046    int hitLength
047  );
048
049  /**
050   * A simple wrapper implementation.
051   *
052   * <p>Extend this and over-ride any of the interface methods to implement
053   * SearchListeners that filter hits before passing them on to an
054   * underlying listener.</p>
055   * You can modify the search events the delegate sees by over-riding any of
056   * the SearchListener methods, modify the arguments
057   * and then call the method on super with the new arguments.
058   * You can drop hits by just not passing them onto the delegate using
059   * super.hits().
060   * <em>Note:</em> Be sure to maintain the nesting of start/stop search and
061   * hit, or you will confuse the delegate.
062   *
063   * @author Matthew Pocock
064   * @since 1.4
065   */
066  public static abstract class Wrapper
067  implements SearchListener {
068    private final SearchListener delegate;
069
070    public Wrapper(SearchListener delegate) {
071      this.delegate = delegate;
072    }
073
074    public void startSearch(String seqID) {
075      delegate.startSearch(seqID);
076    }
077
078    public void endSearch(String seqID) {
079      delegate.endSearch(seqID);
080    }
081
082    public void hit(
083      int hitID,
084      int queryOffset,
085      int hitOffset,
086      int hitLength
087    ) {
088      delegate.hit(hitID, queryOffset, hitOffset, hitLength);
089    }
090  }
091
092  /**
093   * A SearchListener that passes events on to two delegate listeners.
094   *
095   * <p>This allows you to build trees of listeners. This is usefull, for
096   * example, when echoing output from different listeners.</p>
097   *
098   * @author Matthew Pocock
099   * @since 1.4
100   */
101  public static final class Tee
102  implements SearchListener {
103    private final SearchListener d1;
104    private final SearchListener d2;
105
106    public Tee(SearchListener d1, SearchListener d2) {
107      if(d1 == null || d2 == null) {
108        throw new IllegalArgumentException(
109          "Delegates can not be null: " + d1 + " " + d2 );
110      }
111
112      this.d1 = d1;
113      this.d2 = d2;
114    }
115
116    public void startSearch(String seqID) {
117      d1.startSearch(seqID);
118      d2.startSearch(seqID);
119    }
120
121    public void endSearch(String seqID) {
122      d1.endSearch(seqID);
123      d2.endSearch(seqID);
124    }
125
126    public void hit(
127      int hitID,
128      int queryOffset,
129      int hitOffset,
130      int hitLength
131    ) {
132      d1.hit(hitID, queryOffset, hitOffset, hitLength);
133      d2.hit(hitID, queryOffset, hitOffset, hitLength);
134    }
135  }
136
137  /**
138   * A simple listener that filters out all hits that are too short.
139   *
140   * @author Matthew Pocock
141   * @since 1.4
142   */
143  public static final class FilterByLength
144  extends Wrapper {
145    private final int minLength;
146
147    public FilterByLength(SearchListener delegate, int minLength) {
148      super(delegate);
149      this.minLength = minLength;
150    }
151
152    public void hit(
153      int hitID,
154      int queryOffset,
155      int hitOffset,
156      int hitLength
157    ) {
158      if(hitLength >= minLength) {
159        super.hit(hitID, queryOffset, hitOffset, hitLength);
160      }
161    }
162  }
163
164  /**
165   * A SearchListener that prints events out to a PrintStream.
166   *
167   * <p>Use this for debugging purposes.</p>
168   *
169   * @author Matthew Pocock
170   * @since 1.4
171   */
172  public static final class Echo
173  implements SearchListener {
174    private final PrintStream out;
175
176    public Echo(PrintStream out) {
177      this.out = out;
178    }
179
180    public void startSearch(String seqID) {
181      out.println("startSearch: " + seqID);
182    }
183
184    public void endSearch(String seqID) {
185      out.println("endSearch: " + seqID);
186    }
187
188    public void hit(
189      int hitID,
190      int queryOffset,
191      int hitOffset,
192      int hitLength
193    ) {
194      out.println(
195        "hit." +
196        "\thitID: " + hitID +
197        "\tqueryOffset: " + queryOffset +
198        "\thitOffset: " + hitOffset +
199        "\thitLength: " + hitLength );
200    }
201  }
202}