1 package jalview.analysis;
3 import jalview.datamodel.AlignmentAnnotation;
4 import jalview.datamodel.AlignmentI;
5 import jalview.datamodel.SequenceI;
7 import java.util.Arrays;
8 import java.util.Comparator;
9 import java.util.HashMap;
13 * A helper class to sort all annotations associated with an alignment in
19 public class AnnotationSorter
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
30 public enum SequenceAnnotationOrder
32 // Text descriptions surface in the Preferences Sort by... options
33 SEQUENCE_AND_LABEL("Sequence"), LABEL_AND_SEQUENCE("Label"), NONE(
36 private String description;
38 private SequenceAnnotationOrder(String s)
44 public String toString()
49 public static SequenceAnnotationOrder forDescription(String d)
51 for (SequenceAnnotationOrder order : values())
53 if (order.toString().equals(d))
62 // the alignment with respect to which annotations are sorted
63 private final AlignmentI alignment;
65 // user preference for placement of non-sequence annotations
66 private boolean showAutocalcAbove;
68 // working map of sequence index in alignment
69 private final Map<SequenceI, Integer> sequenceIndices = new HashMap<SequenceI, Integer>();
72 * Constructor given an alignment and the location (top or bottom) of
73 * Consensus and similar.
76 * @param showAutocalculatedAbove
78 public AnnotationSorter(AlignmentI alignmentI,
79 boolean showAutocalculatedAbove)
81 this.alignment = alignmentI;
82 this.showAutocalcAbove = showAutocalculatedAbove;
86 * Default comparator sorts as follows by annotation type within sequence
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>
95 private final Comparator<? super AlignmentAnnotation> bySequenceAndLabel = new Comparator<AlignmentAnnotation>()
98 public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
100 if (o1 == null && o2 == null)
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;
117 * Ignore label (keep existing ordering) for
118 * Conservation/Quality/Consensus etc
120 if (o1auto && o2auto)
126 * Sort autocalculated before or after sequence-related.
130 return showAutocalcAbove ? -1 : 1;
134 return showAutocalcAbove ? 1 : -1;
136 int sequenceOrder = compareSequences(o1, o2);
137 return sequenceOrder == 0 ? compareLabels(o1, o2) : sequenceOrder;
141 public String toString()
143 return "Sort by sequence and label";
148 * This comparator sorts as follows by sequence order within annotation type
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>
156 private final Comparator<? super AlignmentAnnotation> byLabelAndSequence = new Comparator<AlignmentAnnotation>()
159 public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
161 if (o1 == null && o2 == null)
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;
178 * Ignore label (keep existing ordering) for
179 * Conservation/Quality/Consensus etc
181 if (o1auto && o2auto)
187 * Sort autocalculated before or after sequence-related.
191 return showAutocalcAbove ? -1 : 1;
195 return showAutocalcAbove ? 1 : -1;
197 int labelOrder = compareLabels(o1, o2);
198 return labelOrder == 0 ? compareSequences(o1, o2) : labelOrder;
202 public String toString()
204 return "Sort by label and sequence";
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).
214 private Comparator<? super AlignmentAnnotation> noSort = new Comparator<AlignmentAnnotation>()
217 public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
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)
226 if (o1auto && !o2auto)
228 return showAutocalcAbove ? -1 : 1;
230 if (!o1auto && o2auto)
232 return showAutocalcAbove ? 1 : -1;
239 public String toString()
246 * Sort by the specified ordering of sequence-specific annotations.
248 * @param alignmentAnnotations
251 public void sort(AlignmentAnnotation[] alignmentAnnotations,
252 SequenceAnnotationOrder order)
254 if (alignmentAnnotations == null)
258 // cache 'alignment sequence position' for the annotations
259 saveSequenceIndices(alignmentAnnotations);
261 Comparator<? super AlignmentAnnotation> comparator = getComparator(order);
263 if (alignmentAnnotations != null)
265 synchronized (alignmentAnnotations)
267 Arrays.sort(alignmentAnnotations, comparator);
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.
277 * @param alignmentAnnotations
279 private void saveSequenceIndices(
280 AlignmentAnnotation[] alignmentAnnotations)
282 sequenceIndices.clear();
283 for (AlignmentAnnotation ann : alignmentAnnotations)
285 SequenceI seq = ann.sequenceRef;
288 int index = AlignmentUtils.getSequenceIndex(alignment, seq);
289 sequenceIndices.put(seq, index);
295 * Get the comparator for the specified sort order.
300 private Comparator<? super AlignmentAnnotation> getComparator(
301 SequenceAnnotationOrder order)
311 case SEQUENCE_AND_LABEL:
312 return this.bySequenceAndLabel;
313 case LABEL_AND_SEQUENCE:
314 return this.byLabelAndSequence;
316 throw new UnsupportedOperationException(order.toString());
321 * Non-case-sensitive comparison of annotation labels. Returns zero if either
328 private int compareLabels(AlignmentAnnotation o1, AlignmentAnnotation o2)
330 if (o1 == null || o2 == null)
334 String label1 = o1.label;
335 String label2 = o2.label;
336 if (label1 == null && label2 == null)
348 return label1.toUpperCase().compareTo(label2.toUpperCase());
352 * Comparison based on position of associated sequence (if any) in the
353 * alignment. Returns zero if either argument is null.
359 private int compareSequences(AlignmentAnnotation o1,
360 AlignmentAnnotation o2)
362 SequenceI seq1 = o1.sequenceRef;
363 SequenceI seq2 = o2.sequenceRef;
364 if (seq1 == null && seq2 == null)
369 * Sort non-sequence-related before or after sequence-related.
373 return showAutocalcAbove ? -1 : 1;
377 return showAutocalcAbove ? 1 : -1;
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)
394 return Integer.compare(index1, index2);