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