JAL-1152 prototype of new Annotations menu with sort options
[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   private final AlignmentI alignment;
21
22   public AnnotationSorter(AlignmentI alignmentI)
23   {
24     this.alignment = alignmentI;
25   }
26
27   /**
28    * Default comparator sorts as follows by annotation type within sequence
29    * order:
30    * <ul>
31    * <li>annotations with a reference to a sequence in the alignment are sorted
32    * on sequence ordering</li>
33    * <li>other annotations go 'at the end', with their mutual order unchanged</li>
34    * <li>within the same sequence ref, sort by label (non-case-sensitive)</li>
35    * </ul>
36    */
37   private final Comparator<? super AlignmentAnnotation> bySequenceAndType = new Comparator<AlignmentAnnotation>()
38   {
39     @Override
40     public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
41     {
42       if (o1 == null && o2 == null)
43       {
44         return 0;
45       }
46       if (o1 == null)
47       {
48         return -1;
49       }
50       if (o2 == null)
51       {
52         return 1;
53       }
54
55       /*
56        * Ignore label (keep existing ordering) for
57        * Conservation/Quality/Consensus etc
58        */
59       if (o1.sequenceRef == null && o2.sequenceRef == null)
60       {
61         return 0;
62       }
63       int sequenceOrder = compareSequences(o1, o2);
64       return sequenceOrder == 0 ? compareLabels(o1, o2) : sequenceOrder;
65     }
66   };
67
68   /**
69    * This comparator sorts as follows by sequence order within annotation type
70    * <ul>
71    * <li>annotations with a reference to a sequence in the alignment are sorted
72    * on label (non-case-sensitive)</li>
73    * <li>other annotations go 'at the end', with their mutual order unchanged</li>
74    * <li>within the same label, sort by order of the related sequences</li>
75    * </ul>
76    */
77   private final Comparator<? super AlignmentAnnotation> byTypeAndSequence = new Comparator<AlignmentAnnotation>()
78   {
79     @Override
80     public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
81     {
82       if (o1 == null && o2 == null)
83       {
84         return 0;
85       }
86       if (o1 == null)
87       {
88         return -1;
89       }
90       if (o2 == null)
91       {
92         return 1;
93       }
94
95       /*
96        * Ignore label (keep existing ordering) for
97        * Conservation/Quality/Consensus etc
98        */
99       if (o1.sequenceRef == null && o2.sequenceRef == null)
100       {
101         return 0;
102       }
103
104       /*
105        * Sort non-sequence-related after sequence-related.
106        */
107       if (o1.sequenceRef == null)
108       {
109         return 1;
110       }
111       if (o2.sequenceRef == null)
112       {
113         return -1;
114       }
115       int labelOrder = compareLabels(o1, o2);
116       return labelOrder == 0 ? compareSequences(o1, o2) : labelOrder;
117     }
118   };
119
120   /**
121    * Sort by annotation type (label), within sequence order.
122    * Non-sequence-related annotations sort to the end.
123    * 
124    * @param alignmentAnnotations
125    */
126   public void sortBySequenceAndType(
127           AlignmentAnnotation[] alignmentAnnotations)
128   {
129     if (alignmentAnnotations != null)
130     {
131       synchronized (alignmentAnnotations)
132       {
133         Arrays.sort(alignmentAnnotations, bySequenceAndType);
134       }
135     }
136   }
137
138   /**
139    * Sort by sequence order within annotation type (label). Non-sequence-related
140    * annotations sort to the end.
141    * 
142    * @param alignmentAnnotations
143    */
144   public void sortByTypeAndSequence(
145           AlignmentAnnotation[] alignmentAnnotations)
146   {
147     if (alignmentAnnotations != null)
148     {
149       synchronized (alignmentAnnotations)
150       {
151         Arrays.sort(alignmentAnnotations, byTypeAndSequence);
152       }
153     }
154   }
155
156   /**
157    * Non-case-sensitive comparison of annotation labels. Returns zero if either
158    * argument is null.
159    * 
160    * @param o1
161    * @param o2
162    * @return
163    */
164   private int compareLabels(AlignmentAnnotation o1, AlignmentAnnotation o2)
165   {
166     if (o1 == null || o2 == null)
167     {
168       return 0;
169     }
170     String label1 = o1.label;
171     String label2 = o2.label;
172     if (label1 == null && label2 == null)
173     {
174       return 0;
175     }
176     if (label1 == null)
177     {
178       return -1;
179     }
180     if (label2 == null)
181     {
182       return 1;
183     }
184     return label1.toUpperCase().compareTo(label2.toUpperCase());
185   }
186
187   /**
188    * Comparison based on position of associated sequence (if any) in the
189    * alignment. Returns zero if either argument is null.
190    * 
191    * @param o1
192    * @param o2
193    * @return
194    */
195   private int compareSequences(AlignmentAnnotation o1,
196           AlignmentAnnotation o2)
197   {
198     SequenceI seq1 = o1.sequenceRef;
199     SequenceI seq2 = o2.sequenceRef;
200     if (seq1 == null && seq2 == null)
201     {
202       return 0;
203     }
204     if (seq1 == null)
205     {
206       return 1;
207     }
208     if (seq2 == null)
209     {
210       return -1;
211     }
212     // get sequence index - but note -1 means 'at end' so needs special handling
213     int index1 = AlignmentUtils.getSequenceIndex(alignment, seq1);
214     int index2 = AlignmentUtils.getSequenceIndex(alignment, seq2);
215     if (index1 == index2)
216     {
217       return 0;
218     }
219     if (index1 == -1)
220     {
221       return -1;
222     }
223     if (index2 == -1)
224     {
225       return 1;
226     }
227     return Integer.compare(index1, index2);
228   }
229 }