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