JAL-2738 copy to spikes/mungo
[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<>();
116
117     for (FeatureStore featureSet : varargToTypes(type))
118     {
119       result.addAll(featureSet.findOverlappingFeatures(from, to));
120     }
121
122     return result;
123   }
124
125   /**
126    * {@inheritDoc}
127    */
128   @Override
129   public List<SequenceFeature> getAllFeatures(String... type)
130   {
131     List<SequenceFeature> result = new ArrayList<>();
132
133     result.addAll(getPositionalFeatures(type));
134
135     result.addAll(getNonPositionalFeatures());
136
137     return result;
138   }
139
140   /**
141    * {@inheritDoc}
142    */
143   @Override
144   public List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm)
145   {
146     if (ontologyTerm == null || ontologyTerm.length == 0)
147     {
148       return new ArrayList<>();
149     }
150
151     Set<String> featureTypes = getFeatureTypes(ontologyTerm);
152     if (featureTypes.isEmpty())
153     {
154       /*
155        * no features of the specified type or any sub-type
156        */
157       return new ArrayList<>();
158     }
159
160     return getAllFeatures(featureTypes.toArray(new String[featureTypes
161             .size()]));
162   }
163
164   /**
165    * {@inheritDoc}
166    */
167   @Override
168   public int getFeatureCount(boolean positional, String... type)
169   {
170     int result = 0;
171
172     for (FeatureStore featureSet : varargToTypes(type))
173     {
174       result += featureSet.getFeatureCount(positional);
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 (FeatureStore featureSet : varargToTypes(type))
188     {
189       result += featureSet.getTotalFeatureLength();
190     }
191     return result;
192   }
193
194   /**
195    * {@inheritDoc}
196    */
197   @Override
198   public List<SequenceFeature> getPositionalFeatures(String... type)
199   {
200     List<SequenceFeature> result = new ArrayList<>();
201
202     for (FeatureStore featureSet : varargToTypes(type))
203     {
204       result.addAll(featureSet.getPositionalFeatures());
205     }
206     return result;
207   }
208
209   /**
210    * A convenience method that converts a vararg for feature types to an
211    * Iterable over matched feature sets in key order
212    * 
213    * @param type
214    * @return
215    */
216   protected Iterable<FeatureStore> varargToTypes(String... type)
217   {
218     if (type == null || type.length == 0)
219     {
220       /*
221        * no vararg parameter supplied - return all
222        */
223       return featureStore.values();
224     }
225
226     List<FeatureStore> types = new ArrayList<>();
227     List<String> args = Arrays.asList(type);
228     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
229     {
230       if (args.contains(featureType.getKey()))
231       {
232         types.add(featureType.getValue());
233       }
234     }
235     return types;
236   }
237
238   /**
239    * {@inheritDoc}
240    */
241   @Override
242   public List<SequenceFeature> getContactFeatures(String... type)
243   {
244     List<SequenceFeature> result = new ArrayList<>();
245
246     for (FeatureStore featureSet : varargToTypes(type))
247     {
248       result.addAll(featureSet.getContactFeatures());
249     }
250     return result;
251   }
252
253   /**
254    * {@inheritDoc}
255    */
256   @Override
257   public List<SequenceFeature> getNonPositionalFeatures(String... type)
258   {
259     List<SequenceFeature> result = new ArrayList<>();
260
261     for (FeatureStore featureSet : varargToTypes(type))
262     {
263       result.addAll(featureSet.getNonPositionalFeatures());
264     }
265     return result;
266   }
267
268   /**
269    * {@inheritDoc}
270    */
271   @Override
272   public boolean delete(SequenceFeature sf)
273   {
274     for (FeatureStore featureSet : featureStore.values())
275     {
276       if (featureSet.delete(sf))
277       {
278         return true;
279       }
280     }
281     return false;
282   }
283
284   /**
285    * {@inheritDoc}
286    */
287   @Override
288   public boolean hasFeatures()
289   {
290     for (FeatureStore featureSet : featureStore.values())
291     {
292       if (!featureSet.isEmpty())
293       {
294         return true;
295       }
296     }
297     return false;
298   }
299
300   /**
301    * {@inheritDoc}
302    */
303   @Override
304   public Set<String> getFeatureGroups(boolean positionalFeatures,
305           String... type)
306   {
307     Set<String> groups = new HashSet<>();
308
309     for (FeatureStore featureSet : varargToTypes(type))
310     {
311       groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
312     }
313
314     return groups;
315   }
316
317   /**
318    * {@inheritDoc}
319    */
320   @Override
321   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
322           String... groups)
323   {
324     Set<String> result = new HashSet<>();
325
326     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
327     {
328       Set<String> featureGroups = featureType.getValue().getFeatureGroups(
329               positionalFeatures);
330       for (String group : groups)
331       {
332         if (featureGroups.contains(group))
333         {
334           /*
335            * yes this feature type includes one of the query groups
336            */
337           result.add(featureType.getKey());
338           break;
339         }
340       }
341     }
342
343     return result;
344   }
345
346   /**
347    * {@inheritDoc}
348    */
349   @Override
350   public Set<String> getFeatureTypes(String... soTerm)
351   {
352     Set<String> types = new HashSet<>();
353     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
354     {
355       String type = entry.getKey();
356       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
357       {
358         types.add(type);
359       }
360     }
361     return types;
362   }
363
364   /**
365    * Answers true if the given type is one of the specified sequence ontology
366    * terms (or a sub-type of one), or if no terms are supplied. Answers false if
367    * filter terms are specified and the given term does not match any of them.
368    * 
369    * @param type
370    * @param soTerm
371    * @return
372    */
373   protected boolean isOntologyTerm(String type, String... soTerm)
374   {
375     if (soTerm == null || soTerm.length == 0)
376     {
377       return true;
378     }
379     SequenceOntologyI so = SequenceOntologyFactory.getInstance();
380     for (String term : soTerm)
381     {
382       if (so.isA(type, term))
383       {
384         return true;
385       }
386     }
387     return false;
388   }
389
390   /**
391    * {@inheritDoc}
392    */
393   @Override
394   public float getMinimumScore(String type, boolean positional)
395   {
396     return featureStore.containsKey(type) ? featureStore.get(type)
397             .getMinimumScore(positional) : Float.NaN;
398   }
399
400   /**
401    * {@inheritDoc}
402    */
403   @Override
404   public float getMaximumScore(String type, boolean positional)
405   {
406     return featureStore.containsKey(type) ? featureStore.get(type)
407             .getMaximumScore(positional) : Float.NaN;
408   }
409
410   /**
411    * A convenience method to sort features by start position ascending (if on
412    * forward strand), or end position descending (if on reverse strand)
413    * 
414    * @param features
415    * @param forwardStrand
416    */
417   public static void sortFeatures(List<SequenceFeature> features,
418           final boolean forwardStrand)
419   {
420     Collections.sort(features, forwardStrand ? FORWARD_STRAND
421             : REVERSE_STRAND);
422   }
423
424   /**
425    * {@inheritDoc} This method is 'semi-optimised': it only inspects features
426    * for types that include the specified group, but has to inspect every
427    * feature of those types for matching feature group. This is efficient unless
428    * a sequence has features that share the same type but are in different
429    * groups - an unlikely case.
430    * <p>
431    * For example, if RESNUM feature is created with group = PDBID, then features
432    * would only be retrieved for those sequences associated with the target
433    * PDBID (group).
434    */
435   @Override
436   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
437           String group, String... type)
438   {
439     List<SequenceFeature> result = new ArrayList<>();
440     for (FeatureStore featureSet : varargToTypes(type))
441     {
442       if (featureSet.getFeatureGroups(positional).contains(group))
443       {
444         result.addAll(featureSet.getFeaturesForGroup(positional, group));
445       }
446     }
447     return result;
448   }
449
450   /**
451    * {@inheritDoc}
452    */
453   @Override
454   public boolean shiftFeatures(int shift)
455   {
456     boolean modified = false;
457     for (FeatureStore fs : featureStore.values())
458     {
459       modified |= fs.shiftFeatures(shift);
460     }
461     return modified;
462   }
463 }