Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.datamodel

File ColumnSelection.java

 

Coverage histogram

../../img/srcFileCovDistChart8.png
21% of files have more coverage

Code metrics

142
214
42
2
887
554
137
0.64
5.1
21
3.26

Classes

Class Line # Actions
ColumnSelection 36 153 104
0.769759577%
ColumnSelection.IntList 41 61 33
0.5887850558.9%
 

Contributing tests

This file is covered by 245 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;
22   
23    import java.util.ArrayList;
24    import java.util.BitSet;
25    import java.util.Collections;
26    import java.util.List;
27    import java.util.regex.PatternSyntaxException;
28   
29    import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
30    import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
31   
32    /**
33    * Data class holding the selected columns and hidden column ranges for a view.
34    * Ranges are base 1.
35    */
 
36    public class ColumnSelection
37    {
38    /**
39    * A class to hold an efficient representation of selected columns
40    */
 
41    private class IntList
42    {
43    /*
44    * list of selected columns (ordered by selection order, not column order)
45    */
46    private List<Integer> order;
47   
48    /*
49    * an unmodifiable view of the selected columns list
50    */
51    private List<Integer> _uorder;
52   
53    /**
54    * bitfield for column selection - allows quick lookup
55    */
56    private BitSet selected;
57   
58    /**
59    * Constructor
60    */
 
61  680 toggle IntList()
62    {
63  680 order = new ArrayList<>();
64  680 _uorder = Collections.unmodifiableList(order);
65  680 selected = new BitSet();
66    }
67   
68    /**
69    * Copy constructor
70    *
71    * @param other
72    */
 
73  24 toggle IntList(IntList other)
74    {
75  24 this();
76  24 if (other != null)
77    {
78  24 int j = other.size();
79  39 for (int i = 0; i < j; i++)
80    {
81  15 add(other.elementAt(i));
82    }
83    }
84    }
85   
86    /**
87    * adds a new column i to the selection - only if i is not already selected
88    *
89    * @param i
90    */
 
91  2870 toggle void add(int i)
92    {
93  2870 if (!selected.get(i))
94    {
95  2621 order.add(Integer.valueOf(i));
96  2621 selected.set(i);
97    }
98    }
99   
 
100  55 toggle void clear()
101    {
102  55 order.clear();
103  55 selected.clear();
104    }
105   
 
106  48 toggle void remove(int col)
107    {
108   
109  48 Integer colInt = Integer.valueOf(col);
110   
111  48 if (selected.get(col))
112    {
113    // if this ever changes to List.remove(), ensure Integer not int
114    // argument
115    // as List.remove(int i) removes the i'th item which is wrong
116  32 order.remove(colInt);
117  32 selected.clear(col);
118    }
119    }
120   
 
121  0 toggle boolean contains(Integer colInt)
122    {
123  0 return selected.get(colInt);
124    }
125   
 
126  206 toggle boolean isEmpty()
127    {
128  206 return order.isEmpty();
129    }
130   
131    /**
132    * Returns a read-only view of the selected columns list
133    *
134    * @return
135    */
 
136  2247 toggle List<Integer> getList()
137    {
138  2247 return _uorder;
139    }
140   
 
141  33 toggle int size()
142    {
143  33 return order.size();
144    }
145   
146    /**
147    * gets the column that was selected first, second or i'th
148    *
149    * @param i
150    * @return
151    */
 
152  15 toggle int elementAt(int i)
153    {
154  15 return order.get(i);
155    }
156   
 
157  0 toggle protected boolean pruneColumnList(final List<int[]> shifts)
158    {
159  0 int s = 0, t = shifts.size();
160  0 int[] sr = shifts.get(s++);
161  0 boolean pruned = false;
162  0 int i = 0, j = order.size();
163  0 while (i < j && s <= t)
164    {
165  0 int c = order.get(i++).intValue();
166  0 if (sr[0] <= c)
167    {
168  0 if (sr[1] + sr[0] >= c)
169    { // sr[1] -ve means inseriton.
170  0 order.remove(--i);
171  0 selected.clear(c);
172  0 j--;
173    }
174    else
175    {
176  0 if (s < t)
177    {
178  0 sr = shifts.get(s);
179    }
180  0 s++;
181    }
182    }
183    }
184  0 return pruned;
185    }
186   
187    /**
188    * shift every selected column at or above start by change
189    *
190    * @param start
191    * - leftmost column to be shifted
192    * @param change
193    * - delta for shift
194    */
 
195  0 toggle void compensateForEdits(int start, int change)
196    {
197  0 BitSet mask = new BitSet();
198  0 for (int i = 0; i < order.size(); i++)
199    {
200  0 int temp = order.get(i);
201   
202  0 if (temp >= start)
203    {
204    // clear shifted bits and update List of selected columns
205  0 selected.clear(temp);
206  0 mask.set(temp - change);
207  0 order.set(i, Integer.valueOf(temp - change));
208    }
209    }
210    // lastly update the bitfield all at once
211  0 selected.or(mask);
212    }
213   
 
214  543336 toggle boolean isSelected(int column)
215    {
216  543336 return selected.get(column);
217    }
218   
 
219  4 toggle int getMaxColumn()
220    {
221  4 return selected.length() - 1;
222    }
223   
 
224  3 toggle int getMinColumn()
225    {
226  3 return selected.get(0) ? 0 : selected.nextSetBit(0);
227    }
228   
229    /**
230    * @return a series of selection intervals along the range
231    */
 
232  8 toggle List<int[]> getRanges()
233    {
234  8 List<int[]> rlist = new ArrayList<>();
235  8 if (selected.isEmpty())
236    {
237  0 return rlist;
238    }
239  8 int next = selected.nextSetBit(0), clear = -1;
240  23 while (next != -1)
241    {
242  15 clear = selected.nextClearBit(next);
243  15 rlist.add(new int[] { next, clear - 1 });
244  15 next = selected.nextSetBit(clear);
245    }
246  8 return rlist;
247    }
248   
 
249  28 toggle @Override
250    public int hashCode()
251    {
252    // TODO Auto-generated method stub
253  28 return selected.hashCode();
254    }
255   
 
256  9 toggle @Override
257    public boolean equals(Object obj)
258    {
259  9 if (obj instanceof IntList)
260    {
261  9 return ((IntList) obj).selected.equals(selected);
262    }
263  0 return false;
264    }
265    }
266   
267    private IntList selection = new IntList();
268   
269    /**
270    * Add a column to the selection
271    *
272    * @param col
273    * index of column
274    */
 
275  2853 toggle public void addElement(int col)
276    {
277  2853 selection.add(col);
278    }
279   
280    /**
281    * add a series of start,end (inclusive) ranges to the column selection
282    *
283    * @param rng
284    * [start_0, end_0, start_1, end_1, ... ]
285    * @param baseOne
286    * - when true, ranges are base 1 and will be mapped to base 0
287    */
 
288  0 toggle public void addRangeOfElements(int[] rng, boolean baseOne)
289    {
290  0 int base = baseOne ? -1 : 0;
291  0 for (int c = 0; c < rng.length; c += 2)
292    {
293  0 for (int p = rng[c]; p <= rng[c + 1]; p++)
294    {
295  0 selection.add(base + p);
296    }
297    }
298   
299    }
300   
301    /**
302    * clears column selection
303    */
 
304  50 toggle public void clear()
305    {
306  50 selection.clear();
307    }
308   
309    /**
310    * Removes value 'col' from the selection (not the col'th item)
311    *
312    * @param col
313    * index of column to be removed
314    */
 
315  48 toggle public void removeElement(int col)
316    {
317  48 selection.remove(col);
318    }
319   
320    /**
321    * removes a range of columns from the selection
322    *
323    * @param start
324    * int - first column in range to be removed
325    * @param end
326    * int - last col
327    */
 
328  0 toggle public void removeElements(int start, int end)
329    {
330  0 Integer colInt;
331  0 for (int i = start; i < end; i++)
332    {
333  0 colInt = Integer.valueOf(i);
334  0 if (selection.contains(colInt))
335    {
336  0 selection.remove(colInt);
337    }
338    }
339    }
340   
341    /**
342    * Returns a read-only view of the (possibly empty) list of selected columns
343    * (base 1)
344    * <p>
345    * The list contains no duplicates but is not necessarily ordered. Columns are
346    * reported in alignment coordinates (base 1), so may also include columns
347    * hidden from the current view. To modify (for example sort) the list, you
348    * should first make a copy.
349    * <p>
350    * The list is not thread-safe: iterating over it could result in
351    * ConcurrentModificationException if it is modified by another thread.
352    */
 
353  2247 toggle public List<Integer> getSelected()
354    {
355  2247 return selection.getList();
356    }
357   
358    /**
359    * @return list of int arrays containing start and end column position for
360    * runs of selected columns ordered from right to left.
361    */
 
362  3 toggle public List<int[]> getSelectedRanges()
363    {
364  3 return selection.getRanges();
365    }
366   
367    /**
368    *
369    * @param col
370    * index to search for in column selection
371    *
372    * @return true if col is selected
373    */
 
374  8519 toggle public boolean contains(int col)
375    {
376  8519 return (col > -1) ? selection.isSelected(col) : false;
377    }
378   
379    /**
380    *
381    */
 
382  138930 toggle public boolean intersects(int from, int to)
383    {
384    // TODO: do this in a more efficient bitwise way
385  673746 for (int f = from; f <= to; f++)
386    {
387  534822 if (selection.isSelected(f))
388    {
389  4 return true;
390    }
391    }
392  138924 return false;
393    }
394   
395    /**
396    * Answers true if no columns are selected, else false
397    */
 
398  199 toggle public boolean isEmpty()
399    {
400  199 return selection == null || selection.isEmpty();
401    }
402   
403    /**
404    * rightmost selected column
405    *
406    * @return rightmost column in alignment that is selected
407    */
 
408  4 toggle public int getMax()
409    {
410  4 if (selection.isEmpty())
411    {
412  0 return -1;
413    }
414  4 return selection.getMaxColumn();
415    }
416   
417    /**
418    * Leftmost column in selection
419    *
420    * @return column index of leftmost column in selection
421    */
 
422  3 toggle public int getMin()
423    {
424  3 if (selection.isEmpty())
425    {
426  0 return 1000000000;
427    }
428  3 return selection.getMinColumn();
429    }
430   
 
431  5 toggle public void hideSelectedColumns(AlignmentI al)
432    {
433  5 synchronized (selection)
434    {
435  5 for (int[] selregions : selection.getRanges())
436    {
437  8 al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
438    }
439  5 selection.clear();
440    }
441   
442    }
443   
444    /**
445    * Hides the specified column and any adjacent selected columns
446    *
447    * @param res
448    * int
449    */
 
450  17 toggle public void hideSelectedColumns(int col, HiddenColumns hidden)
451    {
452    /*
453    * deselect column (whether selected or not!)
454    */
455  17 removeElement(col);
456   
457    /*
458    * find adjacent selected columns
459    */
460  17 int min = col - 1, max = col + 1;
461  24 while (contains(min))
462    {
463  7 removeElement(min);
464  7 min--;
465    }
466   
467  21 while (contains(max))
468    {
469  4 removeElement(max);
470  4 max++;
471    }
472   
473    /*
474    * min, max are now the closest unselected columns
475    */
476  17 min++;
477  17 max--;
478  17 if (min > max)
479    {
480  0 min = max;
481    }
482   
483  17 hidden.hideColumns(min, max);
484    }
485   
486    /**
487    * Copy constructor
488    *
489    * @param copy
490    */
 
491  24 toggle public ColumnSelection(ColumnSelection copy)
492    {
493  24 if (copy != null)
494    {
495  24 selection = new IntList(copy.selection);
496    }
497    }
498   
499    /**
500    * ColumnSelection
501    */
 
502  630 toggle public ColumnSelection()
503    {
504    }
505   
506    /**
507    * Invert the column selection from first to end-1. leaves hiddenColumns
508    * untouched (and unselected)
509    *
510    * @param first
511    * @param end
512    */
 
513  2 toggle public void invertColumnSelection(int first, int width, AlignmentI al)
514    {
515  2 boolean hasHidden = al.getHiddenColumns().hasHiddenColumns();
516  17 for (int i = first; i < width; i++)
517    {
518  15 if (contains(i))
519    {
520  6 removeElement(i);
521    }
522    else
523    {
524  9 if (!hasHidden || al.getHiddenColumns().isVisible(i))
525    {
526  6 addElement(i);
527    }
528    }
529    }
530    }
531   
532    /**
533    * set the selected columns to the given column selection, excluding any
534    * columns that are hidden.
535    *
536    * @param colsel
537    */
 
538  2 toggle public void setElementsFrom(ColumnSelection colsel,
539    HiddenColumns hiddenColumns)
540    {
541  2 selection = new IntList();
542  2 if (colsel.selection != null && colsel.selection.size() > 0)
543    {
544  2 if (hiddenColumns.hasHiddenColumns())
545    {
546    // only select visible columns in this columns selection
547  1 for (Integer col : colsel.getSelected())
548    {
549  3 if (hiddenColumns != null
550    && hiddenColumns.isVisible(col.intValue()))
551    {
552  2 selection.add(col);
553    }
554    }
555    }
556    else
557    {
558    // add everything regardless
559  1 for (Integer col : colsel.getSelected())
560    {
561  3 addElement(col);
562    }
563    }
564    }
565    }
566   
567    /**
568    *
569    * @return true if there are columns marked
570    */
 
571  7 toggle public boolean hasSelectedColumns()
572    {
573  7 return (selection != null && selection.size() > 0);
574    }
575   
576    /**
577    * Selects columns where the given annotation matches the provided filter
578    * condition(s). Any existing column selections are first cleared. Answers the
579    * number of columns added.
580    *
581    * @param annotations
582    * @param filterParams
583    * @return
584    */
 
585  11 toggle public int filterAnnotations(AlignmentAnnotation ann_row,
586    AnnotationFilterParameter filterParams)
587    {
588  11 Annotation[] annotations = ann_row.annotations;
589    // JBPNote - this method needs to be refactored to become independent of
590    // viewmodel package
591  11 this.clear();
592   
593  11 if (ann_row.graph == AlignmentAnnotation.CONTACT_MAP && (filterParams
594    .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
595    || filterParams
596    .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD))
597    {
598  0 float tVal = filterParams.getThresholdValue();
599  0 if (ann_row.sequenceRef != null)
600    {
601    // TODO - get ContactList from AlignmentView for non-seq-ref associatd
602  0 for (int column = 0; column < annotations.length; column++)
603    {
604  0 if (ann_row.annotations[column] == null)
605    {
606  0 continue;
607    }
608   
609  0 int cpos = ann_row.sequenceRef.findPosition(column) - 1;
610  0 ContactListI clist = ann_row.sequenceRef
611    .getContactListFor(ann_row, cpos);
612  0 for (int row = column + 8, rowEnd = clist
613  0 .getContactHeight(); row < rowEnd; row++)
614    {
615  0 if (filterParams
616    .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
617    ? (clist.getContactAt(row) > tVal)
618    : (clist.getContactAt(row) < tVal))
619    {
620  0 addElement(column);
621  0 break;
622    // int column_forrowpos = ann_row.sequenceRef.findIndex(row + 1);
623    // addElement(column_forrowpos);
624    }
625    }
626    }
627    }
628  0 return selection.size();
629    }
630   
631  11 int addedCount = 0;
632  11 int column = 0;
633  11 do
634    {
635  55 Annotation ann = annotations[column];
636  55 if (ann != null)
637    {
638  45 float value = ann.value;
639  45 boolean matched = false;
640   
641    /*
642    * filter may have multiple conditions -
643    * these are or'd until a match is found
644    */
645  45 if (filterParams
646    .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
647    && value > filterParams.getThresholdValue())
648    {
649  2 matched = true;
650    }
651   
652  45 if (!matched && filterParams
653    .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
654    && value < filterParams.getThresholdValue())
655    {
656  3 matched = true;
657    }
658   
659  45 if (!matched && filterParams.isFilterAlphaHelix()
660    && ann.secondaryStructure == 'H')
661    {
662  3 matched = true;
663    }
664   
665  45 if (!matched && filterParams.isFilterBetaSheet()
666    && ann.secondaryStructure == 'E')
667    {
668  2 matched = true;
669    }
670   
671  45 if (!matched && filterParams.isFilterTurn()
672    && ann.secondaryStructure == 'S')
673    {
674  1 matched = true;
675    }
676   
677  45 String regexSearchString = filterParams.getRegexString();
678  45 if (!matched && regexSearchString != null)
679    {
680  4 List<SearchableAnnotationField> fields = filterParams
681    .getRegexSearchFields();
682  4 for (SearchableAnnotationField field : fields)
683    {
684  4 String compareTo = field == SearchableAnnotationField.DISPLAY_STRING
685    ? ann.displayCharacter // match 'Label'
686    : ann.description; // and/or 'Description'
687  4 if (compareTo != null)
688    {
689  4 try
690    {
691  2 if (compareTo.matches(regexSearchString))
692    {
693  1 matched = true;
694    }
695    } catch (PatternSyntaxException pse)
696    {
697  2 if (compareTo.equals(regexSearchString))
698    {
699  2 matched = true;
700    }
701    }
702  4 if (matched)
703    {
704  3 break;
705    }
706    }
707    }
708    }
709   
710  45 if (matched)
711    {
712  14 this.addElement(column);
713  14 addedCount++;
714    }
715    }
716  55 column++;
717  55 } while (column < annotations.length);
718   
719  11 return addedCount;
720    }
721   
722    /**
723    * Returns a hashCode built from selected columns ranges
724    */
 
725  28 toggle @Override
726    public int hashCode()
727    {
728  28 return selection.hashCode();
729    }
730   
731    /**
732    * Answers true if comparing to a ColumnSelection with the same selected
733    * columns and hidden columns, else false
734    */
 
735  9 toggle @Override
736    public boolean equals(Object obj)
737    {
738  9 if (!(obj instanceof ColumnSelection))
739    {
740  0 return false;
741    }
742  9 ColumnSelection that = (ColumnSelection) obj;
743   
744    /*
745    * check columns selected are either both null, or match
746    */
747  9 if (this.selection == null)
748    {
749  0 if (that.selection != null)
750    {
751  0 return false;
752    }
753    }
754  9 if (!this.selection.equals(that.selection))
755    {
756  2 return false;
757    }
758   
759  7 return true;
760    }
761   
762    /**
763    * Updates the column selection depending on the parameters, and returns true
764    * if any change was made to the selection
765    *
766    * @param markedColumns
767    * a set identifying marked columns (base 0)
768    * @param startCol
769    * the first column of the range to operate over (base 0)
770    * @param endCol
771    * the last column of the range to operate over (base 0)
772    * @param invert
773    * if true, deselect marked columns and select unmarked
774    * @param extendCurrent
775    * if true, extend rather than replacing the current column selection
776    * @param toggle
777    * if true, toggle the selection state of marked columns
778    *
779    * @return
780    */
 
781  16 toggle public boolean markColumns(BitSet markedColumns, int startCol, int endCol,
782    boolean invert, boolean extendCurrent, boolean toggle)
783    {
784  16 boolean changed = false;
785  16 if (!extendCurrent && !toggle)
786    {
787  14 changed = !this.isEmpty();
788  14 clear();
789    }
790  16 if (invert)
791    {
792    // invert only in the currently selected sequence region
793  7 int i = markedColumns.nextClearBit(startCol);
794  7 int ibs = markedColumns.nextSetBit(startCol);
795  35 while (i >= startCol && i <= endCol)
796    {
797  28 if (ibs < 0 || i < ibs)
798    {
799  23 changed = true;
800  23 if (toggle && contains(i))
801    {
802  0 removeElement(i++);
803    }
804    else
805    {
806  23 addElement(i++);
807    }
808    }
809    else
810    {
811  5 i = markedColumns.nextClearBit(ibs);
812  5 ibs = markedColumns.nextSetBit(i);
813    }
814    }
815    }
816    else
817    {
818  9 int i = markedColumns.nextSetBit(startCol);
819  1963 while (i >= startCol && i <= endCol)
820    {
821  1954 changed = true;
822  1954 if (toggle && contains(i))
823    {
824  1 removeElement(i);
825    }
826    else
827    {
828  1953 addElement(i);
829    }
830  1954 i = markedColumns.nextSetBit(i + 1);
831    }
832    }
833  16 return changed;
834    }
835   
836    /**
837    * Adjusts column selections, and the given selection group, to match the
838    * range of a stretch (e.g. mouse drag) operation
839    * <p>
840    * Method refactored from ScalePanel.mouseDragged
841    *
842    * @param res
843    * current column position, adjusted for hidden columns
844    * @param sg
845    * current selection group
846    * @param min
847    * start position of the stretch group
848    * @param max
849    * end position of the stretch group
850    */
 
851  9 toggle public void stretchGroup(int res, SequenceGroup sg, int min, int max)
852    {
853  9 if (!contains(res))
854    {
855  4 addElement(res);
856    }
857   
858  9 if (res > sg.getStartRes())
859    {
860    // expand selection group to the right
861  5 sg.setEndRes(res);
862    }
863  9 if (res < sg.getStartRes())
864    {
865    // expand selection group to the left
866  0 sg.setStartRes(res);
867    }
868   
869    /*
870    * expand or shrink column selection to match the
871    * range of the drag operation
872    */
873  45 for (int col = min; col <= max; col++)
874    {
875  36 if (col < sg.getStartRes() || col > sg.getEndRes())
876    {
877    // shrinking drag - remove from selection
878  1 removeElement(col);
879    }
880    else
881    {
882    // expanding drag - add to selection
883  35 addElement(col);
884    }
885    }
886    }
887    }