Merge remote-tracking branch 'origin/bug/JAL-2282' into develop
authorkiramt <k.mourao@dundee.ac.uk>
Wed, 2 Nov 2016 16:19:54 +0000 (16:19 +0000)
committerkiramt <k.mourao@dundee.ac.uk>
Wed, 2 Nov 2016 16:19:54 +0000 (16:19 +0000)
32 files changed:
help/html/misc/aaproperties.html
src/jalview/analysis/AAFrequency.java
src/jalview/analysis/Conservation.java
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/TreeCanvas.java
src/jalview/datamodel/Profile.java [moved from src/jalview/analysis/Profile.java with 59% similarity]
src/jalview/datamodel/ProfileI.java [new file with mode: 0644]
src/jalview/datamodel/ResidueCount.java [moved from src/jalview/analysis/ResidueCount.java with 99% similarity]
src/jalview/datamodel/SequenceGroup.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/TreeCanvas.java
src/jalview/io/AnnotationFile.java
src/jalview/io/StructureFile.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/schemes/ColourSchemeI.java
src/jalview/schemes/FollowerColourScheme.java
src/jalview/schemes/ResidueColourScheme.java
src/jalview/schemes/ResidueProperties.java
src/jalview/util/Format.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/workers/ConsensusThread.java
src/jalview/workers/ConservationThread.java
test/jalview/analysis/AAFrequencyTest.java
test/jalview/analysis/ConservationTest.java [new file with mode: 0644]
test/jalview/datamodel/ResidueCountTest.java [moved from test/jalview/analysis/ResidueCountTest.java with 96% similarity]
test/jalview/schemes/ResidueColourSchemeTest.java
test/jalview/schemes/ResiduePropertiesTest.java
test/jalview/util/FormatTest.java

index 51274d7..b53f6ef 100755 (executable)
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  -->
 <head>
-<title>Amino Acid Properties</title>
+<style>
+body {
+    font-size: 100%;
+}
+table {
+    border: solid;
+    border-collapse: separate;
+}
+th, td {
+    border-style:none;
+    font-family: "Courier New", Courier, mono;
+    font-size: large;
+}
+</style><title>Amino Acid Properties</title>
 </head>
 <body>
   <div align="center">
     <h1>Amino Acid Properties</h1>
     <img src="properties.gif"> <br>
-    <table width="295" border="1" cellspacing="0" cellpadding="0">
-      <tr>
-        <td width="291"><pre>
-            <font size="4" face="Courier New, Courier, mono"><strong>ILVCAGMFYWHKREQDNSTPBZX-</strong>
-XXXXXXXXXXX&middot;&middot;&middot;&middot;&middot;&middot;&middot;X&middot;&middot;&middot;XX Hydrophobic
-&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;XXXXXXXXXX&middot;XXXXX Polar
-&middot;&middot;XXXX&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;XXXXX&middot;&middot;XX Small
-&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;X&middot;&middot;XX Proline
-&middot;&middot;&middot;&middot;XX&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;X&middot;&middot;&middot;&middot;XX Tiny
-XXX&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;XX Aliphatic
-&middot;&middot;&middot;&middot;&middot;&middot;&middot;XXXX&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;XX Aromatic
-&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;XXX&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;XX Positive
-&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;X&middot;X&middot;&middot;&middot;&middot;&middot;&middot;XX Negative
-&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;XXXX&middot;X&middot;&middot;&middot;&middot;&middot;&middot;XX Charged</font>
-          </pre></td>
-      </tr>
+    <table>
+    <tr><th>ILVCA</th><th>GMFYW</th><th>HKREQ</th><th>DNSTP</th><th>BZX-</th><th></th></tr>
+      <tr><td>XXXXX</td><td>XXXXX</td><td>XX&middot;&middot;&middot;</td><td>&middot;&middot;&middot;X&middot;</td><td>&middot;&middot;XX</td><td>Hydrophobic</td></tr>
+<tr><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;XX</td><td>XXXXX</td><td>XXXX&middot;</td><td>XXXX</td><td>Polar</td></tr>
+<tr><td>&middot;&middot;XXX</td><td>X&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>XXXXX</td><td>&middot;&middot;XX</td><td>Small</td></tr>
+<tr><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;X</td><td>&middot;&middot;XX</td><td>Proline</td></tr>
+<tr><td>&middot;&middot;&middot;&middot;X</td><td>X&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;X&middot;&middot;</td><td>&middot;&middot;XX</td><td>Tiny</td></tr>
+<tr><td>XXX&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;XX</td><td>Aliphatic</td></tr>
+<tr><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;XXX</td><td>X&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;XX</td><td>Aromatic</td></tr>
+<tr><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>XXX&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;XX</td><td>Positive</td></tr>
+<tr><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;X&middot;</td><td>X&middot;&middot;&middot;&middot;</td><td>&middot;&middot;XX</td><td>Negative</td></tr>
+<tr><td>&middot;&middot;&middot;&middot;&middot;</td><td>&middot;&middot;&middot;&middot;&middot;</td><td>XXXX&middot;</td><td>X&middot;&middot;&middot;&middot;</td><td>&middot;&middot;XX</td><td>Charged</td></tr>
     </table>
+    </font>
   </div>
   <p>
     <br> From Livingstone, C. D. and Barton, G. J. (1993), <br>
index 61e11c4..6bdffe1 100755 (executable)
  */
 package jalview.analysis;
 
-import jalview.analysis.ResidueCount.SymbolCounts;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.Profile;
+import jalview.datamodel.ProfileI;
+import jalview.datamodel.ResidueCount;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.ResidueCount.SymbolCounts;
 import jalview.ext.android.SparseIntArray;
 import jalview.util.Comparison;
 import jalview.util.Format;
@@ -62,13 +65,13 @@ public class AAFrequency
     }
   }
 
-  public static final Profile[] calculate(List<SequenceI> list,
+  public static final ProfileI[] calculate(List<SequenceI> list,
           int start, int end)
   {
     return calculate(list, start, end, false);
   }
 
-  public static final Profile[] calculate(List<SequenceI> sequences,
+  public static final ProfileI[] calculate(List<SequenceI> sequences,
           int start, int end, boolean profile)
   {
     SequenceI[] seqs = new SequenceI[sequences.size()];
@@ -84,7 +87,7 @@ public class AAFrequency
         }
       }
 
-      Profile[] reply = new Profile[width];
+      ProfileI[] reply = new ProfileI[width];
 
       if (end >= width)
       {
@@ -110,7 +113,7 @@ public class AAFrequency
    *          if true, store all symbol counts
    */
   public static final void calculate(final SequenceI[] sequences,
-          int start, int end, Profile[] result, boolean saveFullProfile)
+          int start, int end, ProfileI[] result, boolean saveFullProfile)
   {
     // long now = System.currentTimeMillis();
     int seqCount = sequences.length;
@@ -170,7 +173,7 @@ public class AAFrequency
       int maxCount = residueCounts.getModalCount();
       String maxResidue = residueCounts.getResiduesForCount(maxCount);
       int gapCount = residueCounts.getGapCount();
-      Profile profile = new Profile(seqCount, gapCount, maxCount,
+      ProfileI profile = new Profile(seqCount, gapCount, maxCount,
               maxResidue);
 
       if (saveFullProfile)
@@ -231,7 +234,7 @@ public class AAFrequency
    *          number of sequences
    */
   public static void completeConsensus(AlignmentAnnotation consensus,
-          Profile[] profiles, int iStart, int width, boolean ignoreGaps,
+          ProfileI[] profiles, int iStart, int width, boolean ignoreGaps,
           boolean showSequenceLogo, long nseq)
   {
     // long now = System.currentTimeMillis();
@@ -249,7 +252,7 @@ public class AAFrequency
 
     for (int i = iStart; i < width; i++)
     {
-      Profile profile;
+      ProfileI profile;
       if (i >= profiles.length || ((profile = profiles[i]) == null))
       {
         /*
@@ -299,7 +302,7 @@ public class AAFrequency
    *          the number of decimal places to format percentages to
    * @return
    */
-  static String getTooltip(Profile profile, float pid,
+  static String getTooltip(ProfileI profile, float pid,
           boolean showSequenceLogo, boolean ignoreGaps, int dp)
   {
     ResidueCount counts = profile.getCounts();
@@ -350,7 +353,7 @@ public class AAFrequency
    *          calculations
    * @return
    */
-  public static int[] extractProfile(Profile profile,
+  public static int[] extractProfile(ProfileI profile,
           boolean ignoreGaps)
   {
     int[] rtnval = new int[64];
index 8127747..7b9da46 100755 (executable)
@@ -22,25 +22,33 @@ package jalview.analysis;
 
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.ResidueCount;
+import jalview.datamodel.ResidueCount.SymbolCounts;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
-import jalview.ext.android.SparseIntArray;
 import jalview.schemes.ResidueProperties;
+import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.TreeMap;
 import java.util.Vector;
 
 /**
  * Calculates conservation values for a given set of sequences
- * 
- * @author $author$
- * @version $Revision$
  */
 public class Conservation
 {
+  /*
+   * need to have a minimum of 3% of sequences with a residue
+   * for it to be included in the conservation calculation
+   */
+  private static final int THRESHOLD_PERCENT = 3;
+
+  private static final int TOUPPERCASE = 'a' - 'A';
+
   SequenceI[] sequences;
 
   int start;
@@ -54,6 +62,11 @@ public class Conservation
 
   boolean seqNumsChanged = false; // updated after any change via calcSeqNum;
 
+  /*
+   * a map per column with {property, conservation} where conservation value is
+   * 1 (property is conserved), 0 (absence of property is conserved) or -1
+   * (property is not conserved i.e. column has residues with and without it)
+   */
   Map<String, Integer>[] total;
 
   boolean canonicaliseAa = true; // if true then conservation calculation will
@@ -70,6 +83,9 @@ public class Conservation
 
   private Sequence consSequence;
 
+  /*
+   * percentage of residues in a column to qualify for counting conservation
+   */
   private int threshold;
 
   private String name = "";
@@ -79,12 +95,10 @@ public class Conservation
   private String[] consSymbs;
 
   /**
-   * Creates a new Conservation object.
+   * Constructor using default threshold of 3%
    * 
    * @param name
    *          Name of conservation
-   * @param threshold
-   *          to count the residues in residueHash(). commonly used value is 3
    * @param sequences
    *          sequences to be used in calculation
    * @param start
@@ -92,6 +106,27 @@ public class Conservation
    * @param end
    *          end residue position
    */
+  public Conservation(String name, List<SequenceI> sequences, int start,
+          int end)
+  {
+    this(name, THRESHOLD_PERCENT, sequences, start, end);
+  }
+
+  /**
+   * Constructor
+   * 
+   * @param name
+   *          Name of conservation
+   * @param threshold
+   *          percentage of sequences at or below which property conservation is
+   *          ignored
+   * @param sequences
+   *          sequences to be used in calculation
+   * @param start
+   *          start column position
+   * @param end
+   *          end column position
+   */
   public Conservation(String name, int threshold,
           List<SequenceI> sequences, int start, int end)
   {
@@ -189,154 +224,187 @@ public class Conservation
    */
   public void calculate()
   {
-    int jSize = sequences.length;
-    // int[] values; // Replaces residueHash
-    SparseIntArray values = new SparseIntArray();
+    int height = sequences.length;
 
     total = new Map[maxLength];
 
-    for (int i = start; i <= end; i++)
+    for (int column = start; column <= end; column++)
+    {
+      ResidueCount values = countResidues(column);
+
+      /*
+       * percentage count at or below which we ignore residues
+       */
+      int thresh = (threshold * height) / 100;
+
+      /*
+       * check observed residues in column and record whether each 
+       * physico-chemical property is conserved (+1), absence conserved (0),
+       * or not conserved (-1)
+       * Using TreeMap means properties are displayed in alphabetical order
+       */
+      Map<String, Integer> resultHash = new TreeMap<String, Integer>();
+      SymbolCounts symbolCounts = values.getSymbolCounts();
+      char[] symbols = symbolCounts.symbols;
+      int[] counts = symbolCounts.values;
+      for (int j = 0; j < symbols.length; j++)
+      {
+        char c = symbols[j];
+        if (counts[j] > thresh)
+        {
+          recordConservation(resultHash, String.valueOf(c));
+        }
+      }
+      if (values.getGapCount() > thresh)
+      {
+        recordConservation(resultHash, "-");
+      }
+
+      if (total.length > 0)
+      {
+        total[column - start] = resultHash;
+      }
+    }
+  }
+
+  /**
+   * Updates the conservation results for an observed residue
+   * 
+   * @param resultMap
+   *          a map of {property, conservation} where conservation value is +1
+   *          (all residues have the property), 0 (no residue has the property)
+   *          or -1 (some do, some don't)
+   * @param res
+   */
+  protected static void recordConservation(Map<String, Integer> resultMap,
+          String res)
+  {
+    res = res.toUpperCase();
+    for (Entry<String, Map<String, Integer>> property : ResidueProperties.propHash
+            .entrySet())
     {
-      // values = new int[255];
-      values.clear();
+      String propertyName = property.getKey();
+      Integer residuePropertyValue = property.getValue().get(res);
 
-      for (int j = 0; j < jSize; j++)
+      if (!resultMap.containsKey(propertyName))
       {
-        if (sequences[j].getLength() > i)
+        /*
+         * first time we've seen this residue - note whether it has this property
+         */
+        if (residuePropertyValue != null)
         {
-          char c = sequences[j].getCharAt(i);
-
-          if (canonicaliseAa)
-          { // lookup the base aa code symbol
-            c = (char) ResidueProperties.aaIndex[sequences[j].getCharAt(i)];
-            if (c > 20)
-            {
-              c = '-';
-            }
-            else
-            {
-              // recover canonical aa symbol
-              c = ResidueProperties.aa[c].charAt(0);
-            }
-          }
-          else
-          {
-            // original behaviour - operate on ascii symbols directly
-            // No need to check if its a '-'
-            if (c == '.' || c == ' ')
-            {
-              c = '-';
-            }
-
-            c = toUpperCase(c);
-          }
-          // values[c]++;
-          values.add(c, 1);
+          resultMap.put(propertyName, residuePropertyValue);
         }
         else
         {
-          // values['-']++;
-          values.add('-', 1);
+          /*
+           * unrecognised residue - use default value for property
+           */
+          resultMap.put(propertyName, property.getValue().get("-"));
         }
       }
+      else
+      {
+        Integer currentResult = resultMap.get(propertyName);
+        if (currentResult.intValue() != -1
+                && !currentResult.equals(residuePropertyValue))
+        {
+          /*
+           * property is unconserved - residues seen both with and without it
+           */
+          resultMap.put(propertyName, Integer.valueOf(-1));
+        }
+      }
+    }
+  }
 
-      // What is the count threshold to count the residues in residueHash()
-      int thresh = (threshold * jSize) / 100;
+  /**
+   * Counts residues (upper-cased) and gaps in the given column
+   * 
+   * @param column
+   * @return
+   */
+  protected ResidueCount countResidues(int column)
+  {
+    ResidueCount values = new ResidueCount(false);
 
-      // loop over all the found residues
-      // Hashtable<String, Integer> resultHash = new Hashtable<String,
-      // Integer>();
-      Map<String, Integer> resultHash = new TreeMap<String, Integer>();
-      // for (char v = '-'; v < 'Z'; v++)
-      for (int key = 0; key < values.size(); key++)
+    for (int row = 0; row < sequences.length; row++)
+    {
+      if (sequences[row].getLength() > column)
       {
-        char v = (char) values.keyAt(key);
-        // if (values[v] > thresh)
-        if (values.valueAt(key) > thresh)
+        char c = sequences[row].getCharAt(column);
+        if (canonicaliseAa)
         {
-          String res = String.valueOf(v);
-
-          // Now loop over the properties
-          for (String type : ResidueProperties.propHash.keySet())
-          {
-            Map<String, Integer> ht = ResidueProperties.propHash.get(type);
-
-            // Have we ticked this before?
-            if (!resultHash.containsKey(type))
-            {
-              if (ht.containsKey(res))
-              {
-                resultHash.put(type, ht.get(res));
-              }
-              else
-              {
-                resultHash.put(type, ht.get("-"));
-              }
-            }
-            else if (!resultHash.get(type).equals(ht.get(res)))
-            {
-              resultHash.put(type, new Integer(-1));
-            }
-          }
+          int index = ResidueProperties.aaIndex[c];
+          c = index > 20 ? '-' : ResidueProperties.aa[index].charAt(0);
+        }
+        else
+        {
+          c = toUpperCase(c);
+        }
+        if (Comparison.isGap(c))
+        {
+          values.addGap();
+        }
+        else
+        {
+          values.add(c);
         }
       }
-
-      if (total.length > 0)
+      else
       {
-        total[i - start] = resultHash;
+        values.addGap();
       }
     }
+    return values;
   }
 
-  /*****************************************************************************
-   * count conservation for the j'th column of the alignment
+  /**
+   * Counts conservation and gaps for a column of the alignment
    * 
-   * @return { gap count, conserved residue count}
+   * @return { 1 if fully conserved, else 0, gap count }
    */
-  public int[] countConsNGaps(int j)
+  public int[] countConservationAndGaps(int column)
   {
-    int count = 0;
-    int cons = 0;
-    int nres = 0;
-    int[] r = new int[2];
-    char f = '$';
-    int i, iSize = sequences.length;
-    char c;
+    int gapCount = 0;
+    boolean fullyConserved = true;
+    int iSize = sequences.length;
 
-    for (i = 0; i < iSize; i++)
+    if (iSize == 0)
+    {
+      return new int[] { 0, 0 };
+    }
+
+    char lastRes = '0';
+    for (int i = 0; i < iSize; i++)
     {
-      if (j >= sequences[i].getLength())
+      if (column >= sequences[i].getLength())
       {
-        count++;
+        gapCount++;
         continue;
       }
 
-      c = sequences[i].getCharAt(j); // gaps do not have upper/lower case
+      char c = sequences[i].getCharAt(column); // gaps do not have upper/lower case
 
-      if (jalview.util.Comparison.isGap((c)))
+      if (Comparison.isGap((c)))
       {
-        count++;
+        gapCount++;
       }
       else
       {
         c = toUpperCase(c);
-        nres++;
-
-        if (nres == 1)
+        if (lastRes == '0')
         {
-          f = c;
-          cons++;
+          lastRes = c;
         }
-        else if (f == c)
+        if (c != lastRes)
         {
-          cons++;
+          fullyConserved = false;
         }
       }
     }
 
-    r[0] = (nres == cons) ? 1 : 0;
-    r[1] = count;
-
+    int[] r = new int[] { fullyConserved ? 1 : 0, gapCount };
     return r;
   }
 
@@ -351,7 +419,7 @@ public class Conservation
   {
     if ('a' <= c && c <= 'z')
     {
-      c -= (32); // 32 = 'a' - 'A'
+      c -= TOUPPERCASE;
     }
     return c;
   }
@@ -359,14 +427,17 @@ public class Conservation
   /**
    * Calculates the conservation sequence
    * 
-   * @param consflag
-   *          if true, positive conservation; false calculates negative
-   *          conservation
-   * @param percentageGaps
-   *          commonly used value is 25
+   * @param positiveOnly
+   *          if true, calculate positive conservation; else calculate both
+   *          positive and negative conservation
+   * @param maxPercentageGaps
+   *          the percentage of gaps in a column, at or above which no
+   *          conservation is asserted
    */
-  public void verdict(boolean consflag, float percentageGaps)
+  public void verdict(boolean positiveOnly, float maxPercentageGaps)
   {
+    // TODO call this at the end of calculate(), should not be a public method
+
     StringBuilder consString = new StringBuilder(end);
 
     // NOTE THIS SHOULD CHECK IF THE CONSEQUENCE ALREADY
@@ -379,56 +450,43 @@ public class Conservation
     consSymbs = new String[end - start + 1];
     for (int i = start; i <= end; i++)
     {
-      int[] gapcons = countConsNGaps(i);
+      int[] gapcons = countConservationAndGaps(i);
+      boolean fullyConserved = gapcons[0] == 1;
       int totGaps = gapcons[1];
-      float pgaps = ((float) totGaps * 100) / sequences.length;
-      StringBuilder positives = new StringBuilder(64);
-      StringBuilder negatives = new StringBuilder(32);
-      // consSymbs[i - start] = "";
+      float pgaps = (totGaps * 100f) / sequences.length;
 
-      if (percentageGaps > pgaps)
+      if (maxPercentageGaps > pgaps)
       {
         Map<String, Integer> resultHash = total[i - start];
-        // Now find the verdict
         int count = 0;
+        StringBuilder positives = new StringBuilder(64);
+        StringBuilder negatives = new StringBuilder(32);
         for (String type : resultHash.keySet())
         {
           int result = resultHash.get(type).intValue();
-          // Do we want to count +ve conservation or +ve and -ve cons.?
-          if (consflag)
+          if (result == -1)
           {
-            if (result == 1)
-            {
-              // consSymbs[i - start] = type + " " + consSymbs[i - start];
-              positives.append(positives.length() == 0 ? "" : " ");
-              positives.append(type);
-              count++;
-            }
+            /*
+             * not conserved (present or absent)
+             */
+            continue;
           }
-          else
+          count++;
+          if (result == 1)
           {
-            if (result != -1)
-            {
-              if (result == 0)
-              {
-                /*
-                 * add negatively conserved properties on the end
-                 */
-                // consSymbs[i - start] = consSymbs[i - start] + " !" + type;
-                negatives.append(negatives.length() == 0 ? "" : " ");
-                negatives.append("!").append(type);
-              }
-              else
-              {
-                /*
-                 * put positively conserved properties on the front
-                 */
-                // consSymbs[i - start] = type + " " + consSymbs[i - start];
-                positives.append(positives.length() == 0 ? "" : " ");
-                positives.append(type);
-              }
-              count++;
-            }
+            /*
+             * positively conserved property (all residues have it)
+             */
+            positives.append(positives.length() == 0 ? "" : " ");
+            positives.append(type);
+          }
+          if (result == 0 && !positiveOnly)
+          {
+            /*
+             * absense of property is conserved (all residues lack it)
+             */
+            negatives.append(negatives.length() == 0 ? "" : " ");
+            negatives.append("!").append(type);
           }
         }
         if (negatives.length() > 0)
@@ -443,7 +501,7 @@ public class Conservation
         }
         else
         {
-          consString.append((gapcons[0] == 1) ? "*" : "+");
+          consString.append(fullyConserved ? "*" : "+");
         }
       }
       else
@@ -747,29 +805,27 @@ public class Conservation
    * 
    * @param name
    *          - name of conservation
-   * @param threshold
-   *          - minimum number of conserved residues needed to indicate
-   *          conservation (typically 3)
    * @param seqs
    * @param start
    *          first column in calculation window
    * @param end
    *          last column in calculation window
-   * @param posOrNeg
-   *          positive (true) or negative (false) conservation
-   * @param consPercGaps
+   * @param positiveOnly
+   *          calculate positive (true) or positive and negative (false)
+   *          conservation
+   * @param maxPercentGaps
    *          percentage of gaps tolerated in column
    * @param calcQuality
    *          flag indicating if alignment quality should be calculated
    * @return Conservation object ready for use in visualization
    */
   public static Conservation calculateConservation(String name,
-          int threshold, List<SequenceI> seqs, int start, int end,
-          boolean posOrNeg, int consPercGaps, boolean calcQuality)
+          List<SequenceI> seqs, int start, int end, boolean positiveOnly,
+          int maxPercentGaps, boolean calcQuality)
   {
-    Conservation cons = new Conservation(name, threshold, seqs, start, end);
+    Conservation cons = new Conservation(name, seqs, start, end);
     cons.calculate();
-    cons.verdict(posOrNeg, consPercGaps);
+    cons.verdict(positiveOnly, maxPercentGaps);
 
     if (calcQuality)
     {
@@ -778,4 +834,24 @@ public class Conservation
 
     return cons;
   }
+
+  /**
+   * Returns the computed tooltip (annotation description) for a given column.
+   * The tip is empty if the conservation score is zero, otherwise holds the
+   * conserved properties (and, optionally, properties whose absence is
+   * conserved).
+   * 
+   * @param column
+   * @return
+   */
+  String getTooltip(int column)
+  {
+    char[] sequence = getConsSequence().getSequence();
+    char val = column < sequence.length ? sequence[column] : '-';
+    boolean hasConservation = val != '-' && val != '0';
+    int consp = column - start;
+    String tip = (hasConservation && consp > -1 && consp < consSymbs.length) ? consSymbs[consp]
+            : "";
+    return tip;
+  }
 }
index 70463e7..e30a052 100644 (file)
 package jalview.api;
 
 import jalview.analysis.Conservation;
-import jalview.analysis.Profile;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.CigarArray;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.ProfileI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -82,7 +82,7 @@ public interface AlignViewportI extends ViewStyleI
 
   ColumnSelection getColumnSelection();
 
-  Profile[] getSequenceConsensusHash();
+  ProfileI[] getSequenceConsensusHash();
 
   /**
    * Get consensus data table for the cDNA complement of this alignment (if any)
@@ -145,7 +145,7 @@ public interface AlignViewportI extends ViewStyleI
    * 
    * @param hconsensus
    */
-  void setSequenceConsensusHash(Profile[] hconsensus);
+  void setSequenceConsensusHash(ProfileI[] hconsensus);
 
   /**
    * Set the cDNA complement consensus for the viewport
index 6f83cec..d625f78 100644 (file)
@@ -1150,11 +1150,10 @@ public class APopupMenu extends java.awt.PopupMenu implements
 
     if (conservationMenuItem.getState())
     {
-
-      sg.cs.setConservation(Conservation.calculateConservation("Group", 3,
-              sg.getSequences(ap.av.getHiddenRepSequences()), 0, ap.av
-                      .getAlignment().getWidth(), false, ap.av
-                      .getConsPercGaps(), false));
+      sg.cs.setConservation(Conservation.calculateConservation("Group", sg
+              .getSequences(ap.av.getHiddenRepSequences()), 0, ap.av
+              .getAlignment().getWidth(), false, ap.av.getConsPercGaps(),
+              false));
       SliderPanel.setConservationSlider(ap, sg.cs, sg.getName());
       SliderPanel.showConservationSlider();
     }
index 3b509e5..8292a5a 100755 (executable)
@@ -676,8 +676,8 @@ public class TreeCanvas extends Panel implements MouseListener,
       if (av.getGlobalColourScheme() != null
               && av.getGlobalColourScheme().conservationApplied())
       {
-        Conservation c = new Conservation("Group", 3,
-                sg.getSequences(null), sg.getStartRes(), sg.getEndRes());
+        Conservation c = new Conservation("Group", sg.getSequences(null),
+                sg.getStartRes(), sg.getEndRes());
 
         c.calculate();
         c.verdict(false, av.getConsPercGaps());
similarity index 59%
rename from src/jalview/analysis/Profile.java
rename to src/jalview/datamodel/Profile.java
index d94d031..5464596 100644 (file)
@@ -1,22 +1,21 @@
-package jalview.analysis;
+package jalview.datamodel;
 
 
 /**
- * A data bean to hold the result of computing a profile for a column of an
- * alignment
+ * A profile for one column of an alignment
  * 
  * @author gmcarstairs
  *
  */
-public class Profile
+public class Profile implements ProfileI
 {
   /*
-   * counts of keys (chars)
+   * an object holding counts of symbols in the profile
    */
   private ResidueCount counts;
 
   /*
-   * the number of sequences in the profile
+   * the number of sequences (gapped or not) in the profile
    */
   private int height;
 
@@ -57,24 +56,19 @@ public class Profile
     this.modalResidue = modalRes;
   }
 
-  /**
-   * Set the full profile of counts
-   * 
-   * @param residueCounts
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#setCounts(jalview.datamodel.ResidueCount)
    */
+  @Override
   public void setCounts(ResidueCount residueCounts)
   {
     this.counts = residueCounts;
   }
 
-  /**
-   * Returns the percentage identity of the profile, i.e. the highest proportion
-   * of conserved (equal) symbols. The percentage is as a fraction of all
-   * sequences, or only ungapped sequences if flag ignoreGaps is set true.
-   * 
-   * @param ignoreGaps
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getPercentageIdentity(boolean)
    */
+  @Override
   public float getPercentageIdentity(boolean ignoreGaps)
   {
     if (height == 0)
@@ -93,63 +87,55 @@ public class Profile
     return pid;
   }
 
-  /**
-   * Returns the full symbol counts for this profile
-   * 
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getCounts()
    */
+  @Override
   public ResidueCount getCounts()
   {
     return counts;
   }
 
-  /**
-   * Returns the number of sequences in the profile
-   * 
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getHeight()
    */
+  @Override
   public int getHeight()
   {
     return height;
   }
 
-  /**
-   * Returns the number of sequences in the profile which had a gap character
-   * (or were too short to be included in this column's profile)
-   * 
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getGapped()
    */
+  @Override
   public int getGapped()
   {
     return gapped;
   }
 
-  /**
-   * Returns the highest count for any symbol(s) in the profile
-   * 
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getMaxCount()
    */
+  @Override
   public int getMaxCount()
   {
     return maxCount;
   }
 
-  /**
-   * Returns the symbol (or concatenated symbols) which have the highest count
-   * in the profile, or an empty string if there were no symbols counted
-   * 
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getModalResidue()
    */
+  @Override
   public String getModalResidue()
   {
     return modalResidue;
   }
 
-  /**
-   * Answers the number of non-gapped sequences in the profile
-   * 
-   * @return
+  /* (non-Javadoc)
+   * @see jalview.datamodel.ProfileI#getNonGapped()
    */
+  @Override
   public int getNonGapped()
   {
     return height - gapped;
diff --git a/src/jalview/datamodel/ProfileI.java b/src/jalview/datamodel/ProfileI.java
new file mode 100644 (file)
index 0000000..cf2b394
--- /dev/null
@@ -0,0 +1,67 @@
+package jalview.datamodel;
+
+public interface ProfileI
+{
+
+  /**
+   * Set the full profile of counts
+   * 
+   * @param residueCounts
+   */
+  public abstract void setCounts(ResidueCount residueCounts);
+
+  /**
+   * Returns the percentage identity of the profile, i.e. the highest proportion
+   * of conserved (equal) symbols. The percentage is as a fraction of all
+   * sequences, or only ungapped sequences if flag ignoreGaps is set true.
+   * 
+   * @param ignoreGaps
+   * @return
+   */
+  public abstract float getPercentageIdentity(boolean ignoreGaps);
+
+  /**
+   * Returns the full symbol counts for this profile
+   * 
+   * @return
+   */
+  public abstract ResidueCount getCounts();
+
+  /**
+   * Returns the number of sequences in the profile
+   * 
+   * @return
+   */
+  public abstract int getHeight();
+
+  /**
+   * Returns the number of sequences in the profile which had a gap character
+   * (or were too short to be included in this column's profile)
+   * 
+   * @return
+   */
+  public abstract int getGapped();
+
+  /**
+   * Returns the highest count for any symbol(s) in the profile
+   * 
+   * @return
+   */
+  public abstract int getMaxCount();
+
+  /**
+   * Returns the symbol (or concatenated symbols) which have the highest count
+   * in the profile, or an empty string if there were no symbols counted
+   * 
+   * @return
+   */
+  public abstract String getModalResidue();
+
+  /**
+   * Answers the number of non-gapped sequences in the profile
+   * 
+   * @return
+   */
+  public abstract int getNonGapped();
+
+}
\ No newline at end of file
similarity index 99%
rename from src/jalview/analysis/ResidueCount.java
rename to src/jalview/datamodel/ResidueCount.java
index 75decf2..0d0348c 100644 (file)
@@ -1,4 +1,4 @@
-package jalview.analysis;
+package jalview.datamodel;
 
 import jalview.util.Comparison;
 import jalview.util.Format;
@@ -95,7 +95,7 @@ public class ResidueCount
 
   /*
    * keeps track of the maximum count value recorded
-   * (if this class every allows decrements, would need to
+   * (if this class ever allows decrements, would need to
    * calculate this on request instead) 
    */
   int maxCount;
index 98fd8f2..ca90003 100755 (executable)
@@ -22,7 +22,6 @@ package jalview.datamodel;
 
 import jalview.analysis.AAFrequency;
 import jalview.analysis.Conservation;
-import jalview.analysis.Profile;
 import jalview.schemes.ColourSchemeI;
 
 import java.awt.Color;
@@ -528,7 +527,7 @@ public class SequenceGroup implements AnnotatedCollectionI
     boolean upd = false;
     try
     {
-      Profile[] cnsns = AAFrequency.calculate(sequences, startRes,
+      ProfileI[] cnsns = AAFrequency.calculate(sequences, startRes,
               endRes + 1, showSequenceLogo);
       if (consensus != null)
       {
@@ -544,8 +543,8 @@ public class SequenceGroup implements AnnotatedCollectionI
       if ((conservation != null)
               || (cs != null && cs.conservationApplied()))
       {
-        Conservation c = new Conservation(groupName, 3, sequences,
-                startRes, endRes + 1);
+        Conservation c = new Conservation(groupName, sequences, startRes,
+                endRes + 1);
         c.calculate();
         c.verdict(false, consPercGaps);
         if (conservation != null)
@@ -600,9 +599,9 @@ public class SequenceGroup implements AnnotatedCollectionI
     c.completeAnnotations(conservation, null, startRes, endRes + 1);
   }
 
-  public Profile[] consensusData = null;
+  public ProfileI[] consensusData = null;
 
-  private void _updateConsensusRow(Profile[] cnsns, long nseq)
+  private void _updateConsensusRow(ProfileI[] cnsns, long nseq)
   {
     if (consensus == null)
     {
index e8b865b..20fa657 100644 (file)
@@ -71,6 +71,7 @@ import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
 import jalview.io.JnetAnnotationMaker;
 import jalview.io.NewickFile;
+import jalview.io.StructureFile;
 import jalview.io.TCoffeeScoreFile;
 import jalview.jbgui.GAlignFrame;
 import jalview.schemes.Blosum62ColourScheme;
@@ -4885,8 +4886,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               }
               if (type != null)
               {
-                if (type.equalsIgnoreCase("PDB")
-                        || type.equalsIgnoreCase("mmCIF"))
+                if (StructureFile.isStructureFile(type))
                 {
                   filesmatched.add(new Object[] { file, protocol, mtch });
                   continue;
index 1c90889..bc5e1d8 100644 (file)
@@ -3401,8 +3401,8 @@ public class Jalview2XML
         }
         if (jGroup.getConsThreshold() != 0)
         {
-          Conservation c = new Conservation("All", 3,
-                  sg.getSequences(null), 0, sg.getWidth() - 1);
+          Conservation c = new Conservation("All", sg.getSequences(null),
+                  0, sg.getWidth() - 1);
           c.calculate();
           c.verdict(false, 25);
           sg.cs.setConservation(c);
index f8a296f..3b39be7 100755 (executable)
@@ -359,8 +359,8 @@ public class Jalview2XML_V1
 
         if (groups[i].getConsThreshold() != 0)
         {
-          Conservation c = new Conservation("All", 3,
-                  sg.getSequences(null), 0, sg.getWidth() - 1);
+          Conservation c = new Conservation("All", sg.getSequences(null),
+                  0, sg.getWidth() - 1);
           c.calculate();
           c.verdict(false, 25);
           sg.cs.setConservation(c);
index a487fc7..b342f86 100644 (file)
@@ -1969,7 +1969,7 @@ public class PopupMenu extends JPopupMenu
     if (conservationMenuItem.isSelected())
     {
       // JBPNote: Conservation name shouldn't be i18n translated
-      Conservation c = new Conservation("Group", 3, sg.getSequences(ap.av
+      Conservation c = new Conservation("Group", sg.getSequences(ap.av
               .getHiddenRepSequences()), sg.getStartRes(),
               sg.getEndRes() + 1);
 
index 0e513f7..84fd82f 100755 (executable)
@@ -1017,8 +1017,8 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         if (aps[a].av.getGlobalColourScheme() != null
                 && aps[a].av.getGlobalColourScheme().conservationApplied())
         {
-          Conservation c = new Conservation("Group", 3,
-                  sg.getSequences(null), sg.getStartRes(), sg.getEndRes());
+          Conservation c = new Conservation("Group", sg.getSequences(null),
+                  sg.getStartRes(), sg.getEndRes());
           c.calculate();
           c.verdict(false, aps[a].av.getConsPercGaps());
           sg.cs.setConservation(c);
index 34fdabe..61a30f8 100755 (executable)
@@ -1637,9 +1637,8 @@ public class AnnotationFile
         else if (key.equalsIgnoreCase("consThreshold"))
         {
           sg.cs.setConservationInc(Integer.parseInt(value));
-          Conservation c = new Conservation("Group", 3,
-                  sg.getSequences(null), sg.getStartRes(),
-                  sg.getEndRes() + 1);
+          Conservation c = new Conservation("Group", sg.getSequences(null),
+                  sg.getStartRes(), sg.getEndRes() + 1);
 
           c.calculate();
           c.verdict(false, 25); // TODO: refer to conservation percent threshold
index 26c202c..97b246f 100644 (file)
@@ -45,6 +45,11 @@ public abstract class StructureFile extends AlignFile
 
   private String id;
 
+  public enum StructureFileType
+  {
+    PDB, MMCIF, MMTF
+  };
+
   private PDBEntry.Type dbRefType;
 
   /**
@@ -484,4 +489,20 @@ public abstract class StructureFile extends AlignFile
   {
     this.pdbIdAvailable = pdbIdAvailable;
   }
+
+  public static boolean isStructureFile(String fileType)
+  {
+    if (fileType == null)
+    {
+      return false;
+    }
+    for (StructureFileType sfType : StructureFileType.values())
+    {
+      if (sfType.name().equalsIgnoreCase(fileType))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
 }
index ed758c5..d7ae950 100644 (file)
@@ -22,13 +22,13 @@ package jalview.renderer;
 
 import jalview.analysis.AAFrequency;
 import jalview.analysis.CodingUtils;
-import jalview.analysis.Profile;
 import jalview.analysis.Rna;
 import jalview.analysis.StructureFrequency;
 import jalview.api.AlignViewportI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.ProfileI;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
 import jalview.util.Platform;
@@ -74,7 +74,7 @@ public class AnnotationRenderer
 
   private ColumnSelection columnSelection;
 
-  private Profile[] hconsensus;
+  private ProfileI[] hconsensus;
 
   private Hashtable[] complementConsensus;
 
index 165ece9..fb71686 100755 (executable)
@@ -20,8 +20,8 @@
  */
 package jalview.schemes;
 
-import jalview.analysis.Profile;
 import jalview.datamodel.AnnotatedCollectionI;
+import jalview.datamodel.ProfileI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
 
@@ -53,7 +53,7 @@ public interface ColourSchemeI
   /**
    * assign the given consensus profile for the colourscheme
    */
-  public void setConsensus(Profile[] hconsensus);
+  public void setConsensus(ProfileI[] hconsensus);
 
   /**
    * assign the given conservation to the colourscheme
index 0dcf960..86fce4e 100644 (file)
@@ -21,7 +21,7 @@
 package jalview.schemes;
 
 import jalview.analysis.Conservation;
-import jalview.analysis.Profile;
+import jalview.datamodel.ProfileI;
 
 /**
  * Colourscheme that takes its colours from some other colourscheme
@@ -40,7 +40,7 @@ public class FollowerColourScheme extends ResidueColourScheme
   }
 
   @Override
-  public void setConsensus(Profile[] consensus)
+  public void setConsensus(ProfileI[] consensus)
   {
     if (colourScheme != null)
     {
index a15ca20..31b8320 100755 (executable)
@@ -21,8 +21,8 @@
 package jalview.schemes;
 
 import jalview.analysis.Conservation;
-import jalview.analysis.Profile;
 import jalview.datamodel.AnnotatedCollectionI;
+import jalview.datamodel.ProfileI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
 import jalview.util.ColorUtils;
@@ -54,7 +54,7 @@ public class ResidueColourScheme implements ColourSchemeI
   /*
    * Consensus data indexed by column
    */
-  Profile[] consensus;
+  ProfileI[] consensus;
 
   /*
    * Conservation string as a char array 
@@ -234,7 +234,7 @@ public class ResidueColourScheme implements ColourSchemeI
    *          DOCUMENT ME!
    */
   @Override
-  public void setConsensus(Profile[] consensus)
+  public void setConsensus(ProfileI[] consensus)
   {
     if (consensus == null)
     {
index 90a7952..f855fde 100755 (executable)
@@ -222,10 +222,14 @@ public class ResidueProperties
     purinepyrimidineIndex['n'] = 2;
   }
 
+  private static final Integer ONE = Integer.valueOf(1);
+
+  private static final Integer ZERO = Integer.valueOf(0);
+
   static
   {
-    aa3Hash.put("ALA", Integer.valueOf(0));
-    aa3Hash.put("ARG", Integer.valueOf(1));
+    aa3Hash.put("ALA", ZERO);
+    aa3Hash.put("ARG", ONE);
     aa3Hash.put("ASN", Integer.valueOf(2));
     aa3Hash.put("ASP", Integer.valueOf(3)); // D
     aa3Hash.put("CYS", Integer.valueOf(4));
@@ -910,267 +914,267 @@ public class ResidueProperties
 
   static
   {
-    hydrophobic.put("I", Integer.valueOf(1));
-    hydrophobic.put("L", Integer.valueOf(1));
-    hydrophobic.put("V", Integer.valueOf(1));
-    hydrophobic.put("C", Integer.valueOf(1));
-    hydrophobic.put("A", Integer.valueOf(1));
-    hydrophobic.put("G", Integer.valueOf(1));
-    hydrophobic.put("M", Integer.valueOf(1));
-    hydrophobic.put("F", Integer.valueOf(1));
-    hydrophobic.put("Y", Integer.valueOf(1));
-    hydrophobic.put("W", Integer.valueOf(1));
-    hydrophobic.put("H", Integer.valueOf(1));
-    hydrophobic.put("K", Integer.valueOf(1));
-    hydrophobic.put("X", Integer.valueOf(1));
-    hydrophobic.put("-", Integer.valueOf(1));
-    hydrophobic.put("*", Integer.valueOf(1));
-    hydrophobic.put("R", Integer.valueOf(0));
-    hydrophobic.put("E", Integer.valueOf(0));
-    hydrophobic.put("Q", Integer.valueOf(0));
-    hydrophobic.put("D", Integer.valueOf(0));
-    hydrophobic.put("N", Integer.valueOf(0));
-    hydrophobic.put("S", Integer.valueOf(0));
-    hydrophobic.put("T", Integer.valueOf(0));
-    hydrophobic.put("P", Integer.valueOf(0));
+    hydrophobic.put("I", ONE);
+    hydrophobic.put("L", ONE);
+    hydrophobic.put("V", ONE);
+    hydrophobic.put("C", ONE);
+    hydrophobic.put("A", ONE);
+    hydrophobic.put("G", ONE);
+    hydrophobic.put("M", ONE);
+    hydrophobic.put("F", ONE);
+    hydrophobic.put("Y", ONE);
+    hydrophobic.put("W", ONE);
+    hydrophobic.put("H", ONE);
+    hydrophobic.put("K", ONE);
+    hydrophobic.put("X", ONE);
+    hydrophobic.put("-", ONE);
+    hydrophobic.put("*", ONE);
+    hydrophobic.put("R", ZERO);
+    hydrophobic.put("E", ZERO);
+    hydrophobic.put("Q", ZERO);
+    hydrophobic.put("D", ZERO);
+    hydrophobic.put("N", ZERO);
+    hydrophobic.put("S", ZERO);
+    hydrophobic.put("T", ZERO);
+    hydrophobic.put("P", ZERO);
   }
 
   static
   {
-    polar.put("Y", Integer.valueOf(1));
-    polar.put("W", Integer.valueOf(1));
-    polar.put("H", Integer.valueOf(1));
-    polar.put("K", Integer.valueOf(1));
-    polar.put("R", Integer.valueOf(1));
-    polar.put("E", Integer.valueOf(1));
-    polar.put("Q", Integer.valueOf(1));
-    polar.put("D", Integer.valueOf(1));
-    polar.put("N", Integer.valueOf(1));
-    polar.put("S", Integer.valueOf(1));
-    polar.put("T", Integer.valueOf(1));
-    polar.put("X", Integer.valueOf(1));
-    polar.put("-", Integer.valueOf(1));
-    polar.put("*", Integer.valueOf(1));
-    polar.put("I", Integer.valueOf(0));
-    polar.put("L", Integer.valueOf(0));
-    polar.put("V", Integer.valueOf(0));
-    polar.put("C", Integer.valueOf(0));
-    polar.put("A", Integer.valueOf(0));
-    polar.put("G", Integer.valueOf(0));
-    polar.put("M", Integer.valueOf(0));
-    polar.put("F", Integer.valueOf(0));
-    polar.put("P", Integer.valueOf(0));
+    polar.put("Y", ONE);
+    polar.put("W", ONE);
+    polar.put("H", ONE);
+    polar.put("K", ONE);
+    polar.put("R", ONE);
+    polar.put("E", ONE);
+    polar.put("Q", ONE);
+    polar.put("D", ONE);
+    polar.put("N", ONE);
+    polar.put("S", ONE);
+    polar.put("T", ONE);
+    polar.put("X", ONE);
+    polar.put("-", ONE);
+    polar.put("*", ONE);
+    polar.put("I", ZERO);
+    polar.put("L", ZERO);
+    polar.put("V", ZERO);
+    polar.put("C", ZERO);
+    polar.put("A", ZERO);
+    polar.put("G", ZERO);
+    polar.put("M", ZERO);
+    polar.put("F", ZERO);
+    polar.put("P", ZERO);
   }
 
   static
   {
-    small.put("I", Integer.valueOf(0));
-    small.put("L", Integer.valueOf(0));
-    small.put("V", Integer.valueOf(1));
-    small.put("C", Integer.valueOf(1));
-    small.put("A", Integer.valueOf(1));
-    small.put("G", Integer.valueOf(1));
-    small.put("M", Integer.valueOf(0));
-    small.put("F", Integer.valueOf(0));
-    small.put("Y", Integer.valueOf(0));
-    small.put("W", Integer.valueOf(0));
-    small.put("H", Integer.valueOf(0));
-    small.put("K", Integer.valueOf(0));
-    small.put("R", Integer.valueOf(0));
-    small.put("E", Integer.valueOf(0));
-    small.put("Q", Integer.valueOf(0));
-    small.put("D", Integer.valueOf(1));
-    small.put("N", Integer.valueOf(1));
-    small.put("S", Integer.valueOf(1));
-    small.put("T", Integer.valueOf(1));
-    small.put("P", Integer.valueOf(1));
-    small.put("-", Integer.valueOf(1));
-    small.put("*", Integer.valueOf(1));
+    small.put("I", ZERO);
+    small.put("L", ZERO);
+    small.put("V", ONE);
+    small.put("C", ONE);
+    small.put("A", ONE);
+    small.put("G", ONE);
+    small.put("M", ZERO);
+    small.put("F", ZERO);
+    small.put("Y", ZERO);
+    small.put("W", ZERO);
+    small.put("H", ZERO);
+    small.put("K", ZERO);
+    small.put("R", ZERO);
+    small.put("E", ZERO);
+    small.put("Q", ZERO);
+    small.put("D", ONE);
+    small.put("N", ONE);
+    small.put("S", ONE);
+    small.put("T", ONE);
+    small.put("P", ONE);
+    small.put("-", ONE);
+    small.put("*", ONE);
   }
 
   static
   {
-    positive.put("I", Integer.valueOf(0));
-    positive.put("L", Integer.valueOf(0));
-    positive.put("V", Integer.valueOf(0));
-    positive.put("C", Integer.valueOf(0));
-    positive.put("A", Integer.valueOf(0));
-    positive.put("G", Integer.valueOf(0));
-    positive.put("M", Integer.valueOf(0));
-    positive.put("F", Integer.valueOf(0));
-    positive.put("Y", Integer.valueOf(0));
-    positive.put("W", Integer.valueOf(0));
-    positive.put("H", Integer.valueOf(1));
-    positive.put("K", Integer.valueOf(1));
-    positive.put("R", Integer.valueOf(1));
-    positive.put("E", Integer.valueOf(0));
-    positive.put("Q", Integer.valueOf(0));
-    positive.put("D", Integer.valueOf(0));
-    positive.put("N", Integer.valueOf(0));
-    positive.put("S", Integer.valueOf(0));
-    positive.put("T", Integer.valueOf(0));
-    positive.put("P", Integer.valueOf(0));
-    positive.put("-", Integer.valueOf(1));
-    positive.put("*", Integer.valueOf(1));
+    positive.put("I", ZERO);
+    positive.put("L", ZERO);
+    positive.put("V", ZERO);
+    positive.put("C", ZERO);
+    positive.put("A", ZERO);
+    positive.put("G", ZERO);
+    positive.put("M", ZERO);
+    positive.put("F", ZERO);
+    positive.put("Y", ZERO);
+    positive.put("W", ZERO);
+    positive.put("H", ONE);
+    positive.put("K", ONE);
+    positive.put("R", ONE);
+    positive.put("E", ZERO);
+    positive.put("Q", ZERO);
+    positive.put("D", ZERO);
+    positive.put("N", ZERO);
+    positive.put("S", ZERO);
+    positive.put("T", ZERO);
+    positive.put("P", ZERO);
+    positive.put("-", ONE);
+    positive.put("*", ONE);
   }
 
   static
   {
-    negative.put("I", Integer.valueOf(0));
-    negative.put("L", Integer.valueOf(0));
-    negative.put("V", Integer.valueOf(0));
-    negative.put("C", Integer.valueOf(0));
-    negative.put("A", Integer.valueOf(0));
-    negative.put("G", Integer.valueOf(0));
-    negative.put("M", Integer.valueOf(0));
-    negative.put("F", Integer.valueOf(0));
-    negative.put("Y", Integer.valueOf(0));
-    negative.put("W", Integer.valueOf(0));
-    negative.put("H", Integer.valueOf(0));
-    negative.put("K", Integer.valueOf(0));
-    negative.put("R", Integer.valueOf(0));
-    negative.put("E", Integer.valueOf(1));
-    negative.put("Q", Integer.valueOf(0));
-    negative.put("D", Integer.valueOf(1));
-    negative.put("N", Integer.valueOf(0));
-    negative.put("S", Integer.valueOf(0));
-    negative.put("T", Integer.valueOf(0));
-    negative.put("P", Integer.valueOf(0));
-    negative.put("-", Integer.valueOf(1));
-    negative.put("*", Integer.valueOf(1));
+    negative.put("I", ZERO);
+    negative.put("L", ZERO);
+    negative.put("V", ZERO);
+    negative.put("C", ZERO);
+    negative.put("A", ZERO);
+    negative.put("G", ZERO);
+    negative.put("M", ZERO);
+    negative.put("F", ZERO);
+    negative.put("Y", ZERO);
+    negative.put("W", ZERO);
+    negative.put("H", ZERO);
+    negative.put("K", ZERO);
+    negative.put("R", ZERO);
+    negative.put("E", ONE);
+    negative.put("Q", ZERO);
+    negative.put("D", ONE);
+    negative.put("N", ZERO);
+    negative.put("S", ZERO);
+    negative.put("T", ZERO);
+    negative.put("P", ZERO);
+    negative.put("-", ONE);
+    negative.put("*", ONE);
   }
 
   static
   {
-    charged.put("I", Integer.valueOf(0));
-    charged.put("L", Integer.valueOf(0));
-    charged.put("V", Integer.valueOf(0));
-    charged.put("C", Integer.valueOf(0));
-    charged.put("A", Integer.valueOf(0));
-    charged.put("G", Integer.valueOf(0));
-    charged.put("M", Integer.valueOf(0));
-    charged.put("F", Integer.valueOf(0));
-    charged.put("Y", Integer.valueOf(0));
-    charged.put("W", Integer.valueOf(0));
-    charged.put("H", Integer.valueOf(1));
-    charged.put("K", Integer.valueOf(1));
-    charged.put("R", Integer.valueOf(1));
-    charged.put("E", Integer.valueOf(1));
-    charged.put("Q", Integer.valueOf(0));
-    charged.put("D", Integer.valueOf(1));
-    charged.put("N", Integer.valueOf(0)); // Asparagine is polar but not
+    charged.put("I", ZERO);
+    charged.put("L", ZERO);
+    charged.put("V", ZERO);
+    charged.put("C", ZERO);
+    charged.put("A", ZERO);
+    charged.put("G", ZERO);
+    charged.put("M", ZERO);
+    charged.put("F", ZERO);
+    charged.put("Y", ZERO);
+    charged.put("W", ZERO);
+    charged.put("H", ONE);
+    charged.put("K", ONE);
+    charged.put("R", ONE);
+    charged.put("E", ONE);
+    charged.put("Q", ZERO);
+    charged.put("D", ONE);
+    charged.put("N", ZERO); // Asparagine is polar but not
                                           // charged.
     // Alternative would be charged and
     // negative (in basic form)?
-    charged.put("S", Integer.valueOf(0));
-    charged.put("T", Integer.valueOf(0));
-    charged.put("P", Integer.valueOf(0));
-    charged.put("-", Integer.valueOf(1));
-    charged.put("*", Integer.valueOf(1));
+    charged.put("S", ZERO);
+    charged.put("T", ZERO);
+    charged.put("P", ZERO);
+    charged.put("-", ONE);
+    charged.put("*", ONE);
   }
 
   static
   {
-    aromatic.put("I", Integer.valueOf(0));
-    aromatic.put("L", Integer.valueOf(0));
-    aromatic.put("V", Integer.valueOf(0));
-    aromatic.put("C", Integer.valueOf(0));
-    aromatic.put("A", Integer.valueOf(0));
-    aromatic.put("G", Integer.valueOf(0));
-    aromatic.put("M", Integer.valueOf(0));
-    aromatic.put("F", Integer.valueOf(1));
-    aromatic.put("Y", Integer.valueOf(1));
-    aromatic.put("W", Integer.valueOf(1));
-    aromatic.put("H", Integer.valueOf(1));
-    aromatic.put("K", Integer.valueOf(0));
-    aromatic.put("R", Integer.valueOf(0));
-    aromatic.put("E", Integer.valueOf(0));
-    aromatic.put("Q", Integer.valueOf(0));
-    aromatic.put("D", Integer.valueOf(0));
-    aromatic.put("N", Integer.valueOf(0));
-    aromatic.put("S", Integer.valueOf(0));
-    aromatic.put("T", Integer.valueOf(0));
-    aromatic.put("P", Integer.valueOf(0));
-    aromatic.put("-", Integer.valueOf(1));
-    aromatic.put("*", Integer.valueOf(1));
+    aromatic.put("I", ZERO);
+    aromatic.put("L", ZERO);
+    aromatic.put("V", ZERO);
+    aromatic.put("C", ZERO);
+    aromatic.put("A", ZERO);
+    aromatic.put("G", ZERO);
+    aromatic.put("M", ZERO);
+    aromatic.put("F", ONE);
+    aromatic.put("Y", ONE);
+    aromatic.put("W", ONE);
+    aromatic.put("H", ONE);
+    aromatic.put("K", ZERO);
+    aromatic.put("R", ZERO);
+    aromatic.put("E", ZERO);
+    aromatic.put("Q", ZERO);
+    aromatic.put("D", ZERO);
+    aromatic.put("N", ZERO);
+    aromatic.put("S", ZERO);
+    aromatic.put("T", ZERO);
+    aromatic.put("P", ZERO);
+    aromatic.put("-", ONE);
+    aromatic.put("*", ONE);
   }
 
   static
   {
-    aliphatic.put("I", Integer.valueOf(1));
-    aliphatic.put("L", Integer.valueOf(1));
-    aliphatic.put("V", Integer.valueOf(1));
-    aliphatic.put("C", Integer.valueOf(0));
-    aliphatic.put("A", Integer.valueOf(0));
-    aliphatic.put("G", Integer.valueOf(0));
-    aliphatic.put("M", Integer.valueOf(0));
-    aliphatic.put("F", Integer.valueOf(0));
-    aliphatic.put("Y", Integer.valueOf(0));
-    aliphatic.put("W", Integer.valueOf(0));
-    aliphatic.put("H", Integer.valueOf(0));
-    aliphatic.put("K", Integer.valueOf(0));
-    aliphatic.put("R", Integer.valueOf(0));
-    aliphatic.put("E", Integer.valueOf(0));
-    aliphatic.put("Q", Integer.valueOf(0));
-    aliphatic.put("D", Integer.valueOf(0));
-    aliphatic.put("N", Integer.valueOf(0));
-    aliphatic.put("S", Integer.valueOf(0));
-    aliphatic.put("T", Integer.valueOf(0));
-    aliphatic.put("P", Integer.valueOf(0));
-    aliphatic.put("-", Integer.valueOf(1));
-    aliphatic.put("*", Integer.valueOf(1));
+    aliphatic.put("I", ONE);
+    aliphatic.put("L", ONE);
+    aliphatic.put("V", ONE);
+    aliphatic.put("C", ZERO);
+    aliphatic.put("A", ZERO);
+    aliphatic.put("G", ZERO);
+    aliphatic.put("M", ZERO);
+    aliphatic.put("F", ZERO);
+    aliphatic.put("Y", ZERO);
+    aliphatic.put("W", ZERO);
+    aliphatic.put("H", ZERO);
+    aliphatic.put("K", ZERO);
+    aliphatic.put("R", ZERO);
+    aliphatic.put("E", ZERO);
+    aliphatic.put("Q", ZERO);
+    aliphatic.put("D", ZERO);
+    aliphatic.put("N", ZERO);
+    aliphatic.put("S", ZERO);
+    aliphatic.put("T", ZERO);
+    aliphatic.put("P", ZERO);
+    aliphatic.put("-", ONE);
+    aliphatic.put("*", ONE);
   }
 
   static
   {
-    tiny.put("I", Integer.valueOf(0));
-    tiny.put("L", Integer.valueOf(0));
-    tiny.put("V", Integer.valueOf(0));
-    tiny.put("C", Integer.valueOf(0));
-    tiny.put("A", Integer.valueOf(1));
-    tiny.put("G", Integer.valueOf(1));
-    tiny.put("M", Integer.valueOf(0));
-    tiny.put("F", Integer.valueOf(0));
-    tiny.put("Y", Integer.valueOf(0));
-    tiny.put("W", Integer.valueOf(0));
-    tiny.put("H", Integer.valueOf(0));
-    tiny.put("K", Integer.valueOf(0));
-    tiny.put("R", Integer.valueOf(0));
-    tiny.put("E", Integer.valueOf(0));
-    tiny.put("Q", Integer.valueOf(0));
-    tiny.put("D", Integer.valueOf(0));
-    tiny.put("N", Integer.valueOf(0));
-    tiny.put("S", Integer.valueOf(1));
-    tiny.put("T", Integer.valueOf(0));
-    tiny.put("P", Integer.valueOf(0));
-    tiny.put("-", Integer.valueOf(1));
-    tiny.put("*", Integer.valueOf(1));
+    tiny.put("I", ZERO);
+    tiny.put("L", ZERO);
+    tiny.put("V", ZERO);
+    tiny.put("C", ZERO);
+    tiny.put("A", ONE);
+    tiny.put("G", ONE);
+    tiny.put("M", ZERO);
+    tiny.put("F", ZERO);
+    tiny.put("Y", ZERO);
+    tiny.put("W", ZERO);
+    tiny.put("H", ZERO);
+    tiny.put("K", ZERO);
+    tiny.put("R", ZERO);
+    tiny.put("E", ZERO);
+    tiny.put("Q", ZERO);
+    tiny.put("D", ZERO);
+    tiny.put("N", ZERO);
+    tiny.put("S", ONE);
+    tiny.put("T", ZERO);
+    tiny.put("P", ZERO);
+    tiny.put("-", ONE);
+    tiny.put("*", ONE);
   }
 
   static
   {
-    proline.put("I", Integer.valueOf(0));
-    proline.put("L", Integer.valueOf(0));
-    proline.put("V", Integer.valueOf(0));
-    proline.put("C", Integer.valueOf(0));
-    proline.put("A", Integer.valueOf(0));
-    proline.put("G", Integer.valueOf(0));
-    proline.put("M", Integer.valueOf(0));
-    proline.put("F", Integer.valueOf(0));
-    proline.put("Y", Integer.valueOf(0));
-    proline.put("W", Integer.valueOf(0));
-    proline.put("H", Integer.valueOf(0));
-    proline.put("K", Integer.valueOf(0));
-    proline.put("R", Integer.valueOf(0));
-    proline.put("E", Integer.valueOf(0));
-    proline.put("Q", Integer.valueOf(0));
-    proline.put("D", Integer.valueOf(0));
-    proline.put("N", Integer.valueOf(0));
-    proline.put("S", Integer.valueOf(0));
-    proline.put("T", Integer.valueOf(0));
-    proline.put("P", Integer.valueOf(1));
-    proline.put("-", Integer.valueOf(1));
-    proline.put("*", Integer.valueOf(1));
+    proline.put("I", ZERO);
+    proline.put("L", ZERO);
+    proline.put("V", ZERO);
+    proline.put("C", ZERO);
+    proline.put("A", ZERO);
+    proline.put("G", ZERO);
+    proline.put("M", ZERO);
+    proline.put("F", ZERO);
+    proline.put("Y", ZERO);
+    proline.put("W", ZERO);
+    proline.put("H", ZERO);
+    proline.put("K", ZERO);
+    proline.put("R", ZERO);
+    proline.put("E", ZERO);
+    proline.put("Q", ZERO);
+    proline.put("D", ZERO);
+    proline.put("N", ZERO);
+    proline.put("S", ZERO);
+    proline.put("T", ZERO);
+    proline.put("P", ONE);
+    proline.put("-", ONE);
+    proline.put("*", ONE);
   }
 
   static
index 7121985..389afcd 100755 (executable)
@@ -26,6 +26,8 @@
  */
 package jalview.util;
 
+import java.util.Arrays;
+
 /**
  * DOCUMENT ME!
  * 
@@ -664,30 +666,22 @@ public class Format
   }
 
   /**
-   * DOCUMENT ME!
+   * Returns a string consisting of n repeats of character c
    * 
    * @param c
-   *          DOCUMENT ME!
    * @param n
-   *          DOCUMENT ME!
    * 
-   * @return DOCUMENT ME!
+   * @return
    */
-  private static String repeat(char c, int n)
+  static String repeat(char c, int n)
   {
     if (n <= 0)
     {
       return "";
     }
-
-    StringBuffer s = new StringBuffer(n);
-
-    for (int i = 0; i < n; i++)
-    {
-      s.append(c);
-    }
-
-    return s.toString();
+    char[] chars = new char[n];
+    Arrays.fill(chars, c);
+    return new String(chars);
   }
 
   /**
@@ -959,7 +953,27 @@ public class Format
    */
   public static void appendPercentage(StringBuilder sb, float value, int dp)
   {
-    sb.append((int) value);
+    /*
+     * rounding first
+     */
+    double d = value;
+    long factor = 1L;
+    for (int i = 0; i < dp; i++)
+    {
+      factor *= 10;
+    }
+    d *= factor;
+    d += 0.5;
+
+    /*
+     * integer part
+     */
+    value = (float) (d / factor);
+    sb.append((long) value);
+
+    /*
+     * decimal places
+     */
     if (dp > 0)
     {
       sb.append(".");
index c01be4e..57258ef 100644 (file)
@@ -22,7 +22,6 @@ package jalview.viewmodel;
 
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.analysis.Conservation;
-import jalview.analysis.Profile;
 import jalview.api.AlignCalcManagerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
@@ -37,6 +36,7 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.CigarArray;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.HiddenSequences;
+import jalview.datamodel.ProfileI;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceCollectionI;
@@ -701,7 +701,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   /**
    * results of alignment consensus analysis for visible portion of view
    */
-  protected Profile[] hconsensus = null;
+  protected ProfileI[] hconsensus = null;
 
   /**
    * results of cDNA complement consensus visible portion of view
@@ -735,7 +735,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
-  public void setSequenceConsensusHash(Profile[] hconsensus)
+  public void setSequenceConsensusHash(ProfileI[] hconsensus)
   {
     this.hconsensus = hconsensus;
   }
@@ -747,7 +747,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
   }
 
   @Override
-  public Profile[] getSequenceConsensusHash()
+  public ProfileI[] getSequenceConsensusHash()
   {
     return hconsensus;
   }
@@ -1867,7 +1867,7 @@ public abstract class AlignmentViewport implements AlignViewportI,
       cs.setConsensus(hconsensus);
       if (cs.conservationApplied())
       {
-        cs.setConservation(Conservation.calculateConservation("All", 3,
+        cs.setConservation(Conservation.calculateConservation("All",
                 alignment.getSequences(), 0, alignment.getWidth(), false,
                 getConsPercGaps(), false));
       }
index c88a24a..2b11477 100644 (file)
 package jalview.workers;
 
 import jalview.analysis.AAFrequency;
-import jalview.analysis.Profile;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.ProfileI;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ColourSchemeI;
 
@@ -124,7 +124,7 @@ public class ConsensusThread extends AlignCalcWorker
    */
   protected void computeConsensus(AlignmentI alignment)
   {
-    Profile[] hconsensus = new Profile[alignment.getWidth()];
+    ProfileI[] hconsensus = new ProfileI[alignment.getWidth()];
 
     SequenceI[] aseqs = getSequences();
     AAFrequency.calculate(aseqs, 0, alignment.getWidth(), hconsensus, true);
@@ -144,7 +144,7 @@ public class ConsensusThread extends AlignCalcWorker
   /**
    * @param hconsensus
    */
-  protected void setColourSchemeConsensus(Profile[] hconsensus)
+  protected void setColourSchemeConsensus(ProfileI[] hconsensus)
   {
     ColourSchemeI globalColourScheme = alignViewport
             .getGlobalColourScheme();
@@ -177,7 +177,7 @@ public class ConsensusThread extends AlignCalcWorker
   public void updateResultAnnotation(boolean immediate)
   {
     AlignmentAnnotation consensus = getConsensusAnnotation();
-    Profile[] hconsensus = (Profile[]) getViewportConsensus();
+    ProfileI[] hconsensus = (ProfileI[]) getViewportConsensus();
     if (immediate || !calcMan.isWorking(this) && consensus != null
             && hconsensus != null)
     {
@@ -195,7 +195,7 @@ public class ConsensusThread extends AlignCalcWorker
    *          the computed consensus data
    */
   protected void deriveConsensus(AlignmentAnnotation consensusAnnotation,
-          Profile[] hconsensus)
+          ProfileI[] hconsensus)
   {
     long nseq = getSequences().length;
     AAFrequency.completeConsensus(consensusAnnotation, hconsensus, 0,
index 11ec521..e71c4f5 100644 (file)
@@ -94,7 +94,7 @@ public class ConservationThread extends AlignCalcWorker
       }
       try
       {
-        cons = Conservation.calculateConservation("All", 3,
+        cons = Conservation.calculateConservation("All",
                 alignment.getSequences(), 0, alWidth - 1, false,
                 ConsPercGaps, quality != null);
       } catch (IndexOutOfBoundsException x)
index 0ddbddc..1c04b8e 100644 (file)
@@ -25,6 +25,7 @@ import static org.testng.AssertJUnit.assertNull;
 
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.ProfileI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 
@@ -40,12 +41,12 @@ public class AAFrequencyTest
     SequenceI seq3 = new Sequence("Seq3", "C---G");
     SequenceI seq4 = new Sequence("Seq4", "CA--t");
     SequenceI[] seqs = new SequenceI[] { seq1, seq2, seq3, seq4 };
-    Profile[] result = new Profile[seq1.getLength()];
+    ProfileI[] result = new ProfileI[seq1.getLength()];
 
     AAFrequency.calculate(seqs, 0, seq1.getLength(), result, false);
 
     // col 0 is 100% C
-    Profile col = result[0];
+    ProfileI col = result[0];
     assertEquals(100f, col.getPercentageIdentity(false));
     assertEquals(100f, col.getPercentageIdentity(true));
     assertEquals(4, col.getMaxCount());
@@ -89,10 +90,10 @@ public class AAFrequencyTest
     SequenceI seq3 = new Sequence("Seq3", "C--G");
     SequenceI seq4 = new Sequence("Seq4", "CA-t");
     SequenceI[] seqs = new SequenceI[] { seq1, seq2, seq3, seq4 };
-    Profile[] result = new Profile[seq1.getLength()];
+    ProfileI[] result = new ProfileI[seq1.getLength()];
 
     AAFrequency.calculate(seqs, 0, seq1.getLength(), result, true);
-    Profile profile = result[0];
+    ProfileI profile = result[0];
     assertEquals(4, profile.getCounts().getCount('C'));
     assertEquals(4, profile.getHeight());
     assertEquals(4, profile.getNonGapped());
@@ -123,7 +124,7 @@ public class AAFrequencyTest
     SequenceI seq3 = new Sequence("Seq3", "C--G");
     SequenceI seq4 = new Sequence("Seq4", "CA-t");
     SequenceI[] seqs = new SequenceI[] { seq1, seq2, seq3, seq4 };
-    Profile[] result = new Profile[seq1.getLength()];
+    ProfileI[] result = new ProfileI[seq1.getLength()];
 
     // ensure class loaded and initialized
     AAFrequency.calculate(seqs, 0, seq1.getLength(), result, true);
@@ -153,7 +154,7 @@ public class AAFrequencyTest
     SequenceI seq3 = new Sequence("Seq3", "C---G");
     SequenceI seq4 = new Sequence("Seq4", "CA--t");
     SequenceI[] seqs = new SequenceI[] { seq1, seq2, seq3, seq4 };
-    Profile[] profiles = new Profile[seq1.getLength()];
+    ProfileI[] profiles = new ProfileI[seq1.getLength()];
     AAFrequency.calculate(seqs, 0, seq1.getLength(), profiles, true);
 
     AlignmentAnnotation consensus = new AlignmentAnnotation("Consensus",
@@ -194,7 +195,7 @@ public class AAFrequencyTest
     SequenceI seq3 = new Sequence("Seq3", "C---G");
     SequenceI seq4 = new Sequence("Seq4", "CA--t");
     SequenceI[] seqs = new SequenceI[] { seq1, seq2, seq3, seq4 };
-    Profile[] profiles = new Profile[seq1.getLength()];
+    ProfileI[] profiles = new ProfileI[seq1.getLength()];
     AAFrequency.calculate(seqs, 0, seq1.getLength(), profiles, true);
   
     AlignmentAnnotation consensus = new AlignmentAnnotation("Consensus",
diff --git a/test/jalview/analysis/ConservationTest.java b/test/jalview/analysis/ConservationTest.java
new file mode 100644 (file)
index 0000000..123fcd6
--- /dev/null
@@ -0,0 +1,316 @@
+package jalview.analysis;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+public class ConservationTest
+{
+  @Test(groups = "Functional")
+  public void testRecordConservation()
+  {
+    Map<String, Integer> resultMap = new HashMap<String, Integer>();
+
+    // V is hydrophobic, aliphatic, small
+    Conservation.recordConservation(resultMap, "V");
+    assertEquals(resultMap.get("hydrophobic").intValue(), 1);
+    assertEquals(resultMap.get("aliphatic").intValue(), 1);
+    assertEquals(resultMap.get("small").intValue(), 1);
+    assertEquals(resultMap.get("tiny").intValue(), 0);
+    assertEquals(resultMap.get("polar").intValue(), 0);
+    assertEquals(resultMap.get("charged").intValue(), 0);
+
+    // now add S: not hydrophobic, small, tiny, polar, not aliphatic
+    Conservation.recordConservation(resultMap, "s");
+    assertEquals(resultMap.get("hydrophobic").intValue(), -1);
+    assertEquals(resultMap.get("aliphatic").intValue(), -1);
+    assertEquals(resultMap.get("small").intValue(), 1);
+    assertEquals(resultMap.get("tiny").intValue(), -1);
+    assertEquals(resultMap.get("polar").intValue(), -1);
+    assertEquals(resultMap.get("charged").intValue(), 0);
+  }
+
+  @Test(groups = "Functional")
+  public void testCountConservationAndGaps()
+  {
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    seqs.add(new Sequence("seq1", "VGnY")); // not case sensitive
+    seqs.add(new Sequence("seq2", "-G-y"));
+    seqs.add(new Sequence("seq3", "VG-Y"));
+    seqs.add(new Sequence("seq4", "VGNW"));
+
+    Conservation cons = new Conservation("", seqs, 0, 50);
+    int[] counts = cons.countConservationAndGaps(0);
+    assertEquals(counts[0], 1); // conserved
+    assertEquals(counts[1], 1); // gap count
+    counts = cons.countConservationAndGaps(1);
+    assertEquals(counts[0], 1);
+    assertEquals(counts[1], 0);
+    counts = cons.countConservationAndGaps(2);
+    assertEquals(counts[0], 1);
+    assertEquals(counts[1], 2);
+    counts = cons.countConservationAndGaps(3);
+    assertEquals(counts[0], 0); // not conserved
+    assertEquals(counts[1], 0);
+  }
+
+  @Test(groups = "Functional")
+  public void testCalculate_noThreshold()
+  {
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    seqs.add(new Sequence("seq1", "VGIV-N"));
+    seqs.add(new Sequence("seq2", "V-iL-N")); // not case sensitive
+    seqs.add(new Sequence("seq3", "V-IW-N"));
+    seqs.add(new Sequence("seq4", "VGLH-L"));
+
+    Conservation cons = new Conservation("", 0, seqs, 0, 5);
+    cons.calculate();
+
+    /*
+     * column 0: all V (hydrophobic/aliphatic/small)
+     */
+    Map<String, Integer> colCons = cons.total[0];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), 1);
+    assertEquals(colCons.get("small").intValue(), 1);
+    assertEquals(colCons.get("tiny").intValue(), 0);
+    assertEquals(colCons.get("proline").intValue(), 0);
+    assertEquals(colCons.get("charged").intValue(), 0);
+    assertEquals(colCons.get("negative").intValue(), 0);
+    assertEquals(colCons.get("polar").intValue(), 0);
+    assertEquals(colCons.get("positive").intValue(), 0);
+    assertEquals(colCons.get("aromatic").intValue(), 0);
+
+    /*
+     * column 1: all G (hydrophobic/small/tiny)
+     * gaps take default value of property present
+     */
+    colCons = cons.total[1];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), -1);
+    assertEquals(colCons.get("small").intValue(), 1);
+    assertEquals(colCons.get("tiny").intValue(), 1);
+    assertEquals(colCons.get("proline").intValue(), -1);
+    assertEquals(colCons.get("charged").intValue(), -1);
+    assertEquals(colCons.get("negative").intValue(), -1);
+    assertEquals(colCons.get("polar").intValue(), -1);
+    assertEquals(colCons.get("positive").intValue(), -1);
+    assertEquals(colCons.get("aromatic").intValue(), -1);
+
+    /*
+     * column 2: I/L (aliphatic/hydrophobic), all others negatively conserved
+     */
+    colCons = cons.total[2];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), 1);
+    assertEquals(colCons.get("small").intValue(), 0);
+    assertEquals(colCons.get("tiny").intValue(), 0);
+    assertEquals(colCons.get("proline").intValue(), 0);
+    assertEquals(colCons.get("charged").intValue(), 0);
+    assertEquals(colCons.get("negative").intValue(), 0);
+    assertEquals(colCons.get("polar").intValue(), 0);
+    assertEquals(colCons.get("positive").intValue(), 0);
+    assertEquals(colCons.get("aromatic").intValue(), 0);
+
+    /*
+     * column 3: VLWH all hydrophobic, none is tiny, negative or proline
+     */
+    colCons = cons.total[3];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), -1);
+    assertEquals(colCons.get("small").intValue(), -1);
+    assertEquals(colCons.get("tiny").intValue(), 0);
+    assertEquals(colCons.get("proline").intValue(), 0);
+    assertEquals(colCons.get("charged").intValue(), -1);
+    assertEquals(colCons.get("negative").intValue(), 0);
+    assertEquals(colCons.get("polar").intValue(), -1);
+    assertEquals(colCons.get("positive").intValue(), -1);
+    assertEquals(colCons.get("aromatic").intValue(), -1);
+
+    /*
+     * column 4: all gaps - counted as having all properties
+     */
+    colCons = cons.total[4];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), 1);
+    assertEquals(colCons.get("small").intValue(), 1);
+    assertEquals(colCons.get("tiny").intValue(), 1);
+    assertEquals(colCons.get("proline").intValue(), 1);
+    assertEquals(colCons.get("charged").intValue(), 1);
+    assertEquals(colCons.get("negative").intValue(), 1);
+    assertEquals(colCons.get("polar").intValue(), 1);
+    assertEquals(colCons.get("positive").intValue(), 1);
+    assertEquals(colCons.get("aromatic").intValue(), 1);
+
+    /*
+     * column 5: N (small polar) and L (aliphatic hydrophobic) 
+     * have nothing in common!
+     */
+    colCons = cons.total[5];
+    assertEquals(colCons.get("hydrophobic").intValue(), -1);
+    assertEquals(colCons.get("aliphatic").intValue(), -1);
+    assertEquals(colCons.get("small").intValue(), -1);
+    assertEquals(colCons.get("tiny").intValue(), 0);
+    assertEquals(colCons.get("proline").intValue(), 0);
+    assertEquals(colCons.get("charged").intValue(), 0);
+    assertEquals(colCons.get("negative").intValue(), 0);
+    assertEquals(colCons.get("polar").intValue(), -1);
+    assertEquals(colCons.get("positive").intValue(), 0);
+    assertEquals(colCons.get("aromatic").intValue(), 0);
+  }
+
+  /**
+   * Test for the case whether the number of non-gapped sequences in a column
+   * has to be above a threshold
+   */
+  @Test(groups = "Functional")
+  public void testCalculate_threshold()
+  {
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    seqs.add(new Sequence("seq1", "VGIV-"));
+    seqs.add(new Sequence("seq2", "V-iL-")); // not case sensitive
+    seqs.add(new Sequence("seq3", "V-IW-"));
+    seqs.add(new Sequence("seq4", "VGLH-"));
+    seqs.add(new Sequence("seq5", "VGLH-"));
+  
+    /*
+     * threshold 50% means a residue has to occur 3 or more times
+     * in a column to be counted for conservation
+     */
+    // TODO: ConservationThread uses a value of 3
+    // calculateConservation states it is the minimum number of sequences
+    // but it is treated as percentage threshold in calculate() ?
+    Conservation cons = new Conservation("", 50, seqs, 0, 4);
+    cons.calculate();
+  
+    /*
+     * column 0: all V (hydrophobic/aliphatic/small)
+     */
+    Map<String, Integer> colCons = cons.total[0];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), 1);
+    assertEquals(colCons.get("small").intValue(), 1);
+    assertEquals(colCons.get("tiny").intValue(), 0);
+    assertEquals(colCons.get("proline").intValue(), 0);
+    assertEquals(colCons.get("charged").intValue(), 0);
+    assertEquals(colCons.get("negative").intValue(), 0);
+    assertEquals(colCons.get("polar").intValue(), 0);
+    assertEquals(colCons.get("positive").intValue(), 0);
+    assertEquals(colCons.get("aromatic").intValue(), 0);
+  
+    /*
+     * column 1: all G (hydrophobic/small/tiny)
+     * gaps are ignored as not above threshold
+     */
+    colCons = cons.total[1];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), 0);
+    assertEquals(colCons.get("small").intValue(), 1);
+    assertEquals(colCons.get("tiny").intValue(), 1);
+    assertEquals(colCons.get("proline").intValue(), 0);
+    assertEquals(colCons.get("charged").intValue(), 0);
+    assertEquals(colCons.get("negative").intValue(), 0);
+    assertEquals(colCons.get("polar").intValue(), 0);
+    assertEquals(colCons.get("positive").intValue(), 0);
+    assertEquals(colCons.get("aromatic").intValue(), 0);
+  
+    /*
+     * column 2: I/L (aliphatic/hydrophobic), all others negatively conserved
+     */
+    colCons = cons.total[2];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), 1);
+    assertEquals(colCons.get("small").intValue(), 0);
+    assertEquals(colCons.get("tiny").intValue(), 0);
+    assertEquals(colCons.get("proline").intValue(), 0);
+    assertEquals(colCons.get("charged").intValue(), 0);
+    assertEquals(colCons.get("negative").intValue(), 0);
+    assertEquals(colCons.get("polar").intValue(), 0);
+    assertEquals(colCons.get("positive").intValue(), 0);
+    assertEquals(colCons.get("aromatic").intValue(), 0);
+  
+    /*
+     * column 3: nothing above threshold
+     */
+    colCons = cons.total[3];
+    assertTrue(colCons.isEmpty());
+  
+    /*
+     * column 4: all gaps - counted as having all properties
+     */
+    colCons = cons.total[4];
+    assertEquals(colCons.get("hydrophobic").intValue(), 1);
+    assertEquals(colCons.get("aliphatic").intValue(), 1);
+    assertEquals(colCons.get("small").intValue(), 1);
+    assertEquals(colCons.get("tiny").intValue(), 1);
+    assertEquals(colCons.get("proline").intValue(), 1);
+    assertEquals(colCons.get("charged").intValue(), 1);
+    assertEquals(colCons.get("negative").intValue(), 1);
+    assertEquals(colCons.get("polar").intValue(), 1);
+    assertEquals(colCons.get("positive").intValue(), 1);
+    assertEquals(colCons.get("aromatic").intValue(), 1);
+  }
+
+  /**
+   * Test the method that derives the conservation 'sequence' and the mouseover
+   * tooltips from the computed conservation
+   */
+  @Test(groups = "Functional")
+  public void testVerdict()
+  {
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    seqs.add(new Sequence("seq1", "VGIVV-H"));
+    seqs.add(new Sequence("seq2", "VGILL-H"));
+    seqs.add(new Sequence("seq3", "VGIW--R"));
+    seqs.add(new Sequence("seq4", "VGLHH--"));
+    seqs.add(new Sequence("seq5", "VGLHH-R"));
+    seqs.add(new Sequence("seq6", "VGLHH--"));
+    seqs.add(new Sequence("seq7", "VGLHH-R"));
+    seqs.add(new Sequence("seq8", "VGLHH-R"));
+
+    // calculate with no threshold
+    Conservation cons = new Conservation("", 0, seqs, 0, 6);
+    cons.calculate();
+    // positive and negative conservation where <25% gaps in columns
+    cons.verdict(false, 25);
+
+    /*
+     * verify conservation 'sequence'
+     * cols 0 fully conserved and above threshold (*)
+     * col 2 properties fully conserved (+)
+     * col 3 VLWH 1 positively and 3 negatively conserved properties
+     * col 4 has 1 positively conserved property, but because gap contributes a
+     * 'positive' for all properties, no negative conservation is counted
+     * col 5 is all gaps
+     * col 6 has 25% gaps so fails threshold test
+     */
+    assertEquals(cons.getConsSequence().getSequenceAsString(), "**+41--");
+
+    /*
+     * verify tooltips; conserved properties are sorted alphabetically within
+     * positive followed by negative
+     */
+    assertEquals(
+            cons.getTooltip(0),
+            "aliphatic hydrophobic small !aromatic !charged !negative !polar !positive !proline !tiny");
+    assertEquals(
+            cons.getTooltip(1),
+            "hydrophobic small tiny !aliphatic !aromatic !charged !negative !polar !positive !proline");
+    assertEquals(
+            cons.getTooltip(2),
+            "aliphatic hydrophobic !aromatic !charged !negative !polar !positive !proline !small !tiny");
+    assertEquals(cons.getTooltip(3), "hydrophobic !negative !proline !tiny");
+    assertEquals(cons.getTooltip(4), "hydrophobic");
+    assertEquals(cons.getTooltip(5), "");
+    assertEquals(cons.getTooltip(6), "");
+  }
+}
similarity index 96%
rename from test/jalview/analysis/ResidueCountTest.java
rename to test/jalview/datamodel/ResidueCountTest.java
index 4a71f89..f98e3d3 100644 (file)
@@ -1,10 +1,10 @@
-package jalview.analysis;
+package jalview.datamodel;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
-import jalview.analysis.ResidueCount.SymbolCounts;
+import jalview.datamodel.ResidueCount.SymbolCounts;
 
 import org.junit.Assert;
 import org.testng.annotations.Test;
@@ -374,11 +374,19 @@ public class ResidueCountTest
     }
     rc.add('F');
     
+    /*
+     * percentages are rounded (0.5 rounded up)
+     * 10/40 9/40 7/40 6/40 1/40
+     */
     assertEquals(rc.getTooltip(40, 0),
-            "P 25%; W 22%; C 17%; Q 17%; K 15%; F 2%");
+            "P 25%; W 23%; C 18%; Q 18%; K 15%; F 3%");
 
+    rc.add('Q');
+    /*
+     * 10/30 9/30 8/30 7/30 6/30 1/30
+     */
     assertEquals(rc.getTooltip(30, 1),
-            "P 33.3%; W 30.0%; C 23.3%; Q 23.3%; K 20.0%; F 3.3%");
+            "P 33.3%; W 30.0%; Q 26.7%; C 23.3%; K 20.0%; F 3.3%");
   }
 
   @Test(groups = "Functional")
index 8173dd1..318ba3f 100644 (file)
@@ -4,7 +4,8 @@ import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
 import static org.testng.AssertJUnit.assertTrue;
 
-import jalview.analysis.Profile;
+import jalview.datamodel.Profile;
+import jalview.datamodel.ProfileI;
 
 import java.awt.Color;
 
@@ -22,7 +23,7 @@ public class ResidueColourSchemeTest
      * SR-T
      * SR-T
      */
-    Profile[] profiles = new Profile[4]; 
+    ProfileI[] profiles = new ProfileI[4]; 
     profiles[0] = new Profile(4, 0, 2, "AS");
     profiles[1] = new Profile(4, 0, 4, "R");
     profiles[2] = new Profile(4, 4, 0, "");
index b68ca68..41d7448 100644 (file)
@@ -25,6 +25,7 @@ import static org.testng.AssertJUnit.assertNull;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.testng.annotations.Test;
 
@@ -1556,4 +1557,62 @@ public class ResiduePropertiesTest
     assertEquals('Q', ResidueProperties.getSingleCharacterCode("Gln"));
     assertEquals('Q', ResidueProperties.getSingleCharacterCode("gln"));
   }
+
+  @Test(groups = { "Functional" })
+  public void testPhysicoChemicalProperties()
+  {
+    checkProperty("aromatic", "FYWH-*");
+    checkProperty("aliphatic", "IVL-*");
+    checkProperty("tiny", "GAS-*");
+    checkProperty("small", "VCTGACSDNP-*");
+    checkProperty("charged", "HKRDE-*");
+    checkProperty("negative", "DE-*");
+    checkProperty("polar", "YWHRKTSNDEQ-*X");
+    checkProperty("positive", "HKR-*");
+    checkProperty("proline", "P-*");
+    checkProperty("hydrophobic", "MILVFYWHKCGAC-*X");
+  }
+
+  /**
+   * Verify that the residues in the list have the named property, and other
+   * residues do not
+   * 
+   * @param property
+   * @param residues
+   */
+  void checkProperty(String property, String residues)
+  {
+    Map<String, Integer> props = ResidueProperties.propHash.get(property);
+
+    /*
+     * assert residues have the property (value 1 in lookup)
+     */
+    for (char res : residues.toCharArray())
+    {
+      assertEquals(res + " should be " + property, 1,
+              props.get(String.valueOf(res)).intValue());
+    }
+
+    /*
+     * assert other residues do not (value 0 in lookup)
+     */
+    for (String res : ResidueProperties.aa)
+    {
+      if (!residues.contains(res))
+      {
+        Integer propValue = props.get(String.valueOf(res));
+
+        if (propValue != null)
+        {
+          /*
+           * conservation calculation assigns unexpected symbols
+           * the same value as '-'; here we just check those which
+           * explicitly do not have the property
+           */
+          assertEquals(res + " should not be " + property, 0,
+                  propValue.intValue());
+        }
+      }
+    }
+  }
 }
index 18199f9..2082963 100644 (file)
@@ -10,23 +10,59 @@ public class FormatTest
   public void testAppendPercentage()
   {
     StringBuilder sb = new StringBuilder();
-    Format.appendPercentage(sb, 123.456f, 0);
+    Format.appendPercentage(sb, 123.436f, 0);
     assertEquals(sb.toString(), "123");
 
     sb.setLength(0);
-    Format.appendPercentage(sb, 123.456f, 1);
+    Format.appendPercentage(sb, 123.536f, 0);
+    assertEquals(sb.toString(), "124");
+
+    sb.setLength(0);
+    Format.appendPercentage(sb, 799.536f, 0);
+    assertEquals(sb.toString(), "800");
+
+    sb.setLength(0);
+    Format.appendPercentage(sb, 123.436f, 1);
     assertEquals(sb.toString(), "123.4");
 
     sb.setLength(0);
-    Format.appendPercentage(sb, 123.456f, 2);
-    assertEquals(sb.toString(), "123.45");
+    Format.appendPercentage(sb, 123.436f, 2);
+    assertEquals(sb.toString(), "123.44");
 
     sb.setLength(0);
-    Format.appendPercentage(sb, 123.456f, 3);
-    assertEquals(sb.toString(), "123.456");
+    Format.appendPercentage(sb, 123.436f, 3);
+    assertEquals(sb.toString(), "123.436");
 
     sb.setLength(0);
-    Format.appendPercentage(sb, 123.456f, 4);
-    assertEquals(sb.toString(), "123.4560");
+    Format.appendPercentage(sb, 123.436f, 4);
+    assertEquals(sb.toString(), "123.4360");
+  }
+
+  @Test(groups = "Functional")
+  public void testForm_float()
+  {
+    Format f = new Format("%3.2f");
+    assertEquals(f.form(123f), "123.00");
+    assertEquals(f.form(123.1f), "123.10");
+    assertEquals(f.form(123.12f), "123.12");
+    assertEquals(f.form(123.124f), "123.12");
+    assertEquals(f.form(123.125f), "123.13");
+    assertEquals(f.form(123.126f), "123.13");
+
+    f = new Format("%3.0f");
+    assertEquals(f.form(123f), "123.");
+    assertEquals(f.form(12f), "12.");
+    assertEquals(f.form(123.4f), "123.");
+    assertEquals(f.form(123.5f), "124.");
+    assertEquals(f.form(123.6f), "124.");
+    assertEquals(f.form(129.6f), "130.");
+  }
+
+  @Test(groups = "Functional")
+  public void testRepeat()
+  {
+    assertEquals(Format.repeat('a', 3), "aaa");
+    assertEquals(Format.repeat('b', 0), "");
+    assertEquals(Format.repeat('c', -1), "");
   }
 }