JAL-722 redone additional derived alignment statistics
[jalview.git] / src / jalview / io / AlignmentProperties.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io;
22
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.SequenceI;
25
26 import java.util.Arrays;
27 import java.util.Comparator;
28 import java.util.Map;
29
30 /**
31  * Render associated attributes of an alignment. The heart of this code was
32  * refactored from jalview.gui.AlignFrame and jalview.appletgui.AlignFrame TODO:
33  * consider extending the html renderer to annotate elements with CSS ids
34  * enabling finer output format control.
35  * 
36  */
37 public class AlignmentProperties
38 {
39   private static final String BR_TAG = "<br>";
40
41   private static final String NEWLINE = System.getProperty("line.separator");
42
43   private static final String PCT_FORMAT = "%.1f%%";
44
45   AlignmentI alignment;
46
47   public AlignmentProperties(AlignmentI alignment)
48   {
49     this.alignment = alignment;
50   }
51
52   /**
53    * render the alignment's properties report as text or an HTML fragment
54    * 
55    * @param html
56    */
57   protected StringBuilder writeProperties(boolean html)
58   {
59     StringBuilder sb = new StringBuilder(256);
60     final String nl = html ? BR_TAG : NEWLINE;
61     int totalLength = 0;
62     int totalGaps = 0;
63     int minLength = Integer.MAX_VALUE;
64     int maxLength = 0;
65     float maxUngapped = 0f;
66     float minUngapped = Float.MAX_VALUE;
67
68     final int height = alignment.getHeight();
69     final int width = alignment.getWidth();
70
71     for (int i = 0; i < height; i++)
72     {
73       SequenceI seq = alignment.getSequenceAt(i);
74       // sequence length including gaps:
75       int seqWidth = seq.getLength();
76       // sequence length excluding gaps:
77       int seqLength = 1 + seq.getEnd() - seq.getStart();
78       int gapCount = seqWidth - seqLength; // includes padding
79       totalLength += seqLength;
80       totalGaps += gapCount;
81       maxLength = Math.max(maxLength, seqLength);
82       minLength = Math.min(minLength, seqLength);
83
84       /*
85        * proportion of aligned sequence that is ungapped
86        * (note: normalised by alignment width here)
87        */
88       float ungapped = (seqWidth - gapCount) / (float) seqWidth;
89       maxUngapped = Math.max(maxUngapped, ungapped);
90       minUngapped = Math.min(minUngapped, ungapped);
91     }
92     float avgLength = totalLength / (float) height;
93
94     sb.append(html ? "<table border=\"1\">" : nl);
95     appendRow(sb, "Sequences", String.valueOf(height), html);
96     appendRow(sb, "Alignment width", String.valueOf(width),
97             html);
98     appendRow(sb, "Minimum Sequence Length", String.valueOf(minLength),
99             html);
100     appendRow(sb, "Maximum Sequence Length", String.valueOf(maxLength),
101             html);
102     appendRow(sb, "Average Length", String.valueOf((int) avgLength), html);
103     appendRow(sb, "Minimum (sequence length / width)",
104             String.format(PCT_FORMAT, minUngapped * 100f), html);
105     appendRow(sb, "Maximum (sequence length / width)",
106             String.format(PCT_FORMAT, maxUngapped * 100f), html);
107     appendRow(sb, "Residue density", String.format(PCT_FORMAT,
108             (height * width - totalGaps) * 100f / (height * width)), html);
109     appendRow(sb, "Gap density",
110             String.format(PCT_FORMAT, totalGaps * 100f / (height * width)),
111             html);
112     sb.append(html ? "</table>" : nl);
113
114     Map<Object, Object> props = alignment.getProperties();
115     if (props != null && !props.isEmpty())
116     {
117       sb.append(nl);
118       sb.append(nl);
119       if (html)
120       {
121         sb.append("<table border=\"1\">");
122       }
123
124       /*
125        * sort keys alphabetically for ease of reading the output
126        */
127       Object[] keys = props.keySet().toArray(new Object[props.size()]);
128       Arrays.sort(keys, new Comparator<Object>()
129       {
130         @Override
131         public int compare(Object o1, Object o2)
132         {
133           return String.CASE_INSENSITIVE_ORDER.compare(o1.toString(),
134                   o2.toString());
135         }
136       });
137       for (Object key : keys)
138       {
139         String value = props.get(key).toString();
140         if (html)
141         {
142           value = value.replaceAll("\\R", value); // Java 8 newline matcher
143           value = formatHrefs(value);
144         }
145         appendRow(sb, key.toString(), value, html);
146       }
147       if (html)
148       {
149         sb.append("</table>");
150       }
151     }
152     return sb;
153   }
154
155   /**
156    * Helper method to change any token starting with http into an html href
157    * 
158    * @param value
159    * @return
160    */
161   private String formatHrefs(String value)
162   {
163     if (!value.contains("http"))
164     {
165       return value;
166     }
167
168     StringBuilder sb = new StringBuilder(value.length() * 3);
169     String[] tokens = value.split("\\s");
170     boolean found = false;
171     boolean first = true;
172     for (String token : tokens)
173     {
174       if (token.startsWith("http"))
175       {
176         token = "<a href=\"" + token + "\">" + token + "</a>";
177         found = true;
178       }
179       if (!first)
180       {
181         sb.append(" ");
182       }
183       sb.append(token);
184       first = false;
185     }
186     return found ? sb.toString() : value;
187   }
188
189   /**
190    * A helper method to add one key-value row, optionally in HTML table entry
191    * format
192    * 
193    * @param sb
194    * @param key
195    * @param value
196    * @param html
197    */
198   private void appendRow(StringBuilder sb, String key, String value,
199           boolean html)
200   {
201     if (html)
202     {
203       sb.append("<tr><td>").append(key).append("</td><td>").append(value)
204               .append("</td></tr>");
205     }
206     else
207     {
208       sb.append(html ? BR_TAG : NEWLINE).append(key).append("\t")
209               .append(value);
210     }
211   }
212
213   /**
214    * generate a report as plain text
215    * 
216    * @return
217    */
218   public StringBuilder formatAsString()
219   {
220     return writeProperties(false);
221   }
222
223   /**
224    * generate a report as a fragment of html
225    * 
226    * @return
227    */
228   public StringBuilder formatAsHtml()
229   {
230     return writeProperties(true);
231   }
232
233 }