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