From: gmungoc Date: Thu, 30 Oct 2014 14:18:42 +0000 (+0000) Subject: JAL-1152 prototype of new Annotations menu with sort options X-Git-Tag: Release_2_8_2b1^2~36^2~15 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=ec493e27abc6b3be84b3c8a873c295a3f589bd53;p=jalview.git JAL-1152 prototype of new Annotations menu with sort options --- diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index f23275a..24b184f 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -102,6 +102,7 @@ action.find_all = Find all action.find_next = Find next action.file = File action.view = View +action.annotations = Annotations action.change_params = Change Parameters action.apply = Apply action.apply_threshold_all_groups = Apply threshold to all groups @@ -479,6 +480,8 @@ label.sort_by = Sort by label.sort_by_score = Sort by Score label.sort_by_density = Sort by Density label.sequence_sort_by_density = Sequence sort by Density +label.sort_annotations_by_sequence = Sort annotations by sequence order +label.sort_annotations_by_type = Sort annotations by type label.reveal = Reveal label.hide_columns = Hide Columns label.load_jalview_annotations = Load Jalview Annotations or Features File diff --git a/src/jalview/analysis/AlignmentAnnotationUtils.java b/src/jalview/analysis/AlignmentAnnotationUtils.java index b5f77fd..ef8e670 100644 --- a/src/jalview/analysis/AlignmentAnnotationUtils.java +++ b/src/jalview/analysis/AlignmentAnnotationUtils.java @@ -1,6 +1,7 @@ package jalview.analysis; import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; import jalview.renderer.AnnotationRenderer; @@ -196,8 +197,7 @@ public class AlignmentAnnotationUtils * @param anns * @return */ - public static List asList( - AlignmentAnnotation[] anns) + public static List asList(AlignmentAnnotation[] anns) { // TODO use AlignmentAnnotationI instead when it exists return (anns == null ? Collections. emptyList() diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index 2feeb91..929a855 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -20,12 +20,12 @@ */ package jalview.analysis; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceI; + import java.util.ArrayList; import java.util.List; -import jalview.datamodel.SequenceI; -import jalview.datamodel.AlignmentI; - /** * grab bag of useful alignment manipulation operations Expect these to be * refactored elsewhere at some point. @@ -124,4 +124,28 @@ public class AlignmentUtils newAl.setDataset(core.getDataset()); return newAl; } + + /** + * Returns the index (zero-based position) of a sequence in an alignment, or + * -1 if not found. + * + * @param al + * @param seq + * @return + */ + public static int getSequenceIndex(AlignmentI al, SequenceI seq) + { + int result = -1; + int pos = 0; + for (SequenceI alSeq : al.getSequences()) + { + if (alSeq == seq) + { + result = pos; + break; + } + pos++; + } + return result; + } } diff --git a/src/jalview/analysis/AnnotationSorter.java b/src/jalview/analysis/AnnotationSorter.java new file mode 100644 index 0000000..4ee2b86 --- /dev/null +++ b/src/jalview/analysis/AnnotationSorter.java @@ -0,0 +1,229 @@ +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: + *
    + *
  • annotations with a reference to a sequence in the alignment are sorted + * on sequence ordering
  • + *
  • other annotations go 'at the end', with their mutual order unchanged
  • + *
  • within the same sequence ref, sort by label (non-case-sensitive)
  • + *
+ */ + 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 + *
    + *
  • annotations with a reference to a sequence in the alignment are sorted + * on label (non-case-sensitive)
  • + *
  • other annotations go 'at the end', with their mutual order unchanged
  • + *
  • within the same label, sort by order of the related sequences
  • + *
+ */ + 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; + } + }; + + /** + * 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); + } +} diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index d9c0c6a..a048721 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -23,6 +23,7 @@ package jalview.gui; import jalview.analysis.AAFrequency; import jalview.analysis.AlignmentSorter; import jalview.analysis.AlignmentUtils; +import jalview.analysis.AnnotationSorter; import jalview.analysis.Conservation; import jalview.analysis.CrossRef; import jalview.analysis.NJTree; @@ -377,7 +378,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, .getKeyCode() >= KeyEvent.VK_NUMPAD0 && evt .getKeyCode() <= KeyEvent.VK_NUMPAD9)) && Character.isDigit(evt.getKeyChar())) + { alignPanel.seqPanel.numberPressed(evt.getKeyChar()); + } switch (evt.getKeyCode()) { @@ -389,32 +392,48 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, case KeyEvent.VK_DOWN: if (evt.isAltDown() || !viewport.cursorMode) + { moveSelectedSequences(false); + } if (viewport.cursorMode) + { alignPanel.seqPanel.moveCursor(0, 1); + } break; case KeyEvent.VK_UP: if (evt.isAltDown() || !viewport.cursorMode) + { moveSelectedSequences(true); + } if (viewport.cursorMode) + { alignPanel.seqPanel.moveCursor(0, -1); + } break; case KeyEvent.VK_LEFT: if (evt.isAltDown() || !viewport.cursorMode) + { slideSequences(false, alignPanel.seqPanel.getKeyboardNo1()); + } else + { alignPanel.seqPanel.moveCursor(-1, 0); + } break; case KeyEvent.VK_RIGHT: if (evt.isAltDown() || !viewport.cursorMode) + { slideSequences(true, alignPanel.seqPanel.getKeyboardNo1()); + } else + { alignPanel.seqPanel.moveCursor(1, 0); + } break; case KeyEvent.VK_SPACE: @@ -551,14 +570,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { case KeyEvent.VK_LEFT: if (evt.isAltDown() || !viewport.cursorMode) + { viewport.firePropertyChange("alignment", null, viewport .getAlignment().getSequences()); + } break; case KeyEvent.VK_RIGHT: if (evt.isAltDown() || !viewport.cursorMode) + { viewport.firePropertyChange("alignment", null, viewport .getAlignment().getSequences()); + } break; } } @@ -1458,7 +1481,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, protected void undoMenuItem_actionPerformed(ActionEvent e) { if (viewport.historyList.empty()) + { return; + } CommandI command = (CommandI) viewport.historyList.pop(); viewport.redoList.push(command); command.undoCommand(getViewAlignments()); @@ -1611,37 +1636,53 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, for (int i = 0; i < viewport.getAlignment().getHeight(); i++) { if (!sg.contains(viewport.getAlignment().getSequenceAt(i))) + { invertGroup.add(viewport.getAlignment().getSequenceAt(i)); + } } SequenceI[] seqs1 = sg.toArray(new SequenceI[0]); SequenceI[] seqs2 = new SequenceI[invertGroup.size()]; for (int i = 0; i < invertGroup.size(); i++) + { seqs2[i] = (SequenceI) invertGroup.elementAt(i); + } SlideSequencesCommand ssc; if (right) + { ssc = new SlideSequencesCommand("Slide Sequences", seqs2, seqs1, size, viewport.getGapCharacter()); + } else + { ssc = new SlideSequencesCommand("Slide Sequences", seqs1, seqs2, size, viewport.getGapCharacter()); + } int groupAdjustment = 0; if (ssc.getGapsInsertedBegin() && right) { if (viewport.cursorMode) + { alignPanel.seqPanel.moveCursor(size, 0); + } else + { groupAdjustment = size; + } } else if (!ssc.getGapsInsertedBegin() && !right) { if (viewport.cursorMode) + { alignPanel.seqPanel.moveCursor(-size, 0); + } else + { groupAdjustment = -size; + } } if (groupAdjustment != 0) @@ -1662,7 +1703,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } if (!appendHistoryItem) + { addHistoryItem(ssc); + } repaint(); } @@ -1997,7 +2040,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { AlignmentAnnotation sann[] = sequences[i].getAnnotation(); if (sann == null) + { continue; + } for (int avnum = 0; avnum < alview.length; avnum++) { if (alview[avnum] != alignment) @@ -4643,7 +4688,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, if (ds.getSequences() == null || !ds.getSequences().contains( sprods[s].getDatasetSequence())) + { ds.addSequence(sprods[s].getDatasetSequence()); + } sprods[s].updatePDBIds(); } Alignment al = new Alignment(sprods); @@ -5734,6 +5781,26 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } this.alignPanel.paintAlignment(true); } + + @Override + protected void sortAnnotationsByType_actionPerformed() + { + AnnotationSorter sorter = new AnnotationSorter( + this.alignPanel.getAlignment()); + sorter.sortByTypeAndSequence(this.alignPanel.getAlignment() + .getAlignmentAnnotation()); + alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState()); + } + + @Override + protected void sortAnnotationsBySequence_actionPerformed() + { + AnnotationSorter sorter = new AnnotationSorter( + this.alignPanel.getAlignment()); + sorter.sortBySequenceAndType(this.alignPanel.getAlignment() + .getAlignmentAnnotation()); + alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState()); + } } class PrintThread extends Thread diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 3da4494..8e0e6e3 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -22,6 +22,7 @@ package jalview.gui; import jalview.analysis.AAFrequency; import jalview.analysis.AlignmentAnnotationUtils; +import jalview.analysis.AnnotationSorter; import jalview.analysis.Conservation; import jalview.commands.ChangeCaseCommand; import jalview.commands.EditCommand; @@ -1875,7 +1876,6 @@ public class PopupMenu extends JPopupMenu * Add annotations at the top of the annotation, in the same order as their * related sequences. */ - int insertPosition = 0; for (SequenceI seq : candidates.keySet()) { for (AlignmentAnnotation ann : candidates.get(seq)) @@ -1896,10 +1896,15 @@ public class PopupMenu extends JPopupMenu // adjust for gaps copyAnn.adjustForAlignment(); // add to the alignment and set visible - this.ap.getAlignment().addAnnotation(copyAnn, insertPosition++); + this.ap.getAlignment().addAnnotation(copyAnn); copyAnn.visible = true; } } + // TODO: save annotation sort order on AlignViewport + // do sorting from AlignmentPanel.updateAnnotation() + new AnnotationSorter(this.ap.getAlignment()) + .sortBySequenceAndType(this.ap.getAlignment() + .getAlignmentAnnotation()); refresh(); } diff --git a/src/jalview/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index afb0dc1..5e60a85 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -65,6 +65,8 @@ public class GAlignFrame extends JInternalFrame protected JMenu viewMenu = new JMenu(); + protected JMenu annotationsMenu = new JMenu(); + protected JMenu colourMenu = new JMenu(); protected JMenu calculateMenu = new JMenu(); @@ -308,6 +310,10 @@ public class GAlignFrame extends JInternalFrame protected JMenuItem hideAllAnnotations = new JMenuItem(); + protected JMenuItem sortAnnBySequence = new JMenuItem(); + + protected JMenuItem sortAnnByType = new JMenuItem(); + protected JCheckBoxMenuItem hiddenMarkers = new JCheckBoxMenuItem(); JMenuItem invertColSel = new JMenuItem(); @@ -611,6 +617,7 @@ public class GAlignFrame extends JInternalFrame }); editMenu.setText(MessageManager.getString("action.edit")); viewMenu.setText(MessageManager.getString("action.view")); + annotationsMenu.setText(MessageManager.getString("action.annotations")); colourMenu.setText(MessageManager.getString("action.colour")); calculateMenu.setText(MessageManager.getString("action.calculate")); webService.setText(MessageManager.getString("action.web_service")); @@ -1098,6 +1105,26 @@ public class GAlignFrame extends JInternalFrame hideAllAnnotations_actionPerformed(); } }); + sortAnnBySequence.setText(MessageManager + .getString("label.sort_annotations_by_sequence")); + sortAnnBySequence.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + sortAnnotationsBySequence_actionPerformed(); + } + }); + sortAnnByType.setText(MessageManager + .getString("label.sort_annotations_by_type")); + sortAnnByType.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + sortAnnotationsByType_actionPerformed(); + } + }); colourTextMenuItem.setText(MessageManager .getString("label.colour_text")); colourTextMenuItem @@ -2101,6 +2128,7 @@ public class GAlignFrame extends JInternalFrame alignFrameMenuBar.add(editMenu); alignFrameMenuBar.add(selectMenu); alignFrameMenuBar.add(viewMenu); + alignFrameMenuBar.add(annotationsMenu); alignFrameMenuBar.add(formatMenu); alignFrameMenuBar.add(colourMenu); alignFrameMenuBar.add(calculateMenu); @@ -2150,9 +2178,11 @@ public class GAlignFrame extends JInternalFrame viewMenu.add(hideMenu); viewMenu.addSeparator(); viewMenu.add(followHighlightMenuItem); - viewMenu.add(annotationPanelMenuItem); - viewMenu.add(showAllAnnotations); - viewMenu.add(hideAllAnnotations); + annotationsMenu.add(annotationPanelMenuItem); + annotationsMenu.add(showAllAnnotations); + annotationsMenu.add(hideAllAnnotations); + annotationsMenu.add(sortAnnBySequence); + annotationsMenu.add(sortAnnByType); autoAnnMenu.add(applyAutoAnnotationSettings); autoAnnMenu.add(showConsensusHistogram); autoAnnMenu.add(showSequenceLogo); @@ -2160,7 +2190,7 @@ public class GAlignFrame extends JInternalFrame autoAnnMenu.addSeparator(); autoAnnMenu.add(showGroupConservation); autoAnnMenu.add(showGroupConsensus); - viewMenu.add(autoAnnMenu); + annotationsMenu.add(autoAnnMenu); viewMenu.addSeparator(); viewMenu.add(showSeqFeatures); // viewMenu.add(showSeqFeaturesHeight); @@ -2272,6 +2302,20 @@ public class GAlignFrame extends JInternalFrame } /** + * Action on clicking sort annotations by type. + */ + protected void sortAnnotationsByType_actionPerformed() + { + } + + /** + * Action on clicking sort annotations by sequence + */ + protected void sortAnnotationsBySequence_actionPerformed() + { + } + + /** * Action on clicking Show all annotations. */ protected void showAllAnnotations_actionPerformed() diff --git a/test/jalview/analysis/AnnotationSorterTest.java b/test/jalview/analysis/AnnotationSorterTest.java new file mode 100644 index 0000000..879aa9b --- /dev/null +++ b/test/jalview/analysis/AnnotationSorterTest.java @@ -0,0 +1,175 @@ +package jalview.analysis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceI; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.junit.Before; +import org.junit.Test; + +public class AnnotationSorterTest +{ + private static final int NUM_SEQS = 6; + + private static final int NUM_ANNS = 7; + + private static final String SS = "secondary structure"; + + AlignmentAnnotation[] anns = new AlignmentAnnotation[0]; + + Alignment al = null; + + /* + * Set up 6 sequences and 7 annotations. + */ + @Before + public void setUp() + { + al = buildAlignment(NUM_SEQS); + anns = buildAnnotations(NUM_ANNS); + } + + /** + * Construct an array of numAnns annotations + * + * @param numAnns + * + * @return + */ + protected AlignmentAnnotation[] buildAnnotations(int numAnns) + { + List annlist = new ArrayList(); + for (int i = 0; i < numAnns; i++) + { + AlignmentAnnotation ann = new AlignmentAnnotation(SS + i, "", 0); + annlist.add(ann); + } + return annlist.toArray(anns); + } + + /** + * Make an alignment with numSeqs sequences in it. + * + * @param numSeqs + * + * @return + */ + private Alignment buildAlignment(int numSeqs) + { + SequenceI[] seqs = new Sequence[numSeqs]; + for (int i = 0; i < numSeqs; i++) + { + seqs[i] = new Sequence("Sequence" + i, "axrdkfp"); + } + return new Alignment(seqs); + } + + /** + * Test sorting by annotation type (label) within sequence order, including + *
    + *
  • annotations with no sequence reference - sort to end keeping mutual + * ordering
  • + *
  • annotations with sequence ref = sort in sequence order
  • + *
  • multiple annotations for same sequence ref - sort by label + * non-case-specific
  • + *
  • annotations with reference to sequence not in alignment - treat like no + * sequence ref
  • + *
+ */ + @Test + public void testSortBySequenceAndType() + { + // @formatter:off + anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0"; + anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure"; + anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron"; + anns[3].sequenceRef = null; anns[3].label = "Quality"; + anns[4].sequenceRef = null; anns[4].label = "Consensus"; + anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "label5"; + anns[6].sequenceRef = al.getSequenceAt(3); anns[6].label = "IRP"; + // @formatter:on + + AnnotationSorter testee = new AnnotationSorter(al); + testee.sortBySequenceAndType(anns); + assertEquals("label5", anns[0].label); // for sequence 0 + assertEquals("label0", anns[1].label); // for sequence 1 + assertEquals("iron", anns[2].label); // sequence 3 /iron + assertEquals("IRP", anns[3].label); // sequence 3/IRP + assertEquals("structure", anns[4].label); // sequence 3/structure + assertEquals("Quality", anns[5].label); // non-sequence annotations + assertEquals("Consensus", anns[6].label); // retain ordering + } + + /** + * Test sorting by annotation type (label) within sequence order, including + *
    + *
  • annotations with no sequence reference - sort to end keeping mutual + * ordering
  • + *
  • annotations with sequence ref = sort in sequence order
  • + *
  • multiple annotations for same sequence ref - sort by label + * non-case-specific
  • + *
  • annotations with reference to sequence not in alignment - treat like no + * sequence ref
  • + *
+ */ + @Test + public void testSortByTypeAndSequence() + { + // @formatter:off + anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0"; + anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure"; + anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron"; + anns[3].sequenceRef = null; anns[3].label = "Quality"; + anns[4].sequenceRef = null; anns[4].label = "Consensus"; + anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "IRON"; + anns[6].sequenceRef = al.getSequenceAt(2); anns[6].label = "Structure"; + // @formatter:on + + AnnotationSorter testee = new AnnotationSorter(al); + testee.sortByTypeAndSequence(anns); + assertEquals("IRON", anns[0].label); // IRON / sequence 0 + assertEquals("iron", anns[1].label); // iron / sequence 3 + assertEquals("label0", anns[2].label); // label0 / sequence 1 + assertEquals("Structure", anns[3].label); // Structure / sequence 2 + assertEquals("structure", anns[4].label); // structure / sequence 3 + assertEquals("Quality", anns[5].label); // non-sequence annotations + assertEquals("Consensus", anns[6].label); // retain ordering + } + + @Test + public void testSortBySequenceAndType_timing() + { + final long targetTime = 300; // ms + final int numSeqs = 10000; + final int numAnns = 20000; + al = buildAlignment(numSeqs); + anns = buildAnnotations(numAnns); + + /* + * Set the annotations in random order with respect to the sequences + */ + Random r = new Random(); + final SequenceI[] sequences = al.getSequencesArray(); + for (int i = 0; i < anns.length; i++) + { + SequenceI randomSequenceRef = sequences[r.nextInt(sequences.length)]; + anns[i].sequenceRef = randomSequenceRef; + } + long startTime = System.currentTimeMillis(); + AnnotationSorter testee = new AnnotationSorter(al); + testee.sortByTypeAndSequence(anns); + long endTime = System.currentTimeMillis(); + final long elapsed = endTime - startTime; + System.out.println("Timing test for " + numSeqs + " sequences and " + + numAnns + " annotations took " + elapsed + "ms"); + assertTrue("Sort took more than " + targetTime + "ms", + elapsed <= targetTime); + } +}