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) {
50 for (SequenceAnnotationOrder order : values())
52 if (order.toString().equals(d))
61 // the alignment with respect to which annotations are sorted
62 private final AlignmentI alignment;
64 // user preference for placement of non-sequence annotations
65 private boolean showAutocalcAbove;
67 // working map of sequence index in alignment
68 private final Map<SequenceI, Integer> sequenceIndices = new HashMap<SequenceI, Integer>();
71 * Constructor given an alignment and the location (top or bottom) of
72 * Consensus and similar.
75 * @param showAutocalculatedAbove
77 public AnnotationSorter(AlignmentI alignmentI,
78 boolean showAutocalculatedAbove)
80 this.alignment = alignmentI;
81 this.showAutocalcAbove = showAutocalculatedAbove;
85 * Default comparator sorts as follows by annotation type within sequence
88 * <li>annotations with a reference to a sequence in the alignment are sorted
89 * on sequence ordering</li>
90 * <li>other annotations go 'at the end', with their mutual order unchanged</li>
91 * <li>within the same sequence ref, sort by label (non-case-sensitive)</li>
94 private final Comparator<? super AlignmentAnnotation> bySequenceAndLabel = new Comparator<AlignmentAnnotation>()
97 public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
99 if (o1 == null && o2 == null)
112 // TODO how to treat sequence-related autocalculated annotation
113 boolean o1auto = o1.autoCalculated && o1.sequenceRef == null;
114 boolean o2auto = o2.autoCalculated && o2.sequenceRef == null;
116 * Ignore label (keep existing ordering) for
117 * Conservation/Quality/Consensus etc
119 if (o1auto && o2auto)
125 * Sort autocalculated before or after sequence-related.
129 return showAutocalcAbove ? -1 : 1;
133 return showAutocalcAbove ? 1 : -1;
135 int sequenceOrder = compareSequences(o1, o2);
136 return sequenceOrder == 0 ? compareLabels(o1, o2) : sequenceOrder;
140 public String toString()
142 return "Sort by sequence and label";
147 * This comparator sorts as follows by sequence order within annotation type
149 * <li>annotations with a reference to a sequence in the alignment are sorted
150 * on label (non-case-sensitive)</li>
151 * <li>other annotations go 'at the end', with their mutual order unchanged</li>
152 * <li>within the same label, sort by order of the related sequences</li>
155 private final Comparator<? super AlignmentAnnotation> byLabelAndSequence = new Comparator<AlignmentAnnotation>()
158 public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
160 if (o1 == null && o2 == null)
173 // TODO how to treat sequence-related autocalculated annotation
174 boolean o1auto = o1.autoCalculated && o1.sequenceRef == null;
175 boolean o2auto = o2.autoCalculated && o2.sequenceRef == null;
177 * Ignore label (keep existing ordering) for
178 * Conservation/Quality/Consensus etc
180 if (o1auto && o2auto)
186 * Sort autocalculated before or after sequence-related.
190 return showAutocalcAbove ? -1 : 1;
194 return showAutocalcAbove ? 1 : -1;
196 int labelOrder = compareLabels(o1, o2);
197 return labelOrder == 0 ? compareSequences(o1, o2) : labelOrder;
201 public String toString()
203 return "Sort by label and sequence";
208 * noSort leaves sort order unchanged, within sequence- and autocalculated
209 * annotations, but may switch the ordering of these groups. Note this is
210 * guaranteed (at least in Java 7) as Arrays.sort() is guaranteed to be
211 * 'stable' (not change ordering of equal items).
213 private Comparator<? super AlignmentAnnotation> noSort = new Comparator<AlignmentAnnotation>()
216 public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
218 // TODO how to treat sequence-related autocalculated annotation
219 boolean o1auto = o1.autoCalculated && o1.sequenceRef == null;
220 boolean o2auto = o2.autoCalculated && o2.sequenceRef == null;
221 // TODO skip this test to allow customised ordering of all annotations
222 // - needs a third option: place autocalculated first / last / none
223 if (o1 != null && o2 != null)
225 if (o1auto && !o2auto)
227 return showAutocalcAbove ? -1 : 1;
229 if (!o1auto && o2auto)
231 return showAutocalcAbove ? 1 : -1;
238 public String toString()
245 * Sort by the specified ordering of sequence-specific annotations.
247 * @param alignmentAnnotations
250 public void sort(AlignmentAnnotation[] alignmentAnnotations,
251 SequenceAnnotationOrder order)
253 if (alignmentAnnotations == null)
257 // cache 'alignment sequence position' for the annotations
258 saveSequenceIndices(alignmentAnnotations);
260 Comparator<? super AlignmentAnnotation> comparator = getComparator(order);
262 if (alignmentAnnotations != null)
264 synchronized (alignmentAnnotations)
266 Arrays.sort(alignmentAnnotations, comparator);
272 * Calculate and save in a temporary map the position of each annotation's
273 * sequence (if it has one) in the alignment. Faster to do this once than for
274 * every annotation comparison.
276 * @param alignmentAnnotations
278 private void saveSequenceIndices(
279 AlignmentAnnotation[] alignmentAnnotations)
281 sequenceIndices.clear();
282 for (AlignmentAnnotation ann : alignmentAnnotations) {
283 SequenceI seq = ann.sequenceRef;
285 int index = AlignmentUtils.getSequenceIndex(alignment, seq);
286 sequenceIndices.put(seq, index);
292 * Get the comparator for the specified sort order.
297 private Comparator<? super AlignmentAnnotation> getComparator(
298 SequenceAnnotationOrder order)
308 case SEQUENCE_AND_LABEL:
309 return this.bySequenceAndLabel;
310 case LABEL_AND_SEQUENCE:
311 return this.byLabelAndSequence;
313 throw new UnsupportedOperationException(order.toString());
318 * Non-case-sensitive comparison of annotation labels. Returns zero if either
325 private int compareLabels(AlignmentAnnotation o1, AlignmentAnnotation o2)
327 if (o1 == null || o2 == null)
331 String label1 = o1.label;
332 String label2 = o2.label;
333 if (label1 == null && label2 == null)
345 return label1.toUpperCase().compareTo(label2.toUpperCase());
349 * Comparison based on position of associated sequence (if any) in the
350 * alignment. Returns zero if either argument is null.
356 private int compareSequences(AlignmentAnnotation o1,
357 AlignmentAnnotation o2)
359 SequenceI seq1 = o1.sequenceRef;
360 SequenceI seq2 = o2.sequenceRef;
361 if (seq1 == null && seq2 == null)
366 * Sort non-sequence-related before or after sequence-related.
370 return showAutocalcAbove ? -1 : 1;
374 return showAutocalcAbove ? 1 : -1;
376 // get sequence index - but note -1 means 'at end' so needs special handling
377 int index1 = sequenceIndices.get(seq1);
378 int index2 = sequenceIndices.get(seq2);
379 if (index1 == index2)
391 return Integer.compare(index1, index2);