JAL-1610 check reference annotations against alignment, not sequence
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 28 Nov 2014 15:48:48 +0000 (15:48 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 28 Nov 2014 15:48:48 +0000 (15:48 +0000)
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AnnotatedCollectionI.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceGroup.java
src/jalview/datamodel/SequenceI.java
src/jalview/gui/PopupMenu.java
test/jalview/datamodel/SequenceTest.java
test/jalview/gui/PopupMenuTest.java

index 4057773..9c5914f 100755 (executable)
@@ -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<AlignmentAnnotation> findAnnotations(SequenceI seq,
+          String calcId, String label)
+  {
+    ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
+    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<SequenceI, SequenceCollectionI> map, boolean up)
index 0b4c117..6814b7e 100644 (file)
@@ -33,6 +33,9 @@ public interface AnnotatedCollectionI extends SequenceCollectionI
 
   Iterable<AlignmentAnnotation> findAnnotation(String calcId);
 
+  Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
+          String calcId, String label);
+
   /**
    * context for this annotated collection
    * 
index 69d6da2..0652fb5 100755 (executable)
@@ -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)
index 17348c2..752c6d4 100755 (executable)
@@ -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<AlignmentAnnotation> findAnnotations(SequenceI seq,
+          String calcId, String label)
+  {
+    ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
+    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
index 8376047..fc67efd 100755 (executable)
@@ -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);
index 9976471..c2bee83 100644 (file)
@@ -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, <List of annotations to add> }
      */
+    AlignmentI al = this.ap.av.getAlignment();
     final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
     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<AlignmentAnnotation> 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
index d9101cf..3f91710 100644 (file)
@@ -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]);
+
   }
 }
index 1d219df..f7b1482 100644 (file)
@@ -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<SequenceI> seqs = new ArrayList<SequenceI>();
 
     /*
      * 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<SequenceI> 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<SequenceI> seqs = new ArrayList<SequenceI>();
+    List<SequenceI> 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<SequenceI> 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 = "<html><table width=350 border=0><tr><td>Add annotations for<br/>JMOL/secondary structure<br/>PBD/Temp</td></tr></table></html>";
+    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<SequenceI> 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 = "<html><table width=350 border=0><tr><td>Add annotations for<br/>JMOL/secondary structure<br/>PBD/Temp</td></tr></table></html>";
+    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<SequenceI> 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 = "<html><table width=350 border=0><tr><td>Add annotations for<br/>JMOL/secondary structure<br/>PBD/Temp</td></tr></table></html>";
-    assertEquals(expected, menu.getToolTipText());
+  /**
+   * Test building the 'add reference annotations' menu for the case where there
+   * are two alignment views:
+   * <ul>
+   * <li>in one view, reference annotations have been added (are on the
+   * datasets, sequences and alignment)</li>
+   * <li>in the current view, reference annotations are on the dataset and
+   * sequence, but not the alignment</li>
+   * </ul>
+   * The menu item should be enabled, and acquire a tooltip which lists the
+   * annotation sources (calcIds) and type (labels).
+   */
+  @Test
+  public void testConfigureReferenceAnnotationsMenu_twoViews()
+  {
   }
 
   /**