JAL-1152 annotation sorting preferences and help documentation
[jalview.git] / src / jalview / analysis / AnnotationSorter.java
1 package jalview.analysis;
2
3 import jalview.datamodel.AlignmentAnnotation;
4 import jalview.datamodel.AlignmentI;
5 import jalview.datamodel.SequenceI;
6
7 import java.util.Arrays;
8 import java.util.Comparator;
9
10 /**
11  * A helper class to sort all annotations associated with an alignment in
12  * various ways.
13  * 
14  * @author gmcarstairs
15  *
16  */
17 public class AnnotationSorter
18 {
19
20   /**
21    * enum for annotation sort options. The text description is used in the
22    * Preferences drop-down options. The enum name is saved in the preferences
23    * file.
24    * 
25    * @author gmcarstairs
26    *
27    */
28   public enum SequenceAnnotationOrder
29   {
30     // Text descriptions surface in the Preferences Sort by... options
31     SEQUENCE_AND_LABEL("Sequence"), LABEL_AND_SEQUENCE("Label"), NONE(
32             "No sort");
33
34     private String description;
35
36     private SequenceAnnotationOrder(String s)
37     {
38       description = s;
39     }
40
41     @Override
42     public String toString()
43     {
44       return description;
45     }
46
47     public static SequenceAnnotationOrder forDescription(String d) {
48       for (SequenceAnnotationOrder order : values())
49       {
50         if (order.toString().equals(d))
51         {
52           return order;
53         }
54       }
55       return null;
56     }
57   }
58
59   private final AlignmentI alignment;
60
61   private boolean showAutocalcAbove;
62
63   /**
64    * Constructor given an alignment and the location (top or bottom) of
65    * Consensus and similar.
66    * 
67    * @param alignmentI
68    * @param showAutocalculatedAbove
69    */
70   public AnnotationSorter(AlignmentI alignmentI,
71           boolean showAutocalculatedAbove)
72   {
73     this.alignment = alignmentI;
74     this.showAutocalcAbove = showAutocalculatedAbove;
75   }
76
77   /**
78    * Default comparator sorts as follows by annotation type within sequence
79    * order:
80    * <ul>
81    * <li>annotations with a reference to a sequence in the alignment are sorted
82    * on sequence ordering</li>
83    * <li>other annotations go 'at the end', with their mutual order unchanged</li>
84    * <li>within the same sequence ref, sort by label (non-case-sensitive)</li>
85    * </ul>
86    */
87   private final Comparator<? super AlignmentAnnotation> bySequenceAndLabel = new Comparator<AlignmentAnnotation>()
88   {
89     @Override
90     public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
91     {
92       if (o1 == null && o2 == null)
93       {
94         return 0;
95       }
96       if (o1 == null)
97       {
98         return -1;
99       }
100       if (o2 == null)
101       {
102         return 1;
103       }
104
105       /*
106        * Ignore label (keep existing ordering) for
107        * Conservation/Quality/Consensus etc
108        */
109       if (o1.sequenceRef == null && o2.sequenceRef == null)
110       {
111         return 0;
112       }
113       int sequenceOrder = compareSequences(o1, o2);
114       return sequenceOrder == 0 ? compareLabels(o1, o2) : sequenceOrder;
115     }
116   };
117
118   /**
119    * This comparator sorts as follows by sequence order within annotation type
120    * <ul>
121    * <li>annotations with a reference to a sequence in the alignment are sorted
122    * on label (non-case-sensitive)</li>
123    * <li>other annotations go 'at the end', with their mutual order unchanged</li>
124    * <li>within the same label, sort by order of the related sequences</li>
125    * </ul>
126    */
127   private final Comparator<? super AlignmentAnnotation> byLabelAndSequence = new Comparator<AlignmentAnnotation>()
128   {
129     @Override
130     public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
131     {
132       if (o1 == null && o2 == null)
133       {
134         return 0;
135       }
136       if (o1 == null)
137       {
138         return -1;
139       }
140       if (o2 == null)
141       {
142         return 1;
143       }
144
145       /*
146        * Ignore label (keep existing ordering) for
147        * Conservation/Quality/Consensus etc
148        */
149       if (o1.sequenceRef == null && o2.sequenceRef == null)
150       {
151         return 0;
152       }
153
154       /*
155        * Sort non-sequence-related before or after sequence-related.
156        */
157       if (o1.sequenceRef == null)
158       {
159         return showAutocalcAbove ? -1 : 1;
160       }
161       if (o2.sequenceRef == null)
162       {
163         return showAutocalcAbove ? 1 : -1;
164       }
165       int labelOrder = compareLabels(o1, o2);
166       return labelOrder == 0 ? compareSequences(o1, o2) : labelOrder;
167     }
168   };
169
170   /**
171    * noSort leaves sort order unchanged, within sequence- and
172    * non-sequence-related annotations, but may switch the ordering of these
173    * groups. Note this is guaranteed (at least in Java 7) as Arrays.sort() is
174    * guaranteed to be 'stable' (not change ordering of equal items).
175    */
176   private Comparator<? super AlignmentAnnotation> noSort = new Comparator<AlignmentAnnotation>()
177   {
178     @Override
179     public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
180     {
181       if (o1 != null && o2 != null)
182       {
183         if (o1.sequenceRef == null && o2.sequenceRef != null)
184         {
185           return showAutocalcAbove ? -1 : 1;
186         }
187         if (o1.sequenceRef != null && o2.sequenceRef == null)
188         {
189           return showAutocalcAbove ? 1 : -1;
190         }
191       }
192       return 0;
193     }
194   };
195
196   /**
197    * Sort by the specified ordering of sequence-specific annotations.
198    * 
199    * @param alignmentAnnotations
200    * @param order
201    */
202   public void sort(AlignmentAnnotation[] alignmentAnnotations,
203           SequenceAnnotationOrder order)
204   {
205     Comparator<? super AlignmentAnnotation> comparator = getComparator(order);
206
207     if (alignmentAnnotations != null)
208     {
209       synchronized (alignmentAnnotations)
210       {
211         Arrays.sort(alignmentAnnotations, comparator);
212       }
213     }
214   }
215
216   /**
217    * Get the comparator for the specified sort order.
218    * 
219    * @param order
220    * @return
221    */
222   private Comparator<? super AlignmentAnnotation> getComparator(
223           SequenceAnnotationOrder order)
224   {
225     if (order == null)
226     {
227       return noSort;
228     }
229     switch (order)
230     {
231     case NONE:
232       return this.noSort;
233     case SEQUENCE_AND_LABEL:
234       return this.bySequenceAndLabel;
235     case LABEL_AND_SEQUENCE:
236       return this.byLabelAndSequence;
237     default:
238       throw new UnsupportedOperationException(order.toString());
239     }
240   }
241
242   /**
243    * Non-case-sensitive comparison of annotation labels. Returns zero if either
244    * argument is null.
245    * 
246    * @param o1
247    * @param o2
248    * @return
249    */
250   private int compareLabels(AlignmentAnnotation o1, AlignmentAnnotation o2)
251   {
252     if (o1 == null || o2 == null)
253     {
254       return 0;
255     }
256     String label1 = o1.label;
257     String label2 = o2.label;
258     if (label1 == null && label2 == null)
259     {
260       return 0;
261     }
262     if (label1 == null)
263     {
264       return -1;
265     }
266     if (label2 == null)
267     {
268       return 1;
269     }
270     return label1.toUpperCase().compareTo(label2.toUpperCase());
271   }
272
273   /**
274    * Comparison based on position of associated sequence (if any) in the
275    * alignment. Returns zero if either argument is null.
276    * 
277    * @param o1
278    * @param o2
279    * @return
280    */
281   private int compareSequences(AlignmentAnnotation o1,
282           AlignmentAnnotation o2)
283   {
284     SequenceI seq1 = o1.sequenceRef;
285     SequenceI seq2 = o2.sequenceRef;
286     if (seq1 == null && seq2 == null)
287     {
288       return 0;
289     }
290     /*
291      * Sort non-sequence-related before or after sequence-related.
292      */
293     if (seq1 == null)
294     {
295       return showAutocalcAbove ? -1 : 1;
296     }
297     if (seq2 == null)
298     {
299       return showAutocalcAbove ? 1 : -1;
300     }
301     // get sequence index - but note -1 means 'at end' so needs special handling
302     int index1 = AlignmentUtils.getSequenceIndex(alignment, seq1);
303     int index2 = AlignmentUtils.getSequenceIndex(alignment, seq2);
304     if (index1 == index2)
305     {
306       return 0;
307     }
308     if (index1 == -1)
309     {
310       return -1;
311     }
312     if (index2 == -1)
313     {
314       return 1;
315     }
316     return Integer.compare(index1, index2);
317   }
318 }