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