--- /dev/null
+package jalview.analysis;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * A helper class to sort all annotations associated with an alignment in
+ * various ways.
+ *
+ * @author gmcarstairs
+ *
+ */
+public class AnnotationSorter
+{
+
+ private final AlignmentI alignment;
+
+ public AnnotationSorter(AlignmentI alignmentI)
+ {
+ this.alignment = alignmentI;
+ }
+
+ /**
+ * Default comparator sorts as follows by annotation type within sequence
+ * order:
+ * <ul>
+ * <li>annotations with a reference to a sequence in the alignment are sorted
+ * on sequence ordering</li>
+ * <li>other annotations go 'at the end', with their mutual order unchanged</li>
+ * <li>within the same sequence ref, sort by label (non-case-sensitive)</li>
+ * </ul>
+ */
+ private final Comparator<? super AlignmentAnnotation> bySequenceAndType = new Comparator<AlignmentAnnotation>()
+ {
+ @Override
+ public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
+ {
+ if (o1 == null && o2 == null)
+ {
+ return 0;
+ }
+ if (o1 == null)
+ {
+ return -1;
+ }
+ if (o2 == null)
+ {
+ return 1;
+ }
+
+ /*
+ * Ignore label (keep existing ordering) for
+ * Conservation/Quality/Consensus etc
+ */
+ if (o1.sequenceRef == null && o2.sequenceRef == null)
+ {
+ return 0;
+ }
+ int sequenceOrder = compareSequences(o1, o2);
+ return sequenceOrder == 0 ? compareLabels(o1, o2) : sequenceOrder;
+ }
+ };
+
+ /**
+ * This comparator sorts as follows by sequence order within annotation type
+ * <ul>
+ * <li>annotations with a reference to a sequence in the alignment are sorted
+ * on label (non-case-sensitive)</li>
+ * <li>other annotations go 'at the end', with their mutual order unchanged</li>
+ * <li>within the same label, sort by order of the related sequences</li>
+ * </ul>
+ */
+ private final Comparator<? super AlignmentAnnotation> byTypeAndSequence = new Comparator<AlignmentAnnotation>()
+ {
+ @Override
+ public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2)
+ {
+ if (o1 == null && o2 == null)
+ {
+ return 0;
+ }
+ if (o1 == null)
+ {
+ return -1;
+ }
+ if (o2 == null)
+ {
+ return 1;
+ }
+
+ /*
+ * Ignore label (keep existing ordering) for
+ * Conservation/Quality/Consensus etc
+ */
+ if (o1.sequenceRef == null && o2.sequenceRef == null)
+ {
+ return 0;
+ }
+
+ /*
+ * Sort non-sequence-related after sequence-related.
+ */
+ if (o1.sequenceRef == null)
+ {
+ return 1;
+ }
+ if (o2.sequenceRef == null)
+ {
+ return -1;
+ }
+ int labelOrder = compareLabels(o1, o2);
+ return labelOrder == 0 ? compareSequences(o1, o2) : labelOrder;
+ }
+ };
+
+ /**
+ * Sort by annotation type (label), within sequence order.
+ * Non-sequence-related annotations sort to the end.
+ *
+ * @param alignmentAnnotations
+ */
+ public void sortBySequenceAndType(
+ AlignmentAnnotation[] alignmentAnnotations)
+ {
+ if (alignmentAnnotations != null)
+ {
+ synchronized (alignmentAnnotations)
+ {
+ Arrays.sort(alignmentAnnotations, bySequenceAndType);
+ }
+ }
+ }
+
+ /**
+ * Sort by sequence order within annotation type (label). Non-sequence-related
+ * annotations sort to the end.
+ *
+ * @param alignmentAnnotations
+ */
+ public void sortByTypeAndSequence(
+ AlignmentAnnotation[] alignmentAnnotations)
+ {
+ if (alignmentAnnotations != null)
+ {
+ synchronized (alignmentAnnotations)
+ {
+ Arrays.sort(alignmentAnnotations, byTypeAndSequence);
+ }
+ }
+ }
+
+ /**
+ * Non-case-sensitive comparison of annotation labels. Returns zero if either
+ * argument is null.
+ *
+ * @param o1
+ * @param o2
+ * @return
+ */
+ private int compareLabels(AlignmentAnnotation o1, AlignmentAnnotation o2)
+ {
+ if (o1 == null || o2 == null)
+ {
+ return 0;
+ }
+ String label1 = o1.label;
+ String label2 = o2.label;
+ if (label1 == null && label2 == null)
+ {
+ return 0;
+ }
+ if (label1 == null)
+ {
+ return -1;
+ }
+ if (label2 == null)
+ {
+ return 1;
+ }
+ return label1.toUpperCase().compareTo(label2.toUpperCase());
+ }
+
+ /**
+ * Comparison based on position of associated sequence (if any) in the
+ * alignment. Returns zero if either argument is null.
+ *
+ * @param o1
+ * @param o2
+ * @return
+ */
+ private int compareSequences(AlignmentAnnotation o1,
+ AlignmentAnnotation o2)
+ {
+ SequenceI seq1 = o1.sequenceRef;
+ SequenceI seq2 = o2.sequenceRef;
+ if (seq1 == null && seq2 == null)
+ {
+ return 0;
+ }
+ if (seq1 == null)
+ {
+ return 1;
+ }
+ if (seq2 == null)
+ {
+ return -1;
+ }
+ // get sequence index - but note -1 means 'at end' so needs special handling
+ int index1 = AlignmentUtils.getSequenceIndex(alignment, seq1);
+ int index2 = AlignmentUtils.getSequenceIndex(alignment, seq2);
+ if (index1 == index2)
+ {
+ return 0;
+ }
+ if (index1 == -1)
+ {
+ return -1;
+ }
+ if (index2 == -1)
+ {
+ return 1;
+ }
+ return Integer.compare(index1, index2);
+ }
+}