From 4935082f08648ab92743e14e5e2dfefa3e0dcd4d Mon Sep 17 00:00:00 2001 From: gmungoc Date: Fri, 28 Nov 2014 15:48:48 +0000 Subject: [PATCH] JAL-1610 check reference annotations against alignment, not sequence --- src/jalview/datamodel/Alignment.java | 21 +++++ src/jalview/datamodel/AnnotatedCollectionI.java | 3 + src/jalview/datamodel/Sequence.java | 30 ++++--- src/jalview/datamodel/SequenceGroup.java | 27 ++++++ src/jalview/datamodel/SequenceI.java | 2 + src/jalview/gui/PopupMenu.java | 36 +++++--- test/jalview/datamodel/SequenceTest.java | 19 ++++- test/jalview/gui/PopupMenuTest.java | 100 ++++++++++++++++++++--- 8 files changed, 201 insertions(+), 37 deletions(-) diff --git a/src/jalview/datamodel/Alignment.java b/src/jalview/datamodel/Alignment.java index 4057773..9c5914f 100755 --- a/src/jalview/datamodel/Alignment.java +++ b/src/jalview/datamodel/Alignment.java @@ -1489,6 +1489,27 @@ public class Alignment implements AlignmentI return aa; } + /** + * Returns an iterable collection of any annotations that match on given + * sequence ref, calcId and label (ignoring null values). + */ + @Override + public Iterable findAnnotations(SequenceI seq, + String calcId, String label) + { + ArrayList aa = new ArrayList(); + for (AlignmentAnnotation ann : getAlignmentAnnotation()) + { + if (ann.getCalcId() != null && ann.getCalcId().equals(calcId) + && ann.sequenceRef != null && ann.sequenceRef == seq + && ann.label != null && ann.label.equals(label)) + { + aa.add(ann); + } + } + return aa; + } + @Override public void moveSelectedSequencesByOne(SequenceGroup sg, Map map, boolean up) diff --git a/src/jalview/datamodel/AnnotatedCollectionI.java b/src/jalview/datamodel/AnnotatedCollectionI.java index 0b4c117..6814b7e 100644 --- a/src/jalview/datamodel/AnnotatedCollectionI.java +++ b/src/jalview/datamodel/AnnotatedCollectionI.java @@ -33,6 +33,9 @@ public interface AnnotatedCollectionI extends SequenceCollectionI Iterable findAnnotation(String calcId); + Iterable findAnnotations(SequenceI seq, + String calcId, String label); + /** * context for this annotated collection * diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index 69d6da2..0652fb5 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -879,22 +879,30 @@ public class Sequence implements SequenceI return datasetSequence; } + /** + * Returns a new array containing this sequence's annotations, or null. + */ public AlignmentAnnotation[] getAnnotation() { - if (annotation == null) - { - return null; - } - - AlignmentAnnotation[] ret = new AlignmentAnnotation[annotation.size()]; - for (int r = 0; r < ret.length; r++) - { - ret[r] = annotation.elementAt(r); - } + return annotation == null ? null : annotation + .toArray(new AlignmentAnnotation[annotation.size()]); + } - return ret; + /** + * Returns true if this sequence has the given annotation (by object + * identity). + */ + @Override + public boolean hasAnnotation(AlignmentAnnotation ann) + { + return annotation == null ? false : annotation.contains(ann); } + /** + * Add the given annotation, if not already added, and set its sequence ref to + * be this sequence. Does nothing if this sequence's annotations already + * include this annotation (by identical object reference). + */ public void addAlignmentAnnotation(AlignmentAnnotation annotation) { if (this.annotation == null) diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index 17348c2..752c6d4 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -178,7 +178,9 @@ public class SequenceGroup implements AnnotatedCollectionI endRes = seqsel.endRes; cs = seqsel.cs; if (seqsel.description != null) + { description = new String(seqsel.description); + } hidecols = seqsel.hidecols; hidereps = seqsel.hidereps; idColour = seqsel.idColour; @@ -238,7 +240,9 @@ public class SequenceGroup implements AnnotatedCollectionI } } if (!found) + { continue; + } } AlignmentAnnotation newannot = new AlignmentAnnotation( seq.getAnnotation()[a]); @@ -1261,6 +1265,29 @@ public class SequenceGroup implements AnnotatedCollectionI } /** + * Returns a list of annotations that match the specified sequenceRef, calcId + * and label, ignoring null values. + * + * @return list of AlignmentAnnotation objects + */ + @Override + public Iterable findAnnotations(SequenceI seq, + String calcId, String label) + { + ArrayList aa = new ArrayList(); + for (AlignmentAnnotation ann : getAlignmentAnnotation()) + { + if (ann.getCalcId() != null && ann.getCalcId().equals(calcId) + && ann.sequenceRef != null && ann.sequenceRef == seq + && ann.label != null && ann.label.equals(label)) + { + aa.add(ann); + } + } + return aa; + } + + /** * Answer true if any annotation matches the calcId passed in (if not null). * * @param calcId diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index 8376047..fc67efd 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -317,6 +317,8 @@ public interface SequenceI public AlignmentAnnotation[] getAnnotation(); + public boolean hasAnnotation(AlignmentAnnotation ann); + public void addAlignmentAnnotation(AlignmentAnnotation annotation); public void removeAlignmentAnnotation(AlignmentAnnotation annotation); diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 9976471..c2bee83 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -27,6 +27,7 @@ import jalview.commands.ChangeCaseCommand; import jalview.commands.EditCommand; import jalview.commands.EditCommand.Action; import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; import jalview.datamodel.Annotation; import jalview.datamodel.DBRefEntry; import jalview.datamodel.PDBEntry; @@ -1765,12 +1766,13 @@ public class PopupMenu extends JPopupMenu /** * Check for any annotations on the underlying dataset sequences (for the - * current selection group) which are not on the alignment annotations for the - * sequence. If any are found, enable the option to add them to the alignment. - * The criteria for 'on the alignment' is finding an alignment annotation on - * the sequence, that matches on calcId and label. A tooltip is also - * constructed that displays the source (calcId) and type (label) of the - * annotations that can be added. + * current selection group) which are not 'on the alignment'.If any are found, + * enable the option to add them to the alignment. The criteria for 'on the + * alignment' is finding an alignment annotation on the alignment, matched on + * calcId, label and sequenceRef. + * + * A tooltip is also constructed that displays the source (calcId) and type + * (label) of the annotations that can be added. * * @param menuItem * @param forSequences @@ -1797,10 +1799,11 @@ public class PopupMenu extends JPopupMenu /* * For each sequence selected in the alignment, make a list of any * annotations on the underlying dataset sequence which are not already on - * the sequence in the alignment. + * the alignment. * * Build a map of { alignmentSequence, } */ + AlignmentI al = this.ap.av.getAlignment(); final Map> candidates = new LinkedHashMap>(); for (SequenceI seq : forSequences) { @@ -1818,11 +1821,12 @@ public class PopupMenu extends JPopupMenu for (AlignmentAnnotation dsann : datasetAnnotations) { /* - * If the sequence has no annotation that matches this one, then add - * this one to the results list. + * Find matching annotations on the alignment. */ - if (seq.getAlignmentAnnotations(dsann.getCalcId(), dsann.label) - .isEmpty()) + final Iterable matchedAlignmentAnnotations = al + .findAnnotations(seq, dsann.getCalcId(), + dsann.label); + if (!matchedAlignmentAnnotations.iterator().hasNext()) { result.add(dsann); tipEntries.put(dsann.getCalcId(), dsann.label); @@ -1891,8 +1895,14 @@ public class PopupMenu extends JPopupMenu } copyAnn.restrict(startRes, endRes); - // add to the sequence (sets copyAnn.datasetSequence) - seq.addAlignmentAnnotation(copyAnn); + /* + * Add to the sequence (sets copyAnn.datasetSequence), unless the + * original annotation is already on the sequence. + */ + if (!seq.hasAnnotation(ann)) + { + seq.addAlignmentAnnotation(copyAnn); + } // adjust for gaps copyAnn.adjustForAlignment(); // add to the alignment and set visible diff --git a/test/jalview/datamodel/SequenceTest.java b/test/jalview/datamodel/SequenceTest.java index d9101cf..3f91710 100644 --- a/test/jalview/datamodel/SequenceTest.java +++ b/test/jalview/datamodel/SequenceTest.java @@ -88,7 +88,8 @@ public class SequenceTest /** * Tests for addAlignmentAnnotation. Note this method has the side-effect of - * setting the sequenceRef on the annotation. + * setting the sequenceRef on the annotation. Adding the same annotation twice + * should be ignored. */ @Test public void testAddAlignmentAnnotation() @@ -102,5 +103,21 @@ public class SequenceTest AlignmentAnnotation[] anns = seq.getAnnotation(); assertEquals(1, anns.length); assertSame(annotation, anns[0]); + + // re-adding does nothing + seq.addAlignmentAnnotation(annotation); + anns = seq.getAnnotation(); + assertEquals(1, anns.length); + assertSame(annotation, anns[0]); + + // an identical but different annotation can be added + final AlignmentAnnotation annotation2 = new AlignmentAnnotation("a", + "b", 2d); + seq.addAlignmentAnnotation(annotation2); + anns = seq.getAnnotation(); + assertEquals(2, anns.length); + assertSame(annotation, anns[0]); + assertSame(annotation2, anns[1]); + } } diff --git a/test/jalview/gui/PopupMenuTest.java b/test/jalview/gui/PopupMenuTest.java index 1d219df..f7b1482 100644 --- a/test/jalview/gui/PopupMenuTest.java +++ b/test/jalview/gui/PopupMenuTest.java @@ -7,6 +7,7 @@ import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; import jalview.io.AppletFormatAdapter; +import jalview.io.FormatAdapter; import jalview.util.MessageManager; import java.awt.Component; @@ -42,7 +43,7 @@ public class PopupMenuTest @Before public void setUp() throws IOException { - alignment = new jalview.io.FormatAdapter().readFile(TEST_DATA, + alignment = new FormatAdapter().readFile(TEST_DATA, AppletFormatAdapter.PASTE, "FASTA"); AlignFrame af = new AlignFrame(alignment, 700, 500); parentPanel = new AlignmentPanel(af, af.getViewport()); @@ -83,14 +84,13 @@ public class PopupMenuTest public void testConfigureReferenceAnnotationsMenu_noReferenceAnnotations() { JMenuItem menu = new JMenuItem(); - List seqs = new ArrayList(); /* * Initial state is that sequences have annotations, and have dataset * sequences, but the dataset sequences have no annotations. Hence nothing * to add. */ - seqs = parentPanel.getAlignment().getSequences(); + List seqs = parentPanel.getAlignment().getSequences(); testee.configureReferenceAnnotationsMenu(menu, seqs); assertFalse(menu.isEnabled()); @@ -105,12 +105,12 @@ public class PopupMenuTest public void testConfigureReferenceAnnotationsMenu_alreadyAdded() { JMenuItem menu = new JMenuItem(); - List seqs = new ArrayList(); + List seqs = parentPanel.getAlignment().getSequences(); + + // make up new annotations and add to dataset sequences, sequences and + // alignment + attachReferenceAnnotations(seqs, true, true); - seqs = parentPanel.getAlignment().getSequences(); - // copy annotation from sequence to dataset - seqs.get(1).getDatasetSequence() - .addAlignmentAnnotation(seqs.get(1).getAnnotation()[0]); testee.configureReferenceAnnotationsMenu(menu, seqs); assertFalse(menu.isEnabled()); } @@ -126,28 +126,104 @@ public class PopupMenuTest { JMenuItem menu = new JMenuItem(); List seqs = parentPanel.getAlignment().getSequences(); + // make up new annotations and add to dataset sequences + attachReferenceAnnotations(seqs, false, false); + + testee.configureReferenceAnnotationsMenu(menu, seqs); + assertTrue(menu.isEnabled()); + String expected = "
Add annotations for
JMOL/secondary structure
PBD/Temp
"; + assertEquals(expected, menu.getToolTipText()); + } + + /** + * Test building the 'add reference annotations' menu for the case where + * several reference annotations are on the dataset and the sequences but not + * on the alignment. The menu item should be enabled, and acquire a tooltip + * which lists the annotation sources (calcIds) and type (labels). + */ + @Test + public void testConfigureReferenceAnnotationsMenu_notOnAlignment() + { + JMenuItem menu = new JMenuItem(); + List seqs = parentPanel.getAlignment().getSequences(); + + // make up new annotations and add to dataset sequences and sequences + attachReferenceAnnotations(seqs, true, false); + + testee.configureReferenceAnnotationsMenu(menu, seqs); + assertTrue(menu.isEnabled()); + String expected = "
Add annotations for
JMOL/secondary structure
PBD/Temp
"; + assertEquals(expected, menu.getToolTipText()); + } + /** + * Generate annotations and add to dataset sequences and (optionally) + * sequences and/or alignment + * + * @param seqs + * @param addToSequence + * @param addToAlignment + */ + private void attachReferenceAnnotations(List seqs, + boolean addToSequence, boolean addToAlignment) + { // PDB.secondary structure on Sequence0 AlignmentAnnotation annotation = new AlignmentAnnotation( "secondary structure", "", 0); annotation.setCalcId("PBD"); seqs.get(0).getDatasetSequence().addAlignmentAnnotation(annotation); + if (addToSequence) + { + seqs.get(0).addAlignmentAnnotation(annotation); + } + if (addToAlignment) + { + this.alignment.addAnnotation(annotation); + } // PDB.Temp on Sequence1 annotation = new AlignmentAnnotation("Temp", "", 0); annotation.setCalcId("PBD"); seqs.get(1).getDatasetSequence().addAlignmentAnnotation(annotation); + if (addToSequence) + { + seqs.get(1).addAlignmentAnnotation(annotation); + } + if (addToAlignment) + { + this.alignment.addAnnotation(annotation); + } // JMOL.secondary structure on Sequence0 annotation = new AlignmentAnnotation("secondary structure", "", 0); annotation.setCalcId("JMOL"); seqs.get(0).getDatasetSequence().addAlignmentAnnotation(annotation); + if (addToSequence) + { + seqs.get(0).addAlignmentAnnotation(annotation); + } + if (addToAlignment) + { + this.alignment.addAnnotation(annotation); + } + } - testee.configureReferenceAnnotationsMenu(menu, seqs); - assertTrue(menu.isEnabled()); - String expected = "
Add annotations for
JMOL/secondary structure
PBD/Temp
"; - assertEquals(expected, menu.getToolTipText()); + /** + * Test building the 'add reference annotations' menu for the case where there + * are two alignment views: + *
    + *
  • in one view, reference annotations have been added (are on the + * datasets, sequences and alignment)
  • + *
  • in the current view, reference annotations are on the dataset and + * sequence, but not the alignment
  • + *
+ * The menu item should be enabled, and acquire a tooltip which lists the + * annotation sources (calcIds) and type (labels). + */ + @Test + public void testConfigureReferenceAnnotationsMenu_twoViews() + { } /** -- 1.7.10.2