1. Project Clover database Fri Dec 6 2024 13:47:14 GMT
  2. Package jalview.analysis

File AnnotationSorter.java

 

Coverage histogram

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

Code metrics

84
113
17
2
455
283
73
0.65
6.65
8.5
4.29

Classes

Class
Line #
Actions
AnnotationSorter 41 107 69
0.7438423674.4%
AnnotationSorter.SequenceAnnotationOrder 52 6 4
0.1818181918.2%
 

Contributing tests

This file is covered by 206 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.analysis;
22   
23    import java.util.Locale;
24   
25    import jalview.datamodel.AlignmentAnnotation;
26    import jalview.datamodel.AlignmentI;
27    import jalview.datamodel.SequenceI;
28   
29    import java.util.Arrays;
30    import java.util.Comparator;
31    import java.util.HashMap;
32    import java.util.Map;
33   
34    /**
35    * A helper class to sort all annotations associated with an alignment in
36    * various ways.
37    *
38    * @author gmcarstairs
39    *
40    */
 
41    public class AnnotationSorter
42    {
43   
44    /**
45    * enum for annotation sort options. The text description is used in the
46    * Preferences drop-down options. The enum name is saved in the preferences
47    * file.
48    *
49    * @author gmcarstairs
50    *
51    */
 
52    public enum SequenceAnnotationOrder
53    {
54    // Text descriptions surface in the Preferences Sort by... options
55    SEQUENCE_AND_LABEL("Sequence"), LABEL_AND_SEQUENCE("Label"),
56    NONE("No sort");
57   
58    private String description;
59   
 
60  150 toggle private SequenceAnnotationOrder(String s)
61    {
62  150 description = s;
63    }
64   
 
65  0 toggle @Override
66    public String toString()
67    {
68  0 return description;
69    }
70   
 
71  0 toggle public static SequenceAnnotationOrder forDescription(String d)
72    {
73  0 for (SequenceAnnotationOrder order : values())
74    {
75  0 if (order.toString().equals(d))
76    {
77  0 return order;
78    }
79    }
80  0 return null;
81    }
82    }
83   
84    // the alignment with respect to which annotations are sorted
85    private final AlignmentI alignment;
86   
87    // user preference for placement of non-sequence annotations
88    private boolean showAutocalcAbove;
89   
90    // working map of sequence index in alignment
91    private final Map<SequenceI, Integer> sequenceIndices = new HashMap<SequenceI, Integer>();
92   
93    /**
94    * Constructor given an alignment and the location (top or bottom) of
95    * Consensus and similar.
96    *
97    * @param alignmentI
98    * @param showAutocalculatedAbove
99    */
 
100  5579 toggle public AnnotationSorter(AlignmentI alignmentI,
101    boolean showAutocalculatedAbove)
102    {
103  5579 this.alignment = alignmentI;
104  5579 this.showAutocalcAbove = showAutocalculatedAbove;
105    }
106   
107    /**
108    * Default comparator sorts as follows by annotation type within sequence
109    * order:
110    * <ul>
111    * <li>annotations with a reference to a sequence in the alignment are sorted
112    * on sequence ordering</li>
113    * <li>other annotations go 'at the end', with their mutual order
114    * unchanged</li>
115    * <li>within the same sequence ref, sort by label (non-case-sensitive)</li>
116    * </ul>
117    */
118    private final Comparator<? super AlignmentAnnotation> bySequenceAndLabel = new Comparator<AlignmentAnnotation>()
119    {
 
120  170385 toggle @Override
121    public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
122    {
123  170385 if (o1 == null && o2 == null)
124    {
125  0 return 0;
126    }
127  170385 if (o1 == null)
128    {
129  0 return -1;
130    }
131  170385 if (o2 == null)
132    {
133  0 return 1;
134    }
135   
136    // TODO how to treat sequence-related autocalculated annotation
137  170385 boolean o1auto = o1.autoCalculated && o1.sequenceRef == null;
138  170385 boolean o2auto = o2.autoCalculated && o2.sequenceRef == null;
139    /*
140    * Ignore label (keep existing ordering) for
141    * Conservation/Quality/Consensus etc
142    */
143  170385 if (o1auto && o2auto)
144    {
145  2 return 0;
146    }
147   
148    /*
149    * Sort autocalculated before or after sequence-related.
150    */
151  170383 if (o1auto)
152    {
153  7 return showAutocalcAbove ? -1 : 1;
154    }
155  170376 if (o2auto)
156    {
157  1 return showAutocalcAbove ? 1 : -1;
158    }
159  170375 int computedOrder = compareSequences(o1, o2);
160  170375 if (computedOrder == 0)
161    {
162  13950 computedOrder = compareLabels(o1, o2);
163    }
164  170375 if (computedOrder == 0)
165    {
166  904 computedOrder = compareDescriptions(o1, o2);
167    }
168  170375 return computedOrder;
169    }
170   
 
171  0 toggle @Override
172    public String toString()
173    {
174  0 return "Sort by sequence and label";
175    }
176    };
177   
178    /**
179    * This comparator sorts as follows by sequence order within annotation type
180    * <ul>
181    * <li>annotations with a reference to a sequence in the alignment are sorted
182    * on label (non-case-sensitive)</li>
183    * <li>other annotations go 'at the end', with their mutual order
184    * unchanged</li>
185    * <li>within the same label, sort by order of the related sequences</li>
186    * </ul>
187    */
188    private final Comparator<? super AlignmentAnnotation> byLabelAndSequence = new Comparator<AlignmentAnnotation>()
189    {
 
190  172125 toggle @Override
191    public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
192    {
193  172125 if (o1 == null && o2 == null)
194    {
195  0 return 0;
196    }
197  172125 if (o1 == null)
198    {
199  0 return -1;
200    }
201  172125 if (o2 == null)
202    {
203  0 return 1;
204    }
205   
206    // TODO how to treat sequence-related autocalculated annotation
207  172125 boolean o1auto = o1.autoCalculated && o1.sequenceRef == null;
208  172125 boolean o2auto = o2.autoCalculated && o2.sequenceRef == null;
209    /*
210    * Ignore label (keep existing ordering) for
211    * Conservation/Quality/Consensus etc
212    */
213  172125 if (o1auto && o2auto)
214    {
215  2 return 0;
216    }
217   
218    /*
219    * Sort autocalculated before or after sequence-related.
220    */
221  172123 if (o1auto)
222    {
223  7 return showAutocalcAbove ? -1 : 1;
224    }
225  172116 if (o2auto)
226    {
227  1 return showAutocalcAbove ? 1 : -1;
228    }
229  172115 int labelOrder = compareLabels(o1, o2);
230  172115 return labelOrder == 0 ? compareSequences(o1, o2) : labelOrder;
231    }
232   
 
233  0 toggle @Override
234    public String toString()
235    {
236  0 return "Sort by label and sequence";
237    }
238    };
239   
240    /**
241    * noSort leaves sort order unchanged, within sequence- and autocalculated
242    * annotations, but may switch the ordering of these groups. Note this is
243    * guaranteed (at least in Java 7) as Arrays.sort() is guaranteed to be
244    * 'stable' (not change ordering of equal items).
245    */
246    private Comparator<? super AlignmentAnnotation> noSort = new Comparator<AlignmentAnnotation>()
247    {
 
248  22167 toggle @Override
249    public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
250    {
251    // TODO how to treat sequence-related autocalculated annotation
252  22167 boolean o1auto = o1.autoCalculated && o1.sequenceRef == null;
253  22167 boolean o2auto = o2.autoCalculated && o2.sequenceRef == null;
254    // TODO skip this test to allow customised ordering of all annotations
255    // - needs a third option: place autocalculated first / last / none
256  22167 if (o1 != null && o2 != null)
257    {
258  22167 if (o1auto && !o2auto)
259    {
260  1729 return showAutocalcAbove ? -1 : 1;
261    }
262  20438 if (!o1auto && o2auto)
263    {
264  380 return showAutocalcAbove ? 1 : -1;
265    }
266    }
267  20058 return 0;
268    }
269   
 
270  0 toggle @Override
271    public String toString()
272    {
273  0 return "No sort";
274    }
275    };
276   
277    /**
278    * Sort by the specified ordering of sequence-specific annotations.
279    *
280    * @param alignmentAnnotations
281    * @param order
282    */
 
283  5585 toggle public void sort(AlignmentAnnotation[] alignmentAnnotations,
284    SequenceAnnotationOrder order)
285    {
286  5585 if (alignmentAnnotations == null)
287    {
288  0 return;
289    }
290    // cache 'alignment sequence position' for the annotations
291  5585 saveSequenceIndices(alignmentAnnotations);
292   
293  5585 Comparator<? super AlignmentAnnotation> comparator = getComparator(
294    order);
295   
296  5585 if (alignmentAnnotations != null)
297    {
298  5585 synchronized (alignmentAnnotations)
299    {
300  5585 Arrays.sort(alignmentAnnotations, comparator);
301    }
302    }
303    }
304   
305    /**
306    * Calculate and save in a temporary map the position of each annotation's
307    * sequence (if it has one) in the alignment. Faster to do this once than for
308    * every annotation comparison.
309    *
310    * @param alignmentAnnotations
311    */
 
312  5585 toggle private void saveSequenceIndices(
313    AlignmentAnnotation[] alignmentAnnotations)
314    {
315  5585 sequenceIndices.clear();
316  5585 for (AlignmentAnnotation ann : alignmentAnnotations)
317    {
318  82861 SequenceI seq = ann.sequenceRef;
319  82863 if (seq != null)
320    {
321  59527 int index = AlignmentUtils.getSequenceIndex(alignment, seq);
322  59527 sequenceIndices.put(seq, index);
323    }
324    }
325    }
326   
327    /**
328    * Get the comparator for the specified sort order.
329    *
330    * @param order
331    * @return
332    */
 
333  5584 toggle private Comparator<? super AlignmentAnnotation> getComparator(
334    SequenceAnnotationOrder order)
335    {
336  5585 if (order == null)
337    {
338  0 return noSort;
339    }
340  5584 switch (order)
341    {
342  5566 case NONE:
343  5566 return this.noSort;
344  8 case SEQUENCE_AND_LABEL:
345  8 return this.bySequenceAndLabel;
346  11 case LABEL_AND_SEQUENCE:
347  11 return this.byLabelAndSequence;
348  0 default:
349  0 throw new UnsupportedOperationException(order.toString());
350    }
351    }
352   
353    /**
354    * Non-case-sensitive comparison of annotation labels. Returns zero if either
355    * argument is null.
356    *
357    * @param o1
358    * @param o2
359    * @return
360    */
 
361  186065 toggle private int compareLabels(AlignmentAnnotation o1, AlignmentAnnotation o2)
362    {
363  186065 if (o1 == null || o2 == null)
364    {
365  0 return 0;
366    }
367  186065 String label1 = o1.label;
368  186065 String label2 = o2.label;
369  186065 return compareString(label1, label2);
370    }
371   
372    /**
373    * Non-case-sensitive comparison of annotation descriptions. Returns zero if
374    * either argument is null.
375    *
376    * @param o1
377    * @param o2
378    * @return
379    */
 
380  904 toggle private int compareDescriptions(AlignmentAnnotation o1,
381    AlignmentAnnotation o2)
382    {
383  904 if (o1 == null || o2 == null)
384    {
385  0 return 0;
386    }
387  904 String label1 = o1.description;
388  904 String label2 = o2.description;
389  904 return compareString(label1, label2);
390    }
391   
 
392  186969 toggle private int compareString(String label1, String label2)
393    {
394  186969 if (label1 == null && label2 == null)
395    {
396  0 return 0;
397    }
398  186969 if (label1 == null)
399    {
400  0 return -1;
401    }
402  186969 if (label2 == null)
403    {
404  0 return 1;
405    }
406  186969 return label1.toUpperCase(Locale.ROOT)
407    .compareTo(label2.toUpperCase(Locale.ROOT));
408    }
409   
410    /**
411    * Comparison based on position of associated sequence (if any) in the
412    * alignment. Returns zero if either argument is null.
413    *
414    * @param o1
415    * @param o2
416    * @return
417    */
 
418  237526 toggle private int compareSequences(AlignmentAnnotation o1,
419    AlignmentAnnotation o2)
420    {
421  237526 SequenceI seq1 = o1.sequenceRef;
422  237526 SequenceI seq2 = o2.sequenceRef;
423  237526 if (seq1 == null && seq2 == null)
424    {
425  0 return 0;
426    }
427    /*
428    * Sort non-sequence-related before or after sequence-related.
429    */
430  237526 if (seq1 == null)
431    {
432  0 return showAutocalcAbove ? -1 : 1;
433    }
434  237526 if (seq2 == null)
435    {
436  0 return showAutocalcAbove ? 1 : -1;
437    }
438    // get sequence index - but note -1 means 'at end' so needs special handling
439  237526 int index1 = sequenceIndices.get(seq1);
440  237526 int index2 = sequenceIndices.get(seq2);
441  237526 if (index1 == index2)
442    {
443  15758 return 0;
444    }
445  221768 if (index1 == -1)
446    {
447  0 return -1;
448    }
449  221768 if (index2 == -1)
450    {
451  0 return 1;
452    }
453  221768 return Integer.compare(index1, index2);
454    }
455    }