JAL-2492 use SequenceFeatures.getNonPositionalFeatures()
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 27 Apr 2017 10:54:45 +0000 (11:54 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 27 Apr 2017 10:54:45 +0000 (11:54 +0100)
src/jalview/appletgui/IdPanel.java
src/jalview/datamodel/features/SequenceFeatures.java
src/jalview/datamodel/features/SequenceFeaturesI.java
src/jalview/gui/IdPanel.java
src/jalview/io/SequenceAnnotationReport.java
test/jalview/datamodel/features/SequenceFeaturesTest.java
test/jalview/io/SequenceAnnotationReportTest.java

index e47c50a..80f03a1 100755 (executable)
@@ -20,7 +20,6 @@
  */
 package jalview.appletgui;
 
-import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -105,64 +104,57 @@ public class IdPanel extends Panel implements MouseListener,
 
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
 
-    // look for non-pos features
     StringBuffer tooltiptext = new StringBuffer();
-    if (sequence != null)
+    if (sequence == null)
     {
-      if (sequence.getDescription() != null)
+      return;
+    }
+    if (sequence.getDescription() != null)
+    {
+      tooltiptext.append(sequence.getDescription());
+      tooltiptext.append("\n");
+    }
+
+    for (SequenceFeature sf : sequence.getFeatures()
+            .getNonPositionalFeatures())
+    {
+      boolean nl = false;
+      if (sf.getFeatureGroup() != null)
       {
-        tooltiptext.append(sequence.getDescription());
-        tooltiptext.append("\n");
+        tooltiptext.append(sf.getFeatureGroup());
+        nl = true;
       }
-
-      SequenceFeature sf[] = sequence.getSequenceFeatures();
-      for (int sl = 0; sf != null && sl < sf.length; sl++)
+      if (sf.getType() != null)
       {
-        if (sf[sl].begin == sf[sl].end && sf[sl].begin == 0)
-        {
-          boolean nl = false;
-          if (sf[sl].getFeatureGroup() != null)
-          {
-            tooltiptext.append(sf[sl].getFeatureGroup());
-            nl = true;
-          }
-          ;
-          if (sf[sl].getType() != null)
-          {
-            tooltiptext.append(" ");
-            tooltiptext.append(sf[sl].getType());
-            nl = true;
-          }
-          ;
-          if (sf[sl].getDescription() != null)
-          {
-            tooltiptext.append(" ");
-            tooltiptext.append(sf[sl].getDescription());
-            nl = true;
-          }
-          ;
-          if (!Float.isNaN(sf[sl].getScore()) && sf[sl].getScore() != 0f)
-          {
-            tooltiptext.append(" Score = ");
-            tooltiptext.append(sf[sl].getScore());
-            nl = true;
-          }
-          ;
-          if (sf[sl].getStatus() != null && sf[sl].getStatus().length() > 0)
-          {
-            tooltiptext.append(" (");
-            tooltiptext.append(sf[sl].getStatus());
-            tooltiptext.append(")");
-            nl = true;
-          }
-          ;
-          if (nl)
-          {
-            tooltiptext.append("\n");
-          }
-        }
+        tooltiptext.append(" ");
+        tooltiptext.append(sf.getType());
+        nl = true;
+      }
+      if (sf.getDescription() != null)
+      {
+        tooltiptext.append(" ");
+        tooltiptext.append(sf.getDescription());
+        nl = true;
+      }
+      if (!Float.isNaN(sf.getScore()) && sf.getScore() != 0f)
+      {
+        tooltiptext.append(" Score = ");
+        tooltiptext.append(sf.getScore());
+        nl = true;
+      }
+      if (sf.getStatus() != null && sf.getStatus().length() > 0)
+      {
+        tooltiptext.append(" (");
+        tooltiptext.append(sf.getStatus());
+        tooltiptext.append(")");
+        nl = true;
+      }
+      if (nl)
+      {
+        tooltiptext.append("\n");
       }
     }
+
     if (tooltiptext.length() == 0)
     {
       // nothing to display - so clear tooltip if one is visible
@@ -283,23 +275,21 @@ public class IdPanel extends Panel implements MouseListener,
 
     if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
     {
-      Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq);
+      SequenceI sq = av.getAlignment().getSequenceAt(seq);
 
-      // build a new links menu based on the current links + any non-positional
-      // features
+      /*
+       *  build a new links menu based on the current links
+       *  and any non-positional features
+       */
       List<String> nlinks = urlProvider.getLinksForMenu();
 
-      SequenceFeature sf[] = sq == null ? null : sq.getSequenceFeatures();
-      for (int sl = 0; sf != null && sl < sf.length; sl++)
+      for (SequenceFeature sf : sq.getFeatures().getNonPositionalFeatures())
       {
-        if (sf[sl].begin == sf[sl].end && sf[sl].begin == 0)
+        if (sf.links != null)
         {
-          if (sf[sl].links != null && sf[sl].links.size() > 0)
+          for (String link : sf.links)
           {
-            for (int l = 0, lSize = sf[sl].links.size(); l < lSize; l++)
-            {
-              nlinks.add(sf[sl].links.elementAt(l));
-            }
+            nlinks.add(link);
           }
         }
       }
index c6af3ea..5fa9a3c 100644 (file)
@@ -4,12 +4,13 @@ import jalview.datamodel.SequenceFeature;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.TreeMap;
 
 /**
  * A class that stores sequence features in a way that supports efficient
@@ -33,7 +34,12 @@ public class SequenceFeatures implements SequenceFeaturesI
    */
   public SequenceFeatures()
   {
-    featureStore = new HashMap<String, FeatureStore>();
+    /*
+     * use a TreeMap so that features are returned in alphabetical order of type
+     * wrap as a synchronized map for add and delete operations
+     */
+    featureStore = Collections
+            .synchronizedSortedMap(new TreeMap<String, FeatureStore>());
   }
 
   /**
@@ -43,6 +49,11 @@ public class SequenceFeatures implements SequenceFeaturesI
   public boolean add(SequenceFeature sf)
   {
     String type = sf.getType();
+    if (type == null)
+    {
+      System.err.println("Feature type may not be null: " + sf.toString());
+      return false;
+    }
 
     if (featureStore.get(type) == null)
     {
@@ -155,8 +166,21 @@ public class SequenceFeatures implements SequenceFeaturesI
    */
   protected Iterable<String> varargToTypes(String... type)
   {
-    return type == null || type.length == 0 ? featureStore
-            .keySet() : Arrays.asList(type);
+    if (type == null || type.length == 0)
+    {
+      /*
+       * no vararg parameter supplied
+       */
+      return featureStore.keySet();
+    }
+
+    /*
+     * else make a copy of the list, and remove any null value just in case,
+     * as it would cause errors looking up the features Map
+     */
+    List<String> types = new ArrayList<String>(Arrays.asList(type));
+    types.remove(null);
+    return types;
   }
 
   /**
index fa77532..cfcdc76 100644 (file)
@@ -12,7 +12,8 @@ public interface SequenceFeaturesI
    * Adds one sequence feature to the store, and returns true, unless the
    * feature is already contained in the store, in which case this method
    * returns false. Containment is determined by SequenceFeature.equals()
-   * comparison.
+   * comparison. Answers false, and does not add the feature, if feature type is
+   * null.
    * 
    * @param sf
    */
index 2074900..32768b7 100755 (executable)
@@ -325,23 +325,19 @@ public class IdPanel extends JPanel implements MouseListener,
   {
     int seq2 = alignPanel.getSeqPanel().findSeq(e);
     Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq2);
-    // build a new links menu based on the current links + any non-positional
-    // features
+
+    /*
+     *  build a new links menu based on the current links
+     *  and any non-positional features
+     */
     List<String> nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
-    SequenceFeature sfs[] = sq == null ? null : sq.getSequenceFeatures();
-    if (sfs != null)
+    for (SequenceFeature sf : sq.getFeatures().getNonPositionalFeatures())
     {
-      for (SequenceFeature sf : sfs)
+      if (sf.links != null)
       {
-        if (sf.begin == sf.end && sf.begin == 0)
+        for (String link : sf.links)
         {
-          if (sf.links != null && sf.links.size() > 0)
-          {
-            for (int l = 0, lSize = sf.links.size(); l < lSize; l++)
-            {
-              nlinks.add(sf.links.elementAt(l));
-            }
-          }
+          nlinks.add(link);
         }
       }
     }
index 6c8f40f..c3b076c 100644 (file)
@@ -57,7 +57,8 @@ public class SequenceAnnotationReport
   final String linkImageURL;
 
   /*
-   * Comparator to order DBRefEntry by Source + accession id (case-insensitive)
+   * Comparator to order DBRefEntry by Source + accession id (case-insensitive),
+   * with 'Primary' sources placed before others
    */
   private static Comparator<DBRefEntry> comparator = new Comparator<DBRefEntry>()
   {
@@ -356,100 +357,121 @@ public class SequenceAnnotationReport
     {
       ds = ds.getDatasetSequence();
     }
+    
+    if (showDbRefs)
+    {
+      maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary));
+    }
+
+    /*
+     * add non-positional features if wanted
+     */
+    if (showNpFeats)
+    {
+      for (SequenceFeature sf : sequence.getFeatures()
+              .getNonPositionalFeatures())
+      {
+        int sz = -sb.length();
+        appendFeature(sb, 0, minmax, sf);
+        sz += sb.length();
+        maxWidth = Math.max(maxWidth, sz);
+      }
+    }
+    sb.append("</i>");
+    return maxWidth;
+  }
+
+  /**
+   * A helper method that appends any DBRefs, returning the maximum line length
+   * added
+   * 
+   * @param sb
+   * @param ds
+   * @param summary
+   * @return
+   */
+  protected int appendDbRefs(final StringBuilder sb, SequenceI ds,
+          boolean summary)
+  {
     DBRefEntry[] dbrefs = ds.getDBRefs();
-    if (showDbRefs && dbrefs != null)
+    if (dbrefs == null)
+    {
+      return 0;
+    }
+
+    // note this sorts the refs held on the sequence!
+    Arrays.sort(dbrefs, comparator);
+    boolean ellipsis = false;
+    String source = null;
+    String lastSource = null;
+    int countForSource = 0;
+    int sourceCount = 0;
+    boolean moreSources = false;
+    int maxLineLength = 0;
+    int lineLength = 0;
+
+    for (DBRefEntry ref : dbrefs)
     {
-      // note this sorts the refs held on the sequence!
-      Arrays.sort(dbrefs, comparator);
-      boolean ellipsis = false;
-      String source = null;
-      String lastSource = null;
-      int countForSource = 0;
-      int sourceCount = 0;
-      boolean moreSources = false;
-      int lineLength = 0;
-
-      for (DBRefEntry ref : dbrefs)
+      source = ref.getSource();
+      if (source == null)
       {
-        source = ref.getSource();
-        if (source == null)
-        {
-          // shouldn't happen
-          continue;
-        }
-        boolean sourceChanged = !source.equals(lastSource);
-        if (sourceChanged)
-        {
-          lineLength = 0;
-          countForSource = 0;
-          sourceCount++;
-        }
-        if (sourceCount > MAX_SOURCES && summary)
-        {
-          ellipsis = true;
-          moreSources = true;
-          break;
-        }
-        lastSource = source;
-        countForSource++;
-        if (countForSource == 1 || !summary)
-        {
-          sb.append("<br>");
-        }
-        if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
-        {
-          String accessionId = ref.getAccessionId();
-          lineLength += accessionId.length() + 1;
-          if (countForSource > 1 && summary)
-          {
-            sb.append(", ").append(accessionId);
-            lineLength++;
-          }
-          else
-          {
-            sb.append(source).append(" ").append(accessionId);
-            lineLength += source.length();
-          }
-          maxWidth = Math.max(maxWidth, lineLength);
-        }
-        if (countForSource == MAX_REFS_PER_SOURCE && summary)
-        {
-          sb.append(COMMA).append(ELLIPSIS);
-          ellipsis = true;
-        }
+        // shouldn't happen
+        continue;
       }
-      if (moreSources)
+      boolean sourceChanged = !source.equals(lastSource);
+      if (sourceChanged)
       {
-        sb.append("<br>").append(ELLIPSIS).append(COMMA).append(source)
-                .append(COMMA).append(ELLIPSIS);
+        lineLength = 0;
+        countForSource = 0;
+        sourceCount++;
       }
-      if (ellipsis)
+      if (sourceCount > MAX_SOURCES && summary)
       {
-        sb.append("<br>(");
-        sb.append(MessageManager.getString("label.output_seq_details"));
-        sb.append(")");
+        ellipsis = true;
+        moreSources = true;
+        break;
       }
-    }
-
-    /*
-     * add non-positional features if wanted
-     */
-    SequenceFeature[] features = sequence.getSequenceFeatures();
-    if (showNpFeats && features != null)
-    {
-      for (int i = 0; i < features.length; i++)
+      lastSource = source;
+      countForSource++;
+      if (countForSource == 1 || !summary)
+      {
+        sb.append("<br>");
+      }
+      if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
       {
-        if (features[i].begin == 0 && features[i].end == 0)
+        String accessionId = ref.getAccessionId();
+        lineLength += accessionId.length() + 1;
+        if (countForSource > 1 && summary)
         {
-          int sz = -sb.length();
-          appendFeature(sb, 0, minmax, features[i]);
-          sz += sb.length();
-          maxWidth = Math.max(maxWidth, sz);
+          sb.append(", ").append(accessionId);
+          lineLength++;
         }
+        else
+        {
+          sb.append(source).append(" ").append(accessionId);
+          lineLength += source.length();
+        }
+        maxLineLength = Math.max(maxLineLength, lineLength);
+      }
+      if (countForSource == MAX_REFS_PER_SOURCE && summary)
+      {
+        sb.append(COMMA).append(ELLIPSIS);
+        ellipsis = true;
       }
     }
-    sb.append("</i>");
-    return maxWidth;
+    if (moreSources)
+    {
+      sb.append("<br>").append(source)
+              .append(COMMA).append(ELLIPSIS);
+    }
+    if (ellipsis)
+    {
+      sb.append("<br>(");
+      sb.append(MessageManager.getString("label.output_seq_details"));
+      sb.append(")");
+    }
+
+    return maxLineLength;
   }
 
   public void createTooltipAnnotationReport(final StringBuilder tip,
index bb11a87..0d1d89d 100644 (file)
@@ -6,6 +6,7 @@ import static org.testng.Assert.assertTrue;
 
 import jalview.datamodel.SequenceFeature;
 
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -237,8 +238,6 @@ public class SequenceFeaturesTest
     SequenceFeature sf10 = addFeature(sf, "Cath", 40, 100);
     SequenceFeature sf11 = addFeature(sf, "Cath", 60, 100);
     SequenceFeature sf12 = addFeature(sf, "Cath", 70, 70);
-    // null type is weird but possible:
-    SequenceFeature sf13 = addFeature(sf, null, 5, 12);
   
     List<SequenceFeature> overlaps = sf.findFeatures(200, 200, "Pfam");
     assertTrue(overlaps.isEmpty());
@@ -281,8 +280,7 @@ public class SequenceFeaturesTest
     assertTrue(sf.findFeatures(0, 1000, "Metal").isEmpty());
 
     overlaps = sf.findFeatures(7, 7, (String) null);
-    assertEquals(overlaps.size(), 1);
-    assertTrue(overlaps.contains(sf13));
+    assertTrue(overlaps.isEmpty());
   }
 
   @Test(groups = "Functional")
@@ -508,13 +506,13 @@ public class SequenceFeaturesTest
     assertEquals(types.size(), 1);
     assertTrue(types.contains("Metal"));
 
-    // null type is possible...
+    // null type is rejected...
     SequenceFeature sf2 = new SequenceFeature(null, "desc", 10, 20,
             Float.NaN, null);
-    store.add(sf2);
+    assertFalse(store.add(sf2));
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 2);
-    assertTrue(types.contains(null));
+    assertEquals(types.size(), 1);
+    assertFalse(types.contains(null));
     assertTrue(types.contains("Metal"));
 
     /*
@@ -524,7 +522,7 @@ public class SequenceFeaturesTest
             Float.NaN, null);
     store.add(sf3);
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 3);
+    assertEquals(types.size(), 2);
     assertTrue(types.contains("Pfam"));
 
     /*
@@ -534,7 +532,7 @@ public class SequenceFeaturesTest
             10, 20, Float.NaN, null);
     store.add(sf4);
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 4);
+    assertEquals(types.size(), 3);
     assertTrue(types.contains("Disulphide Bond"));
 
     /*
@@ -544,14 +542,14 @@ public class SequenceFeaturesTest
             Float.NaN, null);
     store.add(sf5);
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 4); // unchanged
+    assertEquals(types.size(), 3); // unchanged
 
     /*
      * delete first Pfam - still have one
      */
     assertTrue(store.delete(sf3));
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 4);
+    assertEquals(types.size(), 3);
     assertTrue(types.contains("Pfam"));
 
     /*
@@ -559,7 +557,7 @@ public class SequenceFeaturesTest
      */
     assertTrue(store.delete(sf5));
     types = store.getFeatureTypes();
-    assertEquals(types.size(), 3);
+    assertEquals(types.size(), 2);
     assertFalse(types.contains("Pfam"));
   }
 
@@ -580,12 +578,12 @@ public class SequenceFeaturesTest
     assertEquals(store.getFeatureCount(false), 0);
 
     /*
-     * another positional
+     * null feature type is rejected
      */
     SequenceFeature sf2 = new SequenceFeature(null, "desc", 10, 20,
             Float.NaN, null);
-    store.add(sf2);
-    assertEquals(store.getFeatureCount(true), 2);
+    assertFalse(store.add(sf2));
+    assertEquals(store.getFeatureCount(true), 1);
     assertEquals(store.getFeatureCount(false), 0);
   
     /*
@@ -594,7 +592,7 @@ public class SequenceFeaturesTest
     SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 0, 0,
             Float.NaN, null);
     store.add(sf3);
-    assertEquals(store.getFeatureCount(true), 2);
+    assertEquals(store.getFeatureCount(true), 1);
     assertEquals(store.getFeatureCount(false), 1);
   
     /*
@@ -603,39 +601,39 @@ public class SequenceFeaturesTest
     SequenceFeature sf4 = new SequenceFeature("Disulphide Bond", "desc",
             10, 20, Float.NaN, null);
     store.add(sf4);
-    assertEquals(store.getFeatureCount(true), 3);
+    assertEquals(store.getFeatureCount(true), 2);
     assertEquals(store.getFeatureCount(false), 1);
   
     /*
-     * add another Pfam
+     * add another Pfam but this time as a positional feature
      */
     SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 10, 20,
             Float.NaN, null);
     store.add(sf5);
-    assertEquals(store.getFeatureCount(true), 4);
-    assertEquals(store.getFeatureCount(false), 1);
-    assertEquals(store.getFeatureCount(true, "Pfam"), 1);
-    assertEquals(store.getFeatureCount(false, "Pfam"), 1);
+    assertEquals(store.getFeatureCount(true), 3); // sf1, sf4, sf5
+    assertEquals(store.getFeatureCount(false), 1); // sf3
+    assertEquals(store.getFeatureCount(true, "Pfam"), 1); // positional
+    assertEquals(store.getFeatureCount(false, "Pfam"), 1); // non-positional
     // search for type==null
-    assertEquals(store.getFeatureCount(true, (String) null), 1);
+    assertEquals(store.getFeatureCount(true, (String) null), 0);
     // search with no type specified
-    assertEquals(store.getFeatureCount(true, (String[]) null), 4);
+    assertEquals(store.getFeatureCount(true, (String[]) null), 3);
     assertEquals(store.getFeatureCount(true, "Metal", "Cath"), 1);
     assertEquals(store.getFeatureCount(true, "Disulphide Bond"), 1);
-    assertEquals(store.getFeatureCount(true, "Metal", "Pfam", null), 3);
+    assertEquals(store.getFeatureCount(true, "Metal", "Pfam", null), 2);
 
     /*
      * delete first Pfam (non-positional)
      */
     assertTrue(store.delete(sf3));
-    assertEquals(store.getFeatureCount(true), 4);
+    assertEquals(store.getFeatureCount(true), 3);
     assertEquals(store.getFeatureCount(false), 0);
   
     /*
      * delete second Pfam (positional)
      */
     assertTrue(store.delete(sf5));
-    assertEquals(store.getFeatureCount(true), 3);
+    assertEquals(store.getFeatureCount(true), 2);
     assertEquals(store.getFeatureCount(false), 0);
   }
 
@@ -653,7 +651,7 @@ public class SequenceFeaturesTest
     assertEquals(features.size(), 1);
     assertTrue(features.contains(sf1));
   
-    SequenceFeature sf2 = new SequenceFeature(null, "desc", 10, 20,
+    SequenceFeature sf2 = new SequenceFeature("Metallic", "desc", 10, 20,
             Float.NaN, null);
     store.add(sf2);
     features = store.getAllFeatures();
@@ -830,4 +828,85 @@ public class SequenceFeaturesTest
     assertEquals(sf.getMinimumScore("Metal", true), Float.NaN);
     assertEquals(sf.getMaximumScore("Metal", true), Float.NaN);
   }
+
+  @Test(groups = "Functional")
+  public void testVarargsToTypes()
+  {
+    SequenceFeatures sf = new SequenceFeatures();
+    sf.add(new SequenceFeature("Metal", "desc", 0, 0, Float.NaN, "group"));
+    sf.add(new SequenceFeature("Cath", "desc", 10, 20, Float.NaN, "group"));
+
+    /*
+     * no type specified - get all types stored
+     * they are returned in keyset (alphabetical) order
+     */
+    Iterable<String> types = sf.varargToTypes();
+    Iterator<String> iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Cath");
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Metal");
+    assertFalse(iterator.hasNext());
+
+    /*
+     * empty array is the same as no vararg parameter supplied
+     * so treated as all stored types
+     */
+    types = sf.varargToTypes(new String[] {});
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Cath");
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Metal");
+    assertFalse(iterator.hasNext());
+
+    /*
+     * null type specified; this is passed as vararg
+     * String[1] {null}
+     */
+    types = sf.varargToTypes((String) null);
+    assertFalse(types.iterator().hasNext());
+
+    /*
+     * null types array specified; this is passed as vararg null
+     */
+    types = sf.varargToTypes((String[]) null);
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Cath");
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Metal");
+    assertFalse(iterator.hasNext());
+
+    /*
+     * one type specified
+     */
+    types = sf.varargToTypes("Metal");
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Metal");
+    assertFalse(iterator.hasNext());
+
+    /*
+     * two types specified
+     */
+    types = sf.varargToTypes("Metal", "Helix");
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Metal");
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Helix");
+    assertFalse(iterator.hasNext());
+
+    /*
+     * null type included - should get removed
+     */
+    types = sf.varargToTypes("Metal", null, "Helix");
+    iterator = types.iterator();
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Metal");
+    assertTrue(iterator.hasNext());
+    assertEquals(iterator.next(), "Helix");
+    assertFalse(iterator.hasNext());
+  }
 }
index 2895874..9e61bec 100644 (file)
 package jalview.io;
 
 import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertTrue;
 
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
 import jalview.gui.JvOptionPane;
+import jalview.io.gff.GffConstants;
 
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Map;
 
+import junit.extensions.PA;
+
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -192,4 +200,134 @@ public class SequenceAnnotationReportTest
     // if no <html> tag, html-encodes > and < (only):
     assertEquals("METAL 1 3; &lt;br&gt;&kHD&gt;6", sb.toString());
   }
+
+  @Test(groups = "Functional")
+  public void testCreateSequenceAnnotationReport()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuilder sb = new StringBuilder();
+
+    SequenceI seq = new Sequence("s1", "MAKLKRFQSSTLL");
+    seq.setDescription("SeqDesc");
+
+    sar.createSequenceAnnotationReport(sb, seq, true, true, null);
+
+    /*
+     * positional features are ignored
+     */
+    seq.addSequenceFeature(new SequenceFeature("Domain", "Ferredoxin", 5,
+            10, 1f, null));
+    assertEquals("<i><br>SeqDesc</i>", sb.toString());
+
+    /*
+     * non-positional feature
+     */
+    seq.addSequenceFeature(new SequenceFeature("Type1", "Nonpos", 0, 0, 1f,
+            null));
+    sb.setLength(0);
+    sar.createSequenceAnnotationReport(sb, seq, true, true, null);
+    String expected = "<i><br>SeqDesc<br>Type1 ; Nonpos</i>";
+    assertEquals(expected, sb.toString());
+
+    /*
+     * non-positional features not wanted
+     */
+    sb.setLength(0);
+    sar.createSequenceAnnotationReport(sb, seq, true, false, null);
+    assertEquals("<i><br>SeqDesc</i>", sb.toString());
+
+    /*
+     * add non-pos feature with score inside min-max range for feature type
+     * minmax holds { [positionalMin, positionalMax], [nonPosMin, nonPosMax] }
+     * score is only appended for positional features so ignored here!
+     * minMax are not recorded for non-positional features
+     */
+    seq.addSequenceFeature(new SequenceFeature("Metal", "Desc", 0, 0, 5f,
+            null));
+    Map<String, float[][]> minmax = new HashMap<String, float[][]>();
+    minmax.put("Metal", new float[][] { null, new float[] { 2f, 5f } });
+    sb.setLength(0);
+    sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+    expected = "<i><br>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos</i>";
+    assertEquals(expected, sb.toString());
+    
+    /*
+     * 'linkonly' features are ignored; this is obsolete, as linkonly
+     * is only set by DasSequenceFetcher, and DAS is history
+     */
+    SequenceFeature sf = new SequenceFeature("Metal", "Desc", 0, 0, 5f,
+            null);
+    sf.setValue("linkonly", Boolean.TRUE);
+    seq.addSequenceFeature(sf);
+    sb.setLength(0);
+    sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+    assertEquals(expected, sb.toString()); // unchanged!
+
+    /*
+     * 'clinical_significance' currently being specially included
+     */
+    SequenceFeature sf2 = new SequenceFeature("Variant", "Havana", 0, 0,
+            5f, null);
+    sf2.setValue(GffConstants.CLINICAL_SIGNIFICANCE, "benign");
+    seq.addSequenceFeature(sf2);
+    sb.setLength(0);
+    sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+    expected = "<i><br>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; Havana; benign</i>";
+    assertEquals(expected, sb.toString());
+
+    /*
+     * add dbrefs
+     */
+    seq.addDBRef(new DBRefEntry("PDB", "0", "3iu1"));
+    seq.addDBRef(new DBRefEntry("Uniprot", "1", "P30419"));
+    // with showDbRefs = false
+    sb.setLength(0);
+    sar.createSequenceAnnotationReport(sb, seq, false, true, minmax);
+    assertEquals(expected, sb.toString()); // unchanged
+    // with showDbRefs = true
+    sb.setLength(0);
+    sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+    expected = "<i><br>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; Havana; benign</i>";
+    assertEquals(expected, sb.toString());
+    // with showNonPositionalFeatures = false
+    sb.setLength(0);
+    sar.createSequenceAnnotationReport(sb, seq, true, false, minmax);
+    expected = "<i><br>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1</i>";
+    assertEquals(expected, sb.toString());
+
+    // see other tests for treatment of status and html
+  }
+
+  /**
+   * Test that exercises an abbreviated sequence details report, with ellipsis
+   * where there are more than 40 different sources, or more than 4 dbrefs for a
+   * single source
+   */
+  @Test(groups = "Functional")
+  public void testCreateSequenceAnnotationReport_withEllipsis()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuilder sb = new StringBuilder();
+  
+    SequenceI seq = new Sequence("s1", "ABC");
+
+    int maxSources = (int) PA.getValue(sar, "MAX_SOURCES");
+    for (int i = 0; i <= maxSources; i++)
+    {
+      seq.addDBRef(new DBRefEntry("PDB" + i, "0", "3iu1"));
+    }
+    
+    int maxRefs = (int) PA.getValue(sar, "MAX_REFS_PER_SOURCE");
+    for (int i = 0; i <= maxRefs; i++)
+    {
+      seq.addDBRef(new DBRefEntry("Uniprot", "0", "P3041" + i));
+    }
+  
+    sar.createSequenceAnnotationReport(sb, seq, true, true, null, true);
+    String report = sb.toString();
+    assertTrue(report
+            .startsWith("<i><br>UNIPROT P30410, P30411, P30412, P30413,...<br>PDB0 3iu1"));
+    assertTrue(report
+            .endsWith("<br>PDB7 3iu1<br>PDB8,...<br>(Output Sequence Details to list all database references)</i>"));
+  }
 }