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>SimpleSeqSimilaritySearchHit</code> objects represent a
039 * similarity search hit of a query sequence to a sequence referenced
040 * in a SequenceDB object. The core data (score, E-value, P-value)
041 * have accessors, while supplementary data are stored in the
042 * Annotation object. Supplementary data are typically the more
043 * loosely formatted details which vary from one search program to
044 * another (and between 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 Gerald Loeffler
063 * @since 1.1
064 *
065 * @see AbstractChangeable
066 * @see SeqSimilaritySearchHit
067 * @see Annotatable
068 */
069public class SimpleSeqSimilaritySearchHit extends AbstractChangeable
070    implements SeqSimilaritySearchHit
071{
072    protected transient ChangeForwarder annotationForwarder;
073
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>SimpleSeqSimilaritySearchHit</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 SimpleSeqSimilaritySearchHit(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        // Lock the annotation by vetoing all changes
162        annotation.addChangeListener(ChangeListener.ALWAYS_VETO);
163
164        this.score         = score;
165        this.eValue        = eValue;
166        this.pValue        = pValue;
167        this.queryStart    = queryStart;
168        this.queryEnd      = queryEnd;
169        this.queryStrand   = queryStrand;
170        this.subjectStart  = subjectStart;
171        this.subjectEnd    = subjectEnd;
172        this.subjectStrand = subjectStrand;
173        this.subjectID     = subjectID;
174        this.annotation    = annotation;
175        this.subHits       = Collections.unmodifiableList(subHits);
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        SimpleSeqSimilaritySearchHit that =
254            (SimpleSeqSimilaritySearchHit) other;
255
256        if (! ObjectUtil.equals(this.score, that.score))
257            return false;
258        if (! ObjectUtil.equals(this.pValue, that.pValue))
259            return false;
260        if (! ObjectUtil.equals(this.eValue, that.eValue))
261            return false;
262        if (! ObjectUtil.equals(this.subjectID, that.subjectID))
263            return false;
264        if (! ObjectUtil.equals(this.subHits, that.subHits))
265            return false;
266
267        return true;
268    }
269
270    public int hashCode()
271    {
272        if (! hcCalc)
273        {
274            hc = ObjectUtil.hashCode(hc, score);
275            hc = ObjectUtil.hashCode(hc, pValue);
276            hc = ObjectUtil.hashCode(hc, eValue);
277            hc = ObjectUtil.hashCode(hc, subjectID);
278            hc = ObjectUtil.hashCode(hc, subHits);
279            hcCalc = true;
280        }
281
282        return hc;
283    }
284
285    public String toString()
286    {
287        return "SimpleSeqSimilaritySearchHit to " + getSubjectID()
288            + " with score " + getScore();
289    }
290
291    protected ChangeSupport getChangeSupport(ChangeType ct)
292    {
293        ChangeSupport cs = super.getChangeSupport(ct);
294
295        if (annotationForwarder == null &&
296            (ct.isMatchingType(Annotatable.ANNOTATION) || Annotatable.ANNOTATION.isMatchingType(ct)))
297        {
298            annotationForwarder =
299                new ChangeForwarder.Retyper(this, cs, Annotation.PROPERTY);
300            getAnnotation().addChangeListener(annotationForwarder,
301                                              Annotatable.ANNOTATION);
302        }
303
304        return cs;
305    }
306}