import jalview.analysis.AlignSeq;
import jalview.api.DBRefEntryI;
import jalview.datamodel.features.SequenceFeatures;
+import jalview.datamodel.features.SequenceFeaturesI;
import jalview.util.Comparison;
import jalview.util.DBRefUtils;
import jalview.util.MapList;
}
@Override
+ public SequenceFeaturesI getFeatures()
+ {
+ return sequenceFeatureStore;
+ }
+
+ @Override
public boolean addPDBId(PDBEntry entry)
{
if (pdbIds == null)
*/
package jalview.datamodel;
+import jalview.datamodel.features.SequenceFeaturesI;
+
import java.util.List;
import java.util.Vector;
public SequenceFeature[] getSequenceFeatures();
/**
+ * Answers the object holding features for the sequence
+ *
+ * @return
+ */
+ SequenceFeaturesI getFeatures();
+
+ /**
* Replaces the array of sequence features associated with this sequence with
* a new array reference. If this sequence has a dataset sequence, then this
* method will update the dataset sequence's feature array
*/
Set<String> nonPositionalFeatureGroups;
+ /*
+ * the total length of all positional features; contact features count 1 to
+ * the total and 1 to size(), consistent with an average 'feature length' of 1
+ */
+ int totalExtent;
+
/**
* Constructor
*/
}
}
+ /*
+ * record the total extent of positional features, to make
+ * getAverageFeatureLength possible; we count the length of a
+ * contact feature as 1
+ */
+ if (added && !feature.isNonPositional())
+ {
+ int featureLength = feature.isContactFeature() ? 1 : 1
+ + feature.getEnd() - feature.getBegin();
+ totalExtent += featureLength;
+ }
+
return added;
}
if (removed)
{
rebuildFeatureGroups(sf.getFeatureGroup(), removedNonPositional);
+ // TODO and recalculate totalExtent (feature may have changed length!)
}
return removed;
return matched;
}
+
+ /**
+ * Answers the number of positional (or non-positional) features stored
+ *
+ * @param positional
+ * @return
+ */
+ public int size(boolean positional)
+ {
+ if (!positional)
+ {
+ return nonPositionalFeatures == null ? 0 : nonPositionalFeatures
+ .size();
+ }
+
+ int size = nonNestedFeatures.size();
+
+ if (contactFeatureStarts != null)
+ {
+ // note a contact feature (start/end) counts as one
+ size += contactFeatureStarts.size();
+ }
+
+ if (nestedFeatures != null)
+ {
+ size += nestedFeatures.size();
+ }
+
+ return size;
+ }
+
+ /**
+ * Answers the average length of positional features (or zero if there are
+ * none). Contact features contribute a value of 1 to the average.
+ *
+ * @return
+ */
+ public float getAverageFeatureLength()
+ {
+ int d = size(true);
+ return d == 0 ? 0f : (float) totalExtent / d;
+ }
}
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
* @author gmcarstairs
*
*/
-public class SequenceFeatures
+public class SequenceFeatures implements SequenceFeaturesI
{
/*
featureStore = new HashMap<String, FeatureStore>();
}
- /**
- * 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 sf
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#add(jalview.datamodel.SequenceFeature)
*/
+ @Override
public boolean add(SequenceFeature sf)
{
String type = sf.getType();
return featureStore.get(type).addFeature(sf);
}
- /**
- * Returns a (possibly empty) list of features, optionally restricted to
- * specified types, which overlap the given (inclusive) sequence position
- * range
- *
- * @param from
- * @param to
- * @param type
- * @return
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#findFeatures(int, int, java.lang.String)
*/
+ @Override
public List<SequenceFeature> findFeatures(int from, int to,
String... type)
{
return result;
}
- /**
- * Answers a list of all features stored, optionally restricted to specified
- * types, in no particular guaranteed order
- *
- * @param type
- * @return
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#getAllFeatures(java.lang.String)
*/
+ @Override
public List<SequenceFeature> getAllFeatures(String... type)
{
List<SequenceFeature> result = new ArrayList<SequenceFeature>();
return result;
}
- /**
- * Answers a list of all positional features, optionally restricted to
- * specified types, in no particular guaranteed order
- *
- * @param type
- * @return
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#getFeatureCount(boolean, java.lang.String)
*/
+ @Override
+ public int getFeatureCount(boolean positional, String... type)
+ {
+ int result = 0;
+ for (FeatureStore fs : featureStore.values())
+ {
+ result += fs.size(positional);
+ }
+
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#getPositionalFeatures(java.lang.String)
+ */
+ @Override
public List<SequenceFeature> getPositionalFeatures(String... type)
{
List<SequenceFeature> result = new ArrayList<SequenceFeature>();
.keySet() : Arrays.asList(type);
}
- /**
- * Answers a list of all contact features, optionally restricted to specified
- * types, in no particular guaranteed order
- *
- * @return
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#getContactFeatures(java.lang.String)
*/
+ @Override
public List<SequenceFeature> getContactFeatures(String... type)
{
List<SequenceFeature> result = new ArrayList<SequenceFeature>();
return result;
}
- /**
- * Answers a list of all non-positional features, optionally restricted to
- * specified types, in no particular guaranteed order
- *
- * @param type
- * if no type is specified, all are returned
- * @return
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#getNonPositionalFeatures(java.lang.String)
*/
+ @Override
public List<SequenceFeature> getNonPositionalFeatures(String... type)
{
List<SequenceFeature> result = new ArrayList<SequenceFeature>();
return result;
}
- /**
- * 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
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#delete(jalview.datamodel.SequenceFeature)
*/
+ @Override
public boolean delete(SequenceFeature sf)
{
for (FeatureStore featureSet : featureStore.values())
return false;
}
- /**
- * Answers true if this store contains at least one feature, else false
- *
- * @return
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#hasFeatures()
*/
+ @Override
public boolean hasFeatures()
{
for (FeatureStore featureSet : featureStore.values())
return false;
}
- /**
- * Returns a set of the distinct feature groups present in the collection. The
- * set may include null. The boolean parameter determines whether the groups
- * for positional or for non-positional features are returned. The optional
- * type parameter may be used to restrict to groups for specified feature
- * types.
- *
- * @param positionalFeatures
- * @param type
- * @return
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#getFeatureGroups(boolean, java.lang.String)
*/
+ @Override
public Set<String> getFeatureGroups(boolean positionalFeatures,
String... type)
{
return groups;
}
- /**
- * Answers the set of distinct feature types for which there is at least one
- * feature with one of the given feature group(s). The parameter determines
- * whether the groups for positional or for non-positional features are
- * returned.
- *
- * @param positionalFeatures
- * @param groups
- * @return
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#getFeatureTypesForGroups(boolean, java.lang.String)
*/
+ @Override
public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
String... groups)
{
return result;
}
+
+ /* (non-Javadoc)
+ * @see jalview.datamodel.features.SequenceFeaturesI#getFeatureTypes()
+ */
+ @Override
+ public Set<String> getFeatureTypes()
+ {
+ return Collections.unmodifiableSet(featureStore.keySet());
+ }
}
--- /dev/null
+package jalview.datamodel.features;
+
+import jalview.datamodel.SequenceFeature;
+
+import java.util.List;
+import java.util.Set;
+
+public interface SequenceFeaturesI
+{
+
+ /**
+ * 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 sf
+ */
+ public abstract boolean add(SequenceFeature sf);
+
+ /**
+ * Returns a (possibly empty) list of features, optionally restricted to
+ * specified types, which overlap the given (inclusive) sequence position
+ * range
+ *
+ * @param from
+ * @param to
+ * @param type
+ * @return
+ */
+ public abstract List<SequenceFeature> findFeatures(int from, int to,
+ String... type);
+
+ /**
+ * Answers a list of all features stored, optionally restricted to specified
+ * types, in no particular guaranteed order
+ *
+ * @param type
+ * @return
+ */
+ public abstract List<SequenceFeature> getAllFeatures(String... type);
+
+ public abstract int getFeatureCount(boolean positional, String... type);
+
+ /**
+ * Answers a list of all positional features, optionally restricted to
+ * specified types, in no particular guaranteed order
+ *
+ * @param type
+ * @return
+ */
+ public abstract List<SequenceFeature> getPositionalFeatures(
+ String... type);
+
+ /**
+ * Answers a list of all contact features, optionally restricted to specified
+ * types, in no particular guaranteed order
+ *
+ * @return
+ */
+ public abstract List<SequenceFeature> getContactFeatures(String... type);
+
+ /**
+ * Answers a list of all non-positional features, optionally restricted to
+ * specified types, in no particular guaranteed order
+ *
+ * @param type
+ * if no type is specified, all are returned
+ * @return
+ */
+ public abstract List<SequenceFeature> getNonPositionalFeatures(
+ String... type);
+
+ /**
+ * 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 abstract boolean delete(SequenceFeature sf);
+
+ /**
+ * Answers true if this store contains at least one feature, else false
+ *
+ * @return
+ */
+ public abstract boolean hasFeatures();
+
+ /**
+ * Returns a set of the distinct feature groups present in the collection. The
+ * set may include null. The boolean parameter determines whether the groups
+ * for positional or for non-positional features are returned. The optional
+ * type parameter may be used to restrict to groups for specified feature
+ * types.
+ *
+ * @param positionalFeatures
+ * @param type
+ * @return
+ */
+ public abstract Set<String> getFeatureGroups(boolean positionalFeatures,
+ String... type);
+
+ /**
+ * Answers the set of distinct feature types for which there is at least one
+ * feature with one of the given feature group(s). The boolean parameter
+ * determines whether the groups for positional or for non-positional features
+ * are returned.
+ *
+ * @param positionalFeatures
+ * @param groups
+ * @return
+ */
+ public abstract Set<String> getFeatureTypesForGroups(
+ boolean positionalFeatures, String... groups);
+
+ /**
+ * Answers an immutable set of the distinct feature types for which a feature
+ * is stored
+ * @return
+ */
+ public abstract Set<String> getFeatureTypes();
+
+}
\ No newline at end of file
import jalview.api.FeatureColourI;
import jalview.api.FeatureSettingsControllerI;
import jalview.bin.Cache;
+import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
import jalview.gui.Help.HelpId;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
@Override
synchronized public void discoverAllFeatureData()
{
- Vector<String> allFeatures = new Vector<String>();
- Vector<String> allGroups = new Vector<String>();
- SequenceFeature[] tmpfeatures;
- String group;
- for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
- {
- tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
- .getSequenceFeatures();
- if (tmpfeatures == null)
- {
- continue;
- }
+ Set<String> allGroups = new HashSet<String>();
+ AlignmentI alignment = af.getViewport().getAlignment();
- int index = 0;
- while (index < tmpfeatures.length)
+ for (int i = 0; i < alignment.getHeight(); i++)
+ {
+ SequenceI seq = alignment.getSequenceAt(i);
+ for (String group : seq.getFeatures().getFeatureGroups(true))
{
- if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
+ if (group != null && !allGroups.contains(group))
{
- index++;
- continue;
- }
-
- if (tmpfeatures[index].getFeatureGroup() != null)
- {
- group = tmpfeatures[index].featureGroup;
- if (!allGroups.contains(group))
- {
- allGroups.addElement(group);
- checkGroupState(group);
- }
+ allGroups.add(group);
+ checkGroupState(group);
}
-
- if (!allFeatures.contains(tmpfeatures[index].getType()))
- {
- allFeatures.addElement(tmpfeatures[index].getType());
- }
- index++;
}
}
synchronized void resetTable(String[] groupChanged)
{
- if (resettingTable == true)
+ if (resettingTable)
{
return;
}
for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
{
- tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
- .getSequenceFeatures();
+ SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
+ tmpfeatures = seq.getSequenceFeatures();
if (tmpfeatures == null)
{
continue;
}
-
+ Set<String> types = seq.getFeatures().getFeatureTypes();
int index = 0;
while (index < tmpfeatures.length)
{
Float.NaN, null);
assertTrue(fs.addFeature(sf1));
+ assertEquals(fs.size(true), 1); // positional
+ assertEquals(fs.size(false), 0); // non-positional
/*
* re-adding the same or an identical feature should fail
*/
assertFalse(fs.addFeature(sf1));
+ assertEquals(fs.size(true), 1);
assertFalse(fs.addFeature(sf2));
+ assertEquals(fs.size(true), 1);
}
@Test(groups = "Functional")
{
FeatureStore fs = new FeatureStore();
assertTrue(fs.isEmpty());
+ assertEquals(fs.size(true), 0);
/*
* non-nested feature
Float.NaN, null);
fs.addFeature(sf1);
assertFalse(fs.isEmpty());
+ assertEquals(fs.size(true), 1);
fs.delete(sf1);
assertTrue(fs.isEmpty());
+ assertEquals(fs.size(true), 0);
/*
* non-positional feature
sf1 = new SequenceFeature("Cath", "", 0, 0, Float.NaN, null);
fs.addFeature(sf1);
assertFalse(fs.isEmpty());
+ assertEquals(fs.size(false), 1); // non-positional
+ assertEquals(fs.size(true), 0); // positional
fs.delete(sf1);
assertTrue(fs.isEmpty());
+ assertEquals(fs.size(false), 0);
/*
* contact feature
sf1 = new SequenceFeature("Disulfide bond", "", 19, 49, Float.NaN, null);
fs.addFeature(sf1);
assertFalse(fs.isEmpty());
+ assertEquals(fs.size(true), 1);
fs.delete(sf1);
assertTrue(fs.isEmpty());
+ assertEquals(fs.size(true), 0);
/*
* sf2, sf3 added as nested features
fs.addFeature(sf1);
fs.addFeature(sf2);
fs.addFeature(sf3);
+ assertEquals(fs.size(true), 3);
assertTrue(fs.delete(sf1));
+ assertEquals(fs.size(true), 2);
// FeatureStore should now only contain features in the NCList
assertTrue(fs.nonNestedFeatures.isEmpty());
assertEquals(fs.nestedFeatures.size(), 2);
assertFalse(fs.isEmpty());
assertTrue(fs.delete(sf2));
+ assertEquals(fs.size(true), 1);
assertFalse(fs.isEmpty());
assertTrue(fs.delete(sf3));
+ assertEquals(fs.size(true), 0);
assertTrue(fs.isEmpty()); // all gone
}
groups = fs.getFeatureGroups(true);
assertTrue(groups.isEmpty());
}
+
+ @Test(groups = "Functional")
+ public void testGetAverageFeatureLength()
+ {
+ FeatureStore fs = new FeatureStore();
+ assertEquals(fs.getAverageFeatureLength(), 0f);
+
+ addFeature(fs, 10, 20); // 11
+ assertEquals(fs.getAverageFeatureLength(), 11f);
+ addFeature(fs, 17, 37); // 21
+ addFeature(fs, 14, 74); // 61
+ assertEquals(fs.getAverageFeatureLength(), 31f);
+
+ // non-positional features don't count
+ SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
+ "group1");
+ fs.addFeature(sf1);
+ assertEquals(fs.getAverageFeatureLength(), 31f);
+
+ // contact features count 1
+ SequenceFeature sf2 = new SequenceFeature("disulphide bond", "desc",
+ 15, 35, 1f, "group1");
+ fs.addFeature(sf2);
+ assertEquals(fs.getAverageFeatureLength(), 94f / 4f);
+ }
}
@Test(groups = "Functional")
public void testGetPositionalFeatures()
{
- SequenceFeatures store = new SequenceFeatures();
+ SequenceFeaturesI store = new SequenceFeatures();
SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
Float.NaN, null);
store.add(sf1);
@Test(groups = "Functional")
public void testGetContactFeatures()
{
- SequenceFeatures store = new SequenceFeatures();
+ SequenceFeaturesI store = new SequenceFeatures();
// non-contact
SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
Float.NaN, null);
@Test(groups = "Functional")
public void testGetNonPositionalFeatures()
{
- SequenceFeatures store = new SequenceFeatures();
+ SequenceFeaturesI store = new SequenceFeatures();
// positional
SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
Float.NaN, null);
* @param to
* @return
*/
- SequenceFeature addFeature(SequenceFeatures sf, String type, int from,
+ SequenceFeature addFeature(SequenceFeaturesI sf, String type, int from,
int to)
{
SequenceFeature sf1 = new SequenceFeature(type, "", from, to,
@Test(groups = "Functional")
public void testFindFeatures()
{
- SequenceFeatures sf = new SequenceFeatures();
+ SequenceFeaturesI sf = new SequenceFeatures();
SequenceFeature sf1 = addFeature(sf, "Pfam", 10, 50);
SequenceFeature sf2 = addFeature(sf, "Pfam", 1, 15);
SequenceFeature sf3 = addFeature(sf, "Pfam", 20, 30);
@Test(groups = "Functional")
public void testDelete()
{
- SequenceFeatures sf = new SequenceFeatures();
+ SequenceFeaturesI sf = new SequenceFeatures();
SequenceFeature sf1 = addFeature(sf, "Pfam", 10, 50);
assertTrue(sf.getPositionalFeatures().contains(sf1));
@Test(groups = "Functional")
public void testHasFeatures()
{
- SequenceFeatures sf = new SequenceFeatures();
+ SequenceFeaturesI sf = new SequenceFeatures();
assertFalse(sf.hasFeatures());
SequenceFeature sf1 = addFeature(sf, "Pfam", 10, 50);
@Test(groups = "Functional")
public void testGetFeatureGroups()
{
- SequenceFeatures sf = new SequenceFeatures();
+ SequenceFeaturesI sf = new SequenceFeatures();
assertTrue(sf.getFeatureGroups(true).isEmpty());
assertTrue(sf.getFeatureGroups(false).isEmpty());
@Test(groups = "Functional")
public void testGetFeatureTypesForGroups()
{
- SequenceFeatures sf = new SequenceFeatures();
+ SequenceFeaturesI sf = new SequenceFeatures();
assertTrue(sf.getFeatureTypesForGroups(true, (String) null).isEmpty());
/*