Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
Finder | 48 | 177 | 73 |
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.analysis; | |
22 | ||
23 | import java.util.ArrayList; | |
24 | import java.util.Arrays; | |
25 | import java.util.Iterator; | |
26 | import java.util.List; | |
27 | import java.util.Locale; | |
28 | ||
29 | import com.stevesoft.pat.Regex; | |
30 | ||
31 | import jalview.api.AlignViewportI; | |
32 | import jalview.api.FeatureRenderer; | |
33 | import jalview.api.FinderI; | |
34 | import jalview.datamodel.AlignmentI; | |
35 | import jalview.datamodel.SearchResultMatchI; | |
36 | import jalview.datamodel.SearchResults; | |
37 | import jalview.datamodel.SearchResultsI; | |
38 | import jalview.datamodel.SequenceFeature; | |
39 | import jalview.datamodel.SequenceGroup; | |
40 | import jalview.datamodel.SequenceI; | |
41 | import jalview.datamodel.features.SequenceFeaturesI; | |
42 | import jalview.util.Comparison; | |
43 | import jalview.util.MapList; | |
44 | ||
45 | /** | |
46 | * Implements the search algorithm for the Find dialog | |
47 | */ | |
48 | public class Finder implements FinderI | |
49 | { | |
50 | /* | |
51 | * matched residue locations | |
52 | */ | |
53 | private SearchResultsI searchResults; | |
54 | ||
55 | /* | |
56 | * sequences matched by id or description | |
57 | */ | |
58 | private List<SequenceI> idMatches; | |
59 | ||
60 | /* | |
61 | * the viewport to search over | |
62 | */ | |
63 | private AlignViewportI viewport; | |
64 | ||
65 | /* | |
66 | * feature renderer model - if available | |
67 | */ | |
68 | FeatureRenderer frm = null; | |
69 | ||
70 | /* | |
71 | * sequence index in alignment to search from | |
72 | */ | |
73 | private int sequenceIndex; | |
74 | ||
75 | /* | |
76 | * position offset in sequence to search from, base 0 | |
77 | * (position after start of last match for a 'find next') | |
78 | */ | |
79 | private int residueIndex; | |
80 | ||
81 | /* | |
82 | * last feature matched when incrementally searching sequence features | |
83 | */ | |
84 | private SequenceFeature lastFeature; | |
85 | ||
86 | /* | |
87 | * last sequenceIndex used when lastFeature was discovered | |
88 | */ | |
89 | private int lastFeatureSequenceIndex; | |
90 | ||
91 | /* | |
92 | * the true sequence position of the start of the | |
93 | * last sequence searched (when 'ignore hidden regions' does not apply) | |
94 | */ | |
95 | private int searchedSequenceStartPosition; | |
96 | ||
97 | /* | |
98 | * when 'ignore hidden regions' applies, this holds the mapping from | |
99 | * the visible sequence positions (1, 2, ...) to true sequence positions | |
100 | */ | |
101 | private MapList searchedSequenceMap; | |
102 | ||
103 | private String seqToSearch; | |
104 | ||
105 | /** | |
106 | * Constructor for searching a viewport | |
107 | * | |
108 | * @param av | |
109 | */ | |
110 | 38 | public Finder(AlignViewportI av) |
111 | { | |
112 | 38 | this.viewport = av; |
113 | 38 | this.sequenceIndex = 0; |
114 | 38 | this.residueIndex = -1; |
115 | } | |
116 | ||
117 | 32 | @Override |
118 | public void findAll(String theSearchString, boolean matchCase, | |
119 | boolean searchDescription, boolean searchFeatureDesc, | |
120 | boolean ignoreHidden) | |
121 | { | |
122 | /* | |
123 | * search from the start | |
124 | */ | |
125 | 32 | lastFeature = null; |
126 | 32 | lastFeatureSequenceIndex = 0; |
127 | 32 | sequenceIndex = 0; |
128 | 32 | residueIndex = -1; |
129 | ||
130 | 32 | doFind(theSearchString, matchCase, searchDescription, searchFeatureDesc, |
131 | true, ignoreHidden); | |
132 | ||
133 | /* | |
134 | * reset to start for next search | |
135 | */ | |
136 | 32 | sequenceIndex = 0; |
137 | 32 | residueIndex = -1; |
138 | 32 | lastFeature = null; |
139 | 32 | lastFeatureSequenceIndex = 0; |
140 | } | |
141 | ||
142 | 23 | @Override |
143 | public void findNext(String theSearchString, boolean matchCase, | |
144 | boolean searchDescription, boolean searchFeatureDesc, | |
145 | boolean ignoreHidden) | |
146 | { | |
147 | 23 | doFind(theSearchString, matchCase, searchDescription, searchFeatureDesc, |
148 | false, ignoreHidden); | |
149 | ||
150 | 23 | if (searchResults.isEmpty() && idMatches.isEmpty()) |
151 | { | |
152 | /* | |
153 | * search failed - reset to start for next search | |
154 | */ | |
155 | 3 | sequenceIndex = 0; |
156 | 3 | residueIndex = -1; |
157 | 3 | lastFeature = null; |
158 | 3 | lastFeatureSequenceIndex = 0; |
159 | } | |
160 | } | |
161 | ||
162 | /** | |
163 | * Performs a 'find next' or 'find all' | |
164 | * | |
165 | * @param theSearchString | |
166 | * @param matchCase | |
167 | * @param searchDescription | |
168 | * @param findAll | |
169 | * @param ignoreHidden | |
170 | */ | |
171 | 55 | protected void doFind(String theSearchString, boolean matchCase, |
172 | boolean searchDescription, boolean searchFeatureDesc, | |
173 | boolean findAll, boolean ignoreHidden) | |
174 | { | |
175 | 55 | searchResults = new SearchResults(); |
176 | 55 | idMatches = new ArrayList<>(); |
177 | ||
178 | 55 | String searchString = matchCase ? theSearchString |
179 | : theSearchString.toUpperCase(Locale.ROOT); | |
180 | 55 | Regex searchPattern = new Regex(searchString); |
181 | 55 | searchPattern.setIgnoreCase(!matchCase); |
182 | ||
183 | 55 | SequenceGroup selection = viewport.getSelectionGroup(); |
184 | 55 | if (selection != null && selection.getSize() < 1) |
185 | { | |
186 | 0 | selection = null; // ? ignore column-only selection |
187 | } | |
188 | ||
189 | 55 | AlignmentI alignment = viewport.getAlignment(); |
190 | 55 | int end = alignment.getHeight(); |
191 | ||
192 | 55 | getSequence(ignoreHidden); |
193 | ||
194 | 55 | boolean found = false; |
195 | 272 | while ((!found || findAll) && sequenceIndex < end) |
196 | { | |
197 | 217 | found = findNextMatch(searchString, searchPattern, searchDescription, |
198 | searchFeatureDesc, ignoreHidden); | |
199 | } | |
200 | } | |
201 | ||
202 | /** | |
203 | * Calculates and saves the sequence string to search. The string is | |
204 | * restricted to the current selection region if there is one, and is saved | |
205 | * with all gaps removed. | |
206 | * <p> | |
207 | * If there are hidden columns, and option {@ignoreHidden} is selected, then | |
208 | * only visible positions of the sequence are included, and a mapping is also | |
209 | * constructed from the returned string positions to the true sequence | |
210 | * positions. | |
211 | * <p> | |
212 | * Note we have to do this each time {@code findNext} or {@code findAll} is | |
213 | * called, in case the alignment, selection group or hidden columns have | |
214 | * changed. In particular, if the sequence at offset {@code sequenceIndex} in | |
215 | * the alignment is (no longer) in the selection group, search is advanced to | |
216 | * the next sequence that is. | |
217 | * <p> | |
218 | * Sets sequence string to the empty string if there are no more sequences (in | |
219 | * selection group if any) at or after {@code sequenceIndex}. | |
220 | * <p> | |
221 | * Returns true if a sequence could be found, false if end of alignment was | |
222 | * reached | |
223 | * | |
224 | * @param ignoreHidden | |
225 | * @return | |
226 | */ | |
227 | 207 | private boolean getSequence(boolean ignoreHidden) |
228 | { | |
229 | 207 | AlignmentI alignment = viewport.getAlignment(); |
230 | 207 | if (sequenceIndex >= alignment.getHeight()) |
231 | { | |
232 | 35 | seqToSearch = ""; |
233 | 35 | return false; |
234 | } | |
235 | 172 | SequenceI seq = alignment.getSequenceAt(sequenceIndex); |
236 | 172 | SequenceGroup selection = viewport.getSelectionGroup(); |
237 | 172 | if (selection != null && !selection.contains(seq)) |
238 | { | |
239 | 9 | if (!nextSequence(ignoreHidden)) |
240 | { | |
241 | 4 | return false; |
242 | } | |
243 | 5 | seq = alignment.getSequenceAt(sequenceIndex); |
244 | } | |
245 | ||
246 | 168 | String seqString = null; |
247 | 168 | if (ignoreHidden) |
248 | { | |
249 | 33 | seqString = getVisibleSequence(seq); |
250 | 33 | this.searchedSequenceStartPosition = 1; |
251 | } | |
252 | else | |
253 | { | |
254 | 135 | int startCol = 0; |
255 | 135 | int endCol = seq.getLength() - 1; |
256 | 135 | this.searchedSequenceStartPosition = seq.getStart(); |
257 | 135 | if (selection != null) |
258 | { | |
259 | 21 | startCol = selection.getStartRes(); |
260 | 21 | endCol = Math.min(endCol, selection.getEndRes()); |
261 | 21 | this.searchedSequenceStartPosition = seq.findPosition(startCol); |
262 | } | |
263 | 135 | seqString = seq.getSequenceAsString(startCol, endCol + 1); |
264 | } | |
265 | ||
266 | /* | |
267 | * remove gaps; note that even if this leaves an empty string, we 'search' | |
268 | * the sequence anyway (for possible match on name or description) | |
269 | */ | |
270 | 168 | String ungapped = AlignSeq.extractGaps(Comparison.GapChars, seqString); |
271 | 168 | this.seqToSearch = ungapped; |
272 | ||
273 | 168 | return true; |
274 | } | |
275 | ||
276 | /** | |
277 | * Returns a string consisting of only the visible residues of {@code seq} | |
278 | * from alignment column {@ fromColumn}, restricted to the current selection | |
279 | * region if there is one. | |
280 | * <p> | |
281 | * As a side-effect, also computes the mapping from the true sequence | |
282 | * positions to the positions (1, 2, ...) of the returned sequence. This is to | |
283 | * allow search matches in the visible sequence to be converted to sequence | |
284 | * positions. | |
285 | * | |
286 | * @param seq | |
287 | * @return | |
288 | */ | |
289 | 33 | private String getVisibleSequence(SequenceI seq) |
290 | { | |
291 | /* | |
292 | * get start / end columns of sequence and convert to base 0 | |
293 | * (so as to match the visible column ranges) | |
294 | */ | |
295 | 33 | int seqStartCol = seq.findIndex(seq.getStart()) - 1; |
296 | 33 | int seqEndCol = seq.findIndex(seq.getStart() + seq.getLength() - 1) - 1; |
297 | 33 | Iterator<int[]> visibleColumns = viewport.getViewAsVisibleContigs(true); |
298 | 33 | StringBuilder visibleSeq = new StringBuilder(seqEndCol - seqStartCol); |
299 | 33 | List<int[]> fromRanges = new ArrayList<>(); |
300 | ||
301 | 93 | while (visibleColumns.hasNext()) |
302 | { | |
303 | 63 | int[] range = visibleColumns.next(); |
304 | 63 | if (range[0] > seqEndCol) |
305 | { | |
306 | // beyond the end of the sequence | |
307 | 3 | break; |
308 | } | |
309 | 60 | if (range[1] < seqStartCol) |
310 | { | |
311 | // before the start of the sequence | |
312 | 3 | continue; |
313 | } | |
314 | 57 | String subseq = seq.getSequenceAsString(range[0], range[1] + 1); |
315 | 57 | String ungapped = AlignSeq.extractGaps(Comparison.GapChars, subseq); |
316 | 57 | visibleSeq.append(ungapped); |
317 | 57 | if (!ungapped.isEmpty()) |
318 | { | |
319 | /* | |
320 | * visible region includes at least one non-gap character, | |
321 | * so add the range to the mapping being constructed | |
322 | */ | |
323 | 57 | int seqResFrom = seq.findPosition(range[0]); |
324 | 57 | int seqResTo = seqResFrom + ungapped.length() - 1; |
325 | 57 | fromRanges.add(new int[] { seqResFrom, seqResTo }); |
326 | } | |
327 | } | |
328 | ||
329 | /* | |
330 | * construct the mapping | |
331 | * from: visible sequence positions 1..length | |
332 | * to: true residue positions of the alignment sequence | |
333 | */ | |
334 | 33 | List<int[]> toRange = Arrays |
335 | .asList(new int[] | |
336 | { 1, visibleSeq.length() }); | |
337 | 33 | searchedSequenceMap = new MapList(fromRanges, toRange, 1, 1); |
338 | ||
339 | 33 | return visibleSeq.toString(); |
340 | } | |
341 | ||
342 | /** | |
343 | * Advances the search to the next sequence in the alignment. Sequences not in | |
344 | * the current selection group (if there is one) are skipped. The | |
345 | * (sub-)sequence to be searched is extracted, gaps removed, and saved, or set | |
346 | * to null if there are no more sequences to search. | |
347 | * <p> | |
348 | * Returns true if a sequence could be found, false if end of alignment was | |
349 | * reached | |
350 | * | |
351 | * @param ignoreHidden | |
352 | */ | |
353 | 152 | private boolean nextSequence(boolean ignoreHidden) |
354 | { | |
355 | 152 | sequenceIndex++; |
356 | 152 | residueIndex = -1; |
357 | ||
358 | 152 | return getSequence(ignoreHidden); |
359 | } | |
360 | ||
361 | /** | |
362 | * Finds the next match in the given sequence, starting at offset | |
363 | * {@code residueIndex}. Answers true if a match is found, else false. | |
364 | * <p> | |
365 | * If a match is found, {@code residueIndex} is advanced to the position after | |
366 | * the start of the matched region, ready for the next search. | |
367 | * <p> | |
368 | * If no match is found, {@code sequenceIndex} is advanced ready to search the | |
369 | * next sequence. | |
370 | * | |
371 | * @param seqToSearch | |
372 | * @param searchString | |
373 | * @param searchPattern | |
374 | * @param matchDescription | |
375 | * @param ignoreHidden | |
376 | * @return | |
377 | */ | |
378 | 217 | protected boolean findNextMatch(String searchString, Regex searchPattern, |
379 | boolean matchDescription, boolean matchFeatureDesc, | |
380 | boolean ignoreHidden) | |
381 | { | |
382 | 217 | if (residueIndex < 0) |
383 | { | |
384 | /* | |
385 | * at start of sequence; try find by residue number, in sequence id, | |
386 | * or (optionally) in sequence description | |
387 | */ | |
388 | 153 | if (doNonMotifSearches(searchString, searchPattern, matchDescription)) |
389 | { | |
390 | 17 | return true; |
391 | } | |
392 | } | |
393 | ||
394 | /* | |
395 | * search for next match in sequence string | |
396 | */ | |
397 | 200 | int end = seqToSearch.length(); |
398 | 354 | while (residueIndex < end) |
399 | { | |
400 | 211 | boolean matched = searchPattern.searchFrom(seqToSearch, residueIndex); |
401 | 211 | if (matched) |
402 | { | |
403 | 72 | if (recordMatch(searchPattern, ignoreHidden)) |
404 | { | |
405 | 51 | return true; |
406 | } | |
407 | } | |
408 | else | |
409 | { | |
410 | 139 | if (matchFeatureDesc) |
411 | { | |
412 | 17 | matched = searchSequenceFeatures(residueIndex, searchPattern); |
413 | 17 | if (matched) |
414 | { | |
415 | 6 | return true; |
416 | } | |
417 | 11 | lastFeature = null; |
418 | } | |
419 | 133 | residueIndex = Integer.MAX_VALUE; |
420 | } | |
421 | } | |
422 | ||
423 | 143 | nextSequence(ignoreHidden); |
424 | 143 | return false; |
425 | } | |
426 | ||
427 | /** | |
428 | * Adds the match held in the <code>searchPattern</code> Regex to the | |
429 | * <code>searchResults</code>, unless it is a subregion of the last match | |
430 | * recorded. <code>residueIndex</code> is advanced to the position after the | |
431 | * start of the matched region, ready for the next search. Answers true if a | |
432 | * match was added, else false. | |
433 | * <p> | |
434 | * Matches that lie entirely within hidden regions of the alignment are not | |
435 | * added. | |
436 | * | |
437 | * @param searchPattern | |
438 | * @param ignoreHidden | |
439 | * @return | |
440 | */ | |
441 | 72 | protected boolean recordMatch(Regex searchPattern, boolean ignoreHidden) |
442 | { | |
443 | 72 | SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex); |
444 | ||
445 | /* | |
446 | * convert start/end of the match to sequence coordinates | |
447 | */ | |
448 | 72 | int offset = searchPattern.matchedFrom(); |
449 | 72 | int matchStartPosition = this.searchedSequenceStartPosition + offset; |
450 | 72 | int matchEndPosition = matchStartPosition + searchPattern.charsMatched() |
451 | - 1; | |
452 | ||
453 | /* | |
454 | * update residueIndex to next position after the start of the match | |
455 | * (findIndex returns a value base 1, columnIndex is held base 0) | |
456 | */ | |
457 | 72 | residueIndex = searchPattern.matchedFrom() + 1; |
458 | ||
459 | /* | |
460 | * return false if the match is entirely in a hidden region | |
461 | */ | |
462 | 72 | if (allHidden(seq, matchStartPosition, matchEndPosition)) |
463 | { | |
464 | 5 | return false; |
465 | } | |
466 | ||
467 | /* | |
468 | * check that this match is not a subset of the previous one (JAL-2302) | |
469 | */ | |
470 | 67 | List<SearchResultMatchI> matches = searchResults.getResults(); |
471 | 67 | SearchResultMatchI lastMatch = matches.isEmpty() ? null |
472 | : matches.get(matches.size() - 1); | |
473 | ||
474 | 67 | if (lastMatch == null || !lastMatch.contains(seq, matchStartPosition, |
475 | matchEndPosition)) | |
476 | { | |
477 | 51 | addMatch(seq, matchStartPosition, matchEndPosition, ignoreHidden); |
478 | 51 | return true; |
479 | } | |
480 | ||
481 | 16 | return false; |
482 | } | |
483 | ||
484 | /** | |
485 | * Adds one match to the stored list. If hidden residues are being skipped, | |
486 | * then the match may need to be split into contiguous positions of the | |
487 | * sequence (so it does not include skipped residues). | |
488 | * | |
489 | * @param seq | |
490 | * @param matchStartPosition | |
491 | * @param matchEndPosition | |
492 | * @param ignoreHidden | |
493 | */ | |
494 | 51 | private void addMatch(SequenceI seq, int matchStartPosition, |
495 | int matchEndPosition, boolean ignoreHidden) | |
496 | { | |
497 | 51 | if (!ignoreHidden) |
498 | { | |
499 | /* | |
500 | * simple case | |
501 | */ | |
502 | 46 | searchResults.addResult(seq, matchStartPosition, matchEndPosition); |
503 | 46 | return; |
504 | } | |
505 | ||
506 | /* | |
507 | * get start-end contiguous ranges in underlying sequence | |
508 | */ | |
509 | 5 | int[] truePositions = searchedSequenceMap |
510 | .locateInFrom(matchStartPosition, matchEndPosition); | |
511 | 5 | searchResults.addResult(seq, truePositions); |
512 | } | |
513 | ||
514 | /** | |
515 | * Returns true if all residues are hidden, else false | |
516 | * | |
517 | * @param seq | |
518 | * @param fromPos | |
519 | * @param toPos | |
520 | * @return | |
521 | */ | |
522 | 72 | private boolean allHidden(SequenceI seq, int fromPos, int toPos) |
523 | { | |
524 | 72 | if (!viewport.hasHiddenColumns()) |
525 | { | |
526 | 49 | return false; |
527 | } | |
528 | 28 | for (int res = fromPos; res <= toPos; res++) |
529 | { | |
530 | 23 | if (isVisible(seq, res)) |
531 | { | |
532 | 18 | return false; |
533 | } | |
534 | } | |
535 | 5 | return true; |
536 | } | |
537 | ||
538 | /** | |
539 | * Does searches other than for residue patterns. Currently this includes | |
540 | * <ul> | |
541 | * <li>find residue by position (if search string is a number)</li> | |
542 | * <li>match search string to sequence id</li> | |
543 | * <li>match search string to sequence description (optional)</li> | |
544 | * </ul> | |
545 | * Answers true if a match is found, else false. | |
546 | * | |
547 | * @param searchString | |
548 | * @param searchPattern | |
549 | * @param includeDescription | |
550 | * @return | |
551 | */ | |
552 | 153 | protected boolean doNonMotifSearches(String searchString, |
553 | Regex searchPattern, boolean includeDescription) | |
554 | { | |
555 | 153 | SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex); |
556 | ||
557 | /* | |
558 | * position sequence search to start of sequence | |
559 | */ | |
560 | 153 | residueIndex = 0; |
561 | 153 | try |
562 | { | |
563 | 153 | int res = Integer.parseInt(searchString); |
564 | 5 | return searchForResidueNumber(seq, res); |
565 | } catch (NumberFormatException ex) | |
566 | { | |
567 | // search pattern is not a number | |
568 | } | |
569 | ||
570 | 148 | if (searchSequenceName(seq, searchPattern)) |
571 | { | |
572 | 10 | return true; |
573 | } | |
574 | 138 | if (includeDescription && searchSequenceDescription(seq, searchPattern)) |
575 | { | |
576 | 4 | return true; |
577 | } | |
578 | 134 | return false; |
579 | } | |
580 | ||
581 | /** | |
582 | * Searches for a match with the sequence features, and if found, adds the | |
583 | * sequence to the list of match ids, (but not as a duplicate). Answers true | |
584 | * if a match was added, else false. | |
585 | * | |
586 | * @param seq | |
587 | * @param searchPattern | |
588 | * @return | |
589 | */ | |
590 | 17 | protected boolean searchSequenceFeatures(int from, Regex searchPattern) |
591 | { | |
592 | 17 | if (lastFeatureSequenceIndex != sequenceIndex) |
593 | { | |
594 | 9 | lastFeatureSequenceIndex = sequenceIndex; |
595 | 9 | lastFeature = null; |
596 | } | |
597 | 17 | SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex); |
598 | 17 | SequenceFeaturesI sf = seq.getFeatures(); |
599 | ||
600 | // TODO - stash feature list and search incrementally | |
601 | 17 | List<SequenceFeature> allFeatures = null; |
602 | 17 | if (frm != null) |
603 | { | |
604 | 0 | allFeatures = frm.findFeaturesAtResidue(seq, seq.getStart(), |
605 | seq.getEnd()); | |
606 | } | |
607 | else | |
608 | { | |
609 | // allFeatures = sf.getAllFeatures(null); | |
610 | 17 | allFeatures = sf.getAllFeatures(); |
611 | } | |
612 | // so we can check we are advancing when debugging | |
613 | 17 | long fpos = 0; |
614 | ||
615 | 17 | for (SequenceFeature feature : allFeatures) |
616 | { | |
617 | 15 | fpos++; |
618 | 15 | if (lastFeature != null) |
619 | { | |
620 | // iterate till we find last feature matched | |
621 | 6 | if (lastFeature != feature) |
622 | { | |
623 | 1 | continue; |
624 | } | |
625 | else | |
626 | { | |
627 | 5 | lastFeature = null; |
628 | 5 | continue; |
629 | } | |
630 | } | |
631 | ||
632 | 9 | if (searchPattern.search(feature.type) || (feature.description != null |
633 | && searchPattern.search(feature.description))) | |
634 | { | |
635 | 6 | searchResults.addResult(seq, feature.getBegin(), feature.getEnd()); |
636 | 6 | lastFeature = feature; |
637 | 6 | return true; |
638 | } | |
639 | } | |
640 | 11 | residueIndex = Integer.MAX_VALUE; |
641 | 11 | lastFeature = null; |
642 | 11 | return false; |
643 | } | |
644 | ||
645 | /** | |
646 | * Searches for a match with the sequence description, and if found, adds the | |
647 | * sequence to the list of match ids (but not as a duplicate). Answers true if | |
648 | * a match was added, else false. | |
649 | * | |
650 | * @param seq | |
651 | * @param searchPattern | |
652 | * @return | |
653 | */ | |
654 | 11 | protected boolean searchSequenceDescription(SequenceI seq, |
655 | Regex searchPattern) | |
656 | { | |
657 | 11 | String desc = seq.getDescription(); |
658 | 11 | if (desc != null && searchPattern.search(desc) |
659 | && !idMatches.contains(seq)) | |
660 | { | |
661 | 4 | idMatches.add(seq); |
662 | 4 | return true; |
663 | } | |
664 | 7 | return false; |
665 | } | |
666 | ||
667 | /** | |
668 | * Searches for a match with the sequence name, and if found, adds the | |
669 | * sequence to the list of match ids (but not as a duplicate). Answers true if | |
670 | * a match was added, else false. | |
671 | * | |
672 | * @param seq | |
673 | * @param searchPattern | |
674 | * @return | |
675 | */ | |
676 | 148 | protected boolean searchSequenceName(SequenceI seq, Regex searchPattern) |
677 | { | |
678 | 148 | if (searchPattern.search(seq.getName()) && !idMatches.contains(seq)) |
679 | { | |
680 | 10 | idMatches.add(seq); |
681 | 10 | return true; |
682 | } | |
683 | 138 | return false; |
684 | } | |
685 | ||
686 | /** | |
687 | * If the residue position is valid for the sequence, and in a visible column, | |
688 | * adds the position to the search results and returns true, else answers | |
689 | * false. | |
690 | * | |
691 | * @param seq | |
692 | * @param resNo | |
693 | * @return | |
694 | */ | |
695 | 5 | protected boolean searchForResidueNumber(SequenceI seq, int resNo) |
696 | { | |
697 | 5 | if (seq.getStart() <= resNo && seq.getEnd() >= resNo) |
698 | { | |
699 | 3 | if (isVisible(seq, resNo)) |
700 | { | |
701 | 3 | searchResults.addResult(seq, resNo, resNo); |
702 | 3 | return true; |
703 | } | |
704 | } | |
705 | 2 | return false; |
706 | } | |
707 | ||
708 | /** | |
709 | * Returns true if the residue is in a visible column, else false | |
710 | * | |
711 | * @param seq | |
712 | * @param res | |
713 | * @return | |
714 | */ | |
715 | 26 | private boolean isVisible(SequenceI seq, int res) |
716 | { | |
717 | 26 | if (!viewport.hasHiddenColumns()) |
718 | { | |
719 | 0 | return true; |
720 | } | |
721 | 26 | int col = seq.findIndex(res); // base 1 |
722 | 26 | return viewport.getAlignment().getHiddenColumns().isVisible(col - 1); // base |
723 | // 0 | |
724 | } | |
725 | ||
726 | 30 | @Override |
727 | public List<SequenceI> getIdMatches() | |
728 | { | |
729 | 30 | return idMatches; |
730 | } | |
731 | ||
732 | 62 | @Override |
733 | public SearchResultsI getSearchResults() | |
734 | { | |
735 | 62 | return searchResults; |
736 | } | |
737 | ||
738 | 0 | @Override |
739 | public void setFeatureRenderer(FeatureRenderer featureRenderer) | |
740 | { | |
741 | 0 | frm = featureRenderer; |
742 | } | |
743 | } |