62e219d327281f37ce3c77adcd38559a2aee389d
[jalview.git] / test / jalview / analysis / FinderTest.java
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 static org.testng.Assert.assertEquals;
24 import static org.testng.Assert.assertSame;
25 import static org.testng.Assert.assertTrue;
26
27 import java.util.List;
28
29 import org.testng.annotations.AfterMethod;
30 import org.testng.annotations.BeforeClass;
31 import org.testng.annotations.Test;
32
33 import jalview.api.AlignViewportI;
34 import jalview.api.FinderI;
35 import jalview.bin.Cache;
36 import jalview.datamodel.Alignment;
37 import jalview.datamodel.AlignmentI;
38 import jalview.datamodel.ColumnSelection;
39 import jalview.datamodel.HiddenColumns;
40 import jalview.datamodel.SearchResultMatchI;
41 import jalview.datamodel.SearchResultsI;
42 import jalview.datamodel.Sequence;
43 import jalview.datamodel.SequenceFeature;
44 import jalview.datamodel.SequenceGroup;
45 import jalview.datamodel.SequenceI;
46 import jalview.gui.AlignFrame;
47 import jalview.gui.AlignViewport;
48 import jalview.gui.JvOptionPane;
49 import jalview.io.DataSourceType;
50 import jalview.io.FileLoader;
51 import junit.extensions.PA;
52
53 public class FinderTest
54 {
55   @BeforeClass(alwaysRun = true)
56   public void setUpJvOptionPane()
57   {
58     JvOptionPane.setInteractiveMode(false);
59     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
60   }
61
62   private AlignFrame af;
63
64   private AlignmentI al;
65
66   private AlignViewportI av;
67
68   @BeforeClass(groups = "Functional")
69   public void setUp()
70   {
71     Cache.loadProperties("test/jalview/io/testProps.jvprops");
72     Cache.applicationProperties.setProperty("PAD_GAPS",
73             Boolean.FALSE.toString());
74
75     //@formatter:off
76     String seqData = 
77         "seq1/8-18 ABCD--EF-GHIJI\n" + 
78         "seq2      A--BCDefHI\n" + 
79         "seq3      --bcdEFH\n" + 
80         "seq4      aa---aMMMMMaaa\n";
81     //@formatter:on
82     af = new FileLoader().LoadFileWaitTillLoaded(seqData,
83             DataSourceType.PASTE);
84     av = af.getViewport();
85     al = av.getAlignment();
86     al.getSequenceAt(0).addSequenceFeature(
87             new SequenceFeature("BBBB", "FeatureB", 9, 11, ""));
88     al.getSequenceAt(3).addSequenceFeature(
89             new SequenceFeature("BBAB", "FeatureA", 1, 3, ""));
90     al.getSequenceAt(3).addSequenceFeature(
91             new SequenceFeature("AAAA", "FeatureA", 9, 11, ""));
92   }
93
94   @AfterMethod(alwaysRun = true)
95   public void tearDownAfterTest()
96   {
97     av.setSelectionGroup(null);
98   }
99
100   /**
101    * Test for find matches of a regular expression
102    */
103   @Test(groups = "Functional")
104   public void testFind_regex()
105   {
106     /*
107      * find next match only
108      */
109     Finder f = new Finder(av);
110     f.findNext("E.H", false, false, false, false); // 'E, any character, H'
111     // should match seq2 efH only
112     SearchResultsI sr = f.getSearchResults();
113     assertEquals(sr.getCount(), 1);
114     List<SearchResultMatchI> matches = sr.getResults();
115     assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
116     assertEquals(matches.get(0).getStart(), 5);
117     assertEquals(matches.get(0).getEnd(), 7);
118
119     f = new Finder(av);
120     f.findAll("E.H", false, false, false, false); // 'E, any character, H'
121     // should match seq2 efH and seq3 EFH
122     sr = f.getSearchResults();
123     assertEquals(sr.getCount(), 2);
124     matches = sr.getResults();
125     assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
126     assertSame(matches.get(1).getSequence(), al.getSequenceAt(2));
127     assertEquals(matches.get(0).getStart(), 5);
128     assertEquals(matches.get(0).getEnd(), 7);
129     assertEquals(matches.get(1).getStart(), 4);
130     assertEquals(matches.get(1).getEnd(), 6);
131   }
132
133   @Test(groups = "Functional")
134   public void testFind_findAll()
135   {
136     /*
137      * simple JAL-3765 test
138      * single symbol should find *all* matching symbols 
139      */
140     Finder f = new Finder(av);
141     f.findAll("M", false, false, false, false);
142     SearchResultsI sr = f.getSearchResults();
143     assertEquals(sr.getCount(), 5);
144
145   }
146
147   /**
148    * Test for (undocumented) find residue by position
149    */
150   @Test(groups = "Functional")
151   public void testFind_residueNumber()
152   {
153     Finder f = new Finder(av);
154
155     /*
156      * find first match should return seq1 residue 9
157      */
158     f.findNext("9", false, false, false, false);
159     SearchResultsI sr = f.getSearchResults();
160     assertEquals(sr.getCount(), 1);
161     List<SearchResultMatchI> matches = sr.getResults();
162     assertSame(matches.get(0).getSequence(), al.getSequenceAt(0));
163     assertEquals(matches.get(0).getStart(), 9);
164     assertEquals(matches.get(0).getEnd(), 9);
165
166     /*
167      * find all matches should return seq1 and seq4 (others are too short)
168      * (and not matches in sequence ids)
169      */
170     f = new Finder(av);
171     String name = al.getSequenceAt(0).getName();
172     al.getSequenceAt(0).setName("Q9XA0");
173     f.findAll("9", false, false, false, false);
174     sr = f.getSearchResults();
175     assertEquals(sr.getCount(), 2);
176     matches = sr.getResults();
177     assertSame(matches.get(0).getSequence(), al.getSequenceAt(0));
178     assertSame(matches.get(1).getSequence(), al.getSequenceAt(3));
179     assertEquals(matches.get(0).getStart(), 9);
180     assertEquals(matches.get(0).getEnd(), 9);
181     assertEquals(matches.get(1).getStart(), 9);
182     assertEquals(matches.get(1).getEnd(), 9);
183     al.getSequenceAt(0).setName(name);
184
185     /*
186      * parsing of search string as integer is strict
187      */
188     f = new Finder(av);
189     f.findNext(" 9", false, false, false, false);
190     assertTrue(f.getSearchResults().isEmpty());
191   }
192
193   /**
194    * Test for find next action
195    */
196   @Test(groups = "Functional")
197   public void testFindNext()
198   {
199     /*
200      * start at second sequence; residueIndex of -1
201      * means sequence id / description is searched
202      */
203     Finder f = new Finder(av);
204     PA.setValue(f, "sequenceIndex", 1);
205     PA.setValue(f, "residueIndex", -1);
206     f.findNext("e", false, false, false, false); // matches id
207
208     assertTrue(f.getSearchResults().isEmpty());
209     assertEquals(f.getIdMatches().size(), 1);
210     assertSame(f.getIdMatches().get(0), al.getSequenceAt(1));
211
212     // residueIndex is now 0 - for use in next find next
213     // searching A--BCDefHI
214     assertEquals(PA.getValue(f, "residueIndex"), 0);
215     f = new Finder(av);
216     PA.setValue(f, "sequenceIndex", 1);
217     PA.setValue(f, "residueIndex", 0);
218     f.findNext("e", false, false, false, false); // matches in sequence
219     assertTrue(f.getIdMatches().isEmpty());
220     assertEquals(f.getSearchResults().getCount(), 1);
221     List<SearchResultMatchI> matches = f.getSearchResults().getResults();
222     assertEquals(matches.get(0).getStart(), 5);
223     assertEquals(matches.get(0).getEnd(), 5);
224     assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
225     // still in the second sequence
226     assertEquals(PA.getValue(f, "sequenceIndex"), 1);
227     // next residue offset to search from is 5
228     assertEquals(PA.getValue(f, "residueIndex"), 5);
229
230     // find next from end of sequence - finds next sequence id
231     f = new Finder(av);
232     PA.setValue(f, "sequenceIndex", 1);
233     PA.setValue(f, "residueIndex", 7);
234     f.findNext("e", false, false, false, false);
235     assertEquals(f.getIdMatches().size(), 1);
236     assertSame(f.getIdMatches().get(0), al.getSequenceAt(2));
237     assertTrue(f.getSearchResults().isEmpty());
238   }
239
240   /**
241    * Test for matching within sequence descriptions
242    */
243   @Test(groups = "Functional")
244   public void testFind_inDescription()
245   {
246     AlignmentI al2 = new Alignment(al);
247     al2.getSequenceAt(0).setDescription("BRAF");
248     al2.getSequenceAt(1).setDescription("braf");
249
250     AlignViewportI av2 = new AlignViewport(al2);
251
252     /*
253      * find first match only
254      */
255     Finder f = new Finder(av2);
256     f.findNext("rAF", false, true, false, false);
257     assertEquals(f.getIdMatches().size(), 1);
258     assertSame(f.getIdMatches().get(0), al2.getSequenceAt(0));
259     assertTrue(f.getSearchResults().isEmpty());
260
261     /*
262      * find all matches
263      */
264     f = new Finder(av2);
265     f.findAll("rAF", false, true, false, false);
266     assertEquals(f.getIdMatches().size(), 2);
267     assertSame(f.getIdMatches().get(0), al2.getSequenceAt(0));
268     assertSame(f.getIdMatches().get(1), al2.getSequenceAt(1));
269     assertTrue(f.getSearchResults().isEmpty());
270
271     /*
272      * case sensitive
273      */
274     f = new Finder(av2);
275     f.findAll("RAF", true, true, false, false);
276     assertEquals(f.getIdMatches().size(), 1);
277     assertSame(f.getIdMatches().get(0), al2.getSequenceAt(0));
278     assertTrue(f.getSearchResults().isEmpty());
279
280     /*
281      * match sequence id, description and sequence!
282      */
283     al2.getSequenceAt(0).setDescription("the efh sequence");
284     al2.getSequenceAt(0).setName("mouseEFHkinase");
285     al2.getSequenceAt(1).setName("humanEFHkinase");
286     f = new Finder(av2);
287
288     /*
289      * sequence matches should have no duplicates
290      */
291     f.findAll("EFH", false, true, false, false);
292     assertEquals(f.getIdMatches().size(), 2);
293     assertSame(f.getIdMatches().get(0), al2.getSequenceAt(0));
294     assertSame(f.getIdMatches().get(1), al2.getSequenceAt(1));
295
296     assertEquals(f.getSearchResults().getCount(), 2);
297     SearchResultMatchI match = f.getSearchResults().getResults().get(0);
298     assertSame(match.getSequence(), al2.getSequenceAt(1));
299     assertEquals(match.getStart(), 5);
300     assertEquals(match.getEnd(), 7);
301     match = f.getSearchResults().getResults().get(1);
302     assertSame(match.getSequence(), al2.getSequenceAt(2));
303     assertEquals(match.getStart(), 4);
304     assertEquals(match.getEnd(), 6);
305   }
306
307   /**
308    * Test for matching within sequence ids
309    */
310   @Test(groups = "Functional")
311   public void testFindAll_sequenceIds()
312   {
313     Finder f = new Finder(av);
314
315     /*
316      * case insensitive; seq1 occurs twice in sequence id but
317      * only one match should be returned
318      */
319     f.findAll("SEQ1", false, false, false, false);
320     assertEquals(f.getIdMatches().size(), 1);
321     assertSame(f.getIdMatches().get(0), al.getSequenceAt(0));
322     SearchResultsI searchResults = f.getSearchResults();
323     assertTrue(searchResults.isEmpty());
324
325     /*
326      * case sensitive
327      */
328     f = new Finder(av);
329     f.findAll("SEQ1", true, false, false, false);
330     searchResults = f.getSearchResults();
331     assertTrue(searchResults.isEmpty());
332
333     /*
334      * match both sequence id and sequence
335      */
336     AlignmentI al2 = new Alignment(al);
337     AlignViewportI av2 = new AlignViewport(al2);
338     al2.addSequence(new Sequence("aBz", "xyzabZpqrAbZ"));
339     f = new Finder(av2);
340     f.findAll("ABZ", false, false, false, false);
341     assertEquals(f.getIdMatches().size(), 1);
342     assertSame(f.getIdMatches().get(0), al2.getSequenceAt(4));
343     searchResults = f.getSearchResults();
344     assertEquals(searchResults.getCount(), 2);
345     SearchResultMatchI match = searchResults.getResults().get(0);
346     assertSame(match.getSequence(), al2.getSequenceAt(4));
347     assertEquals(match.getStart(), 4);
348     assertEquals(match.getEnd(), 6);
349     match = searchResults.getResults().get(1);
350     assertSame(match.getSequence(), al2.getSequenceAt(4));
351     assertEquals(match.getStart(), 10);
352     assertEquals(match.getEnd(), 12);
353   }
354
355   /**
356    * Test finding next match of a sequence pattern in an alignment
357    */
358   @Test(groups = "Functional")
359   public void testFind_findNext()
360   {
361     // "seq1/8-18 ABCD--EF-GHIJI\n" +
362     // "seq2 A--BCDefHI\n" +
363     // "seq3 --bcdEFH\n" +
364     // "seq4 aa---aMMMMMaaa\n";
365     /*
366      * efh should be matched in seq2 only
367      */
368     FinderI f = new Finder(av);
369     f.findNext("EfH", false, false, false, false);
370     SearchResultsI searchResults = f.getSearchResults();
371     assertEquals(searchResults.getCount(), 1);
372     SearchResultMatchI match = searchResults.getResults().get(0);
373     assertSame(match.getSequence(), al.getSequenceAt(1));
374     assertEquals(match.getStart(), 5);
375     assertEquals(match.getEnd(), 7);
376
377     /*
378      * I should be found in seq1 (twice) and seq2 (once)
379      */
380     f = new Finder(av);
381     f.findNext("I", false, false, false, false); // find next: seq1/16
382     searchResults = f.getSearchResults();
383     assertEquals(searchResults.getCount(), 1);
384     match = searchResults.getResults().get(0);
385     assertSame(match.getSequence(), al.getSequenceAt(0));
386     assertEquals(match.getStart(), 16);
387     assertEquals(match.getEnd(), 16);
388
389     f.findNext("I", false, false, false, false); // find next: seq1/18
390     searchResults = f.getSearchResults();
391     assertEquals(searchResults.getCount(), 1);
392     match = searchResults.getResults().get(0);
393     assertSame(match.getSequence(), al.getSequenceAt(0));
394     assertEquals(match.getStart(), 18);
395     assertEquals(match.getEnd(), 18);
396
397     f.findNext("I", false, false, false, false); // find next: seq2/8
398     searchResults = f.getSearchResults();
399     assertEquals(searchResults.getCount(), 1);
400     match = searchResults.getResults().get(0);
401     assertSame(match.getSequence(), al.getSequenceAt(1));
402     assertEquals(match.getStart(), 8);
403     assertEquals(match.getEnd(), 8);
404
405     f.findNext("I", false, false, false, false);
406     assertTrue(f.getSearchResults().isEmpty());
407
408     /*
409      * find should reset to start of alignment after a failed search
410      */
411     f.findNext("I", false, false, false, false); // find next: seq1/16
412     searchResults = f.getSearchResults();
413     assertEquals(searchResults.getCount(), 1);
414     match = searchResults.getResults().get(0);
415     assertSame(match.getSequence(), al.getSequenceAt(0));
416     assertEquals(match.getStart(), 16);
417     assertEquals(match.getEnd(), 16);
418   }
419
420   /**
421    * Test for JAL-2302 to verify that sub-matches are not included in a find all
422    * result
423    */
424   @Test(groups = "Functional")
425   public void testFindAll_maximalResultOnly()
426   {
427     Finder f = new Finder(av);
428     f.findAll("M+", false, false, false, false);
429     SearchResultsI searchResults = f.getSearchResults();
430     assertEquals(searchResults.getCount(), 1);
431     SearchResultMatchI match = searchResults.getResults().get(0);
432     assertSame(match.getSequence(), al.getSequenceAt(3));
433     assertEquals(match.getStart(), 4); // dataset sequence positions
434     assertEquals(match.getEnd(), 8); // base 1
435   }
436
437   /**
438    * Test finding all matches of a sequence pattern in an alignment
439    */
440   @Test(groups = "Functional")
441   public void testFindAll()
442   {
443     Finder f = new Finder(av);
444     f.findAll("EfH", false, false, false, false);
445     SearchResultsI searchResults = f.getSearchResults();
446     assertEquals(searchResults.getCount(), 2);
447     SearchResultMatchI match = searchResults.getResults().get(0);
448     assertSame(match.getSequence(), al.getSequenceAt(1));
449     assertEquals(match.getStart(), 5);
450     assertEquals(match.getEnd(), 7);
451     match = searchResults.getResults().get(1);
452     assertSame(match.getSequence(), al.getSequenceAt(2));
453     assertEquals(match.getStart(), 4);
454     assertEquals(match.getEnd(), 6);
455
456     /*
457      * find all I should find 2 positions in seq1, 1 in seq2
458      */
459     f.findAll("I", false, false, false, false);
460     searchResults = f.getSearchResults();
461     assertEquals(searchResults.getCount(), 3);
462     match = searchResults.getResults().get(0);
463     assertSame(match.getSequence(), al.getSequenceAt(0));
464     assertEquals(match.getStart(), 16);
465     assertEquals(match.getEnd(), 16);
466     match = searchResults.getResults().get(1);
467     assertSame(match.getSequence(), al.getSequenceAt(0));
468     assertEquals(match.getStart(), 18);
469     assertEquals(match.getEnd(), 18);
470     match = searchResults.getResults().get(2);
471     assertSame(match.getSequence(), al.getSequenceAt(1));
472     assertEquals(match.getStart(), 8);
473     assertEquals(match.getEnd(), 8);
474   }
475
476   /**
477    * Test finding all matches, case-sensitive
478    */
479   @Test(groups = "Functional")
480   public void testFindAll_caseSensitive()
481   {
482     Finder f = new Finder(av);
483
484     /*
485      * BC should match seq1/9-10 and seq2/2-3
486      */
487     f.findAll("BC", true, false, false, false);
488     SearchResultsI searchResults = f.getSearchResults();
489     assertEquals(searchResults.getCount(), 2);
490     SearchResultMatchI match = searchResults.getResults().get(0);
491     assertSame(match.getSequence(), al.getSequenceAt(0));
492     assertEquals(match.getStart(), 9);
493     assertEquals(match.getEnd(), 10);
494     match = searchResults.getResults().get(1);
495     assertSame(match.getSequence(), al.getSequenceAt(1));
496     assertEquals(match.getStart(), 2);
497     assertEquals(match.getEnd(), 3);
498
499     /*
500      * bc should match seq3/1-2
501      */
502     f = new Finder(av);
503     f.findAll("bc", true, false, false, false);
504     searchResults = f.getSearchResults();
505     assertEquals(searchResults.getCount(), 1);
506     match = searchResults.getResults().get(0);
507     assertSame(match.getSequence(), al.getSequenceAt(2));
508     assertEquals(match.getStart(), 1);
509     assertEquals(match.getEnd(), 2);
510
511     f.findAll("bC", true, false, false, false);
512     assertTrue(f.getSearchResults().isEmpty());
513   }
514
515   /**
516    * Test finding next match of a sequence pattern in a selection group
517    */
518   @Test(groups = "Functional")
519   public void testFindNext_inSelection()
520   {
521     /*
522      * select sequences 2 and 3, columns 4-6 which contains
523      * BCD
524      * cdE
525      */
526     SequenceGroup sg = new SequenceGroup();
527     sg.setStartRes(3);
528     sg.setEndRes(5);
529     sg.addSequence(al.getSequenceAt(1), false);
530     sg.addSequence(al.getSequenceAt(2), false);
531     av.setSelectionGroup(sg);
532
533     FinderI f = new Finder(av);
534     f.findNext("b", false, false, false, false);
535     assertTrue(f.getIdMatches().isEmpty());
536     SearchResultsI searchResults = f.getSearchResults();
537     assertEquals(searchResults.getCount(), 1);
538     SearchResultMatchI match = searchResults.getResults().get(0);
539     assertSame(match.getSequence(), al.getSequenceAt(1));
540     assertEquals(match.getStart(), 2);
541     assertEquals(match.getEnd(), 2);
542
543     /*
544      * a second Find should not return the 'b' in seq3 as outside the selection
545      */
546     f.findNext("b", false, false, false, false);
547     assertTrue(f.getSearchResults().isEmpty());
548     assertTrue(f.getIdMatches().isEmpty());
549
550     f = new Finder(av);
551     f.findNext("d", false, false, false, false);
552     assertTrue(f.getIdMatches().isEmpty());
553     searchResults = f.getSearchResults();
554     assertEquals(searchResults.getCount(), 1);
555     match = searchResults.getResults().get(0);
556     assertSame(match.getSequence(), al.getSequenceAt(1));
557     assertEquals(match.getStart(), 4);
558     assertEquals(match.getEnd(), 4);
559     f.findNext("d", false, false, false, false);
560     assertTrue(f.getIdMatches().isEmpty());
561     searchResults = f.getSearchResults();
562     assertEquals(searchResults.getCount(), 1);
563     match = searchResults.getResults().get(0);
564     assertSame(match.getSequence(), al.getSequenceAt(2));
565     assertEquals(match.getStart(), 3);
566     assertEquals(match.getEnd(), 3);
567   }
568
569   /**
570    * Test finding all matches of a search pattern in a selection group
571    */
572   @Test(groups = "Functional")
573   public void testFindAll_inSelection()
574   {
575     /*
576      * select sequences 2 and 3, columns 4-6 which contains
577      * BCD
578      * cdE
579      */
580     SequenceGroup sg = new SequenceGroup();
581     sg.setStartRes(3);
582     sg.setEndRes(5);
583     sg.addSequence(al.getSequenceAt(1), false);
584     sg.addSequence(al.getSequenceAt(2), false);
585     av.setSelectionGroup(sg);
586
587     /*
588      * search for 'e' should match two sequence ids and one residue
589      */
590     Finder f = new Finder(av);
591     f.findAll("e", false, false, false, false);
592     assertEquals(f.getIdMatches().size(), 2);
593     assertSame(f.getIdMatches().get(0), al.getSequenceAt(1));
594     assertSame(f.getIdMatches().get(1), al.getSequenceAt(2));
595     SearchResultsI searchResults = f.getSearchResults();
596     assertEquals(searchResults.getCount(), 1);
597     SearchResultMatchI match = searchResults.getResults().get(0);
598     assertSame(match.getSequence(), al.getSequenceAt(2));
599     assertEquals(match.getStart(), 4);
600     assertEquals(match.getEnd(), 4);
601
602     /*
603      * search for 'Q' should match two sequence ids only
604      */
605     f = new Finder(av);
606     f.findAll("Q", false, false, false, false);
607     assertEquals(f.getIdMatches().size(), 2);
608     assertSame(f.getIdMatches().get(0), al.getSequenceAt(1));
609     assertSame(f.getIdMatches().get(1), al.getSequenceAt(2));
610     assertTrue(f.getSearchResults().isEmpty());
611   }
612
613   /**
614    * Test finding in selection with a sequence too short to reach it
615    */
616   @Test(groups = "Functional")
617   public void testFind_findAllInSelectionWithShortSequence()
618   {
619     /*
620      * select all sequences, columns 10-12
621      * BCD
622      * cdE
623      */
624     SequenceGroup sg = new SequenceGroup();
625     sg.setStartRes(9);
626     sg.setEndRes(11);
627     sg.addSequence(al.getSequenceAt(0), false);
628     sg.addSequence(al.getSequenceAt(1), false);
629     sg.addSequence(al.getSequenceAt(2), false);
630     sg.addSequence(al.getSequenceAt(3), false);
631     av.setSelectionGroup(sg);
632
633     /*
634      * search for 'I' should match two sequence positions
635      */
636     Finder f = new Finder(av);
637     f.findAll("I", false, false, false, false);
638     assertTrue(f.getIdMatches().isEmpty());
639     SearchResultsI searchResults = f.getSearchResults();
640     assertEquals(searchResults.getCount(), 2);
641     SearchResultMatchI match = searchResults.getResults().get(0);
642     assertSame(match.getSequence(), al.getSequenceAt(0));
643     assertEquals(match.getStart(), 16);
644     assertEquals(match.getEnd(), 16);
645     match = searchResults.getResults().get(1);
646     assertSame(match.getSequence(), al.getSequenceAt(1));
647     assertEquals(match.getStart(), 8);
648     assertEquals(match.getEnd(), 8);
649   }
650
651   /**
652    * Test that find does not report hidden positions, but does report matches
653    * that span hidden gaps
654    */
655   @Test(groups = "Functional")
656   public void testFind_withHiddenColumns()
657   {
658     /*
659      * 0    5   9
660      * ABCD--EF-GHI
661      * A--BCDefHI
662      * --bcdEFH
663      * aa---aMMMMMaaa
664      */
665
666     /*
667      * hide column 3 only, search for aaa
668      * should find two matches: aa-[-]-aa and trailing aaa
669      */
670     HiddenColumns hc = new HiddenColumns();
671     hc.hideColumns(3, 3);
672     al.setHiddenColumns(hc);
673     Finder f = new Finder(av);
674     f.findAll("aaa", false, false, false, false);
675     SearchResultsI searchResults = f.getSearchResults();
676     assertEquals(searchResults.getCount(), 2);
677     SearchResultMatchI match = searchResults.getResults().get(0);
678     assertSame(match.getSequence(), al.getSequenceAt(3));
679     assertEquals(match.getStart(), 1);
680     assertEquals(match.getEnd(), 3);
681     match = searchResults.getResults().get(1);
682     assertSame(match.getSequence(), al.getSequenceAt(3));
683     assertEquals(match.getStart(), 9);
684     assertEquals(match.getEnd(), 11);
685
686     /*
687      * hide 2-4 (CD- -BC bcd ---)
688      */
689     hc.hideColumns(2, 4);
690
691     /*
692      * find all search for D should ignore hidden positions in seq1 and seq3,
693      * find the visible D in seq2
694      */
695     f = new Finder(av);
696     f.findAll("D", false, false, false, false);
697     searchResults = f.getSearchResults();
698     assertEquals(searchResults.getCount(), 1);
699     match = searchResults.getResults().get(0);
700     assertSame(match.getSequence(), al.getSequenceAt(1));
701     assertEquals(match.getStart(), 4);
702     assertEquals(match.getEnd(), 4);
703
704     /*
705      * search for AD should fail although these are now
706      * consecutive in the visible columns
707      */
708     f = new Finder(av);
709     f.findAll("AD", false, false, false, false);
710     searchResults = f.getSearchResults();
711     assertTrue(searchResults.isEmpty());
712
713     /*
714      * find all 'aaa' should find both start and end of seq4
715      * (first run includes hidden gaps)
716      */
717     f = new Finder(av);
718     f.findAll("aaa", false, false, false, false);
719     searchResults = f.getSearchResults();
720     assertEquals(searchResults.getCount(), 2);
721     match = searchResults.getResults().get(0);
722     assertSame(match.getSequence(), al.getSequenceAt(3));
723     assertEquals(match.getStart(), 1);
724     assertEquals(match.getEnd(), 3);
725     match = searchResults.getResults().get(1);
726     assertSame(match.getSequence(), al.getSequenceAt(3));
727     assertEquals(match.getStart(), 9);
728     assertEquals(match.getEnd(), 11);
729
730     /*
731      * hide columns 2-5:
732      * find all 'aaa' should match twice in seq4
733      * (first match partly hidden, second all visible)
734      */
735     hc.hideColumns(2, 5);
736     f = new Finder(av);
737     f.findAll("aaa", false, false, false, false);
738     searchResults = f.getSearchResults();
739     assertEquals(searchResults.getCount(), 2);
740     match = searchResults.getResults().get(0);
741     assertSame(match.getSequence(), al.getSequenceAt(3));
742     assertEquals(match.getStart(), 1);
743     assertEquals(match.getEnd(), 3);
744     match = searchResults.getResults().get(1);
745     assertSame(match.getSequence(), al.getSequenceAt(3));
746     assertEquals(match.getStart(), 9);
747     assertEquals(match.getEnd(), 11);
748
749     /*
750      * find all 'BE' should not match across hidden columns in seq1
751      */
752     f.findAll("BE", false, false, false, false);
753     assertTrue(f.getSearchResults().isEmpty());
754
755     /*
756      * boundary case: hide columns at end of alignment
757      * search for H should match seq3/6 only
758      */
759     hc.revealAllHiddenColumns(new ColumnSelection());
760     hc.hideColumns(8, 13);
761     f = new Finder(av);
762     f.findNext("H", false, false, false, false);
763     searchResults = f.getSearchResults();
764     assertEquals(searchResults.getCount(), 1);
765     match = searchResults.getResults().get(0);
766     assertSame(match.getSequence(), al.getSequenceAt(2));
767     assertEquals(match.getStart(), 6);
768     assertEquals(match.getEnd(), 6);
769   }
770
771   @Test(groups = "Functional")
772   public void testFind_withHiddenColumnsAndSelection()
773   {
774     /*
775      * 0    5   9
776      * ABCD--EF-GHI
777      * A--BCDefHI
778      * --bcdEFH
779      * aa---aMMMMMaaa
780      */
781
782     /*
783      * hide columns 2-4 and 6-7
784      */
785     HiddenColumns hc = new HiddenColumns();
786     hc.hideColumns(2, 4);
787     hc.hideColumns(6, 7);
788     al.setHiddenColumns(hc);
789
790     /*
791      * select rows 2-3
792      */
793     SequenceGroup sg = new SequenceGroup();
794     sg.addSequence(al.getSequenceAt(1), false);
795     sg.addSequence(al.getSequenceAt(2), false);
796     sg.setStartRes(0);
797     sg.setEndRes(13);
798     av.setSelectionGroup(sg);
799
800     /*
801      * find all search for A or H
802      * should match seq2/1, seq2/7, not seq3/6
803      */
804     Finder f = new Finder(av);
805     f.findAll("[AH]", false, false, false, false);
806     SearchResultsI searchResults = f.getSearchResults();
807     assertEquals(searchResults.getCount(), 2);
808     SearchResultMatchI match = searchResults.getResults().get(0);
809     assertSame(match.getSequence(), al.getSequenceAt(1));
810     assertEquals(match.getStart(), 1);
811     assertEquals(match.getEnd(), 1);
812     match = searchResults.getResults().get(1);
813     assertSame(match.getSequence(), al.getSequenceAt(1));
814     assertEquals(match.getStart(), 7);
815     assertEquals(match.getEnd(), 7);
816   }
817
818   @Test(groups = "Functional")
819   public void testFind_ignoreHiddenColumns()
820   {
821     /*
822      * 0    5   9
823      * ABCD--EF-GHI
824      * A--BCDefHI
825      * --bcdEFH
826      * aa---aMMMMMaaa
827      */
828     HiddenColumns hc = new HiddenColumns();
829     hc.hideColumns(2, 4);
830     hc.hideColumns(7, 7);
831     al.setHiddenColumns(hc);
832
833     /*
834      * now have
835      * 015689
836      * AB-E-GHI
837      * A-DeHI
838      * --EF
839      * aaaMMMMaaa
840      */
841     Finder f = new Finder(av);
842     f.findAll("abe", false, false, false, true); // true = ignore hidden
843     SearchResultsI searchResults = f.getSearchResults();
844
845     /*
846      * match of seq1 ABE made up of AB and E
847      * note only one match is counted
848      */
849     assertEquals(searchResults.getCount(), 1);
850     assertEquals(searchResults.getResults().size(), 2);
851     SearchResultMatchI match = searchResults.getResults().get(0);
852     assertSame(match.getSequence(), al.getSequenceAt(0));
853     assertEquals(match.getStart(), 8); // A
854     assertEquals(match.getEnd(), 9); // B
855     match = searchResults.getResults().get(1);
856     assertSame(match.getSequence(), al.getSequenceAt(0));
857     assertEquals(match.getStart(), 12); // E
858     assertEquals(match.getEnd(), 12);
859
860     f = new Finder(av);
861     f.findNext("a.E", false, false, false, true);
862     searchResults = f.getSearchResults();
863     assertEquals(searchResults.getCount(), 1);
864     assertEquals(searchResults.getResults().size(), 2);
865     match = searchResults.getResults().get(0);
866     assertSame(match.getSequence(), al.getSequenceAt(0));
867     assertEquals(match.getStart(), 8); // A
868     assertEquals(match.getEnd(), 9); // B
869     match = searchResults.getResults().get(1);
870     assertSame(match.getSequence(), al.getSequenceAt(0));
871     assertEquals(match.getStart(), 12); // E
872     assertEquals(match.getEnd(), 12);
873
874     f.findNext("a.E", false, false, false, true);
875     searchResults = f.getSearchResults();
876     assertEquals(searchResults.getCount(), 1);
877     assertEquals(searchResults.getResults().size(), 2);
878     match = searchResults.getResults().get(0);
879     assertSame(match.getSequence(), al.getSequenceAt(1));
880     assertEquals(match.getStart(), 1); // a
881     assertEquals(match.getEnd(), 1);
882     match = searchResults.getResults().get(1);
883     assertSame(match.getSequence(), al.getSequenceAt(1));
884     assertEquals(match.getStart(), 4); // D
885     assertEquals(match.getEnd(), 5); // e
886
887     /*
888      * find all matching across two hidden column regions
889      * note one 'match' is returned as three contiguous matches
890      */
891     f.findAll("BEG", false, false, false, true);
892     searchResults = f.getSearchResults();
893     assertEquals(searchResults.getCount(), 1);
894     assertEquals(searchResults.getResults().size(), 3);
895     match = searchResults.getResults().get(0);
896     assertSame(match.getSequence(), al.getSequenceAt(0));
897     assertEquals(match.getStart(), 9); // B
898     assertEquals(match.getEnd(), 9);
899     match = searchResults.getResults().get(1);
900     assertSame(match.getSequence(), al.getSequenceAt(0));
901     assertEquals(match.getStart(), 12); // E
902     assertEquals(match.getEnd(), 12);
903     match = searchResults.getResults().get(2);
904     assertSame(match.getSequence(), al.getSequenceAt(0));
905     assertEquals(match.getStart(), 14); // G
906     assertEquals(match.getEnd(), 14);
907
908     /*
909      * now select columns 0-9 and search for A.*H
910      * this should match in the second sequence (split as 3 matches)
911      * but not the first (as H is outside the selection)
912      */
913     SequenceGroup selection = new SequenceGroup();
914     selection.setStartRes(0);
915     selection.setEndRes(9);
916     al.getSequences().forEach(seq -> selection.addSequence(seq, false));
917     av.setSelectionGroup(selection);
918     f.findAll("A.*H", false, false, false, true);
919     searchResults = f.getSearchResults();
920     assertEquals(searchResults.getCount(), 1);
921     assertEquals(searchResults.getResults().size(), 3);
922     // match made of contiguous matches A, DE, H
923     match = searchResults.getResults().get(0);
924     assertSame(match.getSequence(), al.getSequenceAt(1));
925     assertEquals(match.getStart(), 1); // A
926     assertEquals(match.getEnd(), 1);
927     match = searchResults.getResults().get(1);
928     assertSame(match.getSequence(), al.getSequenceAt(1));
929     assertEquals(match.getStart(), 4); // D
930     assertEquals(match.getEnd(), 5); // E
931     match = searchResults.getResults().get(2);
932     assertSame(match.getSequence(), al.getSequenceAt(1));
933     assertEquals(match.getStart(), 7); // H (there is no G)
934     assertEquals(match.getEnd(), 7);
935   }
936
937   @Test(groups = "Functional")
938   public void testFind_featuresOnly()
939   {
940     Finder f = new Finder(av);
941     // no match when not searching feature descriptions
942     f.findAll("Feature", false, false, false, true);
943     assertEquals(f.getSearchResults().getCount(), 0);
944
945     // no match when case sensitive on feature descriptions
946     f.findAll("feature", true, false, true, true);
947     assertEquals(f.getSearchResults().getCount(), 0);
948
949     // search feature descriptions - all match
950     f.findAll("Feature", false, false, true, true);
951     assertEquals(f.getSearchResults().getCount(), 3);
952
953     List<SequenceI> seqs = f.getSearchResults().getMatchingSubSequences();
954     // assume order is preserved in results
955     assertEquals(al.getSequenceAt(0).getDatasetSequence(),
956             seqs.get(0).getDatasetSequence());
957     assertEquals(seqs.get(0).getStart(), 9);
958     assertEquals(seqs.get(0).getEnd(), 11);
959     assertEquals(al.getSequenceAt(3).getDatasetSequence(),
960             seqs.get(1).getDatasetSequence());
961     assertEquals(seqs.get(1).getStart(), 9);
962     assertEquals(seqs.get(1).getEnd(), 11);
963     assertEquals(al.getSequenceAt(3).getDatasetSequence(),
964             seqs.get(2).getDatasetSequence());
965     assertEquals(seqs.get(2).getStart(), 1);
966     assertEquals(seqs.get(2).getEnd(), 3);
967   }
968 }