JAL-2557 removal of Sequence.sequenceFeatures
[jalview.git] / src / jalview / datamodel / features / SequenceFeatures.java
1 package jalview.datamodel.features;
2
3 import jalview.datamodel.SequenceFeature;
4 import jalview.io.gff.SequenceOntologyFactory;
5 import jalview.io.gff.SequenceOntologyI;
6
7 import java.util.ArrayList;
8 import java.util.Arrays;
9 import java.util.Collections;
10 import java.util.Comparator;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Map.Entry;
15 import java.util.Set;
16 import java.util.TreeMap;
17
18 /**
19  * A class that stores sequence features in a way that supports efficient
20  * querying by type and location (overlap). Intended for (but not limited to)
21  * storage of features for one sequence.
22  * 
23  * @author gmcarstairs
24  *
25  */
26 public class SequenceFeatures implements SequenceFeaturesI
27 {
28   /**
29    * a comparator for sorting features by start position ascending
30    */
31   private static Comparator<ContiguousI> FORWARD_STRAND = new Comparator<ContiguousI>()
32   {
33     @Override
34     public int compare(ContiguousI o1, ContiguousI o2)
35     {
36       return Integer.compare(o1.getBegin(), o2.getBegin());
37     }
38   };
39
40   /**
41    * a comparator for sorting features by end position descending
42    */
43   private static Comparator<ContiguousI> REVERSE_STRAND = new Comparator<ContiguousI>()
44   {
45     @Override
46     public int compare(ContiguousI o1, ContiguousI o2)
47     {
48       return Integer.compare(o2.getEnd(), o1.getEnd());
49     }
50   };
51
52   /*
53    * map from feature type to structured store of features for that type
54    * null types are permitted (but not a good idea!)
55    */
56   private Map<String, FeatureStore> featureStore;
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, FeatureStore>());
69     featureStore = new TreeMap<String, FeatureStore>();
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, new FeatureStore());
103     }
104     return featureStore.get(type).addFeature(sf);
105   }
106
107   /**
108    * {@inheritDoc}
109    */
110   @Override
111   public List<SequenceFeature> findFeatures(int from, int to,
112           String... type)
113   {
114     List<SequenceFeature> result = new ArrayList<SequenceFeature>();
115
116     for (String featureType : varargToTypes(type))
117     {
118       FeatureStore features = featureStore.get(featureType);
119       if (features != null)
120       {
121         result.addAll(features.findOverlappingFeatures(from, to));
122       }
123     }
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<SequenceFeature>();
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<SequenceFeature>();
152     }
153
154     Set<String> featureTypes = getFeatureTypes(ontologyTerm);
155     return getAllFeatures(featureTypes.toArray(new String[featureTypes
156             .size()]));
157   }
158
159   /**
160    * {@inheritDoc}
161    */
162   @Override
163   public int getFeatureCount(boolean positional, String... type)
164   {
165     int result = 0;
166
167     for (String featureType : varargToTypes(type))
168     {
169       FeatureStore featureSet = featureStore.get(featureType);
170       if (featureSet != null)
171       {
172         result += featureSet.getFeatureCount(positional);
173       }
174     }
175     return result;
176   }
177
178   /**
179    * {@inheritDoc}
180    */
181   @Override
182   public int getTotalFeatureLength(String... type)
183   {
184     int result = 0;
185
186     for (String featureType : varargToTypes(type))
187     {
188       FeatureStore featureSet = featureStore.get(featureType);
189       if (featureSet != null)
190       {
191         result += featureSet.getTotalFeatureLength();
192       }
193     }
194     return result;
195
196   }
197
198   /**
199    * {@inheritDoc}
200    */
201   @Override
202   public List<SequenceFeature> getPositionalFeatures(String... type)
203   {
204     List<SequenceFeature> result = new ArrayList<SequenceFeature>();
205
206     for (String featureType : varargToTypes(type))
207     {
208       FeatureStore featureSet = featureStore.get(featureType);
209       if (featureSet != null)
210       {
211         result.addAll(featureSet.getPositionalFeatures());
212       }
213     }
214     return result;
215   }
216
217   /**
218    * A convenience method that converts a vararg for feature types to an
219    * Iterable, replacing the value with the stored feature types if it is null
220    * or empty
221    * 
222    * @param type
223    * @return
224    */
225   protected Iterable<String> varargToTypes(String... type)
226   {
227     if (type == null || type.length == 0)
228     {
229       /*
230        * no vararg parameter supplied
231        */
232       return featureStore.keySet();
233     }
234
235     /*
236      * else make a copy of the list, and remove any null value just in case,
237      * as it would cause errors looking up the features Map
238      * sort in alphabetical order for consistent output behaviour
239      */
240     List<String> types = new ArrayList<String>(Arrays.asList(type));
241     types.remove(null);
242     Collections.sort(types);
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<SequenceFeature>();
253
254     for (String featureType : varargToTypes(type))
255     {
256       FeatureStore featureSet = featureStore.get(featureType);
257       if (featureSet != null)
258       {
259         result.addAll(featureSet.getContactFeatures());
260       }
261     }
262     return result;
263   }
264
265   /**
266    * {@inheritDoc}
267    */
268   @Override
269   public List<SequenceFeature> getNonPositionalFeatures(String... type)
270   {
271     List<SequenceFeature> result = new ArrayList<SequenceFeature>();
272
273     for (String featureType : varargToTypes(type))
274     {
275       FeatureStore featureSet = featureStore.get(featureType);
276       if (featureSet != null)
277       {
278         result.addAll(featureSet.getNonPositionalFeatures());
279       }
280     }
281     return result;
282   }
283
284   /**
285    * {@inheritDoc}
286    */
287   @Override
288   public boolean delete(SequenceFeature sf)
289   {
290     for (FeatureStore featureSet : featureStore.values())
291     {
292       if (featureSet.delete(sf))
293       {
294         return true;
295       }
296     }
297     return false;
298   }
299
300   /**
301    * {@inheritDoc}
302    */
303   @Override
304   public boolean hasFeatures()
305   {
306     for (FeatureStore featureSet : featureStore.values())
307     {
308       if (!featureSet.isEmpty())
309       {
310         return true;
311       }
312     }
313     return false;
314   }
315
316   /**
317    * {@inheritDoc}
318    */
319   @Override
320   public Set<String> getFeatureGroups(boolean positionalFeatures,
321           String... type)
322   {
323     Set<String> groups = new HashSet<String>();
324
325     Iterable<String> types = varargToTypes(type);
326
327     for (String featureType : types)
328     {
329       FeatureStore featureSet = featureStore.get(featureType);
330       if (featureSet != null)
331       {
332         groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
333       }
334     }
335
336     return groups;
337   }
338
339   /**
340    * {@inheritDoc}
341    */
342   @Override
343   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
344           String... groups)
345   {
346     Set<String> result = new HashSet<String>();
347
348     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
349     {
350       Set<String> featureGroups = featureType.getValue().getFeatureGroups(
351               positionalFeatures);
352       for (String group : groups)
353       {
354         if (featureGroups.contains(group))
355         {
356           /*
357            * yes this feature type includes one of the query groups
358            */
359           result.add(featureType.getKey());
360           break;
361         }
362       }
363     }
364
365     return result;
366   }
367
368   /**
369    * {@inheritDoc}
370    */
371   @Override
372   public Set<String> getFeatureTypes(String... soTerm)
373   {
374     Set<String> types = new HashSet<String>();
375     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
376     {
377       String type = entry.getKey();
378       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
379       {
380         types.add(type);
381       }
382     }
383     return types;
384   }
385
386   /**
387    * Answers true if the given type is one of the specified sequence ontology
388    * terms (or a sub-type of one), or if no terms are supplied. Answers false if
389    * filter terms are specified and the given term does not match any of them.
390    * 
391    * @param type
392    * @param soTerm
393    * @return
394    */
395   protected boolean isOntologyTerm(String type, String... soTerm)
396   {
397     if (soTerm == null || soTerm.length == 0)
398     {
399       return true;
400     }
401     SequenceOntologyI so = SequenceOntologyFactory.getInstance();
402     for (String term : soTerm)
403     {
404       if (so.isA(type, term))
405       {
406         return true;
407       }
408     }
409     return false;
410   }
411
412   /**
413    * {@inheritDoc}
414    */
415   @Override
416   public float getMinimumScore(String type, boolean positional)
417   {
418     return featureStore.containsKey(type) ? featureStore.get(type)
419             .getMinimumScore(positional) : Float.NaN;
420   }
421
422   /**
423    * {@inheritDoc}
424    */
425   @Override
426   public float getMaximumScore(String type, boolean positional)
427   {
428     return featureStore.containsKey(type) ? featureStore.get(type)
429             .getMaximumScore(positional) : Float.NaN;
430   }
431
432   /**
433    * A convenience method to sort features by start position ascending (if on
434    * forward strand), or end position descending (if on reverse strand)
435    * 
436    * @param features
437    * @param forwardStrand
438    */
439   public static void sortFeatures(List<SequenceFeature> features,
440           final boolean forwardStrand)
441   {
442     Collections.sort(features, forwardStrand ? FORWARD_STRAND
443             : REVERSE_STRAND);
444   }
445
446   /**
447    * {@inheritDoc} This method is 'semi-optimised': it only inspects features
448    * for types that include the specified group, but has to inspect every
449    * feature of those types for matching feature group. This is efficient unless
450    * a sequence has features that share the same type but are in different
451    * groups - an unlikely case.
452    * <p>
453    * For example, if RESNUM feature is created with group = PDBID, then features
454    * would only be retrieved for those sequences associated with the target
455    * PDBID (group).
456    */
457   @Override
458   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
459           String group, String... type)
460   {
461     List<SequenceFeature> result = new ArrayList<SequenceFeature>();
462     Iterable<String> types = varargToTypes(type);
463
464     for (String featureType : types)
465     {
466       /*
467        * check whether the feature type is present, and also
468        * whether it has features for the specified group
469        */
470       FeatureStore features = featureStore.get(featureType);
471       if (features != null
472               && features.getFeatureGroups(positional).contains(group))
473       {
474         result.addAll(features.getFeaturesForGroup(positional, group));
475       }
476     }
477     return result;
478   }
479
480   /**
481    * {@inheritDoc}
482    */
483   @Override
484   public boolean shiftFeatures(int shift)
485   {
486     boolean modified = false;
487     for (FeatureStore fs : featureStore.values())
488     {
489       modified |= fs.shiftFeatures(shift);
490     }
491     return modified;
492   }
493 }