JAL-2480 always include all non-positional features in getAllFeatures()
[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      */
224     List<String> types = new ArrayList<String>(Arrays.asList(type));
225     types.remove(null);
226     return types;
227   }
228
229   /**
230    * {@inheritDoc}
231    */
232   @Override
233   public List<SequenceFeature> getContactFeatures(String... type)
234   {
235     List<SequenceFeature> result = new ArrayList<SequenceFeature>();
236
237     for (String featureType : varargToTypes(type))
238     {
239       FeatureStore featureSet = featureStore.get(featureType);
240       if (featureSet != null)
241       {
242         result.addAll(featureSet.getContactFeatures());
243       }
244     }
245     return result;
246   }
247
248   /**
249    * {@inheritDoc}
250    */
251   @Override
252   public List<SequenceFeature> getNonPositionalFeatures(String... type)
253   {
254     List<SequenceFeature> result = new ArrayList<SequenceFeature>();
255
256     for (String featureType : varargToTypes(type))
257     {
258       FeatureStore featureSet = featureStore.get(featureType);
259       if (featureSet != null)
260       {
261         result.addAll(featureSet.getNonPositionalFeatures());
262       }
263     }
264     return result;
265   }
266
267   /**
268    * {@inheritDoc}
269    */
270   @Override
271   public boolean delete(SequenceFeature sf)
272   {
273     for (FeatureStore featureSet : featureStore.values())
274     {
275       if (featureSet.delete(sf))
276       {
277         return true;
278       }
279     }
280     return false;
281   }
282
283   /**
284    * {@inheritDoc}
285    */
286   @Override
287   public boolean hasFeatures()
288   {
289     for (FeatureStore featureSet : featureStore.values())
290     {
291       if (!featureSet.isEmpty())
292       {
293         return true;
294       }
295     }
296     return false;
297   }
298
299   /**
300    * {@inheritDoc}
301    */
302   @Override
303   public Set<String> getFeatureGroups(boolean positionalFeatures,
304           String... type)
305   {
306     Set<String> groups = new HashSet<String>();
307
308     Iterable<String> types = varargToTypes(type);
309
310     for (String featureType : types)
311     {
312       FeatureStore featureSet = featureStore.get(featureType);
313       if (featureSet != null)
314       {
315         groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
316       }
317     }
318
319     return groups;
320   }
321
322   /**
323    * {@inheritDoc}
324    */
325   @Override
326   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
327           String... groups)
328   {
329     Set<String> result = new HashSet<String>();
330
331     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
332     {
333       Set<String> featureGroups = featureType.getValue().getFeatureGroups(
334               positionalFeatures);
335       for (String group : groups)
336       {
337         if (featureGroups.contains(group))
338         {
339           /*
340            * yes this feature type includes one of the query groups
341            */
342           result.add(featureType.getKey());
343           break;
344         }
345       }
346     }
347
348     return result;
349   }
350
351   /**
352    * {@inheritDoc}
353    */
354   @Override
355   public Set<String> getFeatureTypes(String... soTerm)
356   {
357     Set<String> types = new HashSet<String>();
358     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
359     {
360       String type = entry.getKey();
361       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
362       {
363         types.add(type);
364       }
365     }
366     return types;
367   }
368
369   /**
370    * Answers true if the given type is one of the specified sequence ontology
371    * terms (or a sub-type of one), or if no terms are supplied. Answers false if
372    * filter terms are specified and the given term does not match any of them.
373    * 
374    * @param type
375    * @param soTerm
376    * @return
377    */
378   protected boolean isOntologyTerm(String type, String... soTerm)
379   {
380     if (soTerm == null || soTerm.length == 0)
381     {
382       return true;
383     }
384     SequenceOntologyI so = SequenceOntologyFactory.getInstance();
385     for (String term : soTerm)
386     {
387       if (so.isA(type, term))
388       {
389         return true;
390       }
391     }
392     return false;
393   }
394
395   /**
396    * {@inheritDoc}
397    */
398   @Override
399   public float getMinimumScore(String type, boolean positional)
400   {
401     return featureStore.containsKey(type) ? featureStore.get(type)
402             .getMinimumScore(positional) : Float.NaN;
403   }
404
405   /**
406    * {@inheritDoc}
407    */
408   @Override
409   public float getMaximumScore(String type, boolean positional)
410   {
411     return featureStore.containsKey(type) ? featureStore.get(type)
412             .getMaximumScore(positional) : Float.NaN;
413   }
414
415   /**
416    * A convenience method to sort features by start position ascending (if on
417    * forward strand), or end position descending (if on reverse strand)
418    * 
419    * @param features
420    * @param forwardStrand
421    */
422   public static void sortFeatures(List<SequenceFeature> features,
423           final boolean forwardStrand)
424   {
425     Collections.sort(features, forwardStrand ? FORWARD_STRAND
426             : REVERSE_STRAND);
427   }
428 }