JAL-4090 JAL-1551 spotlessApply
[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       jalview.bin.Console
92               .errPrintln("Feature type may not be null: " + sf.toString());
93       return false;
94     }
95
96     if (featureStore.get(type) == null)
97     {
98       featureStore.put(type, new FeatureStore());
99     }
100     return featureStore.get(type).addFeature(sf);
101   }
102
103   /**
104    * {@inheritDoc}
105    */
106   @Override
107   public List<SequenceFeature> findFeatures(int from, int to,
108           String... type)
109   {
110     List<SequenceFeature> result = new ArrayList<>();
111
112     for (FeatureStore featureSet : varargToTypes(type))
113     {
114       result.addAll(featureSet.findOverlappingFeatures(from, to));
115     }
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       result.addAll(featureSet.getPositionalFeatures());
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. If no types are specified, all feature
207    * sets are returned. If one or more types are specified, feature sets for
208    * those types are returned, preserving the order of the types.
209    * 
210    * @param type
211    * @return
212    */
213   protected Iterable<FeatureStore> varargToTypes(String... type)
214   {
215     if (type == null || type.length == 0)
216     {
217       /*
218        * no vararg parameter supplied - return all
219        */
220       return featureStore.values();
221     }
222
223     List<FeatureStore> types = new ArrayList<>();
224     for (String theType : type)
225     {
226       if (theType != null && featureStore.containsKey(theType))
227       {
228         types.add(featureStore.get(theType));
229       }
230     }
231     return types;
232   }
233
234   /**
235    * {@inheritDoc}
236    */
237   @Override
238   public List<SequenceFeature> getContactFeatures(String... type)
239   {
240     List<SequenceFeature> result = new ArrayList<>();
241
242     for (FeatureStore featureSet : varargToTypes(type))
243     {
244       result.addAll(featureSet.getContactFeatures());
245     }
246     return result;
247   }
248
249   /**
250    * {@inheritDoc}
251    */
252   @Override
253   public List<SequenceFeature> getNonPositionalFeatures(String... type)
254   {
255     List<SequenceFeature> result = new ArrayList<>();
256
257     for (FeatureStore featureSet : varargToTypes(type))
258     {
259       result.addAll(featureSet.getNonPositionalFeatures());
260     }
261     return result;
262   }
263
264   /**
265    * {@inheritDoc}
266    */
267   @Override
268   public boolean delete(SequenceFeature sf)
269   {
270     for (FeatureStore featureSet : featureStore.values())
271     {
272       if (featureSet.delete(sf))
273       {
274         return true;
275       }
276     }
277     return false;
278   }
279
280   /**
281    * {@inheritDoc}
282    */
283   @Override
284   public boolean hasFeatures()
285   {
286     for (FeatureStore featureSet : featureStore.values())
287     {
288       if (!featureSet.isEmpty())
289       {
290         return true;
291       }
292     }
293     return false;
294   }
295
296   /**
297    * {@inheritDoc}
298    */
299   @Override
300   public Set<String> getFeatureGroups(boolean positionalFeatures,
301           String... type)
302   {
303     Set<String> groups = new HashSet<>();
304
305     for (FeatureStore featureSet : varargToTypes(type))
306     {
307       groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
308     }
309
310     return groups;
311   }
312
313   /**
314    * {@inheritDoc}
315    */
316   @Override
317   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
318           String... groups)
319   {
320     Set<String> result = new HashSet<>();
321
322     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
323     {
324       Set<String> featureGroups = featureType.getValue()
325               .getFeatureGroups(positionalFeatures);
326       for (String group : groups)
327       {
328         if (featureGroups.contains(group))
329         {
330           /*
331            * yes this feature type includes one of the query groups
332            */
333           result.add(featureType.getKey());
334           break;
335         }
336       }
337     }
338
339     return result;
340   }
341
342   /**
343    * {@inheritDoc}
344    */
345   @Override
346   public Set<String> getFeatureTypes(String... soTerm)
347   {
348     Set<String> types = new HashSet<>();
349     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
350     {
351       String type = entry.getKey();
352       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
353       {
354         types.add(type);
355       }
356     }
357     return types;
358   }
359
360   /**
361    * Answers true if the given type matches one of the specified terms (or is a
362    * sub-type of one in the Sequence Ontology), or if no terms are supplied.
363    * Answers false if filter terms are specified and the given term does not
364    * match any of them.
365    * 
366    * @param type
367    * @param soTerm
368    * @return
369    */
370   protected boolean isOntologyTerm(String type, String... soTerm)
371   {
372     if (soTerm == null || soTerm.length == 0)
373     {
374       return true;
375     }
376     SequenceOntologyI so = SequenceOntologyFactory.getInstance();
377     for (String term : soTerm)
378     {
379       if (type.equals(term) || so.isA(type, term))
380       {
381         return true;
382       }
383     }
384     return false;
385   }
386
387   /**
388    * {@inheritDoc}
389    */
390   @Override
391   public float getMinimumScore(String type, boolean positional)
392   {
393     return featureStore.containsKey(type)
394             ? featureStore.get(type).getMinimumScore(positional)
395             : Float.NaN;
396   }
397
398   /**
399    * {@inheritDoc}
400    */
401   @Override
402   public float getMaximumScore(String type, boolean positional)
403   {
404     return featureStore.containsKey(type)
405             ? featureStore.get(type).getMaximumScore(positional)
406             : Float.NaN;
407   }
408
409   /**
410    * A convenience method to sort features by start position ascending (if on
411    * forward strand), or end position descending (if on reverse strand)
412    * 
413    * @param features
414    * @param forwardStrand
415    */
416   public static void sortFeatures(List<? extends IntervalI> features,
417           final boolean forwardStrand)
418   {
419     Collections.sort(features,
420             forwardStrand ? IntervalI.COMPARE_BEGIN_ASC_END_DESC
421                     : IntervalI.COMPARE_END_DESC);
422   }
423
424   /**
425    * {@inheritDoc} This method is 'semi-optimised': it only inspects features
426    * for types that include the specified group, but has to inspect every
427    * feature of those types for matching feature group. This is efficient unless
428    * a sequence has features that share the same type but are in different
429    * groups - an unlikely case.
430    * <p>
431    * For example, if RESNUM feature is created with group = PDBID, then features
432    * would only be retrieved for those sequences associated with the target
433    * PDBID (group).
434    */
435   @Override
436   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
437           String group, String... type)
438   {
439     List<SequenceFeature> result = new ArrayList<>();
440     for (FeatureStore featureSet : varargToTypes(type))
441     {
442       if (featureSet.getFeatureGroups(positional).contains(group))
443       {
444         result.addAll(featureSet.getFeaturesForGroup(positional, group));
445       }
446     }
447     return result;
448   }
449
450   /**
451    * {@inheritDoc}
452    */
453   @Override
454   public boolean shiftFeatures(int fromPosition, int shiftBy)
455   {
456     boolean modified = false;
457     for (FeatureStore fs : featureStore.values())
458     {
459       modified |= fs.shiftFeatures(fromPosition, shiftBy);
460     }
461     return modified;
462   }
463
464   /**
465    * {@inheritDoc}
466    */
467   @Override
468   public void deleteAll()
469   {
470     featureStore.clear();
471   }
472 }