JAL-3140 NCList now via IntervalStore library
[jalview.git] / src / jalview / datamodel / features / SequenceFeatures.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel.features;
22
23 import jalview.datamodel.SequenceFeature;
24 import jalview.io.gff.SequenceOntologyFactory;
25 import jalview.io.gff.SequenceOntologyI;
26
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 import java.util.Set;
36 import java.util.TreeMap;
37
38 import intervalstore.api.IntervalI;
39
40 /**
41  * A class that stores sequence features in a way that supports efficient
42  * querying by type and location (overlap). Intended for (but not limited to)
43  * storage of features for one sequence.
44  * 
45  * @author gmcarstairs
46  *
47  */
48 public class SequenceFeatures implements SequenceFeaturesI
49 {
50   /**
51    * a comparator for sorting features by start position ascending
52    */
53   private static Comparator<IntervalI> FORWARD_STRAND = new Comparator<IntervalI>()
54   {
55     @Override
56     public int compare(IntervalI o1, IntervalI o2)
57     {
58       return Integer.compare(o1.getBegin(), o2.getBegin());
59     }
60   };
61
62   /**
63    * a comparator for sorting features by end position descending
64    */
65   private static Comparator<IntervalI> REVERSE_STRAND = new Comparator<IntervalI>()
66   {
67     @Override
68     public int compare(IntervalI o1, IntervalI o2)
69     {
70       return Integer.compare(o2.getEnd(), o1.getEnd());
71     }
72   };
73
74   /*
75    * map from feature type to structured store of features for that type
76    * null types are permitted (but not a good idea!)
77    */
78   private Map<String, FeatureStore> featureStore;
79
80   /**
81    * Constructor
82    */
83   public SequenceFeatures()
84   {
85     /*
86      * use a TreeMap so that features are returned in alphabetical order of type
87      * ? wrap as a synchronized map for add and delete operations
88      */
89     // featureStore = Collections
90     // .synchronizedSortedMap(new TreeMap<String, FeatureStore>());
91     featureStore = new TreeMap<>();
92   }
93
94   /**
95    * Constructor given a list of features
96    */
97   public SequenceFeatures(List<SequenceFeature> features)
98   {
99     this();
100     if (features != null)
101     {
102       for (SequenceFeature feature : features)
103       {
104         add(feature);
105       }
106     }
107   }
108
109   /**
110    * {@inheritDoc}
111    */
112   @Override
113   public boolean add(SequenceFeature sf)
114   {
115     String type = sf.getType();
116     if (type == null)
117     {
118       System.err.println("Feature type may not be null: " + sf.toString());
119       return false;
120     }
121
122     if (featureStore.get(type) == null)
123     {
124       featureStore.put(type, new FeatureStore());
125     }
126     return featureStore.get(type).addFeature(sf);
127   }
128
129   /**
130    * {@inheritDoc}
131    */
132   @Override
133   public List<SequenceFeature> findFeatures(int from, int to,
134           String... type)
135   {
136     List<SequenceFeature> result = new ArrayList<>();
137
138     for (FeatureStore featureSet : varargToTypes(type))
139     {
140       result.addAll(featureSet.findOverlappingFeatures(from, to));
141     }
142
143     return result;
144   }
145
146   /**
147    * {@inheritDoc}
148    */
149   @Override
150   public List<SequenceFeature> getAllFeatures(String... type)
151   {
152     List<SequenceFeature> result = new ArrayList<>();
153
154     result.addAll(getPositionalFeatures(type));
155
156     result.addAll(getNonPositionalFeatures());
157
158     return result;
159   }
160
161   /**
162    * {@inheritDoc}
163    */
164   @Override
165   public List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm)
166   {
167     if (ontologyTerm == null || ontologyTerm.length == 0)
168     {
169       return new ArrayList<>();
170     }
171
172     Set<String> featureTypes = getFeatureTypes(ontologyTerm);
173     if (featureTypes.isEmpty())
174     {
175       /*
176        * no features of the specified type or any sub-type
177        */
178       return new ArrayList<>();
179     }
180
181     return getAllFeatures(featureTypes.toArray(new String[featureTypes
182             .size()]));
183   }
184
185   /**
186    * {@inheritDoc}
187    */
188   @Override
189   public int getFeatureCount(boolean positional, String... type)
190   {
191     int result = 0;
192
193     for (FeatureStore featureSet : varargToTypes(type))
194     {
195       result += featureSet.getFeatureCount(positional);
196     }
197     return result;
198   }
199
200   /**
201    * {@inheritDoc}
202    */
203   @Override
204   public int getTotalFeatureLength(String... type)
205   {
206     int result = 0;
207
208     for (FeatureStore featureSet : varargToTypes(type))
209     {
210       result += featureSet.getTotalFeatureLength();
211     }
212     return result;
213   }
214
215   /**
216    * {@inheritDoc}
217    */
218   @Override
219   public List<SequenceFeature> getPositionalFeatures(String... type)
220   {
221     List<SequenceFeature> result = new ArrayList<>();
222
223     for (FeatureStore featureSet : varargToTypes(type))
224     {
225       result.addAll(featureSet.getPositionalFeatures());
226     }
227     return result;
228   }
229
230   /**
231    * A convenience method that converts a vararg for feature types to an
232    * Iterable over matched feature sets in key order
233    * 
234    * @param type
235    * @return
236    */
237   protected Iterable<FeatureStore> varargToTypes(String... type)
238   {
239     if (type == null || type.length == 0)
240     {
241       /*
242        * no vararg parameter supplied - return all
243        */
244       return featureStore.values();
245     }
246
247     List<FeatureStore> types = new ArrayList<>();
248     List<String> args = Arrays.asList(type);
249     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
250     {
251       if (args.contains(featureType.getKey()))
252       {
253         types.add(featureType.getValue());
254       }
255     }
256     return types;
257   }
258
259   /**
260    * {@inheritDoc}
261    */
262   @Override
263   public List<SequenceFeature> getContactFeatures(String... type)
264   {
265     List<SequenceFeature> result = new ArrayList<>();
266
267     for (FeatureStore featureSet : varargToTypes(type))
268     {
269       result.addAll(featureSet.getContactFeatures());
270     }
271     return result;
272   }
273
274   /**
275    * {@inheritDoc}
276    */
277   @Override
278   public List<SequenceFeature> getNonPositionalFeatures(String... type)
279   {
280     List<SequenceFeature> result = new ArrayList<>();
281
282     for (FeatureStore featureSet : varargToTypes(type))
283     {
284       result.addAll(featureSet.getNonPositionalFeatures());
285     }
286     return result;
287   }
288
289   /**
290    * {@inheritDoc}
291    */
292   @Override
293   public boolean delete(SequenceFeature sf)
294   {
295     for (FeatureStore featureSet : featureStore.values())
296     {
297       if (featureSet.delete(sf))
298       {
299         return true;
300       }
301     }
302     return false;
303   }
304
305   /**
306    * {@inheritDoc}
307    */
308   @Override
309   public boolean hasFeatures()
310   {
311     for (FeatureStore featureSet : featureStore.values())
312     {
313       if (!featureSet.isEmpty())
314       {
315         return true;
316       }
317     }
318     return false;
319   }
320
321   /**
322    * {@inheritDoc}
323    */
324   @Override
325   public Set<String> getFeatureGroups(boolean positionalFeatures,
326           String... type)
327   {
328     Set<String> groups = new HashSet<>();
329
330     for (FeatureStore featureSet : varargToTypes(type))
331     {
332       groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
333     }
334
335     return groups;
336   }
337
338   /**
339    * {@inheritDoc}
340    */
341   @Override
342   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
343           String... groups)
344   {
345     Set<String> result = new HashSet<>();
346
347     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
348     {
349       Set<String> featureGroups = featureType.getValue().getFeatureGroups(
350               positionalFeatures);
351       for (String group : groups)
352       {
353         if (featureGroups.contains(group))
354         {
355           /*
356            * yes this feature type includes one of the query groups
357            */
358           result.add(featureType.getKey());
359           break;
360         }
361       }
362     }
363
364     return result;
365   }
366
367   /**
368    * {@inheritDoc}
369    */
370   @Override
371   public Set<String> getFeatureTypes(String... soTerm)
372   {
373     Set<String> types = new HashSet<>();
374     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
375     {
376       String type = entry.getKey();
377       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
378       {
379         types.add(type);
380       }
381     }
382     return types;
383   }
384
385   /**
386    * Answers true if the given type matches one of the specified terms (or is a
387    * sub-type of one in the Sequence Ontology), or if no terms are supplied.
388    * Answers false if filter terms are specified and the given term does not
389    * 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 (type.equals(term) || 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<>();
462     for (FeatureStore featureSet : varargToTypes(type))
463     {
464       if (featureSet.getFeatureGroups(positional).contains(group))
465       {
466         result.addAll(featureSet.getFeaturesForGroup(positional, group));
467       }
468     }
469     return result;
470   }
471
472   /**
473    * {@inheritDoc}
474    */
475   @Override
476   public boolean shiftFeatures(int shift)
477   {
478     boolean modified = false;
479     for (FeatureStore fs : featureStore.values())
480     {
481       modified |= fs.shiftFeatures(shift);
482     }
483     return modified;
484   }
485 }