001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.bio.search;
023
024import java.util.Collections;
025import java.util.List;
026
027import org.biojava.bio.Annotatable;
028import org.biojava.bio.Annotation;
029import org.biojava.bio.seq.StrandedFeature.Strand;
030import org.biojava.utils.AbstractChangeable;
031import org.biojava.utils.ChangeForwarder;
032import org.biojava.utils.ChangeListener;
033import org.biojava.utils.ChangeSupport;
034import org.biojava.utils.ChangeType;
035import org.biojava.utils.ObjectUtil;
036
037/**
038 * <p><code>SequenceDBSearchHit</code> objects represent a similarity
039 * search hit of a query sequence to a sequence referenced in a
040 * SequenceDB object. The core data (score, E-value, P-value) have
041 * accessors, while supplementary data are stored in the Annotation
042 * object. Supplementary data are typically the more loosely formatted
043 * details which vary from one search program to another (and between
044 * versions of those programs).</p>
045 *
046 * <p>It is up to the user to define the meaning of the hit's
047 * query/subject start/end/strand with respect to its constituent
048 * sub-hits. One approach could be:</p>
049 *
050 * <ul>
051 * <li>Hit query/subject start == start of first sub-hit</li>
052 * <li>Hit query/subject   end == end of last sub-hit</li>
053 * <li>Hit strand == POSITIVE if all sub-hits have strand POSITIVE</li>
054 * <li>Hit strand == NEGATIVE if all sub-hits have strand NEGATIVE</li>
055 * <li>Hit strand == UNKNOWN if sub-hits have either mixed or any UNKNOWN
056 *     strands</li>
057 * <li>Hit strand == null if the concept of strandedness is inappropriate
058 *     for the sequence type i.e. for protein</li>
059 * </ul>
060 *
061 * @author Keith James
062 * @author Matthew Pocock
063 * @since 1.1
064 * @deprecated SimpleSeqSimilaritySearchHit has been made Annotatable
065 * and is now functionally identical.
066 * @see AbstractChangeable
067 * @see SeqSimilaritySearchHit
068 * @see Annotatable
069 */
070public class SequenceDBSearchHit extends AbstractChangeable
071    implements SeqSimilaritySearchHit, Annotatable
072{
073    protected transient ChangeForwarder annotationForwarder;
074    private double     score;
075    private double     pValue;
076    private double     eValue;
077    private int        queryStart;
078    private int        queryEnd;
079    private Strand     queryStrand;
080    private int        subjectStart;
081    private int        subjectEnd;
082    private Strand     subjectStrand;
083    private String     subjectID;
084    private Annotation annotation;
085    private List       subHits;
086
087    // Hashcode is cached after first calculation because the data on
088    // which is is based do not change
089    private int hc;
090    private boolean hcCalc;
091
092    /**
093     * Creates a new <code>SequenceDBSearchHit</code> object.
094     *
095     * @param score a <code>double</code> value; the score of the hit,
096     * which may not be NaN.
097     * @param eValue a <code>double</code> value; the E-value of the
098     * hit, which may be NaN.
099     * @param pValue a <code>double</code> value; the P-value of the
100     * hit, which may be NaN.
101     * @param queryStart the start of the first sub-hit on the query
102     * sequence.
103     * @param queryEnd the end of the last sub-hit on the query
104     * sequence.
105     * @param queryStrand the strand of the sub-hits on the query
106     * sequence, which may be null for protein similarities. If they
107     * are not all positive or all negative, then this should be the
108     * unknown strand.
109     * @param subjectStart the start of the first sub-hit on the subject
110     * sequence.
111     * @param subjectEnd the end of the last sub-hit on the subject
112     * sequence.
113     * @param subjectStrand the strand of the sub-hits on the subject
114     * sequence, which may be null for protein similarities. If they
115     * are not all positive or all negative, then this should be the
116     * unknown strand.
117     * @param subjectID a <code>String</code> representing the ID in
118     * the SequenceDB of the sequence which was hit, which may not be
119     * null.
120     * @param annotation an <code>Annotation</code> object, which may
121     * not be null.
122     * @param subHits a <code>List</code> object containing the
123     * subhits, which may not be null. They should be sorted in the
124     * order specified by the search program.
125     */
126    public SequenceDBSearchHit(double     score,
127                               double     eValue,
128                               double     pValue,
129                               int        queryStart,
130                               int        queryEnd,
131                               Strand     queryStrand,
132                               int        subjectStart,
133                               int        subjectEnd,
134                               Strand     subjectStrand,
135                               String     subjectID,
136                               Annotation annotation,
137                               List       subHits)
138    {
139        if (Double.isNaN(score))
140        {
141            throw new IllegalArgumentException("score was NaN");
142        }
143
144        // pValue may be NaN
145        // eValue may be NaN
146        if (subjectID == null)
147        {
148            throw new IllegalArgumentException("subjectID was null");
149        }
150
151        if (annotation == null)
152        {
153            throw new IllegalArgumentException("annotation was null");
154        }
155
156        if (subHits == null)
157        {
158            throw new IllegalArgumentException("subHits was null");
159        }
160
161        this.score         = score;
162        this.eValue        = eValue;
163        this.pValue        = pValue;
164        this.queryStart    = queryStart;
165        this.queryEnd      = queryEnd;
166        this.queryStrand   = queryStrand;
167        this.subjectStart  = subjectStart;
168        this.subjectEnd    = subjectEnd;
169        this.subjectStrand = subjectStrand;
170        this.subjectID     = subjectID;
171        this.annotation    = annotation;
172        this.subHits       = Collections.unmodifiableList(subHits);
173
174        // Lock the annotation by vetoing all changes
175        this.annotation.addChangeListener(ChangeListener.ALWAYS_VETO);
176
177        hcCalc = false;
178    }
179
180    public double getScore()
181    {
182        return score;
183    }
184
185    public double getPValue()
186    {
187        return pValue;
188    }
189
190    public double getEValue()
191    {
192        return eValue;
193    }
194
195    public int getQueryStart()
196    {
197        return queryStart;
198    }
199
200    public int getQueryEnd()
201    {
202        return queryEnd;
203    }
204
205    public Strand getQueryStrand()
206    {
207        return queryStrand;
208    }
209
210    public int getSubjectStart()
211    {
212        return subjectStart;
213    }
214
215    public int getSubjectEnd()
216    {
217        return subjectEnd;
218    }
219
220    public Strand getSubjectStrand()
221    {
222        return subjectStrand;
223    }
224
225    public String getSubjectID()
226    {
227        return subjectID;
228    }
229
230    public List getSubHits()
231    {
232        return subHits;
233    }
234
235    /**
236     * <code>getAnnotation</code> returns the Annotation associated
237     * with this hit.
238     *
239     * @return an <code>Annotation</code>.
240     */
241    public Annotation getAnnotation()
242    {
243        return annotation;
244    }
245
246    public boolean equals(Object other)
247    {
248        if (other == this) return true;
249        if (other == null) return false;
250
251        if (! other.getClass().equals(this.getClass())) return false;
252
253        SequenceDBSearchHit that = (SequenceDBSearchHit) other;
254
255        if (! ObjectUtil.equals(this.score, that.score))
256            return false;
257        if (! ObjectUtil.equals(this.pValue, that.pValue))
258            return false;
259        if (! ObjectUtil.equals(this.eValue, that.eValue))
260            return false;
261        if (! ObjectUtil.equals(this.subjectID, that.subjectID))
262            return false;
263        if (! ObjectUtil.equals(this.subHits, that.subHits))
264            return false;
265
266        return true;
267    }
268
269    public int hashCode()
270    {
271        if (! hcCalc)
272        {
273            hc = ObjectUtil.hashCode(hc, score);
274            hc = ObjectUtil.hashCode(hc, pValue);
275            hc = ObjectUtil.hashCode(hc, eValue);
276            hc = ObjectUtil.hashCode(hc, subjectID);
277            hc = ObjectUtil.hashCode(hc, subHits);
278            hcCalc = true;
279        }
280
281        return hc;
282    }
283
284    public String toString()
285    {
286        return "SequenceDBSearchHit to " + getSubjectID()
287            + " with score " + getScore();
288    }
289
290    protected ChangeSupport getChangeSupport(ChangeType ct)
291    {
292        ChangeSupport cs = super.getChangeSupport(ct);
293
294        if (annotationForwarder == null &&
295            (ct.isMatchingType(Annotatable.ANNOTATION) || Annotatable.ANNOTATION.isMatchingType(ct)))
296        {
297            annotationForwarder =
298                new ChangeForwarder.Retyper(this, cs, Annotation.PROPERTY);
299            getAnnotation().addChangeListener(annotationForwarder,
300                                              Annotatable.ANNOTATION);
301        }
302
303        return cs;
304    }
305}