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