JAL-3560 tweak to force use of standard Java HashSet
[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 import jalview.util.Platform;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
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    * map from feature type to structured store of features for that type
52    * null types are permitted (but not a good idea!)
53    */
54   private Map<String, FeatureStore> featureStore;
55
56   /**
57    * Constructor
58    */
59   public SequenceFeatures()
60   {
61     /*
62      * use a TreeMap so that features are returned in alphabetical order of type
63      * ? wrap as a synchronized map for add and delete operations
64      */
65     // featureStore = Collections
66     // .synchronizedSortedMap(new TreeMap<String, FeatureStore>());
67     featureStore = new TreeMap<>();
68   }
69
70   /**
71    * Constructor given a list of features
72    */
73   public SequenceFeatures(List<SequenceFeature> features)
74   {
75     this();
76     if (features != null)
77     {
78       for (SequenceFeature feature : features)
79       {
80         add(feature);
81       }
82     }
83   }
84
85   /**
86    * {@inheritDoc}
87    */
88   @Override
89   public boolean add(SequenceFeature sf)
90   {
91     String type = sf.getType();
92     if (type == null)
93     {
94       System.err.println("Feature type may not be null: " + sf.toString());
95       return false;
96     }
97
98     if (featureStore.get(type) == null)
99     {
100       featureStore.put(type, new FeatureStore());
101     }
102     return featureStore.get(type).addFeature(sf);
103   }
104
105   /**
106    * {@inheritDoc}
107    */
108   @Override
109   public List<SequenceFeature> findFeatures(int from, int to,
110           String... type)
111   {
112     List<SequenceFeature> result = new ArrayList<>();
113     for (FeatureStore featureSet : varargToTypes(type))
114     {
115       featureSet.findFeatures(from, to, result);
116     }
117     return result;
118   }
119
120   /**
121    * {@inheritDoc}
122    */
123   @Override
124   public List<SequenceFeature> getAllFeatures(String... type)
125   {
126     List<SequenceFeature> result = new ArrayList<>();
127
128     result.addAll(getPositionalFeatures(type));
129
130     result.addAll(getNonPositionalFeatures());
131
132     return result;
133   }
134
135   /**
136    * {@inheritDoc}
137    */
138   @Override
139   public List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm)
140   {
141     if (ontologyTerm == null || ontologyTerm.length == 0)
142     {
143       return new ArrayList<>();
144     }
145
146     Set<String> featureTypes = getFeatureTypes(ontologyTerm);
147     if (featureTypes.isEmpty())
148     {
149       /*
150        * no features of the specified type or any sub-type
151        */
152       return new ArrayList<>();
153     }
154
155     return getAllFeatures(
156             featureTypes.toArray(new String[featureTypes.size()]));
157   }
158
159   /**
160    * {@inheritDoc}
161    */
162   @Override
163   public int getFeatureCount(boolean positional, String... type)
164   {
165     int result = 0;
166
167     for (FeatureStore featureSet : varargToTypes(type))
168     {
169       result += featureSet.getFeatureCount(positional);
170     }
171     return result;
172   }
173
174   /**
175    * {@inheritDoc}
176    */
177   @Override
178   public int getTotalFeatureLength(String... type)
179   {
180     int result = 0;
181
182     for (FeatureStore featureSet : varargToTypes(type))
183     {
184       result += featureSet.getTotalFeatureLength();
185     }
186     return result;
187   }
188
189   /**
190    * {@inheritDoc}
191    */
192   @Override
193   public List<SequenceFeature> getPositionalFeatures(String... type)
194   {
195     List<SequenceFeature> result = new ArrayList<>();
196
197     for (FeatureStore featureSet : varargToTypes(type))
198     {
199       featureSet.getPositionalFeatures(result);
200     }
201     return result;
202   }
203
204   /**
205    * A convenience method that converts a vararg for feature types to an
206    * Iterable over matched feature sets in key order
207    * 
208    * @param type
209    * @return
210    */
211   protected Iterable<FeatureStore> varargToTypes(String... type)
212   {
213     if (type == null || type.length == 0)
214     {
215       /*
216        * no vararg parameter supplied - return all
217        */
218       return featureStore.values();
219     }
220
221     List<FeatureStore> types = new ArrayList<>();
222     List<String> args = Arrays.asList(type);
223     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
224     {
225       if (args.contains(featureType.getKey()))
226       {
227         types.add(featureType.getValue());
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       featureSet.getContactFeatures(result);
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       featureSet.getNonPositionalFeatures(result);
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     // BH 2020.03.21 This set is the one that sets the initial ordering for
320     // feature rendering. We set it to new HashSet<>(16,0.75) to force it to
321     // be backed by a Java hash-ordered HashMap instead of a JavaScript Map.
322     Set<String> result = Platform.getJavaOrderedHashSet();
323
324     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
325     {
326       Set<String> featureGroups = featureType.getValue()
327               .getFeatureGroups(positionalFeatures);
328       for (String group : groups)
329       {
330         if (featureGroups.contains(group))
331         {
332           /*
333            * yes this feature type includes one of the query groups
334            */
335           result.add(featureType.getKey());
336           break;
337         }
338       }
339     }
340
341     return result;
342   }
343
344   /**
345    * {@inheritDoc}
346    */
347   @Override
348   public Set<String> getFeatureTypes(String... soTerm)
349   {
350     Set<String> types = new HashSet<>(15, 0.75f);
351     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
352     {
353       String type = entry.getKey();
354       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
355       {
356         types.add(type);
357       }
358     }
359     return types;
360   }
361
362   /**
363    * Answers true if the given type matches one of the specified terms (or is a
364    * sub-type of one in the Sequence Ontology), or if no terms are supplied.
365    * Answers false if filter terms are specified and the given term does not
366    * match any of them.
367    * 
368    * @param type
369    * @param soTerm
370    * @return
371    */
372   protected boolean isOntologyTerm(String type, String... soTerm)
373   {
374     if (soTerm == null || soTerm.length == 0)
375     {
376       return true;
377     }
378     SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
379     for (String term : soTerm)
380     {
381       if (type.equals(term) || so.isA(type, term))
382       {
383         return true;
384       }
385     }
386     return false;
387   }
388
389   /**
390    * {@inheritDoc}
391    */
392   @Override
393   public float getMinimumScore(String type, boolean positional)
394   {
395     return featureStore.containsKey(type)
396             ? featureStore.get(type).getMinimumScore(positional)
397             : Float.NaN;
398   }
399
400   /**
401    * {@inheritDoc}
402    */
403   @Override
404   public float getMaximumScore(String type, boolean positional)
405   {
406     return featureStore.containsKey(type)
407             ? featureStore.get(type).getMaximumScore(positional)
408             : Float.NaN;
409   }
410
411   /**
412    * A convenience method to sort features by start position ascending (if on
413    * forward strand), or end position descending (if on reverse strand)
414    * 
415    * @param features
416    * @param forwardStrand
417    */
418   public static void sortFeatures(List<? extends IntervalI> features,
419           final boolean forwardStrand)
420   {
421     Collections.sort(features,
422             forwardStrand ? IntervalI.COMPARE_BEGIN_ASC
423                     : IntervalI.COMPARE_END_DESC);
424   }
425
426   /**
427    * {@inheritDoc} This method is 'semi-optimised': it only inspects features
428    * for types that include the specified group, but has to inspect every
429    * feature of those types for matching feature group. This is efficient unless
430    * a sequence has features that share the same type but are in different
431    * groups - an unlikely case.
432    * <p>
433    * For example, if RESNUM feature is created with group = PDBID, then features
434    * would only be retrieved for those sequences associated with the target
435    * PDBID (group).
436    */
437   @Override
438   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
439           String group, String... type)
440   {
441     List<SequenceFeature> result = new ArrayList<>();
442     for (FeatureStore featureSet : varargToTypes(type))
443     {
444       if (featureSet.getFeatureGroups(positional).contains(group))
445       {
446         result.addAll(featureSet.getFeaturesForGroup(positional, group));
447       }
448     }
449     return result;
450   }
451
452   /**
453    * {@inheritDoc}
454    */
455   @Override
456   public boolean shiftFeatures(int fromPosition, int shiftBy)
457   {
458     boolean modified = false;
459     for (FeatureStore fs : featureStore.values())
460     {
461       modified |= fs.shiftFeatures(fromPosition, shiftBy);
462     }
463     return modified;
464   }
465
466   /**
467    * {@inheritDoc}
468    */
469   @Override
470   public void deleteAll()
471   {
472     featureStore.clear();
473   }
474
475   @Override
476   public List<SequenceFeature> findFeatures(int pos, String type,
477           List<SequenceFeature> list)
478   {
479     FeatureStore fs = featureStore.get(type);
480     if (fs == null)
481     {
482       return list == null ? new ArrayList<>() : list;
483     }
484     return fs.findFeatures(pos, pos, list);
485   }
486
487   @Override
488   public boolean hasFeatures(String type)
489   {
490     return featureStore.containsKey(type)
491             && !featureStore.get(type).isEmpty();
492   }
493
494 }