Clover icon

jalviewX

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

File ColumnSelection.java

 

Coverage histogram

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

Code metrics

118
191
40
2
807
497
121
0.63
4.78
20
3.03

Classes

Class Line # Actions
ColumnSelection 36 130 88 32
0.867768686.8%
ColumnSelection.IntList 41 61 33 44
0.5887850558.9%
 

Contributing tests

This file is covered by 133 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 jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
24    import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
25   
26    import java.util.ArrayList;
27    import java.util.BitSet;
28    import java.util.Collections;
29    import java.util.List;
30    import java.util.regex.PatternSyntaxException;
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  393 toggle IntList()
62    {
63  393 order = new ArrayList<>();
64  393 _uorder = Collections.unmodifiableList(order);
65  393 selected = new BitSet();
66    }
67   
68    /**
69    * Copy constructor
70    *
71    * @param other
72    */
 
73  14 toggle IntList(IntList other)
74    {
75  14 this();
76  14 if (other != null)
77    {
78  14 int j = other.size();
79  16 for (int i = 0; i < j; i++)
80    {
81  2 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  908 toggle void add(int i)
92    {
93  908 if (!selected.get(i))
94    {
95  667 order.add(Integer.valueOf(i));
96  667 selected.set(i);
97    }
98    }
99   
 
100  45 toggle void clear()
101    {
102  45 order.clear();
103  45 selected.clear();
104    }
105   
 
106  46 toggle void remove(int col)
107    {
108   
109  46 Integer colInt = new Integer(col);
110   
111  46 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  30 order.remove(colInt);
117  30 selected.clear(col);
118    }
119    }
120   
 
121  0 toggle boolean contains(Integer colInt)
122    {
123  0 return selected.get(colInt);
124    }
125   
 
126  155 toggle boolean isEmpty()
127    {
128  155 return order.isEmpty();
129    }
130   
131    /**
132    * Returns a read-only view of the selected columns list
133    *
134    * @return
135    */
 
136  790 toggle List<Integer> getList()
137    {
138  790 return _uorder;
139    }
140   
 
141  19 toggle int size()
142    {
143  19 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  2 toggle int elementAt(int i)
153    {
154  2 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, new Integer(temp - change));
208    }
209    }
210    // lastly update the bitfield all at once
211  0 selected.or(mask);
212    }
213   
 
214  84 toggle boolean isSelected(int column)
215    {
216  84 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  16 toggle @Override
250    public int hashCode()
251    {
252    // TODO Auto-generated method stub
253  16 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  904 toggle public void addElement(int col)
276    {
277  904 selection.add(col);
278    }
279   
280    /**
281    * clears column selection
282    */
 
283  40 toggle public void clear()
284    {
285  40 selection.clear();
286    }
287   
288    /**
289    * Removes value 'col' from the selection (not the col'th item)
290    *
291    * @param col
292    * index of column to be removed
293    */
 
294  46 toggle public void removeElement(int col)
295    {
296  46 selection.remove(col);
297    }
298   
299    /**
300    * removes a range of columns from the selection
301    *
302    * @param start
303    * int - first column in range to be removed
304    * @param end
305    * int - last col
306    */
 
307  0 toggle public void removeElements(int start, int end)
308    {
309  0 Integer colInt;
310  0 for (int i = start; i < end; i++)
311    {
312  0 colInt = new Integer(i);
313  0 if (selection.contains(colInt))
314    {
315  0 selection.remove(colInt);
316    }
317    }
318    }
319   
320    /**
321    * Returns a read-only view of the (possibly empty) list of selected columns
322    * <p>
323    * The list contains no duplicates but is not necessarily ordered. It also may
324    * include columns hidden from the current view. To modify (for example sort)
325    * the list, you should first make a copy.
326    * <p>
327    * The list is not thread-safe: iterating over it could result in
328    * ConcurrentModificationException if it is modified by another thread.
329    */
 
330  790 toggle public List<Integer> getSelected()
331    {
332  790 return selection.getList();
333    }
334   
335    /**
336    * @return list of int arrays containing start and end column position for
337    * runs of selected columns ordered from right to left.
338    */
 
339  3 toggle public List<int[]> getSelectedRanges()
340    {
341  3 return selection.getRanges();
342    }
343   
344    /**
345    *
346    * @param col
347    * index to search for in column selection
348    *
349    * @return true if col is selected
350    */
 
351  86 toggle public boolean contains(int col)
352    {
353  86 return (col > -1) ? selection.isSelected(col) : false;
354    }
355   
356    /**
357    * Answers true if no columns are selected, else false
358    */
 
359  148 toggle public boolean isEmpty()
360    {
361  148 return selection == null || selection.isEmpty();
362    }
363   
364    /**
365    * rightmost selected column
366    *
367    * @return rightmost column in alignment that is selected
368    */
 
369  4 toggle public int getMax()
370    {
371  4 if (selection.isEmpty())
372    {
373  0 return -1;
374    }
375  4 return selection.getMaxColumn();
376    }
377   
378    /**
379    * Leftmost column in selection
380    *
381    * @return column index of leftmost column in selection
382    */
 
383  3 toggle public int getMin()
384    {
385  3 if (selection.isEmpty())
386    {
387  0 return 1000000000;
388    }
389  3 return selection.getMinColumn();
390    }
391   
 
392  5 toggle public void hideSelectedColumns(AlignmentI al)
393    {
394  5 synchronized (selection)
395    {
396  5 for (int[] selregions : selection.getRanges())
397    {
398  8 al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
399    }
400  5 selection.clear();
401    }
402   
403    }
404   
405    /**
406    * Hides the specified column and any adjacent selected columns
407    *
408    * @param res
409    * int
410    */
 
411  17 toggle public void hideSelectedColumns(int col, HiddenColumns hidden)
412    {
413    /*
414    * deselect column (whether selected or not!)
415    */
416  17 removeElement(col);
417   
418    /*
419    * find adjacent selected columns
420    */
421  17 int min = col - 1, max = col + 1;
422  24 while (contains(min))
423    {
424  7 removeElement(min);
425  7 min--;
426    }
427   
428  21 while (contains(max))
429    {
430  4 removeElement(max);
431  4 max++;
432    }
433   
434    /*
435    * min, max are now the closest unselected columns
436    */
437  17 min++;
438  17 max--;
439  17 if (min > max)
440    {
441  0 min = max;
442    }
443   
444  17 hidden.hideColumns(min, max);
445    }
446   
447    /**
448    * Copy constructor
449    *
450    * @param copy
451    */
 
452  14 toggle public ColumnSelection(ColumnSelection copy)
453    {
454  14 if (copy != null)
455    {
456  14 selection = new IntList(copy.selection);
457    }
458    }
459   
460    /**
461    * ColumnSelection
462    */
 
463  363 toggle public ColumnSelection()
464    {
465    }
466   
467    /**
468    * Invert the column selection from first to end-1. leaves hiddenColumns
469    * untouched (and unselected)
470    *
471    * @param first
472    * @param end
473    */
 
474  2 toggle public void invertColumnSelection(int first, int width, AlignmentI al)
475    {
476  2 boolean hasHidden = al.getHiddenColumns().hasHiddenColumns();
477  17 for (int i = first; i < width; i++)
478    {
479  15 if (contains(i))
480    {
481  6 removeElement(i);
482    }
483    else
484    {
485  9 if (!hasHidden || al.getHiddenColumns().isVisible(i))
486    {
487  6 addElement(i);
488    }
489    }
490    }
491    }
492   
493    /**
494    * set the selected columns to the given column selection, excluding any
495    * columns that are hidden.
496    *
497    * @param colsel
498    */
 
499  2 toggle public void setElementsFrom(ColumnSelection colsel,
500    HiddenColumns hiddenColumns)
501    {
502  2 selection = new IntList();
503  2 if (colsel.selection != null && colsel.selection.size() > 0)
504    {
505  2 if (hiddenColumns.hasHiddenColumns())
506    {
507    // only select visible columns in this columns selection
508  1 for (Integer col : colsel.getSelected())
509    {
510  3 if (hiddenColumns != null
511    && hiddenColumns.isVisible(col.intValue()))
512    {
513  2 selection.add(col);
514    }
515    }
516    }
517    else
518    {
519    // add everything regardless
520  1 for (Integer col : colsel.getSelected())
521    {
522  3 addElement(col);
523    }
524    }
525    }
526    }
527   
528    /**
529    *
530    * @return true if there are columns marked
531    */
 
532  3 toggle public boolean hasSelectedColumns()
533    {
534  3 return (selection != null && selection.size() > 0);
535    }
536   
537    /**
538    * Selects columns where the given annotation matches the provided filter
539    * condition(s). Any existing column selections are first cleared. Answers the
540    * number of columns added.
541    *
542    * @param annotations
543    * @param filterParams
544    * @return
545    */
 
546  11 toggle public int filterAnnotations(Annotation[] annotations,
547    AnnotationFilterParameter filterParams)
548    {
549    // JBPNote - this method needs to be refactored to become independent of
550    // viewmodel package
551  11 this.clear();
552  11 int addedCount = 0;
553  11 int column = 0;
554  11 do
555    {
556  55 Annotation ann = annotations[column];
557  55 if (ann != null)
558    {
559  45 boolean matched = false;
560   
561    /*
562    * filter may have multiple conditions -
563    * these are or'd until a match is found
564    */
565  45 if (filterParams
566    .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
567    && ann.value > filterParams.getThresholdValue())
568    {
569  2 matched = true;
570    }
571   
572  45 if (!matched && filterParams
573    .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
574    && ann.value < filterParams.getThresholdValue())
575    {
576  3 matched = true;
577    }
578   
579  45 if (!matched && filterParams.isFilterAlphaHelix()
580    && ann.secondaryStructure == 'H')
581    {
582  3 matched = true;
583    }
584   
585  45 if (!matched && filterParams.isFilterBetaSheet()
586    && ann.secondaryStructure == 'E')
587    {
588  2 matched = true;
589    }
590   
591  45 if (!matched && filterParams.isFilterTurn()
592    && ann.secondaryStructure == 'S')
593    {
594  1 matched = true;
595    }
596   
597  45 String regexSearchString = filterParams.getRegexString();
598  45 if (!matched && regexSearchString != null)
599    {
600  4 List<SearchableAnnotationField> fields = filterParams
601    .getRegexSearchFields();
602  4 for (SearchableAnnotationField field : fields)
603    {
604  4 String compareTo = field == SearchableAnnotationField.DISPLAY_STRING
605    ? ann.displayCharacter // match 'Label'
606    : ann.description; // and/or 'Description'
607  4 if (compareTo != null)
608    {
609  4 try
610    {
611  2 if (compareTo.matches(regexSearchString))
612    {
613  1 matched = true;
614    }
615    } catch (PatternSyntaxException pse)
616    {
617  2 if (compareTo.equals(regexSearchString))
618    {
619  2 matched = true;
620    }
621    }
622  4 if (matched)
623    {
624  3 break;
625    }
626    }
627    }
628    }
629   
630  45 if (matched)
631    {
632  14 this.addElement(column);
633  14 addedCount++;
634    }
635    }
636  55 column++;
637  55 } while (column < annotations.length);
638   
639  11 return addedCount;
640    }
641   
642    /**
643    * Returns a hashCode built from selected columns ranges
644    */
 
645  16 toggle @Override
646    public int hashCode()
647    {
648  16 return selection.hashCode();
649    }
650   
651    /**
652    * Answers true if comparing to a ColumnSelection with the same selected
653    * columns and hidden columns, else false
654    */
 
655  9 toggle @Override
656    public boolean equals(Object obj)
657    {
658  9 if (!(obj instanceof ColumnSelection))
659    {
660  0 return false;
661    }
662  9 ColumnSelection that = (ColumnSelection) obj;
663   
664    /*
665    * check columns selected are either both null, or match
666    */
667  9 if (this.selection == null)
668    {
669  0 if (that.selection != null)
670    {
671  0 return false;
672    }
673    }
674  9 if (!this.selection.equals(that.selection))
675    {
676  2 return false;
677    }
678   
679  7 return true;
680    }
681   
682    /**
683    * Updates the column selection depending on the parameters, and returns true
684    * if any change was made to the selection
685    *
686    * @param markedColumns
687    * a set identifying marked columns (base 0)
688    * @param startCol
689    * the first column of the range to operate over (base 0)
690    * @param endCol
691    * the last column of the range to operate over (base 0)
692    * @param invert
693    * if true, deselect marked columns and select unmarked
694    * @param extendCurrent
695    * if true, extend rather than replacing the current column selection
696    * @param toggle
697    * if true, toggle the selection state of marked columns
698    *
699    * @return
700    */
 
701  11 toggle public boolean markColumns(BitSet markedColumns, int startCol, int endCol,
702    boolean invert, boolean extendCurrent, boolean toggle)
703    {
704  11 boolean changed = false;
705  11 if (!extendCurrent && !toggle)
706    {
707  9 changed = !this.isEmpty();
708  9 clear();
709    }
710  11 if (invert)
711    {
712    // invert only in the currently selected sequence region
713  5 int i = markedColumns.nextClearBit(startCol);
714  5 int ibs = markedColumns.nextSetBit(startCol);
715  33 while (i >= startCol && i <= endCol)
716    {
717  28 if (ibs < 0 || i < ibs)
718    {
719  23 changed = true;
720  23 if (toggle && contains(i))
721    {
722  0 removeElement(i++);
723    }
724    else
725    {
726  23 addElement(i++);
727    }
728    }
729    else
730    {
731  5 i = markedColumns.nextClearBit(ibs);
732  5 ibs = markedColumns.nextSetBit(i);
733    }
734    }
735    }
736    else
737    {
738  6 int i = markedColumns.nextSetBit(startCol);
739  37 while (i >= startCol && i <= endCol)
740    {
741  31 changed = true;
742  31 if (toggle && contains(i))
743    {
744  1 removeElement(i);
745    }
746    else
747    {
748  30 addElement(i);
749    }
750  31 i = markedColumns.nextSetBit(i + 1);
751    }
752    }
753  11 return changed;
754    }
755   
756    /**
757    * Adjusts column selections, and the given selection group, to match the
758    * range of a stretch (e.g. mouse drag) operation
759    * <p>
760    * Method refactored from ScalePanel.mouseDragged
761    *
762    * @param res
763    * current column position, adjusted for hidden columns
764    * @param sg
765    * current selection group
766    * @param min
767    * start position of the stretch group
768    * @param max
769    * end position of the stretch group
770    */
 
771  3 toggle public void stretchGroup(int res, SequenceGroup sg, int min, int max)
772    {
773  3 if (!contains(res))
774    {
775  2 addElement(res);
776    }
777   
778  3 if (res > sg.getStartRes())
779    {
780    // expand selection group to the right
781  3 sg.setEndRes(res);
782    }
783  3 if (res < sg.getStartRes())
784    {
785    // expand selection group to the left
786  0 sg.setStartRes(res);
787    }
788   
789    /*
790    * expand or shrink column selection to match the
791    * range of the drag operation
792    */
793  26 for (int col = min; col <= max; col++)
794    {
795  23 if (col < sg.getStartRes() || col > sg.getEndRes())
796    {
797    // shrinking drag - remove from selection
798  1 removeElement(col);
799    }
800    else
801    {
802    // expanding drag - add to selection
803  22 addElement(col);
804    }
805    }
806    }
807    }