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 { public enum SortOrder { SEQUENCE_AND_TYPE, TYPE_AND_SEQUENCE } private final AlignmentI alignment; public AnnotationSorter(AlignmentI alignmentI) { this.alignment = alignmentI; } /** * Default comparator sorts as follows by annotation type within sequence * order: * */ private final Comparator bySequenceAndType = new Comparator() { @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 * */ private final Comparator byTypeAndSequence = new Comparator() { @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; } }; private final Comparator DEFAULT_COMPARATOR = bySequenceAndType; /** * Sort by the specified order. * * @param alignmentAnnotations * @param order */ public void sort(AlignmentAnnotation[] alignmentAnnotations, SortOrder order) { Comparator comparator = getComparator(order); if (alignmentAnnotations != null) { synchronized (alignmentAnnotations) { Arrays.sort(alignmentAnnotations, comparator); } } } /** * Get the comparator for the specified sort order. * * @param order * @return */ private Comparator getComparator( SortOrder order) { if (order == null) { return DEFAULT_COMPARATOR; } switch (order) { case SEQUENCE_AND_TYPE: return this.bySequenceAndType; case TYPE_AND_SEQUENCE: return this.byTypeAndSequence; default: throw new UnsupportedOperationException(order.toString()); } } /** * 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); } }