/* * 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.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 * querying by type and location (overlap). Intended for (but not limited to) * storage of features for one sequence. * * @author gmcarstairs * */ 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!) */ private Map featureStore; /** * original NCList-based IntervalStore */ private final static int INTERVAL_STORE_NCLIST = 0; /** * linked-list deferred-sort IntervalStore - experimental only; unused */ private final static int INTERVAL_STORE_LINKED_LIST_NO_PRESORT = 1; /** * linked-list IntervalStore option for JavaScript */ private final static int INTERVAL_STORE_LINKED_LIST = -1; /** * mode for Java or JavaScript; can be set differently for testing, but * default is LINKED_LIST for JalviewJS and NCLIST for Java */ private final int INTERVAL_STORE_MODE = ( // true || // Platform.isJS() ? // INTERVAL_STORE_LINKED_LIST // : INTERVAL_STORE_NCLIST// ); /** * Constructor */ public SequenceFeatures() { /* * 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<>(); } /** * 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) { featureStore.put(type, newFeatureStore()); } return featureStore.get(type).addFeature(sf); } private FeatureStoreI newFeatureStore() { switch (INTERVAL_STORE_MODE) { default: case INTERVAL_STORE_NCLIST: return new FeatureStoreImpl(true); case INTERVAL_STORE_LINKED_LIST_NO_PRESORT: return new FeatureStoreImpl(false); case INTERVAL_STORE_LINKED_LIST: return new FeatureStoreJS(); } } /** * {@inheritDoc} */ @Override public List findFeatures(int from, int to, String... type) { List result = new ArrayList<>(); for (FeatureStoreI featureSet : varargToTypes(type)) { // System.err.println("SF findFeature " + System.currentTimeMillis() // + " " + from + " " + to + " " // + featureSet.getPositionalFeatures().get(0).type); // result.addAll(featureSet.findOverlappingFeatures(from, to, null)); } return result; } /** * {@inheritDoc} */ @Override public List getAllFeatures(String... type) { 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) { return new ArrayList<>(); } 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()])); } /** * {@inheritDoc} */ @Override public int getFeatureCount(boolean positional, String... type) { int result = 0; for (FeatureStoreI featureSet : varargToTypes(type)) { result += featureSet.getFeatureCount(positional); } return result; } /** * {@inheritDoc} */ @Override public int getTotalFeatureLength(String... type) { int result = 0; for (FeatureStoreI featureSet : varargToTypes(type)) { result += featureSet.getTotalFeatureLength(); } return result; } /** * {@inheritDoc} */ @Override public List getPositionalFeatures(String... type) { List result = new ArrayList<>(); for (FeatureStoreI featureSet : varargToTypes(type)) { featureSet.getPositionalFeatures(result); } return result; } /** * A convenience method that converts a vararg for feature types to an * Iterable over matched feature sets in key order * * @param type * @return */ protected Iterable varargToTypes(String... type) { if (type == null || type.length == 0) { /* * 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 (FeatureStoreI featureSet : varargToTypes(type)) { featureSet.getContactFeatures(result); } return result; } /** * {@inheritDoc} */ @Override public List getNonPositionalFeatures(String... type) { List result = new ArrayList<>(); for (FeatureStoreI featureSet : varargToTypes(type)) { featureSet.getNonPositionalFeatures(result); } return result; } /** * {@inheritDoc} */ @Override public boolean delete(SequenceFeature sf) { for (FeatureStoreI featureSet : featureStore.values()) { if (featureSet.delete(sf)) { return true; } } return false; } /** * {@inheritDoc} */ @Override public boolean hasFeatures() { for (FeatureStoreI featureSet : featureStore.values()) { if (!featureSet.isEmpty()) { return true; } } return false; } /** * {@inheritDoc} */ @Override public Set getFeatureGroups(boolean positionalFeatures, String... type) { Set groups = new HashSet<>(); for (FeatureStoreI featureSet : varargToTypes(type)) { groups.addAll(featureSet.getFeatureGroups(positionalFeatures)); } return groups; } /** * {@inheritDoc} */ @Override public Set getFeatureTypesForGroups(boolean positionalFeatures, String... groups) { Set result = new HashSet<>(); 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<>(); 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 */ protected boolean isOntologyTerm(String type, String... soTerm) { if (soTerm == null || soTerm.length == 0) { return true; } 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) { IntervalI.sortIntervals(features, forwardStrand); } /** * {@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 (FeatureStoreI 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 (FeatureStoreI fs : featureStore.values()) { modified |= fs.shiftFeatures(fromPosition, shiftBy); } return modified; } /** * {@inheritDoc} */ @Override public void deleteAll() { featureStore.clear(); } /** * Simplified find for features associated with a given position. * * JavaScript set to not use IntervalI, but easily testable by setting false * to true in javadoc * * FeatureRenderer has checked already that featureStore does contain type. * * @author Bob Hanson 2019.07.30 */ @Override public List findFeatures(int pos, String type, List list) { FeatureStoreI fs = featureStore.get(type); return fs.findOverlappingFeatures(pos, pos, list); } // Chrome; developer console closed // BH 2019.08.01 useIntervalStore true, redraw false: // Platform: timer mark 13.848 0.367 overviewrender 16000 pixels row:14 // Platform: timer mark 15.391 0.39 overviewrender 16000 pixels row:14 // Platform: timer mark 16.498 0.39 overviewrender 16000 pixels row:14 // Platform: timer mark 17.596 0.401 overviewrender 16000 pixels row:14 // Platform: timer mark 18.738 0.363 overviewrender 16000 pixels row:14 // Platform: timer mark 19.659 0.358 overviewrender 16000 pixels row:14 // Platform: timer mark 20.737 0.359 overviewrender 16000 pixels row:14 // Platform: timer mark 21.797 0.391 overviewrender 16000 pixels row:14 // Platform: timer mark 22.851 0.361 overviewrender 16000 pixels row:14 // Platform: timer mark 24.019 0.395 overviewrender 16000 pixels row:14 // BH 2019.08.01 useIntervalStore false, redraw false: // Platform: timer mark 19.011 0.181 overviewrender 16000 pixels row:14 // Platform: timer mark 20.311 0.183 overviewrender 16000 pixels row:14 // Platform: timer mark 21.368 0.175 overviewrender 16000 pixels row:14 // Platform: timer mark 22.347 0.178 overviewrender 16000 pixels row:14 // Platform: timer mark 23.605 0.216 overviewrender 16000 pixels row:14 // Platform: timer mark 24.836 0.191 overviewrender 16000 pixels row:14 // Platform: timer mark 26.016 0.181 overviewrender 16000 pixels row:14 // Platform: timer mark 27.278 0.178 overviewrender 16000 pixels row:14 // Platform: timer mark 28.158 0.181 overviewrender 16000 pixels row:14 // Platform: timer mark 29.227 0.196 overviewrender 16000 pixels row:14 // Platform: timer mark 30.1 0.171 overviewrender 16000 pixels row:14 // Platform: timer mark 31.684 0.196 overviewrender 16000 pixels row:14 // Platform: timer mark 32.779 0.18 overviewrender 16000 pixels row:14 // Platform: timer mark 52.355 0.185 overviewrender 16000 pixels row:14 // Platform: timer mark 53.829 0.186 overviewrender 16000 pixels row:14 /** * @author Bob Hanson 2019.08.01 */ @Override public boolean hasFeatures(String type) { return featureStore.containsKey(type); } }