X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2Ffeatures%2FSequenceFeatures.java;h=2101a2f50d8eba34ea94ca6c37a9681bbbc615ce;hb=14bfc6fb57f123b815f08dbf5b35544abd33b3af;hp=a61eff21955471bf55a7d538503b1b08b7e39bdb;hpb=8f8c3ca7ac8630c75b76fcbab825444c5c672b86;p=jalview.git diff --git a/src/jalview/datamodel/features/SequenceFeatures.java b/src/jalview/datamodel/features/SequenceFeatures.java index a61eff2..2101a2f 100644 --- a/src/jalview/datamodel/features/SequenceFeatures.java +++ b/src/jalview/datamodel/features/SequenceFeatures.java @@ -1,14 +1,41 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel.features; import jalview.datamodel.SequenceFeature; +import jalview.io.gff.SequenceOntologyFactory; +import jalview.io.gff.SequenceOntologyI; +import jalview.util.Platform; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; + +import intervalstore.api.IntervalI; /** * A class that stores sequence features in a way that supports efficient @@ -18,9 +45,8 @@ import java.util.Set; * @author gmcarstairs * */ -public class SequenceFeatures +public class SequenceFeatures implements SequenceFeaturesI { - /* * map from feature type to structured store of features for that type * null types are permitted (but not a good idea!) @@ -32,20 +58,42 @@ public class SequenceFeatures */ public SequenceFeatures() { - featureStore = new HashMap(); + /* + * use a TreeMap so that features are returned in alphabetical order of type + * ? wrap as a synchronized map for add and delete operations + */ + // featureStore = Collections + // .synchronizedSortedMap(new TreeMap()); + featureStore = new TreeMap<>(); } /** - * 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 + * Constructor given a list of features + */ + public SequenceFeatures(List features) + { + this(); + if (features != null) + { + for (SequenceFeature feature : features) + { + add(feature); + } + } + } + + /** + * {@inheritDoc} */ + @Override public boolean add(SequenceFeature sf) { String type = sf.getType(); + if (type == null) + { + System.err.println("Feature type may not be null: " + sf.toString()); + return false; + } if (featureStore.get(type) == null) { @@ -55,132 +103,167 @@ public class SequenceFeatures } /** - * Returns a (possibly empty) list of features of the given type which overlap - * the (inclusive) sequence position range - * - * @param type - * @param from - * @param to - * @return + * {@inheritDoc} */ - public List findFeatures(String type, int from, - int to) + @Override + public List findFeatures(int from, int to, + String... type) { - FeatureStore features = featureStore.get(type); - if (features == null) + List result = new ArrayList<>(); + for (FeatureStore featureSet : varargToTypes(type)) { - return Collections.emptyList(); + featureSet.findFeatures(from, to, result); } - return features.findOverlappingFeatures(from, to); + return result; } /** - * Answers a list of all features stored (including non-positional), in no - * particular guaranteed order - * - * @return + * {@inheritDoc} */ - public List getFeatures() + @Override + public List getAllFeatures(String... type) { - List result = new ArrayList(); - for (FeatureStore featureSet : featureStore.values()) + List result = new ArrayList<>(); + + result.addAll(getPositionalFeatures(type)); + + result.addAll(getNonPositionalFeatures()); + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public List getFeaturesByOntology(String... ontologyTerm) + { + if (ontologyTerm == null || ontologyTerm.length == 0) { - result.addAll(featureSet.getFeatures()); + return new ArrayList<>(); } - return result; + + Set featureTypes = getFeatureTypes(ontologyTerm); + if (featureTypes.isEmpty()) + { + /* + * no features of the specified type or any sub-type + */ + return new ArrayList<>(); + } + + return getAllFeatures( + featureTypes.toArray(new String[featureTypes.size()])); } /** - * Answers a list of all non-positional features stored, in no particular - * guaranteed order - * - * @return + * {@inheritDoc} */ - public List getNonPositionalFeatures() + @Override + public int getFeatureCount(boolean positional, String... type) { - List result = new ArrayList(); - for (FeatureStore featureSet : featureStore.values()) + int result = 0; + + for (FeatureStore featureSet : varargToTypes(type)) { - result.addAll(featureSet.getNonPositionalFeatures()); + result += featureSet.getFeatureCount(positional); } return result; } /** - * Answers a list of all contact features stored, in no particular guaranteed - * order - * - * @return + * {@inheritDoc} */ - public List getContactFeatures() + @Override + public int getTotalFeatureLength(String... type) { - List result = new ArrayList(); - for (FeatureStore featureSet : featureStore.values()) + int result = 0; + + for (FeatureStore featureSet : varargToTypes(type)) { - result.addAll(featureSet.getContactFeatures()); + result += featureSet.getTotalFeatureLength(); } return result; } /** - * Answers a list of all features of the given type (including - * non-positional), in no particular guaranteed order - * - * @return + * {@inheritDoc} */ - public List getFeatures(String type) + @Override + public List getPositionalFeatures(String... type) { - List result = new ArrayList(); - FeatureStore featureSet = featureStore.get(type); - if (featureSet != null) + List result = new ArrayList<>(); + + for (FeatureStore featureSet : varargToTypes(type)) { - result.addAll(featureSet.getFeatures()); + featureSet.getPositionalFeatures(result); } return result; } /** - * Answers a list of all contact features of the given type, in no particular - * guaranteed order + * A convenience method that converts a vararg for feature types to an + * Iterable over matched feature sets in key order * + * @param type * @return */ - public List getContactFeatures(String type) + protected Iterable varargToTypes(String... type) { - List result = new ArrayList(); - FeatureStore featureSet = featureStore.get(type); - if (featureSet != null) + if (type == null || type.length == 0) { - result.addAll(featureSet.getContactFeatures()); + /* + * no vararg parameter supplied - return all + */ + return featureStore.values(); + } + + List types = new ArrayList<>(); + List args = Arrays.asList(type); + for (Entry featureType : featureStore.entrySet()) + { + if (args.contains(featureType.getKey())) + { + types.add(featureType.getValue()); + } + } + return types; + } + + /** + * {@inheritDoc} + */ + @Override + public List getContactFeatures(String... type) + { + List result = new ArrayList<>(); + + for (FeatureStore featureSet : varargToTypes(type)) + { + featureSet.getContactFeatures(result); } return result; } /** - * Answers a list of all non-positional features of the given type, in no - * particular guaranteed order - * - * @return + * {@inheritDoc} */ - public List getNonPositionalFeatures(String type) + @Override + public List getNonPositionalFeatures(String... type) { - List result = new ArrayList(); - FeatureStore featureSet = featureStore.get(type); - if (featureSet != null) + List result = new ArrayList<>(); + + for (FeatureStore featureSet : varargToTypes(type)) { - result.addAll(featureSet.getNonPositionalFeatures()); + featureSet.getNonPositionalFeatures(result); } 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 + * {@inheritDoc} */ + @Override public boolean delete(SequenceFeature sf) { for (FeatureStore featureSet : featureStore.values()) @@ -194,10 +277,9 @@ public class SequenceFeatures } /** - * Answers true if this store contains at least one feature, else false - * - * @return + * {@inheritDoc} */ + @Override public boolean hasFeatures() { for (FeatureStore featureSet : featureStore.values()) @@ -211,18 +293,204 @@ public class SequenceFeatures } /** - * Returns a set of the distinct feature groups present in the collection. The - * set may include null. + * {@inheritDoc} + */ + @Override + public Set getFeatureGroups(boolean positionalFeatures, + String... type) + { + // BH 2020.03.21 This is the set that orders the list of groups + // at the top of the FeatureSettings panel. + Set groups = Platform.getJavaOrderedHashSet(); + + for (FeatureStore featureSet : varargToTypes(type)) + { + groups.addAll(featureSet.getFeatureGroups(positionalFeatures)); + } + + return groups; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getFeatureTypesForGroups(boolean positionalFeatures, + String... groups) + { + // BH 2020.03.21 This set is the one that sets the initial ordering for + // feature rendering. We set it to new HashSet<>(16,0.75) to force it to + // be backed by a Java hash-ordered HashMap instead of a JavaScript Map. + Set result = Platform.getJavaOrderedHashSet(); + + for (Entry featureType : featureStore.entrySet()) + { + Set featureGroups = featureType.getValue() + .getFeatureGroups(positionalFeatures); + for (String group : groups) + { + if (featureGroups.contains(group)) + { + /* + * yes this feature type includes one of the query groups + */ + result.add(featureType.getKey()); + break; + } + } + } + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getFeatureTypes(String... soTerm) + { + Set types = new HashSet<>(15, 0.75f); + for (Entry entry : featureStore.entrySet()) + { + String type = entry.getKey(); + if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm)) + { + types.add(type); + } + } + return types; + } + + /** + * Answers true if the given type matches one of the specified terms (or is a + * sub-type of one in the Sequence Ontology), or if no terms are supplied. + * Answers false if filter terms are specified and the given term does not + * match any of them. * + * @param type + * @param soTerm * @return */ - public Set getFeatureGroups() + protected boolean isOntologyTerm(String type, String... soTerm) { - Set groups = new HashSet(); - for (FeatureStore featureSet : featureStore.values()) + if (soTerm == null || soTerm.length == 0) { - groups.addAll(featureSet.getFeatureGroups()); + return true; } - return groups; + SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology(); + for (String term : soTerm) + { + if (type.equals(term) || so.isA(type, term)) + { + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public float getMinimumScore(String type, boolean positional) + { + return featureStore.containsKey(type) + ? featureStore.get(type).getMinimumScore(positional) + : Float.NaN; + } + + /** + * {@inheritDoc} + */ + @Override + public float getMaximumScore(String type, boolean positional) + { + return featureStore.containsKey(type) + ? featureStore.get(type).getMaximumScore(positional) + : Float.NaN; + } + + /** + * A convenience method to sort features by start position ascending (if on + * forward strand), or end position descending (if on reverse strand) + * + * @param features + * @param forwardStrand + */ + public static void sortFeatures(List features, + final boolean forwardStrand) + { + Collections.sort(features, + forwardStrand ? IntervalI.COMPARE_BEGIN_ASC + : IntervalI.COMPARE_END_DESC); + } + + /** + * {@inheritDoc} This method is 'semi-optimised': it only inspects features + * for types that include the specified group, but has to inspect every + * feature of those types for matching feature group. This is efficient unless + * a sequence has features that share the same type but are in different + * groups - an unlikely case. + *

+ * For example, if RESNUM feature is created with group = PDBID, then features + * would only be retrieved for those sequences associated with the target + * PDBID (group). + */ + @Override + public List getFeaturesForGroup(boolean positional, + String group, String... type) + { + List result = new ArrayList<>(); + for (FeatureStore featureSet : varargToTypes(type)) + { + if (featureSet.getFeatureGroups(positional).contains(group)) + { + result.addAll(featureSet.getFeaturesForGroup(positional, group)); + } + } + return result; } + + /** + * {@inheritDoc} + */ + @Override + public boolean shiftFeatures(int fromPosition, int shiftBy) + { + boolean modified = false; + for (FeatureStore fs : featureStore.values()) + { + modified |= fs.shiftFeatures(fromPosition, shiftBy); + } + return modified; + } + + /** + * {@inheritDoc} + */ + @Override + public void deleteAll() + { + featureStore.clear(); + } + + @Override + public List findFeatures(int pos, String type, + List list) + { + FeatureStore fs = featureStore.get(type); + if (fs == null) + { + return list == null ? new ArrayList<>() : list; + } + return fs.findFeatures(pos, pos, list); + } + + @Override + public boolean hasFeatures(String type) + { + return featureStore.containsKey(type) + && !featureStore.get(type).isEmpty(); + } + }