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