Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.datamodel.features

File FeatureStore.java

 

Coverage histogram

../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

146
240
37
2
1,084
571
126
0.52
6.49
18.5
3.41

Classes

Class Line # Actions
FeatureStore 41 234 120 9
0.978102297.8%
FeatureStore.SearchCriterion 46 6 6 0
1.0100%
 

Contributing tests

This file is covered by 4 tests. .

Source view

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.ContiguousI;
24    import jalview.datamodel.SequenceFeature;
25   
26    import java.util.ArrayList;
27    import java.util.Collections;
28    import java.util.Comparator;
29    import java.util.HashSet;
30    import java.util.List;
31    import java.util.Set;
32   
33    /**
34    * A data store for a set of sequence features that supports efficient lookup of
35    * features overlapping a given range. Intended for (but not limited to) storage
36    * of features for one sequence and feature type.
37    *
38    * @author gmcarstairs
39    *
40    */
 
41    public class FeatureStore
42    {
43    /**
44    * a class providing criteria for performing a binary search of a list
45    */
 
46    abstract static class SearchCriterion
47    {
48    /**
49    * Answers true if the entry passes the search criterion test
50    *
51    * @param entry
52    * @return
53    */
54    abstract boolean compare(SequenceFeature entry);
55   
56    /**
57    * serves a search condition for finding the first feature whose start
58    * position follows a given target location
59    *
60    * @param target
61    * @return
62    */
 
63  40 toggle static SearchCriterion byStart(final long target)
64    {
65  40 return new SearchCriterion() {
66   
 
67  62 toggle @Override
68    boolean compare(SequenceFeature entry)
69    {
70  62 return entry.getBegin() >= target;
71    }
72    };
73    }
74   
75    /**
76    * serves a search condition for finding the first feature whose end
77    * position is at or follows a given target location
78    *
79    * @param target
80    * @return
81    */
 
82  3855 toggle static SearchCriterion byEnd(final long target)
83    {
84  3855 return new SearchCriterion()
85    {
86   
 
87  9027 toggle @Override
88    boolean compare(SequenceFeature entry)
89    {
90  9027 return entry.getEnd() >= target;
91    }
92    };
93    }
94   
95    /**
96    * serves a search condition for finding the first feature which follows the
97    * given range as determined by a supplied comparator
98    *
99    * @param target
100    * @return
101    */
 
102  78085 toggle static SearchCriterion byFeature(final ContiguousI to,
103    final Comparator<ContiguousI> rc)
104    {
105  78085 return new SearchCriterion()
106    {
107   
 
108  462655 toggle @Override
109    boolean compare(SequenceFeature entry)
110    {
111  462655 return rc.compare(entry, to) >= 0;
112    }
113    };
114    }
115    }
116   
117    /*
118    * Non-positional features have no (zero) start/end position.
119    * Kept as a separate list in case this criterion changes in future.
120    */
121    List<SequenceFeature> nonPositionalFeatures;
122   
123    /*
124    * An ordered list of features, with the promise that no feature in the list
125    * properly contains any other. This constraint allows bounded linear search
126    * of the list for features overlapping a region.
127    * Contact features are not included in this list.
128    */
129    List<SequenceFeature> nonNestedFeatures;
130   
131    /*
132    * contact features ordered by first contact position
133    */
134    List<SequenceFeature> contactFeatureStarts;
135   
136    /*
137    * contact features ordered by second contact position
138    */
139    List<SequenceFeature> contactFeatureEnds;
140   
141    /*
142    * Nested Containment List is used to hold any features that are nested
143    * within (properly contained by) any other feature. This is a recursive tree
144    * which supports depth-first scan for features overlapping a range.
145    * It is used here as a 'catch-all' fallback for features that cannot be put
146    * into a simple ordered list without invalidating the search methods.
147    */
148    NCList<SequenceFeature> nestedFeatures;
149   
150    /*
151    * Feature groups represented in stored positional features
152    * (possibly including null)
153    */
154    Set<String> positionalFeatureGroups;
155   
156    /*
157    * Feature groups represented in stored non-positional features
158    * (possibly including null)
159    */
160    Set<String> nonPositionalFeatureGroups;
161   
162    /*
163    * the total length of all positional features; contact features count 1 to
164    * the total and 1 to size(), consistent with an average 'feature length' of 1
165    */
166    int totalExtent;
167   
168    float positionalMinScore;
169   
170    float positionalMaxScore;
171   
172    float nonPositionalMinScore;
173   
174    float nonPositionalMaxScore;
175   
176    /**
177    * Constructor
178    */
 
179  2789 toggle public FeatureStore()
180    {
181  2789 nonNestedFeatures = new ArrayList<SequenceFeature>();
182  2789 positionalFeatureGroups = new HashSet<String>();
183  2789 nonPositionalFeatureGroups = new HashSet<String>();
184  2789 positionalMinScore = Float.NaN;
185  2789 positionalMaxScore = Float.NaN;
186  2789 nonPositionalMinScore = Float.NaN;
187  2789 nonPositionalMaxScore = Float.NaN;
188   
189    // we only construct nonPositionalFeatures, contactFeatures
190    // or the NCList if we need to
191    }
192   
193    /**
194    * Adds one sequence feature to the store, and returns true, unless the
195    * feature is already contained in the store, in which case this method
196    * returns false. Containment is determined by SequenceFeature.equals()
197    * comparison.
198    *
199    * @param feature
200    */
 
201  46751 toggle public boolean addFeature(SequenceFeature feature)
202    {
203  46751 if (contains(feature))
204    {
205  15309 return false;
206    }
207   
208    /*
209    * keep a record of feature groups
210    */
211  31442 if (!feature.isNonPositional())
212    {
213  31375 positionalFeatureGroups.add(feature.getFeatureGroup());
214    }
215   
216  31442 boolean added = false;
217   
218  31442 if (feature.isContactFeature())
219    {
220  31 added = addContactFeature(feature);
221    }
222  31411 else if (feature.isNonPositional())
223    {
224  67 added = addNonPositionalFeature(feature);
225    }
226    else
227    {
228  31344 added = addNonNestedFeature(feature);
229  31344 if (!added)
230    {
231    /*
232    * detected a nested feature - put it in the NCList structure
233    */
234  277 added = addNestedFeature(feature);
235    }
236    }
237   
238  31442 if (added)
239    {
240    /*
241    * record the total extent of positional features, to make
242    * getTotalFeatureLength possible; we count the length of a
243    * contact feature as 1
244    */
245  31442 totalExtent += getFeatureLength(feature);
246   
247    /*
248    * record the minimum and maximum score for positional
249    * and non-positional features
250    */
251  31442 float score = feature.getScore();
252  31442 if (!Float.isNaN(score))
253    {
254  31298 if (feature.isNonPositional())
255    {
256  44 nonPositionalMinScore = min(nonPositionalMinScore, score);
257  44 nonPositionalMaxScore = max(nonPositionalMaxScore, score);
258    }
259    else
260    {
261  31254 positionalMinScore = min(positionalMinScore, score);
262  31254 positionalMaxScore = max(positionalMaxScore, score);
263    }
264    }
265    }
266   
267  31442 return added;
268    }
269   
270    /**
271    * Answers true if this store contains the given feature (testing by
272    * SequenceFeature.equals), else false
273    *
274    * @param feature
275    * @return
276    */
 
277  46764 toggle public boolean contains(SequenceFeature feature)
278    {
279  46764 if (feature.isNonPositional())
280    {
281  75 return nonPositionalFeatures == null ? false : nonPositionalFeatures
282    .contains(feature);
283    }
284   
285  46689 if (feature.isContactFeature())
286    {
287  35 return contactFeatureStarts == null ? false : listContains(
288    contactFeatureStarts, feature);
289    }
290   
291  46654 if (listContains(nonNestedFeatures, feature))
292    {
293  15273 return true;
294    }
295   
296  31381 return nestedFeatures == null ? false : nestedFeatures
297    .contains(feature);
298    }
299   
300    /**
301    * Answers the 'length' of the feature, counting 0 for non-positional features
302    * and 1 for contact features
303    *
304    * @param feature
305    * @return
306    */
 
307  31842 toggle protected static int getFeatureLength(SequenceFeature feature)
308    {
309  31842 if (feature.isNonPositional())
310    {
311  68 return 0;
312    }
313  31774 if (feature.isContactFeature())
314    {
315  43 return 1;
316    }
317  31731 return 1 + feature.getEnd() - feature.getBegin();
318    }
319   
320    /**
321    * Adds the feature to the list of non-positional features (with lazy
322    * instantiation of the list if it is null), and returns true. The feature
323    * group is added to the set of distinct feature groups for non-positional
324    * features. This method allows duplicate features, so test before calling to
325    * prevent this.
326    *
327    * @param feature
328    */
 
329  67 toggle protected boolean addNonPositionalFeature(SequenceFeature feature)
330    {
331  67 if (nonPositionalFeatures == null)
332    {
333  62 nonPositionalFeatures = new ArrayList<SequenceFeature>();
334    }
335   
336  67 nonPositionalFeatures.add(feature);
337   
338  67 nonPositionalFeatureGroups.add(feature.getFeatureGroup());
339   
340  67 return true;
341    }
342   
343    /**
344    * Adds one feature to the NCList that can manage nested features (creating
345    * the NCList if necessary), and returns true. If the feature is already
346    * stored in the NCList (by equality test), then it is not added, and this
347    * method returns false.
348    */
 
349  277 toggle protected synchronized boolean addNestedFeature(SequenceFeature feature)
350    {
351  277 if (nestedFeatures == null)
352    {
353  202 nestedFeatures = new NCList<>(feature);
354  202 return true;
355    }
356  75 return nestedFeatures.add(feature, false);
357    }
358   
359    /**
360    * Add a feature to the list of non-nested features, maintaining the ordering
361    * of the list. A check is made for whether the feature is nested in (properly
362    * contained by) an existing feature. If there is no nesting, the feature is
363    * added to the list and the method returns true. If nesting is found, the
364    * feature is not added and the method returns false.
365    *
366    * @param feature
367    * @return
368    */
 
369  31354 toggle protected boolean addNonNestedFeature(SequenceFeature feature)
370    {
371  31354 synchronized (nonNestedFeatures)
372    {
373    /*
374    * find the first stored feature which doesn't precede the new one
375    */
376  31354 int insertPosition = binarySearch(nonNestedFeatures,
377    SearchCriterion.byFeature(feature, RangeComparator.BY_START_POSITION));
378   
379    /*
380    * fail if we detect feature enclosure - of the new feature by
381    * the one preceding it, or of the next feature by the new one
382    */
383  31354 if (insertPosition > 0)
384    {
385  28431 if (encloses(nonNestedFeatures.get(insertPosition - 1), feature))
386    {
387  210 return false;
388    }
389    }
390  31144 if (insertPosition < nonNestedFeatures.size())
391    {
392  297 if (encloses(feature, nonNestedFeatures.get(insertPosition)))
393    {
394  73 return false;
395    }
396    }
397   
398    /*
399    * checks passed - add the feature
400    */
401  31071 nonNestedFeatures.add(insertPosition, feature);
402   
403  31071 return true;
404    }
405    }
406   
407    /**
408    * Answers true if range1 properly encloses range2, else false
409    *
410    * @param range1
411    * @param range2
412    * @return
413    */
 
414  28728 toggle protected boolean encloses(ContiguousI range1, ContiguousI range2)
415    {
416  28728 int begin1 = range1.getBegin();
417  28728 int begin2 = range2.getBegin();
418  28728 int end1 = range1.getEnd();
419  28728 int end2 = range2.getEnd();
420  28728 if (begin1 == begin2 && end1 > end2)
421    {
422  68 return true;
423    }
424  28660 if (begin1 < begin2 && end1 >= end2)
425    {
426  215 return true;
427    }
428  28445 return false;
429    }
430   
431    /**
432    * Add a contact feature to the lists that hold them ordered by start (first
433    * contact) and by end (second contact) position, ensuring the lists remain
434    * ordered, and returns true. This method allows duplicate features to be
435    * added, so test before calling to avoid this.
436    *
437    * @param feature
438    * @return
439    */
 
440  31 toggle protected synchronized boolean addContactFeature(SequenceFeature feature)
441    {
442  31 if (contactFeatureStarts == null)
443    {
444  23 contactFeatureStarts = new ArrayList<SequenceFeature>();
445    }
446  31 if (contactFeatureEnds == null)
447    {
448  23 contactFeatureEnds = new ArrayList<SequenceFeature>();
449    }
450   
451    /*
452    * binary search the sorted list to find the insertion point
453    */
454  31 int insertPosition = binarySearch(contactFeatureStarts,
455    SearchCriterion.byFeature(feature,
456    RangeComparator.BY_START_POSITION));
457  31 contactFeatureStarts.add(insertPosition, feature);
458    // and resort to mak siccar...just in case insertion point not quite right
459  31 Collections.sort(contactFeatureStarts, RangeComparator.BY_START_POSITION);
460   
461  31 insertPosition = binarySearch(contactFeatureStarts,
462    SearchCriterion.byFeature(feature,
463    RangeComparator.BY_END_POSITION));
464  31 contactFeatureEnds.add(feature);
465  31 Collections.sort(contactFeatureEnds, RangeComparator.BY_END_POSITION);
466   
467  31 return true;
468    }
469   
470    /**
471    * Answers true if the list contains the feature, else false. This method is
472    * optimised for the condition that the list is sorted on feature start
473    * position ascending, and will give unreliable results if this does not hold.
474    *
475    * @param features
476    * @param feature
477    * @return
478    */
 
479  46672 toggle protected static boolean listContains(List<SequenceFeature> features,
480    SequenceFeature feature)
481    {
482  46672 if (features == null || feature == null)
483    {
484  3 return false;
485    }
486   
487    /*
488    * locate the first entry in the list which does not precede the feature
489    */
490  46669 int pos = binarySearch(features,
491    SearchCriterion.byFeature(feature, RangeComparator.BY_START_POSITION));
492  46669 int len = features.size();
493  46823 while (pos < len)
494    {
495  15667 SequenceFeature sf = features.get(pos);
496  15667 if (sf.getBegin() > feature.getBegin())
497    {
498  236 return false; // no match found
499    }
500  15431 if (sf.equals(feature))
501    {
502  15277 return true;
503    }
504  154 pos++;
505    }
506  31156 return false;
507    }
508   
509    /**
510    * Returns a (possibly empty) list of features whose extent overlaps the given
511    * range. The returned list is not ordered. Contact features are included if
512    * either of the contact points lies within the range.
513    *
514    * @param start
515    * start position of overlap range (inclusive)
516    * @param end
517    * end position of overlap range (inclusive)
518    * @return
519    */
 
520  3815 toggle public List<SequenceFeature> findOverlappingFeatures(long start, long end)
521    {
522  3815 List<SequenceFeature> result = new ArrayList<>();
523   
524  3815 findNonNestedFeatures(start, end, result);
525   
526  3815 findContactFeatures(start, end, result);
527   
528  3815 if (nestedFeatures != null)
529    {
530  18 result.addAll(nestedFeatures.findOverlaps(start, end));
531    }
532   
533  3815 return result;
534    }
535   
536    /**
537    * Adds contact features to the result list where either the second or the
538    * first contact position lies within the target range
539    *
540    * @param from
541    * @param to
542    * @param result
543    */
 
544  3815 toggle protected void findContactFeatures(long from, long to,
545    List<SequenceFeature> result)
546    {
547  3815 if (contactFeatureStarts != null)
548    {
549  40 findContactStartFeatures(from, to, result);
550    }
551  3815 if (contactFeatureEnds != null)
552    {
553  40 findContactEndFeatures(from, to, result);
554    }
555    }
556   
557    /**
558    * Adds to the result list any contact features whose end (second contact
559    * point), but not start (first contact point), lies in the query from-to
560    * range
561    *
562    * @param from
563    * @param to
564    * @param result
565    */
 
566  40 toggle protected void findContactEndFeatures(long from, long to,
567    List<SequenceFeature> result)
568    {
569    /*
570    * find the first contact feature (if any) that does not lie
571    * entirely before the target range
572    */
573  40 int startPosition = binarySearch(contactFeatureEnds,
574    SearchCriterion.byEnd(from));
575  63 for (; startPosition < contactFeatureEnds.size(); startPosition++)
576    {
577  48 SequenceFeature sf = contactFeatureEnds.get(startPosition);
578  48 if (!sf.isContactFeature())
579    {
580  0 System.err.println("Error! non-contact feature type "
581    + sf.getType() + " in contact features list");
582  0 continue;
583    }
584   
585  48 int begin = sf.getBegin();
586  48 if (begin >= from && begin <= to)
587    {
588    /*
589    * this feature's first contact position lies in the search range
590    * so we don't include it in results a second time
591    */
592  8 continue;
593    }
594   
595  40 int end = sf.getEnd();
596  40 if (end >= from && end <= to)
597    {
598  15 result.add(sf);
599    }
600  40 if (end > to)
601    {
602  25 break;
603    }
604    }
605    }
606   
607    /**
608    * Adds non-nested features to the result list that lie within the target
609    * range. Non-positional features (start=end=0), contact features and nested
610    * features are excluded.
611    *
612    * @param from
613    * @param to
614    * @param result
615    */
 
616  3815 toggle protected void findNonNestedFeatures(long from, long to,
617    List<SequenceFeature> result)
618    {
619    /*
620    * find the first feature whose end position is
621    * after the target range start
622    */
623  3815 int startIndex = binarySearch(nonNestedFeatures,
624    SearchCriterion.byEnd(from));
625   
626  3815 final int startIndex1 = startIndex;
627  3815 int i = startIndex1;
628  29896 while (i < nonNestedFeatures.size())
629    {
630  28863 SequenceFeature sf = nonNestedFeatures.get(i);
631  28863 if (sf.getBegin() > to)
632    {
633  2782 break;
634    }
635  26081 if (sf.getBegin() <= to && sf.getEnd() >= from)
636    {
637  26081 result.add(sf);
638    }
639  26081 i++;
640    }
641    }
642   
643    /**
644    * Adds contact features whose start position lies in the from-to range to the
645    * result list
646    *
647    * @param from
648    * @param to
649    * @param result
650    */
 
651  40 toggle protected void findContactStartFeatures(long from, long to,
652    List<SequenceFeature> result)
653    {
654  40 int startPosition = binarySearch(contactFeatureStarts,
655    SearchCriterion.byStart(from));
656   
657  65 for (; startPosition < contactFeatureStarts.size(); startPosition++)
658    {
659  25 SequenceFeature sf = contactFeatureStarts.get(startPosition);
660  25 if (!sf.isContactFeature())
661    {
662  0 System.err.println("Error! non-contact feature type "
663    + sf.getType() + " in contact features list");
664  0 continue;
665    }
666  25 int begin = sf.getBegin();
667  25 if (begin >= from && begin <= to)
668    {
669  8 result.add(sf);
670    }
671    }
672    }
673   
674    /**
675    * Answers a list of all positional features stored, in no guaranteed order
676    *
677    * @return
678    */
 
679  3615 toggle public List<SequenceFeature> getPositionalFeatures()
680    {
681    /*
682    * add non-nested features (may be all features for many cases)
683    */
684  3615 List<SequenceFeature> result = new ArrayList<>();
685  3615 result.addAll(nonNestedFeatures);
686   
687    /*
688    * add any contact features - from the list by start position
689    */
690  3615 if (contactFeatureStarts != null)
691    {
692  46 result.addAll(contactFeatureStarts);
693    }
694   
695    /*
696    * add any nested features
697    */
698  3615 if (nestedFeatures != null)
699    {
700  320 result.addAll(nestedFeatures.getEntries());
701    }
702   
703  3615 return result;
704    }
705   
706    /**
707    * Answers a list of all contact features. If there are none, returns an
708    * immutable empty list.
709    *
710    * @return
711    */
 
712  7 toggle public List<SequenceFeature> getContactFeatures()
713    {
714  7 if (contactFeatureStarts == null)
715    {
716  3 return Collections.emptyList();
717    }
718  4 return new ArrayList<>(contactFeatureStarts);
719    }
720   
721    /**
722    * Answers a list of all non-positional features. If there are none, returns
723    * an immutable empty list.
724    *
725    * @return
726    */
 
727  3552 toggle public List<SequenceFeature> getNonPositionalFeatures()
728    {
729  3552 if (nonPositionalFeatures == null)
730    {
731  3439 return Collections.emptyList();
732    }
733  113 return new ArrayList<>(nonPositionalFeatures);
734    }
735   
736    /**
737    * Deletes the given feature from the store, returning true if it was found
738    * (and deleted), else false. This method makes no assumption that the feature
739    * is in the 'expected' place in the store, in case it has been modified since
740    * it was added.
741    *
742    * @param sf
743    */
 
744  204 toggle public synchronized boolean delete(SequenceFeature sf)
745    {
746    /*
747    * try the non-nested positional features first
748    */
749  204 boolean removed = nonNestedFeatures.remove(sf);
750   
751    /*
752    * if not found, try contact positions (and if found, delete
753    * from both lists of contact positions)
754    */
755  204 if (!removed && contactFeatureStarts != null)
756    {
757  40 removed = contactFeatureStarts.remove(sf);
758  40 if (removed)
759    {
760  8 contactFeatureEnds.remove(sf);
761    }
762    }
763   
764  204 boolean removedNonPositional = false;
765   
766    /*
767    * if not found, try non-positional features
768    */
769  204 if (!removed && nonPositionalFeatures != null)
770    {
771  43 removedNonPositional = nonPositionalFeatures.remove(sf);
772  43 removed = removedNonPositional;
773    }
774   
775    /*
776    * if not found, try nested features
777    */
778  204 if (!removed && nestedFeatures != null)
779    {
780  30 removed = nestedFeatures.delete(sf);
781    }
782   
783  204 if (removed)
784    {
785  120 rescanAfterDelete();
786    }
787   
788  204 return removed;
789    }
790   
791    /**
792    * Rescan all features to recompute any cached values after an entry has been
793    * deleted. This is expected to be an infrequent event, so performance here is
794    * not critical.
795    */
 
796  120 toggle protected synchronized void rescanAfterDelete()
797    {
798  120 positionalFeatureGroups.clear();
799  120 nonPositionalFeatureGroups.clear();
800  120 totalExtent = 0;
801  120 positionalMinScore = Float.NaN;
802  120 positionalMaxScore = Float.NaN;
803  120 nonPositionalMinScore = Float.NaN;
804  120 nonPositionalMaxScore = Float.NaN;
805   
806    /*
807    * scan non-positional features for groups and scores
808    */
809  120 for (SequenceFeature sf : getNonPositionalFeatures())
810    {
811  18 nonPositionalFeatureGroups.add(sf.getFeatureGroup());
812  18 float score = sf.getScore();
813  18 nonPositionalMinScore = min(nonPositionalMinScore, score);
814  18 nonPositionalMaxScore = max(nonPositionalMaxScore, score);
815    }
816   
817    /*
818    * scan positional features for groups, scores and extents
819    */
820  120 for (SequenceFeature sf : getPositionalFeatures())
821    {
822  397 positionalFeatureGroups.add(sf.getFeatureGroup());
823  397 float score = sf.getScore();
824  397 positionalMinScore = min(positionalMinScore, score);
825  397 positionalMaxScore = max(positionalMaxScore, score);
826  397 totalExtent += getFeatureLength(sf);
827    }
828    }
829   
830    /**
831    * A helper method to return the minimum of two floats, where a non-NaN value
832    * is treated as 'less than' a NaN value (unlike Math.min which does the
833    * opposite)
834    *
835    * @param f1
836    * @param f2
837    */
 
838  31717 toggle protected static float min(float f1, float f2)
839    {
840  31717 if (Float.isNaN(f1))
841    {
842  2851 return Float.isNaN(f2) ? f1 : f2;
843    }
844    else
845    {
846  28866 return Float.isNaN(f2) ? f1 : Math.min(f1, f2);
847    }
848    }
849   
850    /**
851    * A helper method to return the maximum of two floats, where a non-NaN value
852    * is treated as 'greater than' a NaN value (unlike Math.max which does the
853    * opposite)
854    *
855    * @param f1
856    * @param f2
857    */
 
858  31717 toggle protected static float max(float f1, float f2)
859    {
860  31717 if (Float.isNaN(f1))
861    {
862  2851 return Float.isNaN(f2) ? f1 : f2;
863    }
864    else
865    {
866  28866 return Float.isNaN(f2) ? f1 : Math.max(f1, f2);
867    }
868    }
869   
870    /**
871    * Answers true if this store has no features, else false
872    *
873    * @return
874    */
 
875  1127 toggle public boolean isEmpty()
876    {
877  1127 boolean hasFeatures = !nonNestedFeatures.isEmpty()
878    || (contactFeatureStarts != null && !contactFeatureStarts
879    .isEmpty())
880    || (nonPositionalFeatures != null && !nonPositionalFeatures
881    .isEmpty())
882    || (nestedFeatures != null && nestedFeatures.size() > 0);
883   
884  1127 return !hasFeatures;
885    }
886   
887    /**
888    * Answers the set of distinct feature groups stored, possibly including null,
889    * as an unmodifiable view of the set. The parameter determines whether the
890    * groups for positional or for non-positional features are returned.
891    *
892    * @param positionalFeatures
893    * @return
894    */
 
895  667 toggle public Set<String> getFeatureGroups(boolean positionalFeatures)
896    {
897  667 if (positionalFeatures)
898    {
899  629 return Collections.unmodifiableSet(positionalFeatureGroups);
900    }
901    else
902    {
903  38 return nonPositionalFeatureGroups == null ? Collections
904    .<String> emptySet() : Collections
905    .unmodifiableSet(nonPositionalFeatureGroups);
906    }
907    }
908   
909    /**
910    * Performs a binary search of the (sorted) list to find the index of the
911    * first entry which returns true for the given comparator function. Returns
912    * the length of the list if there is no such entry.
913    *
914    * @param features
915    * @param sc
916    * @return
917    */
 
918  81980 toggle protected static int binarySearch(List<SequenceFeature> features,
919    SearchCriterion sc)
920    {
921  81980 int start = 0;
922  81980 int end = features.size() - 1;
923  81980 int matched = features.size();
924   
925  553718 while (start <= end)
926    {
927  471737 int mid = (start + end) / 2;
928  471740 SequenceFeature entry = features.get(mid);
929  471742 boolean compare = sc.compare(entry);
930  471740 if (compare)
931    {
932  57560 matched = mid;
933  57560 end = mid - 1;
934    }
935    else
936    {
937  414180 start = mid + 1;
938    }
939    }
940   
941  81980 return matched;
942    }
943   
944    /**
945    * Answers the number of positional (or non-positional) features stored.
946    * Contact features count as 1.
947    *
948    * @param positional
949    * @return
950    */
 
951  79 toggle public int getFeatureCount(boolean positional)
952    {
953  79 if (!positional)
954    {
955  26 return nonPositionalFeatures == null ? 0 : nonPositionalFeatures
956    .size();
957    }
958   
959  53 int size = nonNestedFeatures.size();
960   
961  53 if (contactFeatureStarts != null)
962    {
963    // note a contact feature (start/end) counts as one
964  14 size += contactFeatureStarts.size();
965    }
966   
967  53 if (nestedFeatures != null)
968    {
969  5 size += nestedFeatures.size();
970    }
971   
972  53 return size;
973    }
974   
975    /**
976    * Answers the total length of positional features (or zero if there are
977    * none). Contact features contribute a value of 1 to the total.
978    *
979    * @return
980    */
 
981  42 toggle public int getTotalFeatureLength()
982    {
983  42 return totalExtent;
984    }
985   
986    /**
987    * Answers the minimum score held for positional or non-positional features.
988    * This may be Float.NaN if there are no features, are none has a non-NaN
989    * score.
990    *
991    * @param positional
992    * @return
993    */
 
994  174 toggle public float getMinimumScore(boolean positional)
995    {
996  174 return positional ? positionalMinScore : nonPositionalMinScore;
997    }
998   
999    /**
1000    * Answers the maximum score held for positional or non-positional features.
1001    * This may be Float.NaN if there are no features, are none has a non-NaN
1002    * score.
1003    *
1004    * @param positional
1005    * @return
1006    */
 
1007  148 toggle public float getMaximumScore(boolean positional)
1008    {
1009  148 return positional ? positionalMaxScore : nonPositionalMaxScore;
1010    }
1011   
1012    /**
1013    * Answers a list of all either positional or non-positional features whose
1014    * feature group matches the given group (which may be null)
1015    *
1016    * @param positional
1017    * @param group
1018    * @return
1019    */
 
1020  30 toggle public List<SequenceFeature> getFeaturesForGroup(boolean positional,
1021    String group)
1022    {
1023  30 List<SequenceFeature> result = new ArrayList<>();
1024   
1025    /*
1026    * if we know features don't include the target group, no need
1027    * to inspect them for matches
1028    */
1029  30 if (positional && !positionalFeatureGroups.contains(group)
1030    || !positional && !nonPositionalFeatureGroups.contains(group))
1031    {
1032  7 return result;
1033    }
1034   
1035  23 List<SequenceFeature> sfs = positional ? getPositionalFeatures()
1036    : getNonPositionalFeatures();
1037  23 for (SequenceFeature sf : sfs)
1038    {
1039  32 String featureGroup = sf.getFeatureGroup();
1040  32 if (group == null && featureGroup == null || group != null
1041    && group.equals(featureGroup))
1042    {
1043  23 result.add(sf);
1044    }
1045    }
1046  23 return result;
1047    }
1048   
1049    /**
1050    * Adds the shift value to the start and end of all positional features.
1051    * Returns true if at least one feature was updated, else false.
1052    *
1053    * @param shift
1054    * @return
1055    */
 
1056  11 toggle public synchronized boolean shiftFeatures(int shift)
1057    {
1058    /*
1059    * Because begin and end are final fields (to ensure the data store's
1060    * integrity), we have to delete each feature and re-add it as amended.
1061    * (Although a simple shift of all values would preserve data integrity!)
1062    */
1063  11 boolean modified = false;
1064  11 for (SequenceFeature sf : getPositionalFeatures())
1065    {
1066  12 modified = true;
1067  12 int newBegin = sf.getBegin() + shift;
1068  12 int newEnd = sf.getEnd() + shift;
1069   
1070    /*
1071    * sanity check: don't shift left of the first residue
1072    */
1073  12 if (newEnd > 0)
1074    {
1075  10 newBegin = Math.max(1, newBegin);
1076  10 SequenceFeature sf2 = new SequenceFeature(sf, newBegin, newEnd,
1077    sf.getFeatureGroup(), sf.getScore());
1078  10 addFeature(sf2);
1079    }
1080  12 delete(sf);
1081    }
1082  11 return modified;
1083    }
1084    }