0baeb19d23d3fb62bb8e93d4ae316ac13c1e2a24
[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 //      System.err.println("SF findFeature " + System.currentTimeMillis()
124 //              + " " + from + " " + to + " "
125 //              + featureSet.getPositionalFeatures().get(0).type);
126 //
127       result.addAll(featureSet.findOverlappingFeatures(from, to, null));
128     }
129     return result;
130   }
131
132   /**
133    * {@inheritDoc}
134    */
135   @Override
136   public List<SequenceFeature> getAllFeatures(String... type)
137   {
138     List<SequenceFeature> result = new ArrayList<>();
139
140     result.addAll(getPositionalFeatures(type));
141
142     result.addAll(getNonPositionalFeatures());
143
144     return result;
145   }
146
147   /**
148    * {@inheritDoc}
149    */
150   @Override
151   public List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm)
152   {
153     if (ontologyTerm == null || ontologyTerm.length == 0)
154     {
155       return new ArrayList<>();
156     }
157
158     Set<String> featureTypes = getFeatureTypes(ontologyTerm);
159     if (featureTypes.isEmpty())
160     {
161       /*
162        * no features of the specified type or any sub-type
163        */
164       return new ArrayList<>();
165     }
166
167     return getAllFeatures(featureTypes.toArray(new String[featureTypes
168             .size()]));
169   }
170
171   /**
172    * {@inheritDoc}
173    */
174   @Override
175   public int getFeatureCount(boolean positional, String... type)
176   {
177     int result = 0;
178
179     for (FeatureStoreI featureSet : varargToTypes(type))
180     {
181       result += featureSet.getFeatureCount(positional);
182     }
183     return result;
184   }
185
186   /**
187    * {@inheritDoc}
188    */
189   @Override
190   public int getTotalFeatureLength(String... type)
191   {
192     int result = 0;
193
194     for (FeatureStoreI featureSet : varargToTypes(type))
195     {
196       result += featureSet.getTotalFeatureLength();
197     }
198     return result;
199   }
200
201   /**
202    * {@inheritDoc}
203    */
204   @Override
205   public List<SequenceFeature> getPositionalFeatures(String... type)
206   {
207     List<SequenceFeature> result = new ArrayList<>();
208
209     for (FeatureStoreI featureSet : varargToTypes(type))
210     {
211       featureSet.getPositionalFeatures(result);
212     }
213     return result;
214   }
215
216   /**
217    * A convenience method that converts a vararg for feature types to an
218    * Iterable over matched feature sets in key order
219    * 
220    * @param type
221    * @return
222    */
223   protected Iterable<FeatureStoreI> varargToTypes(String... type)
224   {
225     if (type == null || type.length == 0)
226     {
227       /*
228        * no vararg parameter supplied - return all
229        */
230       return featureStore.values();
231     }
232
233
234     List<FeatureStoreI> types = new ArrayList<>();
235     List<String> args = Arrays.asList(type);
236     for (Entry<String, FeatureStoreI> featureType : featureStore.entrySet())
237     {
238       if (args.contains(featureType.getKey()))
239       {
240         types.add(featureType.getValue());
241       }
242     }
243     return types;
244   }
245
246   /**
247    * {@inheritDoc}
248    */
249   @Override
250   public List<SequenceFeature> getContactFeatures(String... type)
251   {
252     List<SequenceFeature> result = new ArrayList<>();
253
254     for (FeatureStoreI featureSet : varargToTypes(type))
255     {
256       featureSet.getContactFeatures(result);
257     }
258     return result;
259   }
260
261   /**
262    * {@inheritDoc}
263    */
264   @Override
265   public List<SequenceFeature> getNonPositionalFeatures(String... type)
266   {
267     List<SequenceFeature> result = new ArrayList<>();
268
269     for (FeatureStoreI featureSet : varargToTypes(type))
270     {
271       featureSet.getNonPositionalFeatures(result);
272     }
273     return result;
274   }
275
276   /**
277    * {@inheritDoc}
278    */
279   @Override
280   public boolean delete(SequenceFeature sf)
281   {
282     for (FeatureStoreI featureSet : featureStore.values())
283     {
284       if (featureSet.delete(sf))
285       {
286         return true;
287       }
288     }
289     return false;
290   }
291
292   /**
293    * {@inheritDoc}
294    */
295   @Override
296   public boolean hasFeatures()
297   {
298     for (FeatureStoreI featureSet : featureStore.values())
299     {
300       if (!featureSet.isEmpty())
301       {
302         return true;
303       }
304     }
305     return false;
306   }
307
308   /**
309    * {@inheritDoc}
310    */
311   @Override
312   public Set<String> getFeatureGroups(boolean positionalFeatures,
313           String... type)
314   {
315     Set<String> groups = new HashSet<>();
316
317     for (FeatureStoreI featureSet : varargToTypes(type))
318     {
319       groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
320     }
321
322     return groups;
323   }
324
325   /**
326    * {@inheritDoc}
327    */
328   @Override
329   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
330           String... groups)
331   {
332     Set<String> result = new HashSet<>();
333
334     for (Entry<String, FeatureStoreI> featureType : featureStore.entrySet())
335     {
336       Set<String> featureGroups = featureType.getValue().getFeatureGroups(
337               positionalFeatures);
338       for (String group : groups)
339       {
340         if (featureGroups.contains(group))
341         {
342           /*
343            * yes this feature type includes one of the query groups
344            */
345           result.add(featureType.getKey());
346           break;
347         }
348       }
349     }
350
351     return result;
352   }
353
354   /**
355    * {@inheritDoc}
356    */
357   @Override
358   public Set<String> getFeatureTypes(String... soTerm)
359   {
360     Set<String> types = new HashSet<>();
361     for (Entry<String, FeatureStoreI> entry : featureStore.entrySet())
362     {
363       String type = entry.getKey();
364       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
365       {
366         types.add(type);
367       }
368     }
369     return types;
370   }
371
372   /**
373    * Answers true if the given type matches one of the specified terms (or is a
374    * sub-type of one in the Sequence Ontology), or if no terms are supplied.
375    * Answers false if filter terms are specified and the given term does not
376    * match any of them.
377    * 
378    * @param type
379    * @param soTerm
380    * @return
381    */
382   protected boolean isOntologyTerm(String type, String... soTerm)
383   {
384     if (soTerm == null || soTerm.length == 0)
385     {
386       return true;
387     }
388     SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
389     for (String term : soTerm)
390     {
391       if (type.equals(term) || so.isA(type, term))
392       {
393         return true;
394       }
395     }
396     return false;
397   }
398
399   /**
400    * {@inheritDoc}
401    */
402   @Override
403   public float getMinimumScore(String type, boolean positional)
404   {
405     return featureStore.containsKey(type) ? featureStore.get(type)
406             .getMinimumScore(positional) : Float.NaN;
407   }
408
409   /**
410    * {@inheritDoc}
411    */
412   @Override
413   public float getMaximumScore(String type, boolean positional)
414   {
415     return featureStore.containsKey(type) ? featureStore.get(type)
416             .getMaximumScore(positional) : Float.NaN;
417   }
418
419   /**
420    * A convenience method to sort features by start position ascending (if on
421    * forward strand), or end position descending (if on reverse strand)
422    * 
423    * @param features
424    * @param forwardStrand
425    */
426   public static void sortFeatures(List<? extends IntervalI> features,
427           final boolean forwardStrand)
428   {
429     IntervalI.sortIntervals(features, forwardStrand);
430   }
431
432   /**
433    * {@inheritDoc} This method is 'semi-optimised': it only inspects features
434    * for types that include the specified group, but has to inspect every
435    * feature of those types for matching feature group. This is efficient unless
436    * a sequence has features that share the same type but are in different
437    * groups - an unlikely case.
438    * <p>
439    * For example, if RESNUM feature is created with group = PDBID, then features
440    * would only be retrieved for those sequences associated with the target
441    * PDBID (group).
442    */
443   @Override
444   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
445           String group, String... type)
446   {
447     List<SequenceFeature> result = new ArrayList<>();
448     for (FeatureStoreI featureSet : varargToTypes(type))
449     {
450       if (featureSet.getFeatureGroups(positional).contains(group))
451       {
452         result.addAll(featureSet.getFeaturesForGroup(positional, group));
453       }
454     }
455     return result;
456   }
457
458   /**
459    * {@inheritDoc}
460    */
461   @Override
462   public boolean shiftFeatures(int fromPosition, int shiftBy)
463   {
464     boolean modified = false;
465     for (FeatureStoreI fs : featureStore.values())
466     {
467       modified |= fs.shiftFeatures(fromPosition, shiftBy);
468     }
469     return modified;
470   }
471
472   /**
473    * {@inheritDoc}
474    */
475   @Override
476   public void deleteAll()
477   {
478     featureStore.clear();
479   }
480
481   /**
482    * Simplified find for features associated with a given position.
483    * 
484    * JavaScript set to not use IntervalI, but easily testable by setting false
485    * to true in javadoc
486    * 
487    * FeatureRenderer has checked already that featureStore does contain type.
488    * 
489    * @author Bob Hanson 2019.07.30
490    */
491   @Override
492   public List<SequenceFeature> findFeatures(int pos, String type,
493           List<SequenceFeature> list)
494   {
495     FeatureStoreI fs = featureStore.get(type);
496     return fs.findOverlappingFeatures(pos, pos, list);
497   }
498
499   // Chrome; developer console closed
500
501   // BH 2019.08.01 useIntervalStore true, redraw false:
502   // Platform: timer mark 13.848 0.367 overviewrender 16000 pixels row:14
503   // Platform: timer mark 15.391 0.39 overviewrender 16000 pixels row:14
504   // Platform: timer mark 16.498 0.39 overviewrender 16000 pixels row:14
505   // Platform: timer mark 17.596 0.401 overviewrender 16000 pixels row:14
506   // Platform: timer mark 18.738 0.363 overviewrender 16000 pixels row:14
507   // Platform: timer mark 19.659 0.358 overviewrender 16000 pixels row:14
508   // Platform: timer mark 20.737 0.359 overviewrender 16000 pixels row:14
509   // Platform: timer mark 21.797 0.391 overviewrender 16000 pixels row:14
510   // Platform: timer mark 22.851 0.361 overviewrender 16000 pixels row:14
511   // Platform: timer mark 24.019 0.395 overviewrender 16000 pixels row:14
512
513   // BH 2019.08.01 useIntervalStore false, redraw false:
514   // Platform: timer mark 19.011 0.181 overviewrender 16000 pixels row:14
515   // Platform: timer mark 20.311 0.183 overviewrender 16000 pixels row:14
516   // Platform: timer mark 21.368 0.175 overviewrender 16000 pixels row:14
517   // Platform: timer mark 22.347 0.178 overviewrender 16000 pixels row:14
518   // Platform: timer mark 23.605 0.216 overviewrender 16000 pixels row:14
519   // Platform: timer mark 24.836 0.191 overviewrender 16000 pixels row:14
520   // Platform: timer mark 26.016 0.181 overviewrender 16000 pixels row:14
521   // Platform: timer mark 27.278 0.178 overviewrender 16000 pixels row:14
522   // Platform: timer mark 28.158 0.181 overviewrender 16000 pixels row:14
523   // Platform: timer mark 29.227 0.196 overviewrender 16000 pixels row:14
524   // Platform: timer mark 30.1 0.171 overviewrender 16000 pixels row:14
525   // Platform: timer mark 31.684 0.196 overviewrender 16000 pixels row:14
526   // Platform: timer mark 32.779 0.18 overviewrender 16000 pixels row:14
527   // Platform: timer mark 52.355 0.185 overviewrender 16000 pixels row:14
528   // Platform: timer mark 53.829 0.186 overviewrender 16000 pixels row:14
529
530
531
532   /**
533    * @author Bob Hanson 2019.08.01
534    */
535   @Override
536   public boolean hasFeatures(String type)
537   {
538     return featureStore.containsKey(type);
539   }
540
541 }