JAL-4028 experiment - THISISAPLACEHOLDER sequences get additional sequence data ...
[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.Arrays;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Set;
31 import java.util.TreeMap;
32
33 import intervalstore.api.IntervalI;
34 import jalview.datamodel.SequenceFeature;
35 import jalview.io.gff.SequenceOntologyFactory;
36 import jalview.io.gff.SequenceOntologyI;
37
38 /**
39  * A class that stores sequence features in a way that supports efficient
40  * querying by type and location (overlap). Intended for (but not limited to)
41  * storage of features for one sequence.
42  * 
43  * @author gmcarstairs
44  *
45  */
46 public class SequenceFeatures implements SequenceFeaturesI
47 {
48   /*
49    * map from feature type to structured store of features for that type
50    * null types are permitted (but not a good idea!)
51    */
52   private Map<String, FeatureStore> featureStore;
53
54   /**
55    * Constructor
56    */
57   public SequenceFeatures()
58   {
59     /*
60      * use a TreeMap so that features are returned in alphabetical order of type
61      * ? wrap as a synchronized map for add and delete operations
62      */
63     // featureStore = Collections
64     // .synchronizedSortedMap(new TreeMap<String, FeatureStore>());
65     featureStore = new TreeMap<>();
66   }
67
68   /**
69    * Constructor given a list of features
70    */
71   public SequenceFeatures(List<SequenceFeature> features)
72   {
73     this();
74     if (features != null)
75     {
76       for (SequenceFeature feature : features)
77       {
78         add(feature);
79       }
80     }
81   }
82
83   /**
84    * {@inheritDoc}
85    */
86   @Override
87   public boolean add(SequenceFeature sf)
88   {
89     String type = sf.getType();
90     if (type == null)
91     {
92       System.err.println("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<Integer> getFeatureExtent(String... type)
194   {
195     Integer from = 0, to = 0;
196
197     for (FeatureStore featureSet : varargToTypes(type))
198     {
199       if (from == 0)
200       {
201         from = featureSet.getBegin();
202         to = featureSet.getEnd();
203       }
204       else
205       {
206         from = Math.min(from, featureSet.getBegin());
207         to = Math.max(to, featureSet.getEnd());
208       }
209     }
210     return Arrays.asList(from, to);
211   }
212
213   /**
214    * {@inheritDoc}
215    */
216   @Override
217   public List<SequenceFeature> getPositionalFeatures(String... type)
218   {
219     List<SequenceFeature> result = new ArrayList<>();
220
221     for (FeatureStore featureSet : varargToTypes(type))
222     {
223       result.addAll(featureSet.getPositionalFeatures());
224     }
225     return result;
226   }
227
228   /**
229    * A convenience method that converts a vararg for feature types to an
230    * Iterable over matched feature sets. If no types are specified, all feature
231    * sets are returned. If one or more types are specified, feature sets for
232    * those types are returned, preserving the order of the types.
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     for (String theType : type)
249     {
250       if (theType != null && featureStore.containsKey(theType))
251       {
252         types.add(featureStore.get(theType));
253       }
254     }
255     return types;
256   }
257
258   /**
259    * {@inheritDoc}
260    */
261   @Override
262   public List<SequenceFeature> getContactFeatures(String... type)
263   {
264     List<SequenceFeature> result = new ArrayList<>();
265
266     for (FeatureStore featureSet : varargToTypes(type))
267     {
268       result.addAll(featureSet.getContactFeatures());
269     }
270     return result;
271   }
272
273   /**
274    * {@inheritDoc}
275    */
276   @Override
277   public List<SequenceFeature> getNonPositionalFeatures(String... type)
278   {
279     List<SequenceFeature> result = new ArrayList<>();
280
281     for (FeatureStore featureSet : varargToTypes(type))
282     {
283       result.addAll(featureSet.getNonPositionalFeatures());
284     }
285     return result;
286   }
287
288   /**
289    * {@inheritDoc}
290    */
291   @Override
292   public boolean delete(SequenceFeature sf)
293   {
294     for (FeatureStore featureSet : featureStore.values())
295     {
296       if (featureSet.delete(sf))
297       {
298         return true;
299       }
300     }
301     return false;
302   }
303
304   /**
305    * {@inheritDoc}
306    */
307   @Override
308   public boolean hasFeatures()
309   {
310     for (FeatureStore featureSet : featureStore.values())
311     {
312       if (!featureSet.isEmpty())
313       {
314         return true;
315       }
316     }
317     return false;
318   }
319
320   /**
321    * {@inheritDoc}
322    */
323   @Override
324   public Set<String> getFeatureGroups(boolean positionalFeatures,
325           String... type)
326   {
327     Set<String> groups = new HashSet<>();
328
329     for (FeatureStore featureSet : varargToTypes(type))
330     {
331       groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
332     }
333
334     return groups;
335   }
336
337   /**
338    * {@inheritDoc}
339    */
340   @Override
341   public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
342           String... groups)
343   {
344     Set<String> result = new HashSet<>();
345
346     for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
347     {
348       Set<String> featureGroups = featureType.getValue()
349               .getFeatureGroups(positionalFeatures);
350       for (String group : groups)
351       {
352         if (featureGroups.contains(group))
353         {
354           /*
355            * yes this feature type includes one of the query groups
356            */
357           result.add(featureType.getKey());
358           break;
359         }
360       }
361     }
362
363     return result;
364   }
365
366   /**
367    * {@inheritDoc}
368    */
369   @Override
370   public Set<String> getFeatureTypes(String... soTerm)
371   {
372     Set<String> types = new HashSet<>();
373     for (Entry<String, FeatureStore> entry : featureStore.entrySet())
374     {
375       String type = entry.getKey();
376       if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
377       {
378         types.add(type);
379       }
380     }
381     return types;
382   }
383
384   /**
385    * Answers true if the given type matches one of the specified terms (or is a
386    * sub-type of one in the Sequence Ontology), or if no terms are supplied.
387    * Answers false if filter terms are specified and the given term does not
388    * match any of them.
389    * 
390    * @param type
391    * @param soTerm
392    * @return
393    */
394   protected boolean isOntologyTerm(String type, String... soTerm)
395   {
396     if (soTerm == null || soTerm.length == 0)
397     {
398       return true;
399     }
400     SequenceOntologyI so = SequenceOntologyFactory.getInstance();
401     for (String term : soTerm)
402     {
403       if (type.equals(term) || so.isA(type, term))
404       {
405         return true;
406       }
407     }
408     return false;
409   }
410
411   /**
412    * {@inheritDoc}
413    */
414   @Override
415   public float getMinimumScore(String type, boolean positional)
416   {
417     return featureStore.containsKey(type)
418             ? featureStore.get(type).getMinimumScore(positional)
419             : Float.NaN;
420   }
421
422   /**
423    * {@inheritDoc}
424    */
425   @Override
426   public float getMaximumScore(String type, boolean positional)
427   {
428     return featureStore.containsKey(type)
429             ? featureStore.get(type).getMaximumScore(positional)
430             : Float.NaN;
431   }
432
433   /**
434    * A convenience method to sort features by start position ascending (if on
435    * forward strand), or end position descending (if on reverse strand)
436    * 
437    * @param features
438    * @param forwardStrand
439    */
440   public static void sortFeatures(List<? extends IntervalI> features,
441           final boolean forwardStrand)
442   {
443     Collections.sort(features,
444             forwardStrand ? IntervalI.COMPARE_BEGIN_ASC_END_DESC
445                     : IntervalI.COMPARE_END_DESC);
446   }
447
448   /**
449    * {@inheritDoc} This method is 'semi-optimised': it only inspects features
450    * for types that include the specified group, but has to inspect every
451    * feature of those types for matching feature group. This is efficient unless
452    * a sequence has features that share the same type but are in different
453    * groups - an unlikely case.
454    * <p>
455    * For example, if RESNUM feature is created with group = PDBID, then features
456    * would only be retrieved for those sequences associated with the target
457    * PDBID (group).
458    */
459   @Override
460   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
461           String group, String... type)
462   {
463     List<SequenceFeature> result = new ArrayList<>();
464     for (FeatureStore featureSet : varargToTypes(type))
465     {
466       if (featureSet.getFeatureGroups(positional).contains(group))
467       {
468         result.addAll(featureSet.getFeaturesForGroup(positional, group));
469       }
470     }
471     return result;
472   }
473
474   /**
475    * {@inheritDoc}
476    */
477   @Override
478   public boolean shiftFeatures(int fromPosition, int shiftBy)
479   {
480     boolean modified = false;
481     for (FeatureStore fs : featureStore.values())
482     {
483       modified |= fs.shiftFeatures(fromPosition, shiftBy);
484     }
485     return modified;
486   }
487
488   /**
489    * {@inheritDoc}
490    */
491   @Override
492   public void deleteAll()
493   {
494     featureStore.clear();
495   }
496 }