JAL-722 updated from 2.11.2 develop branch - needs further work before release
[jalview.git] / src / jalview / io / AlignmentProperties.java
index 9effa74..69bd658 100644 (file)
  */
 package jalview.io;
 
-import java.io.StringWriter;
-import java.io.PrintWriter;
-import java.util.Enumeration;
-import java.util.Hashtable;
-
-import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
 
 /**
  * Render associated attributes of an alignment. The heart of this code was
@@ -37,6 +36,12 @@ import jalview.datamodel.AlignmentI;
  */
 public class AlignmentProperties
 {
+  private static final String BR_TAG = "<br>";
+
+  private static final String NEWLINE = System.getProperty("line.separator");
+
+  private static final String PCT_FORMAT = "%.1f%%";
+
   AlignmentI alignment;
 
   public AlignmentProperties(AlignmentI alignment)
@@ -47,96 +52,172 @@ public class AlignmentProperties
   /**
    * render the alignment's properties report as text or an HTML fragment
    * 
-   * @param pw
    * @param html
    */
-  public void writeProperties(PrintWriter pw, boolean html)
+  protected StringBuilder writeProperties(boolean html)
   {
-    final String nl = html ? "<br>" : System.getProperty("line.separator");
-    float avg = 0;
-    int min = Integer.MAX_VALUE, max = 0;
-    for (int i = 0; i < alignment.getHeight(); i++)
+    StringBuilder sb = new StringBuilder(256);
+    final String nl = html ? BR_TAG : NEWLINE;
+    int totalLength = 0;
+    int totalGaps = 0;
+    int minLength = Integer.MAX_VALUE;
+    int maxLength = 0;
+    float maxUngapped = 0f;
+    float minUngapped = Float.MAX_VALUE;
+
+    final int height = alignment.getHeight();
+    final int width = alignment.getWidth();
+
+    for (int i = 0; i < height; i++)
     {
-      int size = 1 + alignment.getSequenceAt(i).getEnd()
-              - alignment.getSequenceAt(i).getStart();
-      avg += size;
-      if (size > max)
-        max = size;
-      if (size < min)
-        min = size;
+      SequenceI seq = alignment.getSequenceAt(i);
+      // sequence length including gaps:
+      int seqWidth = seq.getLength();
+      // sequence length excluding gaps:
+      int seqLength = 1 + seq.getEnd() - seq.getStart();
+      int gapCount = seqWidth - seqLength; // includes padding
+      totalLength += seqLength;
+      totalGaps += gapCount;
+      maxLength = Math.max(maxLength, seqLength);
+      minLength = Math.min(minLength, seqLength);
+
+      /*
+       * proportion of aligned sequence that is ungapped
+       * (note: normalised by alignment width here)
+       */
+      float ungapped = (seqWidth - gapCount) / (float) seqWidth;
+      maxUngapped = Math.max(maxUngapped, ungapped);
+      minUngapped = Math.min(minUngapped, ungapped);
     }
-    avg = avg / (float) alignment.getHeight();
-    pw.print(nl);
-    pw.print("Sequences: " + alignment.getHeight());
-    pw.print(nl);
-    pw.print("Minimum Sequence Length: " + min);
-    pw.print(nl);
-    pw.print("Maximum Sequence Length: " + max);
-    pw.print(nl);
-    pw.print("Average Length: " + (int) avg);
-
-    if (((Alignment) alignment).alignmentProperties != null)
+    float avgLength = totalLength / (float) height;
+
+    sb.append(html ? "<table border=\"1\">" : nl);
+    appendRow(sb, "Sequences", String.valueOf(height), html);
+    appendRow(sb, "Alignment width", String.valueOf(width),
+            html);
+    appendRow(sb, "Minimum Sequence Length", String.valueOf(minLength),
+            html);
+    appendRow(sb, "Maximum Sequence Length", String.valueOf(maxLength),
+            html);
+    appendRow(sb, "Average Length", String.valueOf((int) avgLength), html);
+    appendRow(sb, "Minimum (sequence length / width)",
+            String.format(PCT_FORMAT, minUngapped * 100f), html);
+    appendRow(sb, "Maximum (sequence length / width)",
+            String.format(PCT_FORMAT, maxUngapped * 100f), html);
+    appendRow(sb, "Residue density", String.format(PCT_FORMAT,
+            (height * width - totalGaps) * 100f / (height * width)), html);
+    appendRow(sb, "Gap density",
+            String.format(PCT_FORMAT, totalGaps * 100f / (height * width)),
+            html);
+    sb.append(html ? "</table>" : nl);
+
+    Map<Object, Object> props = alignment.getProperties();
+    if (props != null && !props.isEmpty())
     {
-      pw.print(nl);
-      pw.print(nl);
+      sb.append(nl);
+      sb.append(nl);
       if (html)
       {
-        pw.print("<table border=\"1\">");
+        sb.append("<table border=\"1\">");
       }
-      Hashtable props = ((Alignment) alignment).alignmentProperties;
-      Enumeration en = props.keys();
-      while (en.hasMoreElements())
+
+      /*
+       * sort keys alphabetically for ease of reading the output
+       */
+      Object[] keys = props.keySet().toArray(new Object[props.size()]);
+      Arrays.sort(keys, new Comparator<Object>()
       {
-        String key = en.nextElement().toString();
-        String vals = props.get(key).toString();
-        if (html)
+        @Override
+        public int compare(Object o1, Object o2)
         {
-          // wrap the text in the table
-          StringBuffer val = new StringBuffer();
-          int pos = 0, npos;
-          do
-          {
-            npos = vals.indexOf("\n", pos);
-            if (npos == -1)
-            {
-              val.append(vals.substring(pos));
-            }
-            else
-            {
-              val.append(vals.substring(pos, npos));
-              val.append("<br>");
-            }
-            pos = npos + 1;
-          } while (npos != -1);
-          pw.print("<tr><td>" + key + "</td><td>" + val + "</td></tr>");
+          return String.CASE_INSENSITIVE_ORDER.compare(o1.toString(),
+                  o2.toString());
         }
-        else
+      });
+      for (Object key : keys)
+      {
+        String value = props.get(key).toString();
+        if (html)
         {
-          pw.print(nl + key + "\t" + vals);
+          value = value.replaceAll("\\R", value); // Java 8 newline matcher
+          value = formatHrefs(value);
         }
+        appendRow(sb, key.toString(), value, html);
       }
       if (html)
       {
-        pw.print("</table>");
+        sb.append("</table>");
       }
     }
+    return sb;
   }
 
   /**
-   * generate a report as plain text
+   * Helper method to change any token starting with http into an html href
    * 
+   * @param value
    * @return
    */
-  public StringBuffer formatAsString()
+  private String formatHrefs(String value)
+  {
+    if (!value.contains("http"))
+    {
+      return value;
+    }
+
+    StringBuilder sb = new StringBuilder(value.length() * 3);
+    String[] tokens = value.split("\\s");
+    boolean found = false;
+    boolean first = true;
+    for (String token : tokens)
+    {
+      if (token.startsWith("http"))
+      {
+        token = "<a href=\"" + token + "\">" + token + "</a>";
+        found = true;
+      }
+      if (!first)
+      {
+        sb.append(" ");
+      }
+      sb.append(token);
+      first = false;
+    }
+    return found ? sb.toString() : value;
+  }
+
+  /**
+   * A helper method to add one key-value row, optionally in HTML table entry
+   * format
+   * 
+   * @param sb
+   * @param key
+   * @param value
+   * @param html
+   */
+  private void appendRow(StringBuilder sb, String key, String value,
+          boolean html)
   {
-    return formatReport(false);
+    if (html)
+    {
+      sb.append("<tr><td>").append(key).append("</td><td>").append(value)
+              .append("</td></tr>");
+    }
+    else
+    {
+      sb.append(html ? BR_TAG : NEWLINE).append(key).append("\t")
+              .append(value);
+    }
   }
 
-  protected StringBuffer formatReport(boolean html)
+  /**
+   * generate a report as plain text
+   * 
+   * @return
+   */
+  public StringBuilder formatAsString()
   {
-    StringWriter content = new StringWriter();
-    writeProperties(new PrintWriter(content), html);
-    return content.getBuffer();
+    return writeProperties(false);
   }
 
   /**
@@ -144,9 +225,9 @@ public class AlignmentProperties
    * 
    * @return
    */
-  public StringBuffer formatAsHtml()
+  public StringBuilder formatAsHtml()
   {
-    return formatReport(true);
+    return writeProperties(true);
   }
 
 }