JAL-2480 more efficient contains check on add; tidied comparators
[jalview.git] / test / jalview / datamodel / features / FeatureStoreTest.java
index 171b7c3..d2893e5 100644 (file)
@@ -6,7 +6,9 @@ import static org.testng.Assert.assertTrue;
 
 import jalview.datamodel.SequenceFeature;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import org.testng.annotations.Test;
 
@@ -235,7 +237,7 @@ public class FeatureStoreTest
   }
 
   @Test(groups = "Functional")
-  public void testGetFeatures()
+  public void testGetPositionalFeatures()
   {
     FeatureStore store = new FeatureStore();
     SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
@@ -266,15 +268,19 @@ public class FeatureStoreTest
             18, 45, Float.NaN, null);
     store.addFeature(sf7);
 
-    List<SequenceFeature> features = store.getFeatures();
-    assertEquals(features.size(), 7);
+    List<SequenceFeature> features = store.getPositionalFeatures();
+    assertEquals(features.size(), 6);
     assertTrue(features.contains(sf1));
     assertTrue(features.contains(sf2));
     assertTrue(features.contains(sf3));
     assertTrue(features.contains(sf4));
     assertTrue(features.contains(sf5));
-    assertTrue(features.contains(sf6));
+    assertFalse(features.contains(sf6));
     assertTrue(features.contains(sf7));
+
+    features = store.getNonPositionalFeatures();
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf6));
   }
 
   @Test(groups = "Functional")
@@ -282,21 +288,22 @@ public class FeatureStoreTest
   {
     FeatureStore store = new FeatureStore();
     SequenceFeature sf1 = addFeature(store, 10, 20);
-    assertTrue(store.getFeatures().contains(sf1));
+    assertTrue(store.getPositionalFeatures().contains(sf1));
 
     /*
      * simple deletion
      */
     assertTrue(store.delete(sf1));
-    assertTrue(store.getFeatures().isEmpty());
+    assertTrue(store.getPositionalFeatures().isEmpty());
 
     /*
      * non-positional feature deletion
      */
     SequenceFeature sf2 = addFeature(store, 0, 0);
-    assertTrue(store.getFeatures().contains(sf2));
+    assertFalse(store.getPositionalFeatures().contains(sf2));
+    assertTrue(store.getNonPositionalFeatures().contains(sf2));
     assertTrue(store.delete(sf2));
-    assertTrue(store.getFeatures().isEmpty());
+    assertTrue(store.getNonPositionalFeatures().isEmpty());
 
     /*
      * contact feature deletion
@@ -304,10 +311,10 @@ public class FeatureStoreTest
     SequenceFeature sf3 = new SequenceFeature("", "Disulphide Bond", 11,
             23, Float.NaN, null);
     store.addFeature(sf3);
-    assertEquals(store.getFeatures().size(), 1);
-    assertTrue(store.getFeatures().contains(sf3));
+    assertEquals(store.getPositionalFeatures().size(), 1);
+    assertTrue(store.getPositionalFeatures().contains(sf3));
     assertTrue(store.delete(sf3));
-    assertTrue(store.getFeatures().isEmpty());
+    assertTrue(store.getPositionalFeatures().isEmpty());
 
     /*
      * nested feature deletion
@@ -318,34 +325,34 @@ public class FeatureStoreTest
     SequenceFeature sf7 = addFeature(store, 25, 25); // sibling of sf6
     SequenceFeature sf8 = addFeature(store, 24, 24); // child of sf6
     SequenceFeature sf9 = addFeature(store, 23, 23); // child of sf6
-    assertEquals(store.getFeatures().size(), 6);
+    assertEquals(store.getPositionalFeatures().size(), 6);
 
     // delete a node with children - they take its place
     assertTrue(store.delete(sf6)); // sf8, sf9 should become children of sf5
-    assertEquals(store.getFeatures().size(), 5);
-    assertFalse(store.getFeatures().contains(sf6));
+    assertEquals(store.getPositionalFeatures().size(), 5);
+    assertFalse(store.getPositionalFeatures().contains(sf6));
 
     // delete a node with no children
     assertTrue(store.delete(sf7));
-    assertEquals(store.getFeatures().size(), 4);
-    assertFalse(store.getFeatures().contains(sf7));
+    assertEquals(store.getPositionalFeatures().size(), 4);
+    assertFalse(store.getPositionalFeatures().contains(sf7));
 
     // delete root of NCList
     assertTrue(store.delete(sf5));
-    assertEquals(store.getFeatures().size(), 3);
-    assertFalse(store.getFeatures().contains(sf5));
+    assertEquals(store.getPositionalFeatures().size(), 3);
+    assertFalse(store.getPositionalFeatures().contains(sf5));
 
     // continue the killing fields
     assertTrue(store.delete(sf4));
-    assertEquals(store.getFeatures().size(), 2);
-    assertFalse(store.getFeatures().contains(sf4));
+    assertEquals(store.getPositionalFeatures().size(), 2);
+    assertFalse(store.getPositionalFeatures().contains(sf4));
 
     assertTrue(store.delete(sf9));
-    assertEquals(store.getFeatures().size(), 1);
-    assertFalse(store.getFeatures().contains(sf9));
+    assertEquals(store.getPositionalFeatures().size(), 1);
+    assertFalse(store.getPositionalFeatures().contains(sf9));
 
     assertTrue(store.delete(sf8));
-    assertTrue(store.getFeatures().isEmpty());
+    assertTrue(store.getPositionalFeatures().isEmpty());
   }
 
   @Test(groups = "Functional")
@@ -359,12 +366,44 @@ public class FeatureStoreTest
             Float.NaN, null);
 
     assertTrue(fs.addFeature(sf1));
+    assertEquals(fs.getFeatureCount(true), 1); // positional
+    assertEquals(fs.getFeatureCount(false), 0); // non-positional
 
     /*
      * re-adding the same or an identical feature should fail
      */
     assertFalse(fs.addFeature(sf1));
+    assertEquals(fs.getFeatureCount(true), 1);
     assertFalse(fs.addFeature(sf2));
+    assertEquals(fs.getFeatureCount(true), 1);
+
+    /*
+     * add non-positional
+     */
+    SequenceFeature sf3 = new SequenceFeature("Cath", "", 0, 0, Float.NaN,
+            null);
+    assertTrue(fs.addFeature(sf3));
+    assertEquals(fs.getFeatureCount(true), 1); // positional
+    assertEquals(fs.getFeatureCount(false), 1); // non-positional
+    SequenceFeature sf4 = new SequenceFeature("Cath", "", 0, 0, Float.NaN,
+            null);
+    assertFalse(fs.addFeature(sf4)); // already stored
+    assertEquals(fs.getFeatureCount(true), 1); // positional
+    assertEquals(fs.getFeatureCount(false), 1); // non-positional
+
+    /*
+     * add contact
+     */
+    SequenceFeature sf5 = new SequenceFeature("Disulfide bond", "", 0, 0,
+            Float.NaN, null);
+    assertTrue(fs.addFeature(sf5));
+    assertEquals(fs.getFeatureCount(true), 2); // positional - add 1 for contact
+    assertEquals(fs.getFeatureCount(false), 1); // non-positional
+    SequenceFeature sf6 = new SequenceFeature("Disulfide bond", "", 0, 0,
+            Float.NaN, null);
+    assertFalse(fs.addFeature(sf6)); // already stored
+    assertEquals(fs.getFeatureCount(true), 2); // no change
+    assertEquals(fs.getFeatureCount(false), 1); // no change
   }
 
   @Test(groups = "Functional")
@@ -372,6 +411,7 @@ public class FeatureStoreTest
   {
     FeatureStore fs = new FeatureStore();
     assertTrue(fs.isEmpty());
+    assertEquals(fs.getFeatureCount(true), 0);
 
     /*
      * non-nested feature
@@ -380,8 +420,10 @@ public class FeatureStoreTest
             Float.NaN, null);
     fs.addFeature(sf1);
     assertFalse(fs.isEmpty());
+    assertEquals(fs.getFeatureCount(true), 1);
     fs.delete(sf1);
     assertTrue(fs.isEmpty());
+    assertEquals(fs.getFeatureCount(true), 0);
 
     /*
      * non-positional feature
@@ -389,8 +431,11 @@ public class FeatureStoreTest
     sf1 = new SequenceFeature("Cath", "", 0, 0, Float.NaN, null);
     fs.addFeature(sf1);
     assertFalse(fs.isEmpty());
+    assertEquals(fs.getFeatureCount(false), 1); // non-positional
+    assertEquals(fs.getFeatureCount(true), 0); // positional
     fs.delete(sf1);
     assertTrue(fs.isEmpty());
+    assertEquals(fs.getFeatureCount(false), 0);
 
     /*
      * contact feature
@@ -398,8 +443,10 @@ public class FeatureStoreTest
     sf1 = new SequenceFeature("Disulfide bond", "", 19, 49, Float.NaN, null);
     fs.addFeature(sf1);
     assertFalse(fs.isEmpty());
+    assertEquals(fs.getFeatureCount(true), 1);
     fs.delete(sf1);
     assertTrue(fs.isEmpty());
+    assertEquals(fs.getFeatureCount(true), 0);
 
     /*
      * sf2, sf3 added as nested features
@@ -412,14 +459,256 @@ public class FeatureStoreTest
     fs.addFeature(sf1);
     fs.addFeature(sf2);
     fs.addFeature(sf3);
+    assertEquals(fs.getFeatureCount(true), 3);
     assertTrue(fs.delete(sf1));
+    assertEquals(fs.getFeatureCount(true), 2);
     // FeatureStore should now only contain features in the NCList
-    assertEquals(fs.nonNestedFeatures.size(), 0);
+    assertTrue(fs.nonNestedFeatures.isEmpty());
     assertEquals(fs.nestedFeatures.size(), 2);
     assertFalse(fs.isEmpty());
     assertTrue(fs.delete(sf2));
+    assertEquals(fs.getFeatureCount(true), 1);
     assertFalse(fs.isEmpty());
     assertTrue(fs.delete(sf3));
+    assertEquals(fs.getFeatureCount(true), 0);
     assertTrue(fs.isEmpty()); // all gone
   }
+
+  @Test(groups = "Functional")
+  public void testGetFeatureGroups()
+  {
+    FeatureStore fs = new FeatureStore();
+    assertTrue(fs.getFeatureGroups(true).isEmpty());
+    assertTrue(fs.getFeatureGroups(false).isEmpty());
+
+    SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 10, 20, 1f, "group1");
+    fs.addFeature(sf1);
+    Set<String> groups = fs.getFeatureGroups(true);
+    assertEquals(groups.size(), 1);
+    assertTrue(groups.contains("group1"));
+
+    /*
+     * add another feature of the same group, delete one, delete both
+     */
+    SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "group1");
+    fs.addFeature(sf2);
+    groups = fs.getFeatureGroups(true);
+    assertEquals(groups.size(), 1);
+    assertTrue(groups.contains("group1"));
+    fs.delete(sf2);
+    groups = fs.getFeatureGroups(true);
+    assertEquals(groups.size(), 1);
+    assertTrue(groups.contains("group1"));
+    fs.delete(sf1);
+    groups = fs.getFeatureGroups(true);
+    assertTrue(fs.getFeatureGroups(true).isEmpty());
+
+    SequenceFeature sf3 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "group2");
+    fs.addFeature(sf3);
+    SequenceFeature sf4 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "Group2");
+    fs.addFeature(sf4);
+    SequenceFeature sf5 = new SequenceFeature("Cath", "desc", 20, 30, 1f, null);
+    fs.addFeature(sf5);
+    groups = fs.getFeatureGroups(true);
+    assertEquals(groups.size(), 3);
+    assertTrue(groups.contains("group2"));
+    assertTrue(groups.contains("Group2")); // case sensitive
+    assertTrue(groups.contains(null)); // null allowed
+    assertTrue(fs.getFeatureGroups(false).isEmpty()); // non-positional
+
+    fs.delete(sf3);
+    groups = fs.getFeatureGroups(true);
+    assertEquals(groups.size(), 2);
+    assertFalse(groups.contains("group2"));
+    fs.delete(sf4);
+    groups = fs.getFeatureGroups(true);
+    assertEquals(groups.size(), 1);
+    assertFalse(groups.contains("Group2"));
+    fs.delete(sf5);
+    groups = fs.getFeatureGroups(true);
+    assertTrue(groups.isEmpty());
+
+    /*
+     * add non-positional feature
+     */
+    SequenceFeature sf6 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
+            "CathGroup");
+    fs.addFeature(sf6);
+    groups = fs.getFeatureGroups(false);
+    assertEquals(groups.size(), 1);
+    assertTrue(groups.contains("CathGroup"));
+    assertTrue(fs.delete(sf6));
+    assertTrue(fs.getFeatureGroups(false).isEmpty());
+  }
+
+  @Test(groups = "Functional")
+  public void testGetTotalFeatureLength()
+  {
+    FeatureStore fs = new FeatureStore();
+    assertEquals(fs.getTotalFeatureLength(), 0);
+
+    addFeature(fs, 10, 20); // 11
+    assertEquals(fs.getTotalFeatureLength(), 11);
+    addFeature(fs, 17, 37); // 21
+    SequenceFeature sf1 = addFeature(fs, 14, 74); // 61
+    assertEquals(fs.getTotalFeatureLength(), 93);
+
+    // non-positional features don't count
+    SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
+            "group1");
+    fs.addFeature(sf2);
+    assertEquals(fs.getTotalFeatureLength(), 93);
+
+    // contact features count 1
+    SequenceFeature sf3 = new SequenceFeature("disulphide bond", "desc",
+            15, 35, 1f, "group1");
+    fs.addFeature(sf3);
+    assertEquals(fs.getTotalFeatureLength(), 94);
+
+    assertTrue(fs.delete(sf1));
+    assertEquals(fs.getTotalFeatureLength(), 33);
+    assertFalse(fs.delete(sf1));
+    assertEquals(fs.getTotalFeatureLength(), 33);
+    assertTrue(fs.delete(sf2));
+    assertEquals(fs.getTotalFeatureLength(), 33);
+    assertTrue(fs.delete(sf3));
+    assertEquals(fs.getTotalFeatureLength(), 32);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetFeatureLength()
+  {
+    /*
+     * positional feature
+     */
+    SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 10, 20, 1f, "group1");
+    assertEquals(FeatureStore.getFeatureLength(sf1), 11);
+  
+    /*
+     * non-positional feature
+     */
+    SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
+            "CathGroup");
+    assertEquals(FeatureStore.getFeatureLength(sf2), 0);
+
+    /*
+     * contact feature counts 1
+     */
+    SequenceFeature sf3 = new SequenceFeature("Disulphide Bond", "desc",
+            14, 28, 1f, "AGroup");
+    assertEquals(FeatureStore.getFeatureLength(sf3), 1);
+  }
+
+  @Test(groups = "Functional")
+  public void testMin()
+  {
+    assertEquals(FeatureStore.min(Float.NaN, Float.NaN), Float.NaN);
+    assertEquals(FeatureStore.min(Float.NaN, 2f), 2f);
+    assertEquals(FeatureStore.min(-2f, Float.NaN), -2f);
+    assertEquals(FeatureStore.min(2f, -3f), -3f);
+  }
+
+  @Test(groups = "Functional")
+  public void testMax()
+  {
+    assertEquals(FeatureStore.max(Float.NaN, Float.NaN), Float.NaN);
+    assertEquals(FeatureStore.max(Float.NaN, 2f), 2f);
+    assertEquals(FeatureStore.max(-2f, Float.NaN), -2f);
+    assertEquals(FeatureStore.max(2f, -3f), 2f);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetMinimumScore_getMaximumScore()
+  {
+    FeatureStore fs = new FeatureStore();
+    assertEquals(fs.getMinimumScore(true), Float.NaN); // positional
+    assertEquals(fs.getMaximumScore(true), Float.NaN);
+    assertEquals(fs.getMinimumScore(false), Float.NaN); // non-positional
+    assertEquals(fs.getMaximumScore(false), Float.NaN);
+
+    // add features with no score
+    SequenceFeature sf1 = new SequenceFeature("type", "desc", 0, 0,
+            Float.NaN, "group");
+    fs.addFeature(sf1);
+    SequenceFeature sf2 = new SequenceFeature("type", "desc", 10, 20,
+            Float.NaN, "group");
+    fs.addFeature(sf2);
+    assertEquals(fs.getMinimumScore(true), Float.NaN);
+    assertEquals(fs.getMaximumScore(true), Float.NaN);
+    assertEquals(fs.getMinimumScore(false), Float.NaN);
+    assertEquals(fs.getMaximumScore(false), Float.NaN);
+
+    // add positional features with score
+    SequenceFeature sf3 = new SequenceFeature("type", "desc", 10, 20, 1f,
+            "group");
+    fs.addFeature(sf3);
+    SequenceFeature sf4 = new SequenceFeature("type", "desc", 12, 16, 4f,
+            "group");
+    fs.addFeature(sf4);
+    assertEquals(fs.getMinimumScore(true), 1f);
+    assertEquals(fs.getMaximumScore(true), 4f);
+    assertEquals(fs.getMinimumScore(false), Float.NaN);
+    assertEquals(fs.getMaximumScore(false), Float.NaN);
+
+    // add non-positional features with score
+    SequenceFeature sf5 = new SequenceFeature("type", "desc", 0, 0, 11f,
+            "group");
+    fs.addFeature(sf5);
+    SequenceFeature sf6 = new SequenceFeature("type", "desc", 0, 0, -7f,
+            "group");
+    fs.addFeature(sf6);
+    assertEquals(fs.getMinimumScore(true), 1f);
+    assertEquals(fs.getMaximumScore(true), 4f);
+    assertEquals(fs.getMinimumScore(false), -7f);
+    assertEquals(fs.getMaximumScore(false), 11f);
+
+    // delete one positional and one non-positional
+    // min-max should be recomputed
+    assertTrue(fs.delete(sf6));
+    assertTrue(fs.delete(sf3));
+    assertEquals(fs.getMinimumScore(true), 4f);
+    assertEquals(fs.getMaximumScore(true), 4f);
+    assertEquals(fs.getMinimumScore(false), 11f);
+    assertEquals(fs.getMaximumScore(false), 11f);
+
+    // delete remaining features with score
+    assertTrue(fs.delete(sf4));
+    assertTrue(fs.delete(sf5));
+    assertEquals(fs.getMinimumScore(true), Float.NaN);
+    assertEquals(fs.getMaximumScore(true), Float.NaN);
+    assertEquals(fs.getMinimumScore(false), Float.NaN);
+    assertEquals(fs.getMaximumScore(false), Float.NaN);
+
+    // delete all features
+    assertTrue(fs.delete(sf1));
+    assertTrue(fs.delete(sf2));
+    assertTrue(fs.isEmpty());
+    assertEquals(fs.getMinimumScore(true), Float.NaN);
+    assertEquals(fs.getMaximumScore(true), Float.NaN);
+    assertEquals(fs.getMinimumScore(false), Float.NaN);
+    assertEquals(fs.getMaximumScore(false), Float.NaN);
+  }
+
+  @Test(groups = "Functional")
+  public void testContains()
+  {
+    assertFalse(FeatureStore.contains(null, null));
+    List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+    assertFalse(FeatureStore.contains(features, null));
+
+    SequenceFeature sf1 = new SequenceFeature("type1", "desc1", 20, 30, 3f,
+            "group1");
+    assertFalse(FeatureStore.contains(null, sf1));
+    assertFalse(FeatureStore.contains(features, sf1));
+
+    features.add(sf1);
+    SequenceFeature sf2 = new SequenceFeature("type1", "desc1", 20, 30, 3f,
+            "group1");
+    SequenceFeature sf3 = new SequenceFeature("type1", "desc1", 20, 40, 3f,
+            "group1");
+
+    // sf2.equals(sf1) so contains should return true
+    assertTrue(FeatureStore.contains(features, sf2));
+    assertFalse(FeatureStore.contains(features, sf3));
+  }
 }