JAL-3076 refactor for more efficient scan of 'gene' features
[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.ContiguousI;
24 import jalview.datamodel.SequenceFeature;
25 import jalview.io.gff.SequenceOntologyFactory;
26 import jalview.io.gff.SequenceOntologyI;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Set;
37 import java.util.TreeMap;
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    * a comparator for sorting features by start position ascending
51    */
52   private static Comparator<ContiguousI> FORWARD_STRAND = new Comparator<ContiguousI>()
53   {
54     @Override
55     public int compare(ContiguousI o1, ContiguousI o2)
56     {
57       return Integer.compare(o1.getBegin(), o2.getBegin());
58     }
59   };
60
61   /**
62    * a comparator for sorting features by end position descending
63    */
64   private static Comparator<ContiguousI> REVERSE_STRAND = new Comparator<ContiguousI>()
65   {
66     @Override
67     public int compare(ContiguousI o1, ContiguousI o2)
68     {
69       return Integer.compare(o2.getEnd(), o1.getEnd());
70     }
71   };
72
73   /*
74    * map from feature type to structured store of features for that type
75    * null types are permitted (but not a good idea!)
76    */
77   private Map<String, FeatureStore> featureStore;
78
79   /**
80    * Constructor
81    */
82   public SequenceFeatures()
83   {
84     /*
85      * use a TreeMap so that features are returned in alphabetical order of type
86      * ? wrap as a synchronized map for add and delete operations
87      */
88     // featureStore = Collections
89     // .synchronizedSortedMap(new TreeMap<String, FeatureStore>());
90     featureStore = new TreeMap<>();
91   }
92
93   /**
94    * Constructor given a list of features
95    */
96   public SequenceFeatures(List<SequenceFeature> features)
97   {
98     this();
99     if (features != null)
100     {
101       for (SequenceFeature feature : features)
102       {
103         add(feature);
104       }
105     }
106   }
107
108   /**
109    * {@inheritDoc}
110    */
111   @Override
112   public boolean add(SequenceFeature sf)
113   {
114     String type = sf.getType();
115     if (type == null)
116     {
117       System.err.println("Feature type may not be null: " + sf.toString());
118       return false;
119     }
120
121     if (featureStore.get(type) == null)
122     {
123       featureStore.put(type, new FeatureStore());
124     }
125     return featureStore.get(type).addFeature(sf);
126   }
127
128   /**
129    * {@inheritDoc}
130    */
131   @Override
132   public List<SequenceFeature> findFeatures(int from, int to,
133           String... type)
134   {
135     List<SequenceFeature> result = new ArrayList<>();
136
137     for (FeatureStore featureSet : varargToTypes(type))
138     {
139       result.addAll(featureSet.findOverlappingFeatures(from, to));
140     }
141
142     return result;
143   }
144
145   /**
146    * {@inheritDoc}
147    */
148   @Override
149   public List<SequenceFeature> getAllFeatures(String... type)
150   {
151     List<SequenceFeature> result = new ArrayList<>();
152
153     result.addAll(getPositionalFeatures(type));
154
155     result.addAll(getNonPositionalFeatures());
156
157     return result;
158   }
159
160   /**
161    * {@inheritDoc}
162    */
163   @Override
164   public List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm)
165   {
166     if (ontologyTerm == null || ontologyTerm.length == 0)
167     {
168       return new ArrayList<>();
169     }
170
171     Set<String> featureTypes = getFeatureTypes(ontologyTerm);
172     if (featureTypes.isEmpty())
173     {
174       /*
175        * no features of the specified type or any sub-type
176        */
177       return new ArrayList<>();
178     }
179
180     return getAllFeatures(featureTypes.toArray(new String[featureTypes
181             .size()]));
182   }
183
184   /**
185    * {@inheritDoc}
186    */
187   @Override
188   public int getFeatureCount(boolean positional, String... type)
189   {
190     int result = 0;
191
192     for (FeatureStore featureSet : varargToTypes(type))
193     {
194       result += featureSet.getFeatureCount(positional);
195     }
196     return result;
197   }
198
199   /**
200    * {@inheritDoc}
201    */
202   @Override
203   public int getTotalFeatureLength(String... type)
204   {
205     int result = 0;
206
207     for (FeatureStore featureSet : varargToTypes(type))
208     {
209       result += featureSet.getTotalFeatureLength();
210     }
211     return result;
212   }
213
214   /**
215    * {@inheritDoc}
216    */
217   @Override
218   public List<SequenceFeature> getPositionalFeatures(String... type)
219   {
220     List<SequenceFeature> result = new ArrayList<>();
221
222     for (FeatureStore featureSet : varargToTypes(type))
223     {
224       result.addAll(featureSet.getPositionalFeatures());
225     }
226     return result;
227   }
228
229   /**
230    * A convenience method that converts a vararg for feature types to an
231    * Iterable over matched feature sets in key order
232    * 
233    * @param type
234    * @return
235    */
236   protected Iterable<FeatureStore> varargToTypes(String... type)
237   {
238     if (type == null || type.length == 0)
239     {
240       /*
241        * no vararg parameter supplied - return all
242        */
243       return featureStore.values();
244     }
245
246     List<FeatureStore> types = new ArrayList<>();
247     List<String> args = Arrays.asList(type);
248     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
249     {
250       if (args.contains(featureType.getKey()))
251       {
252         types.add(featureType.getValue());
253       }
254     }
255     return types;
256   }
257
258   /**
259    * {@inheritDoc}
260    */
261   @Override
262   public List<SequenceFeature> getContactFeatures(String... type)
263   {
264     List<SequenceFeature> result = new ArrayList<>();
265
266     for (FeatureStore featureSet : varargToTypes(type))
267     {
268       result.addAll(featureSet.getContactFeatures());
269     }
270     return result;
271   }
272
273   /**
274    * {@inheritDoc}
275    */
276   @Override
277   public List<SequenceFeature> getNonPositionalFeatures(String... type)
278   {
279     List<SequenceFeature> result = new ArrayList<>();
280
281     for (FeatureStore featureSet : varargToTypes(type))
282     {
283       result.addAll(featureSet.getNonPositionalFeatures());
284     }
285     return result;
286   }
287
288   /**
289    * {@inheritDoc}
290    */
291   @Override
292   public boolean delete(SequenceFeature sf)
293   {
294     for (FeatureStore featureSet : featureStore.values())
295     {
296       if (featureSet.delete(sf))
297       {
298         return true;
299       }
300     }
301     return false;
302   }
303
304   /**
305    * {@inheritDoc}
306    */
307   @Override
308   public boolean hasFeatures()
309   {
310     for (FeatureStore featureSet : featureStore.values())
311     {
312       if (!featureSet.isEmpty())
313       {
314         return true;
315       }
316     }
317     return false;
318   }
319
320   /**
321    * {@inheritDoc}
322    */
323   @Override
324   public Set<String> getFeatureGroups(boolean positionalFeatures,
325           String... type)
326   {
327     Set<String> groups = new HashSet<>();
328
329     for (FeatureStore featureSet : varargToTypes(type))
330     {
331       groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
332     }
333
334     return groups;
335   }
336
337   /**
338    * {@inheritDoc}
339    */
340   @Override
341   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
342           String... groups)
343   {
344     Set<String> result = new HashSet<>();
345
346     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
347     {
348       Set<String> featureGroups = featureType.getValue().getFeatureGroups(
349               positionalFeatures);
350       for (String group : groups)
351       {
352         if (featureGroups.contains(group))
353         {
354           /*
355            * yes this feature type includes one of the query groups
356            */
357           result.add(featureType.getKey());
358           break;
359         }
360       }
361     }
362
363     return result;
364   }
365
366   /**
367    * {@inheritDoc}
368    */
369   @Override
370   public Set<String> getFeatureTypes(String... soTerm)
371   {
372     Set<String> types = new HashSet<>();
373     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
374     {
375       String type = entry.getKey();
376       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
377       {
378         types.add(type);
379       }
380     }
381     return types;
382   }
383
384   /**
385    * Answers true if the given type matches one of the specified terms (or is a
386    * sub-type of one in the Sequence Ontology), or if no terms are supplied.
387    * Answers false if filter terms are specified and the given term does not
388    * match any of them.
389    * 
390    * @param type
391    * @param soTerm
392    * @return
393    */
394   protected boolean isOntologyTerm(String type, String... soTerm)
395   {
396     if (soTerm == null || soTerm.length == 0)
397     {
398       return true;
399     }
400     SequenceOntologyI so = SequenceOntologyFactory.getInstance();
401     for (String term : soTerm)
402     {
403       if (type.equals(term) || so.isA(type, term))
404       {
405         return true;
406       }
407     }
408     return false;
409   }
410
411   /**
412    * {@inheritDoc}
413    */
414   @Override
415   public float getMinimumScore(String type, boolean positional)
416   {
417     return featureStore.containsKey(type) ? featureStore.get(type)
418             .getMinimumScore(positional) : Float.NaN;
419   }
420
421   /**
422    * {@inheritDoc}
423    */
424   @Override
425   public float getMaximumScore(String type, boolean positional)
426   {
427     return featureStore.containsKey(type) ? featureStore.get(type)
428             .getMaximumScore(positional) : Float.NaN;
429   }
430
431   /**
432    * A convenience method to sort features by start position ascending (if on
433    * forward strand), or end position descending (if on reverse strand)
434    * 
435    * @param features
436    * @param forwardStrand
437    */
438   public static void sortFeatures(List<SequenceFeature> features,
439           final boolean forwardStrand)
440   {
441     Collections.sort(features, forwardStrand ? FORWARD_STRAND
442             : REVERSE_STRAND);
443   }
444
445   /**
446    * {@inheritDoc} This method is 'semi-optimised': it only inspects features
447    * for types that include the specified group, but has to inspect every
448    * feature of those types for matching feature group. This is efficient unless
449    * a sequence has features that share the same type but are in different
450    * groups - an unlikely case.
451    * <p>
452    * For example, if RESNUM feature is created with group = PDBID, then features
453    * would only be retrieved for those sequences associated with the target
454    * PDBID (group).
455    */
456   @Override
457   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
458           String group, String... type)
459   {
460     List<SequenceFeature> result = new ArrayList<>();
461     for (FeatureStore featureSet : varargToTypes(type))
462     {
463       if (featureSet.getFeatureGroups(positional).contains(group))
464       {
465         result.addAll(featureSet.getFeaturesForGroup(positional, group));
466       }
467     }
468     return result;
469   }
470
471   /**
472    * {@inheritDoc}
473    */
474   @Override
475   public boolean shiftFeatures(int shift)
476   {
477     boolean modified = false;
478     for (FeatureStore fs : featureStore.values())
479     {
480       modified |= fs.shiftFeatures(shift);
481     }
482     return modified;
483   }
484 }