JAL-2446 added contains, delete, checks for inclusion on add + tests
[jalview.git] / src / jalview / datamodel / features / FeatureStore.java
index f7757be..cb4bd6f 100644 (file)
@@ -59,66 +59,84 @@ public class FeatureStore
   public FeatureStore()
   {
     nonNestedFeatures = new ArrayList<SequenceFeature>();
-    // we only construct contactFeatures and the NCList if we need to
+    // we only construct nonPositionalFeatures, contactFeatures
+    // or the NCList if we need to
   }
 
   /**
-   * Add one entry to the data store
+   * Adds one sequence feature to the store, and returns true, unless the
+   * feature is already contained in the store, in which case this method
+   * returns false. Containment is determined by SequenceFeature.equals()
+   * comparison.
    * 
    * @param feature
    */
-  public void addFeature(SequenceFeature feature)
+  public boolean addFeature(SequenceFeature feature)
   {
+    boolean added = false;
+
     if (feature.isContactFeature())
     {
-      addContactFeature(feature);
+      added = addContactFeature(feature);
     }
     else if (feature.isNonPositional())
     {
-      addNonPositionalFeature(feature);
+      added = addNonPositionalFeature(feature);
     }
     else
     {
-      boolean added = addNonNestedFeature(feature);
-      if (!added)
+      if (!nonNestedFeatures.contains(feature))
       {
-        /*
-         * detected a nested feature - put it in the NCList structure
-         */
-        addNestedFeature(feature);
+        added = addNonNestedFeature(feature);
+        if (!added)
+        {
+          /*
+           * detected a nested feature - put it in the NCList structure
+           */
+          added = addNestedFeature(feature);
+        }
       }
     }
+
+    return added;
   }
 
   /**
    * Adds the feature to the list of non-positional features (with lazy
-   * instantiation of the list if it is null)
+   * instantiation of the list if it is null), and returns true. If the
+   * non-positional features already include the new feature (by equality test),
+   * then it is not added, and this method returns false.
    * 
    * @param feature
    */
-  protected void addNonPositionalFeature(SequenceFeature feature)
+  protected boolean addNonPositionalFeature(SequenceFeature feature)
   {
     if (nonPositionalFeatures == null)
     {
       nonPositionalFeatures = new ArrayList<SequenceFeature>();
     }
+    if (nonPositionalFeatures.contains(feature))
+    {
+      return false;
+    }
     nonPositionalFeatures.add(feature);
+    return true;
   }
 
   /**
    * Adds one feature to the NCList that can manage nested features (creating
-   * the NCList if necessary)
+   * the NCList if necessary), and returns true. If the feature is already
+   * stored in the NCList (by equality test), then it is not added, and this
+   * method returns false.
    */
-  protected synchronized void addNestedFeature(SequenceFeature feature)
+  protected synchronized boolean addNestedFeature(SequenceFeature feature)
   {
     if (nestedFeatures == null)
     {
       nestedFeatures = new NCList<SequenceFeature>(feature);
+      return true;
     }
-    else
-    {
-      nestedFeatures.add(feature);
-    }
+    return nestedFeatures.add(feature, false);
   }
 
   /**
@@ -225,13 +243,14 @@ public class FeatureStore
   /**
    * Add a contact feature to the lists that hold them ordered by start (first
    * contact) and by end (second contact) position, ensuring the lists remain
-   * ordered
+   * ordered, and returns true. If the contact feature lists already contain the
+   * given feature (by test for equality), does not add it and returns false.
    * 
    * @param feature
+   * @return
    */
-  protected synchronized void addContactFeature(SequenceFeature feature)
+  protected synchronized boolean addContactFeature(SequenceFeature feature)
   {
-    // TODO binary search for insertion points!
     if (contactFeatureStarts == null)
     {
       contactFeatureStarts = new ArrayList<SequenceFeature>();
@@ -240,10 +259,19 @@ public class FeatureStore
     {
       contactFeatureEnds = new ArrayList<SequenceFeature>();
     }
+
+    // TODO binary search for insertion points!
+    if (contactFeatureStarts.contains(feature))
+    {
+      return false;
+    }
+
     contactFeatureStarts.add(feature);
     Collections.sort(contactFeatureStarts, startOrdering);
     contactFeatureEnds.add(feature);
     Collections.sort(contactFeatureEnds, endOrdering);
+
+    return true;
   }
 
   /**
@@ -534,4 +562,51 @@ public class FeatureStore
     }
     return new ArrayList<SequenceFeature>(nonPositionalFeatures);
   }
+
+  /**
+   * Deletes the given feature from the store, returning true if it was found
+   * (and deleted), else false. This method makes no assumption that the feature
+   * is in the 'expected' place in the store, in case it has been modified since
+   * it was added.
+   * 
+   * @param sf
+   */
+  public boolean delete(SequenceFeature sf)
+  {
+    /*
+     * try the non-nested positional features first
+     */
+    boolean removed = nonNestedFeatures.remove(sf);
+
+    /*
+     * if not found, try contact positions (and if found, delete
+     * from both lists of contact positions)
+     */
+    if (!removed && contactFeatureStarts != null)
+    {
+      removed = contactFeatureStarts.remove(sf);
+      if (removed)
+      {
+        contactFeatureEnds.remove(sf);
+      }
+    }
+
+    /*
+     * if not found, try non-positional features
+     */
+    if (!removed && nonPositionalFeatures != null)
+    {
+      removed = nonPositionalFeatures.remove(sf);
+    }
+
+    /*
+     * if not found, try nested features
+     */
+    if (!removed && nestedFeatures != null)
+    {
+      removed = nestedFeatures.delete(sf);
+    }
+
+    return removed;
+  }
 }