JAL-2505 JAL-2542 SequenceFeatures.shift() to shift all positional
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 24 May 2017 13:47:36 +0000 (14:47 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 24 May 2017 13:47:36 +0000 (14:47 +0100)
features

src/jalview/datamodel/features/FeatureStore.java
src/jalview/datamodel/features/SequenceFeatures.java
src/jalview/datamodel/features/SequenceFeaturesI.java
src/jalview/ws/DBRefFetcher.java
test/jalview/datamodel/features/FeatureStoreTest.java
test/jalview/datamodel/features/SequenceFeaturesTest.java

index c6a49db..7218b38 100644 (file)
@@ -1017,4 +1017,40 @@ public class FeatureStore
     }
     return result;
   }
+
+  /**
+   * Adds the shift value to the start and end of all positional features.
+   * Returns true if at least one feature was updated, else false.
+   * 
+   * @param shift
+   * @return
+   */
+  public synchronized boolean shiftFeatures(int shift)
+  {
+    /*
+     * Because begin and end are final fields (to ensure the data store's
+     * integrity), we have to delete each feature and re-add it as amended.
+     * (Although a simple shift of all values would preserve data integrity!)
+     */
+    boolean modified = false;
+    for (SequenceFeature sf : getPositionalFeatures())
+    {
+      modified = true;
+      int newBegin = sf.getBegin() + shift;
+      int newEnd = sf.getEnd() + shift;
+
+      /*
+       * sanity check: don't shift left of the first residue
+       */
+      if (newEnd > 0)
+      {
+        newBegin = Math.max(1, newBegin);
+        SequenceFeature sf2 = new SequenceFeature(sf, newBegin, newEnd,
+                sf.getFeatureGroup());
+        addFeature(sf2);
+      }
+      delete(sf);
+    }
+    return modified;
+  }
 }
index ff570b1..f263938 100644 (file)
@@ -461,4 +461,18 @@ public class SequenceFeatures implements SequenceFeaturesI
     }
     return result;
   }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean shiftFeatures(int shift)
+  {
+    boolean modified = false;
+    for (FeatureStore fs : featureStore.values())
+    {
+      modified |= fs.shiftFeatures(shift);
+    }
+    return modified;
+  }
 }
\ No newline at end of file
index c423391..58beca2 100644 (file)
@@ -193,4 +193,12 @@ public interface SequenceFeaturesI
    * @return
    */
   float getMaximumScore(String type, boolean positional);
+
+  /**
+   * Adds the shift amount to the start and end of all positional features,
+   * returning true if at least one feature was shifted, else false
+   * 
+   * @param shift
+   */
+  abstract boolean shiftFeatures(int shift);
 }
\ No newline at end of file
index 493a13c..8c8a717 100644 (file)
@@ -26,7 +26,6 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.DBRefSource;
 import jalview.datamodel.Mapping;
-import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.gui.CutAndPasteTransfer;
 import jalview.gui.DasSourceBrowser;
@@ -697,28 +696,13 @@ public class DBRefFetcher implements Runnable
 
           if (updateRefFrame)
           {
-            SequenceFeature[] sfs = sequence.getSequenceFeatures();
-            if (sfs != null)
+            /*
+             * relocate existing sequence features by offset
+             */
+            int startShift = absStart - sequenceStart + 1;
+            if (startShift != 0)
             {
-              /*
-               * relocate existing sequence features by offset
-               */
-              int start = sequenceStart;
-              int end = sequence.getEnd();
-              final int startShift = absStart - start + 1;
-
-              if (startShift != 0)
-              {
-                for (SequenceFeature sf : sfs)
-                {
-                  if (sf.getBegin() >= start && sf.getEnd() <= end)
-                  {
-                    sf.setBegin(sf.getBegin() + startShift);
-                    sf.setEnd(sf.getEnd() + startShift);
-                    modified = true;
-                  }
-                }
-              }
+              modified |= sequence.getFeatures().shiftFeatures(startShift);
             }
           }
         }
index 00a57b6..f5be818 100644 (file)
@@ -769,4 +769,62 @@ public class FeatureStoreTest
     assertEquals(features.size(), 1);
     assertTrue(features.contains(sf4));
   }
+
+  @Test(groups = "Functional")
+  public void testShiftFeatures()
+  {
+    FeatureStore fs = new FeatureStore();
+    assertFalse(fs.shiftFeatures(1));
+
+    SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null);
+    fs.addFeature(sf1);
+    // nested feature:
+    SequenceFeature sf2 = new SequenceFeature("Cath", "", 8, 14, 0f, null);
+    fs.addFeature(sf2);
+    // contact feature:
+    SequenceFeature sf3 = new SequenceFeature("Disulfide bond", "", 23, 32,
+            0f, null);
+    fs.addFeature(sf3);
+    // non-positional feature:
+    SequenceFeature sf4 = new SequenceFeature("Cath", "", 0, 0, 0f, null);
+    fs.addFeature(sf4);
+
+    /*
+     * shift features right by 5
+     */
+    assertTrue(fs.shiftFeatures(5));
+
+    // non-positional features untouched:
+    List<SequenceFeature> nonPos = fs.getNonPositionalFeatures();
+    assertEquals(nonPos.size(), 1);
+    assertTrue(nonPos.contains(sf4));
+
+    // positional features are replaced
+    List<SequenceFeature> pos = fs.getPositionalFeatures();
+    assertEquals(pos.size(), 3);
+    assertFalse(pos.contains(sf1));
+    assertFalse(pos.contains(sf2));
+    assertFalse(pos.contains(sf3));
+    SequenceFeatures.sortFeatures(pos, true); // ascending start pos
+    assertEquals(pos.get(0).getBegin(), 7);
+    assertEquals(pos.get(0).getEnd(), 10);
+    assertEquals(pos.get(1).getBegin(), 13);
+    assertEquals(pos.get(1).getEnd(), 19);
+    assertEquals(pos.get(2).getBegin(), 28);
+    assertEquals(pos.get(2).getEnd(), 37);
+
+    /*
+     * now shift left by 15
+     * feature at [7-10] should be removed
+     * feature at [13-19] should become [1-4] 
+     */
+    assertTrue(fs.shiftFeatures(-15));
+    pos = fs.getPositionalFeatures();
+    assertEquals(pos.size(), 2);
+    SequenceFeatures.sortFeatures(pos, true);
+    assertEquals(pos.get(0).getBegin(), 1);
+    assertEquals(pos.get(0).getEnd(), 4);
+    assertEquals(pos.get(1).getBegin(), 13);
+    assertEquals(pos.get(1).getEnd(), 22);
+  }
 }
index e3d587f..f4ec05b 100644 (file)
@@ -1100,4 +1100,67 @@ public class SequenceFeaturesTest
     assertTrue(store.getFeaturesForGroup(false, "Rfam", "Cath", "Pfam")
             .isEmpty());
   }
+
+  @Test(groups = "Functional")
+  public void testShiftFeatures()
+  {
+    SequenceFeatures store = new SequenceFeatures();
+    assertFalse(store.shiftFeatures(1));
+
+    SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null);
+    store.add(sf1);
+    // nested feature:
+    SequenceFeature sf2 = new SequenceFeature("Metal", "", 8, 14, 0f, null);
+    store.add(sf2);
+    // contact feature:
+    SequenceFeature sf3 = new SequenceFeature("Disulfide bond", "", 23, 32,
+            0f, null);
+    store.add(sf3);
+    // non-positional feature:
+    SequenceFeature sf4 = new SequenceFeature("Pfam", "", 0, 0, 0f, null);
+    store.add(sf4);
+  
+    /*
+     * shift features right by 5
+     */
+    assertTrue(store.shiftFeatures(5));
+  
+    // non-positional features untouched:
+    List<SequenceFeature> nonPos = store.getNonPositionalFeatures();
+    assertEquals(nonPos.size(), 1);
+    assertTrue(nonPos.contains(sf4));
+  
+    // positional features are replaced
+    List<SequenceFeature> pos = store.getPositionalFeatures();
+    assertEquals(pos.size(), 3);
+    assertFalse(pos.contains(sf1));
+    assertFalse(pos.contains(sf2));
+    assertFalse(pos.contains(sf3));
+    SequenceFeatures.sortFeatures(pos, true); // ascending start pos
+    assertEquals(pos.get(0).getBegin(), 7);
+    assertEquals(pos.get(0).getEnd(), 10);
+    assertEquals(pos.get(0).getType(), "Cath");
+    assertEquals(pos.get(1).getBegin(), 13);
+    assertEquals(pos.get(1).getEnd(), 19);
+    assertEquals(pos.get(1).getType(), "Metal");
+    assertEquals(pos.get(2).getBegin(), 28);
+    assertEquals(pos.get(2).getEnd(), 37);
+    assertEquals(pos.get(2).getType(), "Disulfide bond");
+  
+    /*
+     * now shift left by 15
+     * feature at [7-10] should be removed
+     * feature at [13-19] should become [1-4] 
+     */
+    assertTrue(store.shiftFeatures(-15));
+    pos = store.getPositionalFeatures();
+    assertEquals(pos.size(), 2);
+    SequenceFeatures.sortFeatures(pos, true);
+    assertEquals(pos.get(0).getBegin(), 1);
+    assertEquals(pos.get(0).getEnd(), 4);
+    assertEquals(pos.get(0).getType(), "Metal");
+    assertEquals(pos.get(1).getBegin(), 13);
+    assertEquals(pos.get(1).getEnd(), 22);
+    assertEquals(pos.get(1).getType(), "Disulfide bond");
+  }
 }