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