bc389427acd2589b0261b2cbe32f476916b2e01d
[jalview.git] / src / jalview / datamodel / features / SequenceFeatures.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel.features;
22
23 import jalview.datamodel.SequenceFeature;
24 import jalview.io.gff.SequenceOntologyFactory;
25 import jalview.io.gff.SequenceOntologyI;
26 import jalview.util.Platform;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 import java.util.Set;
36 import java.util.TreeMap;
37
38 import intervalstore.api.IntervalI;
39
40 /**
41  * A class that stores sequence features in a way that supports efficient
42  * querying by type and location (overlap). Intended for (but not limited to)
43  * storage of features for one sequence.
44  * 
45  * @author gmcarstairs
46  *
47  */
48 public class SequenceFeatures implements SequenceFeaturesI
49 {
50   /*
51    * map from feature type to structured store of features for that type
52    * null types are permitted (but not a good idea!)
53    */
54   private Map<String, FeatureStore> featureStore;
55
56   /**
57    * Constructor
58    */
59   public SequenceFeatures()
60   {
61     /*
62      * use a TreeMap so that features are returned in alphabetical order of type
63      * ? wrap as a synchronized map for add and delete operations
64      */
65     // featureStore = Collections
66     // .synchronizedSortedMap(new TreeMap<String, FeatureStore>());
67     featureStore = new TreeMap<>();
68   }
69
70   /**
71    * Constructor given a list of features
72    */
73   public SequenceFeatures(List<SequenceFeature> features)
74   {
75     this();
76     if (features != null)
77     {
78       for (SequenceFeature feature : features)
79       {
80         add(feature);
81       }
82     }
83   }
84
85   /**
86    * {@inheritDoc}
87    */
88   @Override
89   public boolean add(SequenceFeature sf)
90   {
91     String type = sf.getType();
92     if (type == null)
93     {
94       System.err.println("Feature type may not be null: " + sf.toString());
95       return false;
96     }
97
98     if (featureStore.get(type) == null)
99     {
100       featureStore.put(type, new FeatureStore());
101     }
102     return featureStore.get(type).addFeature(sf);
103   }
104
105   /**
106    * {@inheritDoc}
107    */
108   @Override
109   public List<SequenceFeature> findFeatures(int from, int to,
110           String... type)
111   {
112     List<SequenceFeature> result = new ArrayList<>();
113     for (FeatureStore featureSet : varargToTypes(type))
114     {
115         // BH MUCH more efficient to pass the list
116       featureSet.findFeatures(from, to, result);
117     }
118     return result;
119   }
120
121   /**
122    * {@inheritDoc}
123    */
124   @Override
125   public List<SequenceFeature> getAllFeatures(String... type)
126   {
127     List<SequenceFeature> result = new ArrayList<>();
128
129     result.addAll(getPositionalFeatures(type));
130
131     result.addAll(getNonPositionalFeatures());
132
133     return result;
134   }
135
136   /**
137    * {@inheritDoc}
138    */
139   @Override
140   public List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm)
141   {
142     if (ontologyTerm == null || ontologyTerm.length == 0)
143     {
144       return new ArrayList<>();
145     }
146
147     Set<String> featureTypes = getFeatureTypes(ontologyTerm);
148     if (featureTypes.isEmpty())
149     {
150       /*
151        * no features of the specified type or any sub-type
152        */
153       return new ArrayList<>();
154     }
155
156     return getAllFeatures(featureTypes.toArray(new String[featureTypes
157             .size()]));
158   }
159
160   /**
161    * {@inheritDoc}
162    */
163   @Override
164   public int getFeatureCount(boolean positional, String... type)
165   {
166     int result = 0;
167
168     for (FeatureStore featureSet : varargToTypes(type))
169     {
170       result += featureSet.getFeatureCount(positional);
171     }
172     return result;
173   }
174
175   /**
176    * {@inheritDoc}
177    */
178   @Override
179   public int getTotalFeatureLength(String... type)
180   {
181     int result = 0;
182
183     for (FeatureStore featureSet : varargToTypes(type))
184     {
185       result += featureSet.getTotalFeatureLength();
186     }
187     return result;
188   }
189
190   /**
191    * {@inheritDoc}
192    */
193   @Override
194   public List<SequenceFeature> getPositionalFeatures(String... type)
195   {
196     List<SequenceFeature> result = new ArrayList<>();
197
198     for (FeatureStore featureSet : varargToTypes(type))
199     {
200       featureSet.getPositionalFeatures(result);
201     }
202     return result;
203   }
204
205   /**
206    * A convenience method that converts a vararg for feature types to an
207    * Iterable over matched feature sets. If no types are specified, all feature
208    * sets are returned. If one or more types are specified, feature sets for
209    * those types are returned, preserving the order of the types.
210    * 
211    * @param type
212    * @return
213    */
214   protected Iterable<FeatureStore> varargToTypes(String... type)
215   {
216     if (type == null || type.length == 0)
217     {
218       /*
219        * no vararg parameter supplied - return all
220        */
221       return featureStore.values();
222     }
223
224     List<FeatureStore> types = new ArrayList<>();
225     for (String theType : type)
226     {
227       if (theType != null && featureStore.containsKey(theType))
228       {
229         types.add(featureStore.get(theType));
230       }
231     }
232     return types;
233   }
234
235   /**
236    * {@inheritDoc}
237    */
238   @Override
239   public List<SequenceFeature> getContactFeatures(String... type)
240   {
241     List<SequenceFeature> result = new ArrayList<>();
242
243     for (FeatureStore featureSet : varargToTypes(type))
244     {
245       featureSet.getContactFeatures(result);
246     }
247     return result;
248   }
249
250   /**
251    * {@inheritDoc}
252    */
253   @Override
254   public List<SequenceFeature> getNonPositionalFeatures(String... type)
255   {
256     List<SequenceFeature> result = new ArrayList<>();
257
258     for (FeatureStore featureSet : varargToTypes(type))
259     {
260       featureSet.getNonPositionalFeatures(result);
261     }
262     return result;
263   }
264
265   /**
266    * {@inheritDoc}
267    */
268   @Override
269   public boolean delete(SequenceFeature sf)
270   {
271     for (FeatureStore featureSet : featureStore.values())
272     {
273       if (featureSet.delete(sf))
274       {
275         return true;
276       }
277     }
278     return false;
279   }
280
281   /**
282    * {@inheritDoc}
283    */
284   @Override
285   public boolean hasFeatures()
286   {
287     for (FeatureStore featureSet : featureStore.values())
288     {
289       if (!featureSet.isEmpty())
290       {
291         return true;
292       }
293     }
294     return false;
295   }
296
297   /**
298    * {@inheritDoc}
299    */
300   @Override
301   public Set<String> getFeatureGroups(boolean positionalFeatures,
302           String... type)
303   {
304     // BH 2020.03.21 This is the set that orders the list of groups
305     // at the top of the FeatureSettings panel.
306         // In order to keep the same order as a Java HashSet, we must
307         // request that specifically. See Platform.
308     Set<String> groups = Platform.getJavaOrderedHashSet();
309
310     for (FeatureStore featureSet : varargToTypes(type))
311     {
312       groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
313     }
314
315     return groups;
316   }
317
318   /**
319    * {@inheritDoc}
320    */
321   @Override
322   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
323           String... groups)
324   {
325     // BH 2020.03.21 This set is the one that sets the initial ordering for
326     // feature rendering. We set it to new HashSet<>(16,0.75) to force it to
327     // be backed by a Java hash-ordered HashMap instead of a JavaScript Map.
328     Set<String> result = Platform.getJavaOrderedHashSet();
329
330     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
331     {
332       Set<String> featureGroups = featureType.getValue().getFeatureGroups(
333               positionalFeatures);
334       for (String group : groups)
335       {
336         if (featureGroups.contains(group))
337         {
338           /*
339            * yes this feature type includes one of the query groups
340            */
341           result.add(featureType.getKey());
342           break;
343         }
344       }
345     }
346
347     return result;
348   }
349
350   /**
351    * {@inheritDoc}
352    */
353   @Override
354   public Set<String> getFeatureTypes(String... soTerm)
355   {
356     Set<String> types = Platform.getJavaOrderedHashSet();
357     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
358     {
359       String type = entry.getKey();
360       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
361       {
362         types.add(type);
363       }
364     }
365     return types;
366   }
367
368   /**
369    * Answers true if the given type matches one of the specified terms (or is a
370    * sub-type of one in the Sequence Ontology), or if no terms are supplied.
371    * Answers false if filter terms are specified and the given term does not
372    * match any of them.
373    * 
374    * @param type
375    * @param soTerm
376    * @return
377    */
378   protected boolean isOntologyTerm(String type, String... soTerm)
379   {
380     if (soTerm == null || soTerm.length == 0)
381     {
382       return true;
383     }
384     SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
385     for (String term : soTerm)
386     {
387       if (type.equals(term) || so.isA(type, term))
388       {
389         return true;
390       }
391     }
392     return false;
393   }
394
395   /**
396    * {@inheritDoc}
397    */
398   @Override
399   public float getMinimumScore(String type, boolean positional)
400   {
401     return featureStore.containsKey(type) ? featureStore.get(type)
402             .getMinimumScore(positional) : Float.NaN;
403   }
404
405   /**
406    * {@inheritDoc}
407    */
408   @Override
409   public float getMaximumScore(String type, boolean positional)
410   {
411     return featureStore.containsKey(type) ? featureStore.get(type)
412             .getMaximumScore(positional) : Float.NaN;
413   }
414
415   /**
416    * A convenience method to sort features by start position ascending (if on
417    * forward strand), or end position descending (if on reverse strand)
418    * 
419    * @param features
420    * @param forwardStrand
421    */
422   public static void sortFeatures(List<? extends IntervalI> features,
423           final boolean forwardStrand)
424   {
425     IntervalI.sortIntervals(features, forwardStrand);
426   }
427
428   /**
429    * {@inheritDoc} This method is 'semi-optimised': it only inspects features
430    * for types that include the specified group, but has to inspect every
431    * feature of those types for matching feature group. This is efficient unless
432    * a sequence has features that share the same type but are in different
433    * groups - an unlikely case.
434    * <p>
435    * For example, if RESNUM feature is created with group = PDBID, then features
436    * would only be retrieved for those sequences associated with the target
437    * PDBID (group).
438    */
439   @Override
440   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
441           String group, String... type)
442   {
443     List<SequenceFeature> result = new ArrayList<>();
444     for (FeatureStore featureSet : varargToTypes(type))
445     {
446       if (featureSet.getFeatureGroups(positional).contains(group))
447       {
448         result.addAll(featureSet.getFeaturesForGroup(positional, group));
449       }
450     }
451     return result;
452   }
453
454   /**
455    * {@inheritDoc}
456    */
457   @Override
458   public boolean shiftFeatures(int fromPosition, int shiftBy)
459   {
460     boolean modified = false;
461     for (FeatureStore fs : featureStore.values())
462     {
463       modified |= fs.shiftFeatures(fromPosition, shiftBy);
464     }
465     return modified;
466   }
467
468   /**
469    * {@inheritDoc}
470    */
471   @Override
472   public void deleteAll()
473   {
474     featureStore.clear();
475   }
476
477   @Override
478   public List<SequenceFeature> findFeatures(int pos, String type,
479           List<SequenceFeature> list)
480   {
481     FeatureStore fs = featureStore.get(type);
482     if (fs == null)
483     {
484       return list == null ? new ArrayList<>() : list;
485     }
486     return fs.findFeatures(pos, pos, list);
487   }
488
489   @Override
490   public boolean hasFeatures(String type)
491   {
492     return featureStore.containsKey(type)
493             && !featureStore.get(type).isEmpty();
494   }
495
496 }