From b44349fe3a5f2d70b74a1b110018a753a1036aca Mon Sep 17 00:00:00 2001 From: gmungoc Date: Wed, 24 May 2017 14:47:36 +0100 Subject: [PATCH] JAL-2505 JAL-2542 SequenceFeatures.shift() to shift all positional features --- src/jalview/datamodel/features/FeatureStore.java | 36 +++++++++++ .../datamodel/features/SequenceFeatures.java | 14 +++++ .../datamodel/features/SequenceFeaturesI.java | 8 +++ src/jalview/ws/DBRefFetcher.java | 28 ++------- .../datamodel/features/FeatureStoreTest.java | 58 ++++++++++++++++++ .../datamodel/features/SequenceFeaturesTest.java | 63 ++++++++++++++++++++ 6 files changed, 185 insertions(+), 22 deletions(-) diff --git a/src/jalview/datamodel/features/FeatureStore.java b/src/jalview/datamodel/features/FeatureStore.java index c6a49db..7218b38 100644 --- a/src/jalview/datamodel/features/FeatureStore.java +++ b/src/jalview/datamodel/features/FeatureStore.java @@ -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; + } } diff --git a/src/jalview/datamodel/features/SequenceFeatures.java b/src/jalview/datamodel/features/SequenceFeatures.java index ff570b1..f263938 100644 --- a/src/jalview/datamodel/features/SequenceFeatures.java +++ b/src/jalview/datamodel/features/SequenceFeatures.java @@ -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 diff --git a/src/jalview/datamodel/features/SequenceFeaturesI.java b/src/jalview/datamodel/features/SequenceFeaturesI.java index c423391..58beca2 100644 --- a/src/jalview/datamodel/features/SequenceFeaturesI.java +++ b/src/jalview/datamodel/features/SequenceFeaturesI.java @@ -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 diff --git a/src/jalview/ws/DBRefFetcher.java b/src/jalview/ws/DBRefFetcher.java index 493a13c..8c8a717 100644 --- a/src/jalview/ws/DBRefFetcher.java +++ b/src/jalview/ws/DBRefFetcher.java @@ -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); } } } diff --git a/test/jalview/datamodel/features/FeatureStoreTest.java b/test/jalview/datamodel/features/FeatureStoreTest.java index 00a57b6..f5be818 100644 --- a/test/jalview/datamodel/features/FeatureStoreTest.java +++ b/test/jalview/datamodel/features/FeatureStoreTest.java @@ -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 nonPos = fs.getNonPositionalFeatures(); + assertEquals(nonPos.size(), 1); + assertTrue(nonPos.contains(sf4)); + + // positional features are replaced + List 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); + } } diff --git a/test/jalview/datamodel/features/SequenceFeaturesTest.java b/test/jalview/datamodel/features/SequenceFeaturesTest.java index e3d587f..f4ec05b 100644 --- a/test/jalview/datamodel/features/SequenceFeaturesTest.java +++ b/test/jalview/datamodel/features/SequenceFeaturesTest.java @@ -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 nonPos = store.getNonPositionalFeatures(); + assertEquals(nonPos.size(), 1); + assertTrue(nonPos.contains(sf4)); + + // positional features are replaced + List 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"); + } } -- 1.7.10.2