54102ceaf28952bc9848b2c0cfe9fe919ccbd6a1
[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.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import java.util.Set;
35 import java.util.TreeMap;
36
37 import intervalstore.api.IntervalI;
38
39 /**
40  * A class that stores sequence features in a way that supports efficient
41  * querying by type and location (overlap). Intended for (but not limited to)
42  * storage of features for one sequence.
43  * 
44  * @author gmcarstairs
45  *
46  */
47 public class SequenceFeatures implements SequenceFeaturesI
48 {
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, FeatureStoreI> featureStore;
55
56   private static boolean useIntervalStore = !Platform.isJS();
57
58   /**
59    * Constructor
60    */
61   public SequenceFeatures()
62   {
63     /*
64      * use a TreeMap so that features are returned in alphabetical order of type
65      * ? wrap as a synchronized map for add and delete operations
66      */
67     // featureStore = Collections
68     // .synchronizedSortedMap(new TreeMap<String, FeatureStoreI>());
69     featureStore = new TreeMap<>();
70   }
71
72   /**
73    * Constructor given a list of features
74    */
75   public SequenceFeatures(List<SequenceFeature> features)
76   {
77     this();
78     if (features != null)
79     {
80       for (SequenceFeature feature : features)
81       {
82         add(feature);
83       }
84     }
85   }
86
87   /**
88    * {@inheritDoc}
89    */
90   @Override
91   public boolean add(SequenceFeature sf)
92   {
93     String type = sf.getType();
94     if (type == null)
95     {
96       System.err.println("Feature type may not be null: " + sf.toString());
97       return false;
98     }
99
100     if (featureStore.get(type) == null)
101     {
102       featureStore.put(type, newFeatureStore());
103     }
104     return featureStore.get(type).addFeature(sf);
105   }
106
107   private FeatureStoreI newFeatureStore()
108   {
109     // TODO Auto-generated method stub
110     return (useIntervalStore ? new FeatureStoreImpl() : new FeatureStoreJS());
111   }
112
113   /**
114    * {@inheritDoc}
115    */
116   @Override
117   public List<SequenceFeature> findFeatures(int from, int to,
118           String... type)
119   {
120     List<SequenceFeature> result = new ArrayList<>();
121     for (FeatureStoreI featureSet : varargToTypes(type))
122     {
123       result.addAll(featureSet.findOverlappingFeatures(from, to, null));
124     }
125     return result;
126   }
127
128   /**
129    * {@inheritDoc}
130    */
131   @Override
132   public List<SequenceFeature> getAllFeatures(String... type)
133   {
134     List<SequenceFeature> result = new ArrayList<>();
135
136     result.addAll(getPositionalFeatures(type));
137
138     result.addAll(getNonPositionalFeatures());
139
140     return result;
141   }
142
143   /**
144    * {@inheritDoc}
145    */
146   @Override
147   public List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm)
148   {
149     if (ontologyTerm == null || ontologyTerm.length == 0)
150     {
151       return new ArrayList<>();
152     }
153
154     Set<String> featureTypes = getFeatureTypes(ontologyTerm);
155     if (featureTypes.isEmpty())
156     {
157       /*
158        * no features of the specified type or any sub-type
159        */
160       return new ArrayList<>();
161     }
162
163     return getAllFeatures(featureTypes.toArray(new String[featureTypes
164             .size()]));
165   }
166
167   /**
168    * {@inheritDoc}
169    */
170   @Override
171   public int getFeatureCount(boolean positional, String... type)
172   {
173     int result = 0;
174
175     for (FeatureStoreI featureSet : varargToTypes(type))
176     {
177       result += featureSet.getFeatureCount(positional);
178     }
179     return result;
180   }
181
182   /**
183    * {@inheritDoc}
184    */
185   @Override
186   public int getTotalFeatureLength(String... type)
187   {
188     int result = 0;
189
190     for (FeatureStoreI featureSet : varargToTypes(type))
191     {
192       result += featureSet.getTotalFeatureLength();
193     }
194     return result;
195   }
196
197   /**
198    * {@inheritDoc}
199    */
200   @Override
201   public List<SequenceFeature> getPositionalFeatures(String... type)
202   {
203     List<SequenceFeature> result = new ArrayList<>();
204
205     for (FeatureStoreI featureSet : varargToTypes(type))
206     {
207       featureSet.getPositionalFeatures(result);
208     }
209     return result;
210   }
211
212   /**
213    * A convenience method that converts a vararg for feature types to an
214    * Iterable over matched feature sets in key order
215    * 
216    * @param type
217    * @return
218    */
219   protected Iterable<FeatureStoreI> varargToTypes(String... type)
220   {
221     if (type == null || type.length == 0)
222     {
223       /*
224        * no vararg parameter supplied - return all
225        */
226       return featureStore.values();
227     }
228
229
230     List<FeatureStoreI> types = new ArrayList<>();
231     List<String> args = Arrays.asList(type);
232     for (Entry<String, FeatureStoreI> featureType : featureStore.entrySet())
233     {
234       if (args.contains(featureType.getKey()))
235       {
236         types.add(featureType.getValue());
237       }
238     }
239     return types;
240   }
241
242   /**
243    * {@inheritDoc}
244    */
245   @Override
246   public List<SequenceFeature> getContactFeatures(String... type)
247   {
248     List<SequenceFeature> result = new ArrayList<>();
249
250     for (FeatureStoreI featureSet : varargToTypes(type))
251     {
252       featureSet.getContactFeatures(result);
253     }
254     return result;
255   }
256
257   /**
258    * {@inheritDoc}
259    */
260   @Override
261   public List<SequenceFeature> getNonPositionalFeatures(String... type)
262   {
263     List<SequenceFeature> result = new ArrayList<>();
264
265     for (FeatureStoreI featureSet : varargToTypes(type))
266     {
267       featureSet.getNonPositionalFeatures(result);
268     }
269     return result;
270   }
271
272   /**
273    * {@inheritDoc}
274    */
275   @Override
276   public boolean delete(SequenceFeature sf)
277   {
278     for (FeatureStoreI featureSet : featureStore.values())
279     {
280       if (featureSet.delete(sf))
281       {
282         return true;
283       }
284     }
285     return false;
286   }
287
288   /**
289    * {@inheritDoc}
290    */
291   @Override
292   public boolean hasFeatures()
293   {
294     for (FeatureStoreI featureSet : featureStore.values())
295     {
296       if (!featureSet.isEmpty())
297       {
298         return true;
299       }
300     }
301     return false;
302   }
303
304   /**
305    * {@inheritDoc}
306    */
307   @Override
308   public Set<String> getFeatureGroups(boolean positionalFeatures,
309           String... type)
310   {
311     Set<String> groups = new HashSet<>();
312
313     for (FeatureStoreI featureSet : varargToTypes(type))
314     {
315       groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
316     }
317
318     return groups;
319   }
320
321   /**
322    * {@inheritDoc}
323    */
324   @Override
325   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
326           String... groups)
327   {
328     Set<String> result = new HashSet<>();
329
330     for (Entry<String, FeatureStoreI> 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 = new HashSet<>();
357     for (Entry<String, FeatureStoreI> 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 (FeatureStoreI 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 (FeatureStoreI 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   /**
478    * Simplified find for features associated with a given position.
479    * 
480    * JavaScript set to not use IntervalI, but easily testable by setting false
481    * to true in javadoc
482    * 
483    * FeatureRenderer has checked already that featureStore does contain type.
484    * 
485    * @author Bob Hanson 2019.07.30
486    */
487   @Override
488   public List<SequenceFeature> findFeatures(int pos, String type,
489           List<SequenceFeature> list)
490   {
491     FeatureStoreI fs = featureStore.get(type);
492     return fs.findOverlappingFeatures(pos, pos, list);
493   }
494
495   // Chrome; developer console closed
496
497   // BH 2019.08.01 useIntervalStore true, redraw false:
498   // Platform: timer mark 13.848 0.367 overviewrender 16000 pixels row:14
499   // Platform: timer mark 15.391 0.39 overviewrender 16000 pixels row:14
500   // Platform: timer mark 16.498 0.39 overviewrender 16000 pixels row:14
501   // Platform: timer mark 17.596 0.401 overviewrender 16000 pixels row:14
502   // Platform: timer mark 18.738 0.363 overviewrender 16000 pixels row:14
503   // Platform: timer mark 19.659 0.358 overviewrender 16000 pixels row:14
504   // Platform: timer mark 20.737 0.359 overviewrender 16000 pixels row:14
505   // Platform: timer mark 21.797 0.391 overviewrender 16000 pixels row:14
506   // Platform: timer mark 22.851 0.361 overviewrender 16000 pixels row:14
507   // Platform: timer mark 24.019 0.395 overviewrender 16000 pixels row:14
508
509   // BH 2019.08.01 useIntervalStore false, redraw false:
510   // Platform: timer mark 19.011 0.181 overviewrender 16000 pixels row:14
511   // Platform: timer mark 20.311 0.183 overviewrender 16000 pixels row:14
512   // Platform: timer mark 21.368 0.175 overviewrender 16000 pixels row:14
513   // Platform: timer mark 22.347 0.178 overviewrender 16000 pixels row:14
514   // Platform: timer mark 23.605 0.216 overviewrender 16000 pixels row:14
515   // Platform: timer mark 24.836 0.191 overviewrender 16000 pixels row:14
516   // Platform: timer mark 26.016 0.181 overviewrender 16000 pixels row:14
517   // Platform: timer mark 27.278 0.178 overviewrender 16000 pixels row:14
518   // Platform: timer mark 28.158 0.181 overviewrender 16000 pixels row:14
519   // Platform: timer mark 29.227 0.196 overviewrender 16000 pixels row:14
520   // Platform: timer mark 30.1 0.171 overviewrender 16000 pixels row:14
521   // Platform: timer mark 31.684 0.196 overviewrender 16000 pixels row:14
522   // Platform: timer mark 32.779 0.18 overviewrender 16000 pixels row:14
523   // Platform: timer mark 52.355 0.185 overviewrender 16000 pixels row:14
524   // Platform: timer mark 53.829 0.186 overviewrender 16000 pixels row:14
525
526
527
528   /**
529    * @author Bob Hanson 2019.08.01
530    */
531   @Override
532   public boolean hasFeatures(String type)
533   {
534     return featureStore.containsKey(type);
535   }
536
537 }