Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.datamodel

File SearchResults.java

 

Coverage histogram

../../img/srcFileCovDistChart8.png
21% of files have more coverage

Code metrics

62
136
24
2
487
332
64
0.47
5.67
12
2.67

Classes

Class Line # Actions
SearchResults 34 100 43
0.8846153688.5%
SearchResults.Match 44 36 21
0.5606060656.1%
 

Contributing tests

This file is covered by 133 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.datamodel;
22   
23    import java.util.ArrayList;
24    import java.util.BitSet;
25    import java.util.List;
26   
27    /**
28    * Holds a list of search result matches, where each match is a contiguous
29    * stretch of a single sequence.
30    *
31    * @author gmcarstairs amwaterhouse
32    *
33    */
 
34    public class SearchResults implements SearchResultsI
35    {
36    private int count;
37   
38    private ArrayList<SearchResultMatchI> matches = new ArrayList<>();
39   
40    /**
41    * One match consists of a sequence reference, start and end positions.
42    * Discontiguous ranges in a sequence require two or more Match objects.
43    */
 
44    public class Match
45    implements SearchResultMatchI, Comparable<SearchResultMatchI>
46    {
47    final SequenceI sequence;
48   
49    /**
50    * Start position of match in sequence (base 1)
51    */
52    final int start;
53   
54    /**
55    * End position (inclusive) (base 1)
56    */
57    final int end;
58   
59    /**
60    * create a Match on a range of sequence. Match always holds region in
61    * forwards order, even if given in reverse order (such as from a mapping to
62    * a reverse strand); this avoids trouble for routines that highlight search
63    * results etc
64    *
65    * @param seq
66    * a sequence
67    * @param start
68    * start position of matched range (base 1)
69    * @param end
70    * end of matched range (inclusive, base 1)
71    */
 
72  286 toggle public Match(SequenceI seq, int start, int end)
73    {
74  286 sequence = seq;
75   
76    /*
77    * always hold in forwards order, even if given in reverse order
78    * (such as from a mapping to a reverse strand); this avoids
79    * trouble for routines that highlight search results etc
80    */
81  286 if (start <= end)
82    {
83  285 this.start = start;
84  285 this.end = end;
85    }
86    else
87    {
88    // TODO: JBP could mark match as being specified in reverse direction
89    // for use
90    // by caller ? e.g. visualizing reverse strand highlight
91  1 this.start = end;
92  1 this.end = start;
93    }
94    }
95   
 
96  372 toggle @Override
97    public SequenceI getSequence()
98    {
99  372 return sequence;
100    }
101   
 
102  332 toggle @Override
103    public int getStart()
104    {
105  332 return start;
106    }
107   
 
108  262 toggle @Override
109    public int getEnd()
110    {
111  262 return end;
112    }
113   
114    /**
115    * Returns a representation as "seqid/start-end"
116    */
 
117  7 toggle @Override
118    public String toString()
119    {
120  7 StringBuilder sb = new StringBuilder();
121  7 if (sequence != null)
122    {
123  7 sb.append(sequence.getName()).append("/");
124    }
125  7 sb.append(start).append("-").append(end);
126  7 return sb.toString();
127    }
128   
129    /**
130    * Hashcode is the hashcode of the matched sequence plus a hash of start and
131    * end positions. Match objects that pass the test for equals are guaranteed
132    * to have the same hashcode.
133    */
 
134  8 toggle @Override
135    public int hashCode()
136    {
137  8 int hash = sequence == null ? 0 : sequence.hashCode();
138  8 hash += 31 * start;
139  8 hash += 67 * end;
140  8 return hash;
141    }
142   
143    /**
144    * Two Match objects are equal if they are for the same sequence, start and
145    * end positions
146    */
 
147  143 toggle @Override
148    public boolean equals(Object obj)
149    {
150  143 if (obj == null || !(obj instanceof SearchResultMatchI))
151    {
152  0 return false;
153    }
154  143 SearchResultMatchI m = (SearchResultMatchI) obj;
155  143 return (sequence == m.getSequence() && start == m.getStart()
156    && end == m.getEnd());
157    }
158   
 
159  50 toggle @Override
160    public boolean contains(SequenceI seq, int from, int to)
161    {
162  50 return (sequence == seq && start <= from && end >= to);
163    }
164   
 
165  21 toggle @Override
166    public boolean adjacent(SequenceI seq, int from, int to)
167    {
168  21 return (sequence == seq && ((start <= from && end >= to)
169    || (from <= (end + 1) && to >= (end + 1))
170    || (from <= (start - 1) && to >= (start - 1))));
171    }
172   
 
173  0 toggle @Override
174    public int compareTo(SearchResultMatchI o)
175    {
176  0 if (start < o.getStart())
177    {
178  0 return -1;
179    }
180  0 if (start > o.getStart())
181    {
182  0 return +1;
183    }
184  0 if (end < o.getEnd())
185    {
186  0 return -1;
187    }
188  0 if (end > o.getEnd())
189    {
190  0 return +1;
191    }
192  0 if (sequence != o.getSequence())
193    {
194  0 int hashc = sequence.hashCode(), oseq = o.getSequence().hashCode();
195  0 return (hashc < oseq) ? -1 : 1;
196    }
197  0 return 0;
198    }
199   
200    }
201   
 
202  263 toggle @Override
203    public SearchResultMatchI addResult(SequenceI seq, int start, int end)
204    {
205  263 Match m = new Match(seq, start, end);
206  263 if (!matches.contains(m))
207    {
208  261 matches.add(m);
209  261 count++;
210    }
211  263 return m;
212    }
213   
 
214  5 toggle @Override
215    public void addResult(SequenceI seq, int[] positions)
216    {
217    /*
218    * we only increment the match count by 1 - or not at all,
219    * if the matches are all duplicates of existing
220    */
221  5 int beforeCount = count;
222  17 for (int i = 0; i < positions.length - 1; i += 2)
223    {
224  12 addResult(seq, positions[i], positions[i + 1]);
225    }
226  5 if (count > beforeCount)
227    {
228  5 count = beforeCount + 1;
229    }
230    }
231   
 
232  9 toggle @Override
233    public boolean appendResult(SequenceI sequence, int start, int end)
234    {
235   
236  9 Match m = new Match(sequence, start, end);
237   
238  9 boolean appending = false;
239   
240    // we dynamically maintain an interval to add as we test each range in the
241    // list
242   
243  9 int cstart = start, cend = end;
244  9 List<SearchResultMatchI> toRemove = new ArrayList<>();
245  9 for (SearchResultMatchI thatm : matches)
246    {
247  11 if (thatm.getSequence() == sequence)
248    {
249  7 if (thatm.contains(sequence, cstart, cend))
250    {
251    // found a match containing the current range. nothing else to do
252    // except report if we operated on the list
253  0 return appending;
254    }
255  7 if (thatm.adjacent(sequence, cstart, cend))
256    {
257    // update the match to add with the adjacent start/end
258  6 start = Math.min(m.start, thatm.getStart());
259  6 end = Math.max(m.end, thatm.getEnd());
260    // and check if we keep or remove the old one
261  6 if (thatm.getStart() != start || thatm.getEnd() != end)
262    {
263  6 toRemove.add(thatm);
264  6 count--;
265  6 cstart = start;
266  6 cend = end;
267  6 appending = true;
268    }
269    else
270    {
271  0 return false;
272    }
273    }
274    }
275    }
276  9 matches.removeAll(toRemove);
277    {
278  9 matches.add(new Match(sequence, cstart, cend));
279  9 count++;
280    }
281  9 return appending;
282    }
283   
 
284  69 toggle @Override
285    public boolean involvesSequence(SequenceI sequence)
286    {
287  69 final int start = sequence.getStart();
288  69 final int end = sequence.getEnd();
289   
290  69 SequenceI ds = sequence.getDatasetSequence();
291  69 for (SearchResultMatchI m : matches)
292    {
293  72 SequenceI matched = m.getSequence();
294  72 if (matched != null && (matched == sequence || matched == ds)
295    && (m.getEnd() >= start) && (m.getStart() <= end))
296    {
297  16 return true;
298    }
299    }
300  53 return false;
301    }
302   
 
303  78 toggle @Override
304    public int[] getResults(SequenceI sequence, int start, int end)
305    {
306  78 if (matches.isEmpty())
307    {
308  0 return null;
309    }
310   
311  78 int[] result = null;
312  78 int[] tmp = null;
313  78 int resultLength, matchStart = 0, matchEnd = 0;
314  78 boolean mfound;
315  78 Match m;
316  78 for (SearchResultMatchI _m : matches)
317    {
318  126 m = (Match) _m;
319   
320  126 mfound = false;
321  126 if (m.sequence == sequence
322    || m.sequence == sequence.getDatasetSequence())
323    {
324  39 mfound = true;
325  39 matchStart = sequence.findIndex(m.start) - 1;
326  39 matchEnd = m.start == m.end ? matchStart
327    : sequence.findIndex(m.end) - 1;
328    }
329   
330  126 if (mfound)
331    {
332  39 if (matchStart <= end && matchEnd >= start)
333    {
334  39 if (matchStart < start)
335    {
336  1 matchStart = start;
337    }
338   
339  39 if (matchEnd > end)
340    {
341  1 matchEnd = end;
342    }
343   
344  39 if (result == null)
345    {
346  39 result = new int[] { matchStart, matchEnd };
347    }
348    else
349    {
350  0 resultLength = result.length;
351  0 tmp = new int[resultLength + 2];
352  0 System.arraycopy(result, 0, tmp, 0, resultLength);
353  0 result = tmp;
354  0 result[resultLength] = matchStart;
355  0 result[resultLength + 1] = matchEnd;
356    }
357    }
358    else
359    {
360    // debug
361    // jalview.bin.Console.errPrintln("Outwith bounds!" +
362    // matchStart+">"+end +" or "
363    // + matchEnd+"<"+start);
364    }
365    }
366    }
367  78 return result;
368    }
369   
 
370  8 toggle @Override
371    public int markColumns(SequenceCollectionI sqcol, BitSet bs)
372    {
373  8 int count = 0;
374  8 BitSet mask = new BitSet();
375  8 int startRes = sqcol.getStartRes();
376  8 int endRes = sqcol.getEndRes();
377   
378  8 for (SequenceI s : sqcol.getSequences())
379    {
380  27 int[] cols = getResults(s, startRes, endRes);
381  27 if (cols != null)
382    {
383  24 for (int pair = 0; pair < cols.length; pair += 2)
384    {
385  12 mask.set(cols[pair], cols[pair + 1] + 1);
386    }
387    }
388    }
389    // compute columns that were newly selected
390  8 BitSet original = (BitSet) bs.clone();
391  8 original.and(mask);
392  8 count = mask.cardinality() - original.cardinality();
393    // and mark ranges not already marked
394  8 bs.or(mask);
395  8 return count;
396    }
397   
 
398  49 toggle @Override
399    public int getCount()
400    {
401  49 return count;
402    }
403   
 
404  677 toggle @Override
405    public boolean isEmpty()
406    {
407  677 return matches.isEmpty();
408    }
409   
 
410  306 toggle @Override
411    public List<SearchResultMatchI> getResults()
412    {
413  306 return matches;
414    }
415   
416    /**
417    * Return the results as a list of matches [seq1/from-to, seq2/from-to, ...]
418    *
419    * @return
420    */
 
421  3 toggle @Override
422    public String toString()
423    {
424  3 return matches == null ? "" : matches.toString();
425    }
426   
427    /**
428    * Hashcode is derived from the list of matches. This ensures that when two
429    * SearchResults objects satisfy the test for equals(), then they have the
430    * same hashcode.
431    *
432    * @see Match#hashCode()
433    * @see java.util.AbstractList#hashCode()
434    */
 
435  6 toggle @Override
436    public int hashCode()
437    {
438  6 return matches.hashCode();
439    }
440   
441    /**
442    * Two SearchResults are considered equal if they contain the same matches
443    * (Sequence, start position, end position) in the same order
444    *
445    * @see Match#equals(Object)
446    */
 
447  39 toggle @Override
448    public boolean equals(Object obj)
449    {
450  39 if (obj == null || !(obj instanceof SearchResultsI))
451    {
452  5 return false;
453    }
454  34 SearchResultsI sr = (SearchResultsI) obj;
455  34 return matches.equals(sr.getResults());
456    }
457   
 
458  0 toggle @Override
459    public void addSearchResults(SearchResultsI toAdd)
460    {
461  0 matches.addAll(toAdd.getResults());
462    }
463   
 
464  8 toggle @Override
465    public List<SequenceI> getMatchingSubSequences()
466    {
467  8 List<SequenceI> seqs = new ArrayList<>();
468   
469    /*
470    * assemble dataset sequences, and template new sequence features,
471    * for the amend features dialog
472    */
473  8 for (SearchResultMatchI match : matches)
474    {
475  12 SequenceI seq = match.getSequence();
476  18 while (seq.getDatasetSequence() != null)
477    {
478  6 seq = seq.getDatasetSequence();
479    }
480    // getSubSequence is index-base0, findIndex returns index-base1
481  12 seqs.add(seq.getSubSequence(seq.findIndex(match.getStart()) - 1,
482    seq.findIndex(match.getEnd())));
483    }
484  8 return seqs;
485    }
486   
487    }