JAL-3383 tests and fixes for overloaded SequenceFeatures.findFeatures
[jalview.git] / test / jalview / datamodel / features / FeatureStoreTest.java
1 package jalview.datamodel.features;
2
3 import static org.testng.Assert.assertEquals;
4 import static org.testng.Assert.assertFalse;
5 import static org.testng.Assert.assertSame;
6 import static org.testng.Assert.assertTrue;
7
8 import jalview.datamodel.SequenceFeature;
9
10 import java.util.ArrayList;
11 import java.util.List;
12 import java.util.Set;
13
14 import org.testng.annotations.Test;
15
16 public class FeatureStoreTest
17 {
18   private FeatureStore newFeatureStore()
19   {
20     return new FeatureStore();
21   }
22
23   @Test(groups = "Functional")
24   public void testFindFeatures_nonNested()
25   {
26     FeatureStore fs = newFeatureStore();
27     fs.addFeature(new SequenceFeature("", "", 10, 20, Float.NaN,
28             null));
29     // same range different description
30     fs.addFeature(new SequenceFeature("", "desc", 10, 20, Float.NaN, null));
31     fs.addFeature(new SequenceFeature("", "", 15, 25, Float.NaN, null));
32     fs.addFeature(new SequenceFeature("", "", 20, 35, Float.NaN, null));
33
34     List<SequenceFeature> overlaps = fs.findOverlappingFeatures(1, 9);
35     assertTrue(overlaps.isEmpty());
36
37     overlaps = fs.findOverlappingFeatures(8, 10);
38     assertEquals(overlaps.size(), 2);
39     assertEquals(overlaps.get(0).getEnd(), 20);
40     assertEquals(overlaps.get(1).getEnd(), 20);
41
42     overlaps = fs.findOverlappingFeatures(12, 16);
43     assertEquals(overlaps.size(), 3);
44     assertEquals(overlaps.get(0).getEnd(), 20);
45     assertEquals(overlaps.get(1).getEnd(), 20);
46     assertEquals(overlaps.get(2).getEnd(), 25);
47
48     overlaps = fs.findOverlappingFeatures(33, 33);
49     assertEquals(overlaps.size(), 1);
50     assertEquals(overlaps.get(0).getEnd(), 35);
51   }
52
53   @Test(groups = "Functional")
54   public void testFindFeatures_nested()
55   {
56     FeatureStore fs = newFeatureStore();
57     SequenceFeature sf1 = addFeature(fs, 10, 50);
58     SequenceFeature sf2 = addFeature(fs, 10, 40);
59     SequenceFeature sf3 = addFeature(fs, 20, 30);
60     // fudge feature at same location but different group (so is added)
61     SequenceFeature sf4 = new SequenceFeature("", "", 20, 30, Float.NaN,
62             "different group");
63     fs.addFeature(sf4);
64     SequenceFeature sf5 = addFeature(fs, 35, 36);
65
66     List<SequenceFeature> overlaps = fs.findOverlappingFeatures(1, 9);
67     assertTrue(overlaps.isEmpty());
68
69     overlaps = fs.findOverlappingFeatures(10, 15);
70     assertEquals(overlaps.size(), 2);
71     assertTrue(overlaps.contains(sf1));
72     assertTrue(overlaps.contains(sf2));
73
74     overlaps = fs.findOverlappingFeatures(45, 60);
75     assertEquals(overlaps.size(), 1);
76     assertTrue(overlaps.contains(sf1));
77
78     overlaps = fs.findOverlappingFeatures(32, 38);
79     assertEquals(overlaps.size(), 3);
80     assertTrue(overlaps.contains(sf1));
81     assertTrue(overlaps.contains(sf2));
82     assertTrue(overlaps.contains(sf5));
83
84     overlaps = fs.findOverlappingFeatures(15, 25);
85     assertEquals(overlaps.size(), 4);
86     assertTrue(overlaps.contains(sf1));
87     assertTrue(overlaps.contains(sf2));
88     assertTrue(overlaps.contains(sf3));
89     assertTrue(overlaps.contains(sf4));
90   }
91
92   @Test(groups = "Functional")
93   public void testFindFeatures_mixed()
94   {
95     FeatureStore fs = newFeatureStore();
96     SequenceFeature sf1 = addFeature(fs, 10, 50);
97     SequenceFeature sf2 = addFeature(fs, 1, 15);
98     SequenceFeature sf3 = addFeature(fs, 20, 30);
99     SequenceFeature sf4 = addFeature(fs, 40, 100);
100     SequenceFeature sf5 = addFeature(fs, 60, 100);
101     SequenceFeature sf6 = addFeature(fs, 70, 70);
102
103     List<SequenceFeature> overlaps = fs.findOverlappingFeatures(200, 200);
104     assertTrue(overlaps.isEmpty());
105
106     overlaps = fs.findOverlappingFeatures(1, 9);
107     assertEquals(overlaps.size(), 1);
108     assertTrue(overlaps.contains(sf2));
109
110     overlaps = fs.findOverlappingFeatures(5, 18);
111     assertEquals(overlaps.size(), 2);
112     assertTrue(overlaps.contains(sf1));
113     assertTrue(overlaps.contains(sf2));
114
115     overlaps = fs.findOverlappingFeatures(30, 40);
116     assertEquals(overlaps.size(), 3);
117     assertTrue(overlaps.contains(sf1));
118     assertTrue(overlaps.contains(sf3));
119     assertTrue(overlaps.contains(sf4));
120
121     overlaps = fs.findOverlappingFeatures(80, 90);
122     assertEquals(overlaps.size(), 2);
123     assertTrue(overlaps.contains(sf4));
124     assertTrue(overlaps.contains(sf5));
125
126     overlaps = fs.findOverlappingFeatures(68, 70);
127     assertEquals(overlaps.size(), 3);
128     assertTrue(overlaps.contains(sf4));
129     assertTrue(overlaps.contains(sf5));
130     assertTrue(overlaps.contains(sf6));
131   }
132
133   /**
134    * Helper method to add a feature of no particular type
135    * 
136    * @param fs
137    * @param from
138    * @param to
139    * @return
140    */
141   SequenceFeature addFeature(FeatureStore fs, int from, int to)
142   {
143     SequenceFeature sf1 = new SequenceFeature("", "", from, to, Float.NaN,
144             null);
145     fs.addFeature(sf1);
146     return sf1;
147   }
148
149   @Test(groups = "Functional")
150   public void testFindFeatures_contactFeatures()
151   {
152     FeatureStore fs = newFeatureStore();
153
154     SequenceFeature sf = new SequenceFeature("disulphide bond", "bond", 10,
155             20, Float.NaN, null);
156     fs.addFeature(sf);
157
158     /*
159      * neither contact point in range
160      */
161     List<SequenceFeature> overlaps = fs.findOverlappingFeatures(1, 9);
162     assertTrue(overlaps.isEmpty());
163
164     /*
165      * neither contact point in range
166      */
167     overlaps = fs.findOverlappingFeatures(11, 19);
168     assertTrue(overlaps.isEmpty());
169
170     /*
171      * first contact point in range
172      */
173     overlaps = fs.findOverlappingFeatures(5, 15);
174     assertEquals(overlaps.size(), 1);
175     assertTrue(overlaps.contains(sf));
176
177     /*
178      * second contact point in range
179      */
180     overlaps = fs.findOverlappingFeatures(15, 25);
181     assertEquals(overlaps.size(), 1);
182     assertTrue(overlaps.contains(sf));
183
184     /*
185      * both contact points in range
186      */
187     overlaps = fs.findOverlappingFeatures(5, 25);
188     assertEquals(overlaps.size(), 1);
189     assertTrue(overlaps.contains(sf));
190   }
191
192   @Test(groups = "Functional")
193   public void testGetPositionalFeatures()
194   {
195     FeatureStore store = newFeatureStore();
196     SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
197             Float.NaN, null);
198     store.addFeature(sf1);
199     // same range, different description
200     SequenceFeature sf2 = new SequenceFeature("Metal", "desc2", 10, 20,
201             Float.NaN, null);
202     store.addFeature(sf2);
203     // discontiguous range
204     SequenceFeature sf3 = new SequenceFeature("Metal", "desc", 30, 40,
205             Float.NaN, null);
206     store.addFeature(sf3);
207     // overlapping range
208     SequenceFeature sf4 = new SequenceFeature("Metal", "desc", 15, 35,
209             Float.NaN, null);
210     store.addFeature(sf4);
211     // enclosing range
212     SequenceFeature sf5 = new SequenceFeature("Metal", "desc", 5, 50,
213             Float.NaN, null);
214     store.addFeature(sf5);
215     // non-positional feature
216     SequenceFeature sf6 = new SequenceFeature("Metal", "desc", 0, 0,
217             Float.NaN, null);
218     store.addFeature(sf6);
219     // contact feature
220     SequenceFeature sf7 = new SequenceFeature("Disulphide bond", "desc",
221             18, 45, Float.NaN, null);
222     store.addFeature(sf7);
223
224     List<SequenceFeature> features = store.getPositionalFeatures();
225     assertEquals(features.size(), 6);
226     assertTrue(features.contains(sf1));
227     assertTrue(features.contains(sf2));
228     assertTrue(features.contains(sf3));
229     assertTrue(features.contains(sf4));
230     assertTrue(features.contains(sf5));
231     assertFalse(features.contains(sf6));
232     assertTrue(features.contains(sf7));
233
234     features = store.getNonPositionalFeatures();
235     assertEquals(features.size(), 1);
236     assertTrue(features.contains(sf6));
237   }
238
239   @Test(groups = "Functional")
240   public void testDelete()
241   {
242     FeatureStore store = newFeatureStore();
243     SequenceFeature sf1 = addFeature(store, 10, 20);
244     assertTrue(store.getPositionalFeatures().contains(sf1));
245
246     /*
247      * simple deletion
248      */
249     assertTrue(store.delete(sf1));
250     assertTrue(store.getPositionalFeatures().isEmpty());
251
252     /*
253      * non-positional feature deletion
254      */
255     SequenceFeature sf2 = addFeature(store, 0, 0);
256     assertFalse(store.getPositionalFeatures().contains(sf2));
257     assertTrue(store.getNonPositionalFeatures().contains(sf2));
258     assertTrue(store.delete(sf2));
259     assertTrue(store.getNonPositionalFeatures().isEmpty());
260
261     /*
262      * contact feature deletion
263      */
264     SequenceFeature sf3 = new SequenceFeature("", "Disulphide Bond", 11,
265             23, Float.NaN, null);
266     store.addFeature(sf3);
267     assertEquals(store.getPositionalFeatures().size(), 1);
268     assertTrue(store.getPositionalFeatures().contains(sf3));
269     assertTrue(store.delete(sf3));
270     assertTrue(store.getPositionalFeatures().isEmpty());
271
272     /*
273      * nested feature deletion
274      */
275     SequenceFeature sf4 = addFeature(store, 20, 30);
276     SequenceFeature sf5 = addFeature(store, 22, 26); // to NCList
277     SequenceFeature sf6 = addFeature(store, 23, 24); // child of sf5
278     SequenceFeature sf7 = addFeature(store, 25, 25); // sibling of sf6
279     SequenceFeature sf8 = addFeature(store, 24, 24); // child of sf6
280     SequenceFeature sf9 = addFeature(store, 23, 23); // child of sf6
281     assertEquals(store.getPositionalFeatures().size(), 6);
282
283     // delete a node with children - they take its place
284     assertTrue(store.delete(sf6)); // sf8, sf9 should become children of sf5
285     assertEquals(store.getPositionalFeatures().size(), 5);
286     assertFalse(store.getPositionalFeatures().contains(sf6));
287
288     // delete a node with no children
289     assertTrue(store.delete(sf7));
290     assertEquals(store.getPositionalFeatures().size(), 4);
291     assertFalse(store.getPositionalFeatures().contains(sf7));
292
293     // delete root of NCList
294     assertTrue(store.delete(sf5));
295     assertEquals(store.getPositionalFeatures().size(), 3);
296     assertFalse(store.getPositionalFeatures().contains(sf5));
297
298     // continue the killing fields
299     assertTrue(store.delete(sf4));
300     assertEquals(store.getPositionalFeatures().size(), 2);
301     assertFalse(store.getPositionalFeatures().contains(sf4));
302
303     assertTrue(store.delete(sf9));
304     assertEquals(store.getPositionalFeatures().size(), 1);
305     assertFalse(store.getPositionalFeatures().contains(sf9));
306
307     assertTrue(store.delete(sf8));
308     assertTrue(store.getPositionalFeatures().isEmpty());
309   }
310
311   @Test(groups = "Functional")
312   public void testAddFeature()
313   {
314     FeatureStore fs = newFeatureStore();
315
316     SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20,
317             Float.NaN, null);
318     SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20,
319             Float.NaN, null);
320
321     assertTrue(fs.addFeature(sf1));
322     assertEquals(fs.getFeatureCount(true), 1); // positional
323     assertEquals(fs.getFeatureCount(false), 0); // non-positional
324
325     /*
326      * re-adding the same or an identical feature should fail
327      */
328     assertFalse(fs.addFeature(sf1));
329     assertEquals(fs.getFeatureCount(true), 1);
330     assertFalse(fs.addFeature(sf2));
331     assertEquals(fs.getFeatureCount(true), 1);
332
333     /*
334      * add non-positional
335      */
336     SequenceFeature sf3 = new SequenceFeature("Cath", "", 0, 0, Float.NaN,
337             null);
338     assertTrue(fs.addFeature(sf3));
339     assertEquals(fs.getFeatureCount(true), 1); // positional
340     assertEquals(fs.getFeatureCount(false), 1); // non-positional
341     SequenceFeature sf4 = new SequenceFeature("Cath", "", 0, 0, Float.NaN,
342             null);
343     assertFalse(fs.addFeature(sf4)); // already stored
344     assertEquals(fs.getFeatureCount(true), 1); // positional
345     assertEquals(fs.getFeatureCount(false), 1); // non-positional
346
347     /*
348      * add contact
349      */
350     SequenceFeature sf5 = new SequenceFeature("Disulfide bond", "", 10, 20,
351             Float.NaN, null);
352     assertTrue(fs.addFeature(sf5));
353     assertEquals(fs.getFeatureCount(true), 2); // positional - add 1 for contact
354     assertEquals(fs.getFeatureCount(false), 1); // non-positional
355     SequenceFeature sf6 = new SequenceFeature("Disulfide bond", "", 10, 20,
356             Float.NaN, null);
357     assertFalse(fs.addFeature(sf6)); // already stored
358     assertEquals(fs.getFeatureCount(true), 2); // no change
359     assertEquals(fs.getFeatureCount(false), 1); // no change
360   }
361
362   @Test(groups = "Functional")
363   public void testIsEmpty()
364   {
365     FeatureStore fs = newFeatureStore();
366     assertTrue(fs.isEmpty());
367     assertEquals(fs.getFeatureCount(true), 0);
368
369     /*
370      * non-nested feature
371      */
372     SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20,
373             Float.NaN, null);
374     fs.addFeature(sf1);
375     assertFalse(fs.isEmpty());
376     assertEquals(fs.getFeatureCount(true), 1);
377     fs.delete(sf1);
378     assertTrue(fs.isEmpty());
379     assertEquals(fs.getFeatureCount(true), 0);
380
381     /*
382      * non-positional feature
383      */
384     sf1 = new SequenceFeature("Cath", "", 0, 0, Float.NaN, null);
385     fs.addFeature(sf1);
386     assertFalse(fs.isEmpty());
387     assertEquals(fs.getFeatureCount(false), 1); // non-positional
388     assertEquals(fs.getFeatureCount(true), 0); // positional
389     fs.delete(sf1);
390     assertTrue(fs.isEmpty());
391     assertEquals(fs.getFeatureCount(false), 0);
392
393     /*
394      * contact feature
395      */
396     sf1 = new SequenceFeature("Disulfide bond", "", 19, 49, Float.NaN, null);
397     fs.addFeature(sf1);
398     assertFalse(fs.isEmpty());
399     assertEquals(fs.getFeatureCount(true), 1);
400     fs.delete(sf1);
401     assertTrue(fs.isEmpty());
402     assertEquals(fs.getFeatureCount(true), 0);
403
404     /*
405      * sf2, sf3 added as nested features
406      */
407     sf1 = new SequenceFeature("Cath", "", 19, 49, Float.NaN, null);
408     SequenceFeature sf2 = new SequenceFeature("Cath", "", 20, 40,
409             Float.NaN, null);
410     SequenceFeature sf3 = new SequenceFeature("Cath", "", 25, 35,
411             Float.NaN, null);
412     fs.addFeature(sf1);
413     fs.addFeature(sf2);
414     fs.addFeature(sf3);
415     assertEquals(fs.getFeatureCount(true), 3);
416     assertTrue(fs.delete(sf1));
417     assertEquals(fs.getFeatureCount(true), 2);
418     assertEquals(fs.getFeatures().size(), 2);
419     assertFalse(fs.isEmpty());
420     assertTrue(fs.delete(sf2));
421     assertEquals(fs.getFeatureCount(true), 1);
422     assertFalse(fs.isEmpty());
423     assertTrue(fs.delete(sf3));
424     assertEquals(fs.getFeatureCount(true), 0);
425     assertTrue(fs.isEmpty()); // all gone
426   }
427
428   @Test(groups = "Functional")
429   public void testGetFeatureGroups()
430   {
431     FeatureStore fs = newFeatureStore();
432     assertTrue(fs.getFeatureGroups(true).isEmpty());
433     assertTrue(fs.getFeatureGroups(false).isEmpty());
434
435     SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 10, 20, 1f, "group1");
436     fs.addFeature(sf1);
437     Set<String> groups = fs.getFeatureGroups(true);
438     assertEquals(groups.size(), 1);
439     assertTrue(groups.contains("group1"));
440
441     /*
442      * add another feature of the same group, delete one, delete both
443      */
444     SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "group1");
445     fs.addFeature(sf2);
446     groups = fs.getFeatureGroups(true);
447     assertEquals(groups.size(), 1);
448     assertTrue(groups.contains("group1"));
449     fs.delete(sf2);
450     groups = fs.getFeatureGroups(true);
451     assertEquals(groups.size(), 1);
452     assertTrue(groups.contains("group1"));
453     fs.delete(sf1);
454     groups = fs.getFeatureGroups(true);
455     assertTrue(fs.getFeatureGroups(true).isEmpty());
456
457     SequenceFeature sf3 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "group2");
458     fs.addFeature(sf3);
459     SequenceFeature sf4 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "Group2");
460     fs.addFeature(sf4);
461     SequenceFeature sf5 = new SequenceFeature("Cath", "desc", 20, 30, 1f, null);
462     fs.addFeature(sf5);
463     groups = fs.getFeatureGroups(true);
464     assertEquals(groups.size(), 3);
465     assertTrue(groups.contains("group2"));
466     assertTrue(groups.contains("Group2")); // case sensitive
467     assertTrue(groups.contains(null)); // null allowed
468     assertTrue(fs.getFeatureGroups(false).isEmpty()); // non-positional
469
470     fs.delete(sf3);
471     groups = fs.getFeatureGroups(true);
472     assertEquals(groups.size(), 2);
473     assertFalse(groups.contains("group2"));
474     fs.delete(sf4);
475     groups = fs.getFeatureGroups(true);
476     assertEquals(groups.size(), 1);
477     assertFalse(groups.contains("Group2"));
478     fs.delete(sf5);
479     groups = fs.getFeatureGroups(true);
480     assertTrue(groups.isEmpty());
481
482     /*
483      * add non-positional feature
484      */
485     SequenceFeature sf6 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
486             "CathGroup");
487     fs.addFeature(sf6);
488     groups = fs.getFeatureGroups(false);
489     assertEquals(groups.size(), 1);
490     assertTrue(groups.contains("CathGroup"));
491     assertTrue(fs.delete(sf6));
492     assertTrue(fs.getFeatureGroups(false).isEmpty());
493   }
494
495   @Test(groups = "Functional")
496   public void testGetTotalFeatureLength()
497   {
498     FeatureStore fs = newFeatureStore();
499     assertEquals(fs.getTotalFeatureLength(), 0);
500
501     addFeature(fs, 10, 20); // 11
502     assertEquals(fs.getTotalFeatureLength(), 11);
503     addFeature(fs, 17, 37); // 21
504     SequenceFeature sf1 = addFeature(fs, 14, 74); // 61
505     assertEquals(fs.getTotalFeatureLength(), 93);
506
507     // non-positional features don't count
508     SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
509             "group1");
510     fs.addFeature(sf2);
511     assertEquals(fs.getTotalFeatureLength(), 93);
512
513     // contact features count 1
514     SequenceFeature sf3 = new SequenceFeature("disulphide bond", "desc",
515             15, 35, 1f, "group1");
516     fs.addFeature(sf3);
517     assertEquals(fs.getTotalFeatureLength(), 94);
518
519     assertTrue(fs.delete(sf1));
520     assertEquals(fs.getTotalFeatureLength(), 33);
521     assertFalse(fs.delete(sf1));
522     assertEquals(fs.getTotalFeatureLength(), 33);
523     assertTrue(fs.delete(sf2));
524     assertEquals(fs.getTotalFeatureLength(), 33);
525     assertTrue(fs.delete(sf3));
526     assertEquals(fs.getTotalFeatureLength(), 32);
527   }
528
529   @Test(groups = "Functional")
530   public void testGetFeatureLength()
531   {
532     /*
533      * positional feature
534      */
535     SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 10, 20, 1f, "group1");
536     assertEquals(FeatureStore.getFeatureLength(sf1), 11);
537   
538     /*
539      * non-positional feature
540      */
541     SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
542             "CathGroup");
543     assertEquals(FeatureStore.getFeatureLength(sf2), 0);
544
545     /*
546      * contact feature counts 1
547      */
548     SequenceFeature sf3 = new SequenceFeature("Disulphide Bond", "desc",
549             14, 28, 1f, "AGroup");
550     assertEquals(FeatureStore.getFeatureLength(sf3), 1);
551   }
552
553   @Test(groups = "Functional")
554   public void testMin()
555   {
556     assertEquals(FeatureStore.min(Float.NaN, Float.NaN), Float.NaN);
557     assertEquals(FeatureStore.min(Float.NaN, 2f), 2f);
558     assertEquals(FeatureStore.min(-2f, Float.NaN), -2f);
559     assertEquals(FeatureStore.min(2f, -3f), -3f);
560   }
561
562   @Test(groups = "Functional")
563   public void testMax()
564   {
565     assertEquals(FeatureStore.max(Float.NaN, Float.NaN), Float.NaN);
566     assertEquals(FeatureStore.max(Float.NaN, 2f), 2f);
567     assertEquals(FeatureStore.max(-2f, Float.NaN), -2f);
568     assertEquals(FeatureStore.max(2f, -3f), 2f);
569   }
570
571   @Test(groups = "Functional")
572   public void testGetMinimumScore_getMaximumScore()
573   {
574     FeatureStore fs = newFeatureStore();
575     assertEquals(fs.getMinimumScore(true), Float.NaN); // positional
576     assertEquals(fs.getMaximumScore(true), Float.NaN);
577     assertEquals(fs.getMinimumScore(false), Float.NaN); // non-positional
578     assertEquals(fs.getMaximumScore(false), Float.NaN);
579
580     // add features with no score
581     SequenceFeature sf1 = new SequenceFeature("type", "desc", 0, 0,
582             Float.NaN, "group");
583     fs.addFeature(sf1);
584     SequenceFeature sf2 = new SequenceFeature("type", "desc", 10, 20,
585             Float.NaN, "group");
586     fs.addFeature(sf2);
587     assertEquals(fs.getMinimumScore(true), Float.NaN);
588     assertEquals(fs.getMaximumScore(true), Float.NaN);
589     assertEquals(fs.getMinimumScore(false), Float.NaN);
590     assertEquals(fs.getMaximumScore(false), Float.NaN);
591
592     // add positional features with score
593     SequenceFeature sf3 = new SequenceFeature("type", "desc", 10, 20, 1f,
594             "group");
595     fs.addFeature(sf3);
596     SequenceFeature sf4 = new SequenceFeature("type", "desc", 12, 16, 4f,
597             "group");
598     fs.addFeature(sf4);
599     assertEquals(fs.getMinimumScore(true), 1f);
600     assertEquals(fs.getMaximumScore(true), 4f);
601     assertEquals(fs.getMinimumScore(false), Float.NaN);
602     assertEquals(fs.getMaximumScore(false), Float.NaN);
603
604     // add non-positional features with score
605     SequenceFeature sf5 = new SequenceFeature("type", "desc", 0, 0, 11f,
606             "group");
607     fs.addFeature(sf5);
608     SequenceFeature sf6 = new SequenceFeature("type", "desc", 0, 0, -7f,
609             "group");
610     fs.addFeature(sf6);
611     assertEquals(fs.getMinimumScore(true), 1f);
612     assertEquals(fs.getMaximumScore(true), 4f);
613     assertEquals(fs.getMinimumScore(false), -7f);
614     assertEquals(fs.getMaximumScore(false), 11f);
615
616     // delete one positional and one non-positional
617     // min-max should be recomputed
618     assertTrue(fs.delete(sf6));
619     assertTrue(fs.delete(sf3));
620     assertEquals(fs.getMinimumScore(true), 4f);
621     assertEquals(fs.getMaximumScore(true), 4f);
622     assertEquals(fs.getMinimumScore(false), 11f);
623     assertEquals(fs.getMaximumScore(false), 11f);
624
625     // delete remaining features with score
626     assertTrue(fs.delete(sf4));
627     assertTrue(fs.delete(sf5));
628     assertEquals(fs.getMinimumScore(true), Float.NaN);
629     assertEquals(fs.getMaximumScore(true), Float.NaN);
630     assertEquals(fs.getMinimumScore(false), Float.NaN);
631     assertEquals(fs.getMaximumScore(false), Float.NaN);
632
633     // delete all features
634     assertTrue(fs.delete(sf1));
635     assertTrue(fs.delete(sf2));
636     assertTrue(fs.isEmpty());
637     assertEquals(fs.getMinimumScore(true), Float.NaN);
638     assertEquals(fs.getMaximumScore(true), Float.NaN);
639     assertEquals(fs.getMinimumScore(false), Float.NaN);
640     assertEquals(fs.getMaximumScore(false), Float.NaN);
641   }
642
643   @Test(groups = "Functional")
644   public void testListContains()
645   {
646     FeatureStore featureStore = newFeatureStore();
647     assertFalse(featureStore.listContains(null, null));
648     List<SequenceFeature> features = new ArrayList<>();
649     assertFalse(featureStore.listContains(features, null));
650
651     SequenceFeature sf1 = new SequenceFeature("type1", "desc1", 20, 30, 3f,
652             "group1");
653     assertFalse(featureStore.listContains(null, sf1));
654     assertFalse(featureStore.listContains(features, sf1));
655
656     features.add(sf1);
657     SequenceFeature sf2 = new SequenceFeature("type1", "desc1", 20, 30, 3f,
658             "group1");
659     SequenceFeature sf3 = new SequenceFeature("type1", "desc1", 20, 40, 3f,
660             "group1");
661
662     // sf2.equals(sf1) so contains should return true
663     assertTrue(featureStore.listContains(features, sf2));
664     assertFalse(featureStore.listContains(features, sf3));
665   }
666
667   @Test(groups = "Functional")
668   public void testGetFeaturesForGroup()
669   {
670     FeatureStore fs = newFeatureStore();
671
672     /*
673      * with no features
674      */
675     assertTrue(fs.getFeaturesForGroup(true, null).isEmpty());
676     assertTrue(fs.getFeaturesForGroup(false, null).isEmpty());
677     assertTrue(fs.getFeaturesForGroup(true, "uniprot").isEmpty());
678     assertTrue(fs.getFeaturesForGroup(false, "uniprot").isEmpty());
679
680     /*
681      * sf1: positional feature in the null group
682      */
683     SequenceFeature sf1 = new SequenceFeature("Pfam", "desc", 4, 10, 0f,
684             null);
685     fs.addFeature(sf1);
686     assertTrue(fs.getFeaturesForGroup(true, "uniprot").isEmpty());
687     assertTrue(fs.getFeaturesForGroup(false, "uniprot").isEmpty());
688     assertTrue(fs.getFeaturesForGroup(false, null).isEmpty());
689     List<SequenceFeature> features = fs.getFeaturesForGroup(true, null);
690     assertEquals(features.size(), 1);
691     assertTrue(features.contains(sf1));
692
693     /*
694      * sf2: non-positional feature in the null group
695      * sf3: positional feature in a non-null group
696      * sf4: non-positional feature in a non-null group
697      */
698     SequenceFeature sf2 = new SequenceFeature("Pfam", "desc", 0, 0, 0f,
699             null);
700     SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 4, 10, 0f,
701             "Uniprot");
702     SequenceFeature sf4 = new SequenceFeature("Pfam", "desc", 0, 0, 0f,
703             "Rfam");
704     fs.addFeature(sf2);
705     fs.addFeature(sf3);
706     fs.addFeature(sf4);
707
708     features = fs.getFeaturesForGroup(true, null);
709     assertEquals(features.size(), 1);
710     assertTrue(features.contains(sf1));
711
712     features = fs.getFeaturesForGroup(false, null);
713     assertEquals(features.size(), 1);
714     assertTrue(features.contains(sf2));
715
716     features = fs.getFeaturesForGroup(true, "Uniprot");
717     assertEquals(features.size(), 1);
718     assertTrue(features.contains(sf3));
719
720     features = fs.getFeaturesForGroup(false, "Rfam");
721     assertEquals(features.size(), 1);
722     assertTrue(features.contains(sf4));
723   }
724
725   @Test(groups = "Functional")
726   public void testShiftFeatures()
727   {
728     FeatureStore fs = newFeatureStore();
729     assertFalse(fs.shiftFeatures(0, 1)); // nothing to do
730
731     SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null);
732     fs.addFeature(sf1);
733     // nested feature:
734     SequenceFeature sf2 = new SequenceFeature("Cath", "", 8, 14, 0f, null);
735     fs.addFeature(sf2);
736     // contact feature:
737     SequenceFeature sf3 = new SequenceFeature("Disulfide bond", "", 23, 32,
738             0f, null);
739     fs.addFeature(sf3);
740     // non-positional feature:
741     SequenceFeature sf4 = new SequenceFeature("Cath", "", 0, 0, 0f, null);
742     fs.addFeature(sf4);
743
744     /*
745      * shift all features right by 5
746      */
747     assertTrue(fs.shiftFeatures(0, 5));
748
749     // non-positional features untouched:
750     List<SequenceFeature> nonPos = fs.getNonPositionalFeatures();
751     assertEquals(nonPos.size(), 1);
752     assertTrue(nonPos.contains(sf4));
753
754     // positional features are replaced
755     List<SequenceFeature> pos = fs.getPositionalFeatures();
756     assertEquals(pos.size(), 3);
757     assertFalse(pos.contains(sf1));
758     assertFalse(pos.contains(sf2));
759     assertFalse(pos.contains(sf3));
760     SequenceFeatures.sortFeatures(pos, true); // ascending start pos
761     assertEquals(pos.get(0).getBegin(), 7);
762     assertEquals(pos.get(0).getEnd(), 10);
763     assertEquals(pos.get(1).getBegin(), 13);
764     assertEquals(pos.get(1).getEnd(), 19);
765     assertEquals(pos.get(2).getBegin(), 28);
766     assertEquals(pos.get(2).getEnd(), 37);
767
768     /*
769      * now shift left by 15
770      * feature at [7-10] should be removed
771      * feature at [13-19] should become [1-4] 
772      */
773     assertTrue(fs.shiftFeatures(0, -15));
774     pos = fs.getPositionalFeatures();
775     assertEquals(pos.size(), 2);
776     SequenceFeatures.sortFeatures(pos, true);
777     assertEquals(pos.get(0).getBegin(), 1);
778     assertEquals(pos.get(0).getEnd(), 4);
779     assertEquals(pos.get(1).getBegin(), 13);
780     assertEquals(pos.get(1).getEnd(), 22);
781
782     /*
783      * shift right by 4 from position 2 onwards
784      * feature at [1-4] unchanged, feature at [13-22] shifts
785      */
786     assertTrue(fs.shiftFeatures(2, 4));
787     pos = fs.getPositionalFeatures();
788     assertEquals(pos.size(), 2);
789     SequenceFeatures.sortFeatures(pos, true);
790     assertEquals(pos.get(0).getBegin(), 1);
791     assertEquals(pos.get(0).getEnd(), 4);
792     assertEquals(pos.get(1).getBegin(), 17);
793     assertEquals(pos.get(1).getEnd(), 26);
794
795     /*
796      * shift right by 4 from position 18 onwards
797      * should be no change
798      */
799     SequenceFeature f1 = pos.get(0);
800     SequenceFeature f2 = pos.get(1);
801     assertFalse(fs.shiftFeatures(18, 4)); // no update
802     pos = fs.getPositionalFeatures();
803     assertEquals(pos.size(), 2);
804     SequenceFeatures.sortFeatures(pos, true);
805     assertSame(pos.get(0), f1);
806     assertSame(pos.get(1), f2);
807   }
808
809   @Test(groups = "Functional")
810   public void testDelete_readd()
811   {
812     /*
813      * add a feature and a nested feature
814      */
815     FeatureStore store = newFeatureStore();
816     SequenceFeature sf1 = addFeature(store, 10, 20);
817     // sf2 is nested in sf1 so will be stored in nestedFeatures
818     SequenceFeature sf2 = addFeature(store, 12, 14);
819     List<SequenceFeature> features = store.getPositionalFeatures();
820     assertEquals(features.size(), 2);
821     assertTrue(features.contains(sf1));
822     assertTrue(features.contains(sf2));
823     assertTrue(store.getFeatures().contains(sf1));
824     assertTrue(store.getFeatures().contains(sf2));
825   
826     /*
827      * delete the first feature
828      */
829     assertTrue(store.delete(sf1));
830     features = store.getPositionalFeatures();
831     assertFalse(features.contains(sf1));
832     assertTrue(features.contains(sf2));
833
834     /*
835      * re-add the 'nested' feature; is it now duplicated?
836      */
837     store.addFeature(sf2);
838     features = store.getPositionalFeatures();
839     assertEquals(features.size(), 1);
840     assertTrue(features.contains(sf2));
841   }
842
843   @Test(groups = "Functional")
844   public void testContains()
845   {
846     FeatureStore fs = newFeatureStore();
847     SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20,
848             Float.NaN, "group1");
849     SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20,
850             Float.NaN, "group2");
851     SequenceFeature sf3 = new SequenceFeature("Cath", "", 0, 0, Float.NaN,
852             "group1");
853     SequenceFeature sf4 = new SequenceFeature("Cath", "", 0, 0, 0f,
854             "group1");
855     SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "", 5, 15,
856             Float.NaN, "group1");
857     SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "", 5, 15,
858             Float.NaN, "group2");
859
860     fs.addFeature(sf1);
861     fs.addFeature(sf3);
862     fs.addFeature(sf5);
863     assertTrue(fs.contains(sf1)); // positional feature
864     assertTrue(fs.contains(new SequenceFeature(sf1))); // identical feature
865     assertFalse(fs.contains(sf2)); // different group
866     assertTrue(fs.contains(sf3)); // non-positional
867     assertTrue(fs.contains(new SequenceFeature(sf3)));
868     assertFalse(fs.contains(sf4)); // different score
869     assertTrue(fs.contains(sf5)); // contact feature
870     assertTrue(fs.contains(new SequenceFeature(sf5)));
871     assertFalse(fs.contains(sf6)); // different group
872
873     /*
874      * add a nested feature
875      */
876     SequenceFeature sf7 = new SequenceFeature("Cath", "", 12, 16,
877             Float.NaN, "group1");
878     fs.addFeature(sf7);
879     assertTrue(fs.contains(sf7));
880     assertTrue(fs.contains(new SequenceFeature(sf7)));
881
882     /*
883      * delete the outer (enclosing, non-nested) feature
884      */
885     fs.delete(sf1);
886     assertFalse(fs.contains(sf1));
887     assertTrue(fs.contains(sf7));
888   }
889
890   @Test(groups = "Functional")
891   public void testFindFeatures_withResultList()
892   {
893     FeatureStore fs = newFeatureStore();
894     SequenceFeature sf1 = addFeature(fs, 10, 50);
895     SequenceFeature sf2 = addFeature(fs, 150, 250);
896   
897     List<SequenceFeature> overlaps = new ArrayList<>();
898     List<SequenceFeature> result = fs.findOverlappingFeatures(200, 200,
899             overlaps);
900     assertSame(result, overlaps);
901     assertEquals(result.size(), 1);
902     assertTrue(result.contains(sf2));
903     assertFalse(result.contains(sf1));
904
905     /*
906      * if no list supplied, method creates one
907      */
908     result = fs.findOverlappingFeatures(200, 200, null);
909     assertEquals(result.size(), 1);
910     assertTrue(result.contains(sf2));
911     assertFalse(result.contains(sf1));
912   }
913 }