JAL-2480 cache min-max score values per sequence and feature type
[jalview.git] / src / jalview / datamodel / features / FeatureStore.java
index 23edade..cd7d055 100644 (file)
@@ -127,6 +127,14 @@ public class FeatureStore
    */
   int totalExtent;
 
+  float positionalMinScore;
+
+  float positionalMaxScore;
+
+  float nonPositionalMinScore;
+
+  float nonPositionalMaxScore;
+
   /**
    * Constructor
    */
@@ -134,6 +142,11 @@ public class FeatureStore
   {
     nonNestedFeatures = new ArrayList<SequenceFeature>();
     positionalFeatureGroups = new HashSet<String>();
+    nonPositionalFeatureGroups = new HashSet<String>();
+    positionalMinScore = Float.NaN;
+    positionalMaxScore = Float.NaN;
+    nonPositionalMinScore = Float.NaN;
+    nonPositionalMaxScore = Float.NaN;
 
     // we only construct nonPositionalFeatures, contactFeatures
     // or the NCList if we need to
@@ -182,14 +195,33 @@ public class FeatureStore
       }
     }
 
-    /*
-     * record the total extent of positional features, to make
-     * getTotalFeatureLength possible; we count the length of a 
-     * contact feature as 1
-     */
     if (added)
     {
+      /*
+       * record the total extent of positional features, to make
+       * getTotalFeatureLength possible; we count the length of a 
+       * contact feature as 1
+       */
       totalExtent += getFeatureLength(feature);
+
+      /*
+       * record the minimum and maximum score for positional
+       * and non-positional features
+       */
+      float score = feature.getScore();
+      if (!Float.isNaN(score))
+      {
+        if (feature.isNonPositional())
+        {
+          nonPositionalMinScore = min(nonPositionalMinScore, score);
+          nonPositionalMaxScore = max(nonPositionalMaxScore, score);
+        }
+        else
+        {
+          positionalMinScore = min(positionalMinScore, score);
+          positionalMaxScore = max(positionalMaxScore, score);
+        }
+      }
     }
 
     return added;
@@ -229,7 +261,6 @@ public class FeatureStore
     if (nonPositionalFeatures == null)
     {
       nonPositionalFeatures = new ArrayList<SequenceFeature>();
-      nonPositionalFeatureGroups = new HashSet<String>();
     }
     if (nonPositionalFeatures.contains(feature))
     {
@@ -657,55 +688,87 @@ public class FeatureStore
 
     if (removed)
     {
-      /*
-       * rescan (positional or non-positional) features to rebuild the
-       * set of distinct feature groups present
-       */
-      rebuildFeatureGroups(sf.getFeatureGroup(), removedNonPositional);
-
-      /*
-       * subtract deleted feature's length from stored total length
-       * TODO: can start/end have changed since the feature was added?
-       */
-      int extent = getFeatureLength(sf);
-      totalExtent = Math.max(0, totalExtent - extent);
+      rescanAfterDelete();
     }
 
     return removed;
   }
 
   /**
-   * Check whether the given feature group is still represented, in either
-   * positional or non-positional features, and if not, remove it from the set
-   * of feature groups
+   * Rescan all features to recompute any cached values after an entry has been
+   * deleted
+   */
+  protected synchronized void rescanAfterDelete()
+  {
+    positionalFeatureGroups.clear();
+    nonPositionalFeatureGroups.clear();
+    totalExtent = 0;
+    positionalMinScore = Float.NaN;
+    positionalMaxScore = Float.NaN;
+    nonPositionalMinScore = Float.NaN;
+    nonPositionalMaxScore = Float.NaN;
+
+    /*
+     * scan non-positional features for groups and scores
+     */
+    for (SequenceFeature sf : getNonPositionalFeatures())
+    {
+      nonPositionalFeatureGroups.add(sf.getFeatureGroup());
+      float score = sf.getScore();
+      nonPositionalMinScore = min(nonPositionalMinScore, score);
+      nonPositionalMaxScore = max(nonPositionalMaxScore, score);
+    }
+
+    /*
+     * scan positional features for groups, scores and extents
+     */
+    for (SequenceFeature sf : getPositionalFeatures())
+    {
+      positionalFeatureGroups.add(sf.getFeatureGroup());
+      float score = sf.getScore();
+      positionalMinScore = min(positionalMinScore, score);
+      positionalMaxScore = max(positionalMaxScore, score);
+      totalExtent += getFeatureLength(sf);
+    }
+  }
+
+  /**
+   * A helper method to return the minimum of two floats, where a non-NaN value
+   * is treated as 'less than' a NaN value (unlike Math.min which does the
+   * opposite)
    * 
-   * @param featureGroup
-   * @param nonPositional
+   * @param f1
+   * @param f2
    */
-  protected void rebuildFeatureGroups(String featureGroup,
-          boolean nonPositional)
+  protected static float min(float f1, float f2)
   {
-    if (nonPositional && nonPositionalFeatures != null)
+    if (Float.isNaN(f1))
     {
-      boolean found = false;
-      for (SequenceFeature sf : nonPositionalFeatures)
-      {
-        String group = sf.getFeatureGroup();
-        if (featureGroup == group
-                || (featureGroup != null && featureGroup.equals(group)))
-        {
-          found = true;
-          break;
-        }
-      }
-      if (!found)
-      {
-        nonPositionalFeatureGroups.remove(featureGroup);
-      }
+      return Float.isNaN(f2) ? f1 : f2;
+    }
+    else
+    {
+      return Float.isNaN(f2) ? f1 : Math.min(f1, f2);
+    }
+  }
+
+  /**
+   * A helper method to return the maximum of two floats, where a non-NaN value
+   * is treated as 'greater than' a NaN value (unlike Math.max which does the
+   * opposite)
+   * 
+   * @param f1
+   * @param f2
+   */
+  protected static float max(float f1, float f2)
+  {
+    if (Float.isNaN(f1))
+    {
+      return Float.isNaN(f2) ? f1 : f2;
     }
-    else if (!findFeatureGroup(featureGroup))
+    else
     {
-      positionalFeatureGroups.remove(featureGroup);
+      return Float.isNaN(f2) ? f1 : Math.max(f1, f2);
     }
   }
 
@@ -845,4 +908,30 @@ public class FeatureStore
   {
     return totalExtent;
   }
+
+  /**
+   * Answers the minimum score held for positional or non-positional features.
+   * This may be Float.NaN if there are no features, are none has a non-NaN
+   * score.
+   * 
+   * @param positional
+   * @return
+   */
+  public float getMinimumScore(boolean positional)
+  {
+    return positional ? positionalMinScore : nonPositionalMinScore;
+  }
+
+  /**
+   * Answers the maximum score held for positional or non-positional features.
+   * This may be Float.NaN if there are no features, are none has a non-NaN
+   * score.
+   * 
+   * @param positional
+   * @return
+   */
+  public float getMaximumScore(boolean positional)
+  {
+    return positional ? positionalMaxScore : nonPositionalMaxScore;
+  }
 }