JAL-1421 typed map / lists, explicit imports, method refactoring
[jalview.git] / src / jalview / io / SequenceAnnotationReport.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.DBRefEntry;
24 import jalview.datamodel.SequenceFeature;
25 import jalview.datamodel.SequenceI;
26 import jalview.util.DBRefUtils;
27 import jalview.util.UrlLink;
28
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Map;
32
33 /**
34  * generate HTML reports for a sequence
35  * 
36  * @author jimp
37  */
38 public class SequenceAnnotationReport
39 {
40   final String linkImageURL;
41
42   public SequenceAnnotationReport(String linkImageURL)
43   {
44     this.linkImageURL = linkImageURL;
45   }
46
47   /**
48    * Append text for the list of features to the tooltip
49    * 
50    * @param tooltipText2
51    * @param rpos
52    * @param features
53    * @param minmax
54    */
55   public void appendFeatures(final StringBuffer tooltipText2, int rpos,
56           List<SequenceFeature> features, Map<String, float[][]> minmax)
57   {
58     if (features != null)
59     {
60       for (SequenceFeature feature : features)
61       {
62         appendFeature(tooltipText2, rpos, minmax, feature);
63       }
64     }
65   }
66
67   /**
68    * Appends text for one sequence feature to the string buffer
69    * 
70    * @param sb
71    * @param rpos
72    * @param minmax
73    *          {{min, max}, {min, max}} positional and non-positional feature
74    *          scores for this type
75    * @param feature
76    */
77   void appendFeature(final StringBuffer sb, int rpos,
78           Map<String, float[][]> minmax, SequenceFeature feature)
79   {
80     if ("disulfide bond".equals(feature.getType()))
81     {
82       if (feature.getBegin() == rpos || feature.getEnd() == rpos)
83       {
84         if (sb.length() > 6)
85         {
86           sb.append("<br>");
87         }
88         sb.append("disulfide bond ").append(feature.getBegin()).append(":")
89                 .append(feature.getEnd());
90       }
91     }
92     else
93     {
94       if (sb.length() > 6)
95       {
96         sb.append("<br>");
97       }
98       // TODO: remove this hack to display link only features
99       boolean linkOnly = feature.getValue("linkonly") != null;
100       if (!linkOnly)
101       {
102         sb.append(feature.getType()).append(" ");
103         if (rpos != 0)
104         {
105           // we are marking a positional feature
106           sb.append(feature.begin);
107         }
108         if (feature.begin != feature.end)
109         {
110           sb.append(" " + feature.end);
111         }
112
113         if (feature.getDescription() != null
114                 && !feature.description.equals(feature.getType()))
115         {
116           String tmpString = feature.getDescription();
117           String tmp2up = tmpString.toUpperCase();
118           final int startTag = tmp2up.indexOf("<HTML>");
119           if (startTag > -1)
120           {
121             tmpString = tmpString.substring(startTag + 6);
122             tmp2up = tmp2up.substring(startTag + 6);
123           }
124           // TODO strips off </body> but not <body> - is that intended?
125           int endTag = tmp2up.indexOf("</BODY>");
126           if (endTag > -1)
127           {
128             tmpString = tmpString.substring(0, endTag);
129             tmp2up = tmp2up.substring(0, endTag);
130           }
131           endTag = tmp2up.indexOf("</HTML>");
132           if (endTag > -1)
133           {
134             tmpString = tmpString.substring(0, endTag);
135           }
136
137           if (startTag > -1)
138           {
139             sb.append("; ").append(tmpString);
140           }
141           else
142           {
143             if (tmpString.indexOf("<") > -1
144                     || tmpString.indexOf(">") > -1)
145             {
146               // The description does not specify html is to
147               // be used, so we must remove < > symbols
148               tmpString = tmpString.replaceAll("<", "&lt;");
149               tmpString = tmpString.replaceAll(">", "&gt;");
150               sb.append("; ").append(tmpString);
151             }
152             else
153             {
154               sb.append("; ").append(tmpString);
155             }
156           }
157         }
158
159         /*
160          * score should be shown if there is one, and min != max
161          * for this feature type (e.g. not all 0)
162          */
163         if (!Float.isNaN(feature.getScore()))
164         {
165           float[][] rng = (minmax == null) ? null : minmax.get(feature
166                   .getType());
167           if (rng != null && rng[0] != null && rng[0][0] != rng[0][1])
168           {
169             sb.append(" Score=").append(
170                     String.valueOf(feature.getScore()));
171           }
172         }
173         String status = (String) feature.getValue("status");
174         if (status != null && status.length() > 0)
175         {
176           sb.append("; (").append(status).append(")");
177         }
178       }
179     }
180     appendLinks(sb, feature);
181   }
182
183   /**
184    * Format and appends any hyperlinks for the sequence feature to the string
185    * buffer
186    * 
187    * @param sb
188    * @param feature
189    */
190   void appendLinks(final StringBuffer sb, SequenceFeature feature)
191   {
192     if (feature.links != null)
193     {
194       if (linkImageURL != null)
195       {
196         sb.append(" <img src=\"" + linkImageURL + "\">");
197       }
198       else
199       {
200         for (String urlstring : feature.links)
201         {
202           try
203           {
204             for (String[] urllink : createLinksFrom(null, urlstring))
205             {
206               sb.append("<br/> <a href=\""
207                       + urllink[3]
208                       + "\" target=\""
209                       + urllink[0]
210                       + "\">"
211                       + (urllink[0].toLowerCase().equals(
212                               urllink[1].toLowerCase()) ? urllink[0]
213                               : (urllink[0] + ":" + urllink[1]))
214                       + "</a></br>");
215             }
216           } catch (Exception x)
217           {
218             System.err.println("problem when creating links from "
219                     + urlstring);
220             x.printStackTrace();
221           }
222         }
223       }
224
225     }
226   }
227
228   /**
229    * 
230    * @param seq
231    * @param link
232    * @return String[][] { String[] { link target, link label, dynamic component
233    *         inserted (if any), url }}
234    */
235   String[][] createLinksFrom(SequenceI seq, String link)
236   {
237     List<String[]> urlSets = new ArrayList<String[]>();
238     List<String> uniques = new ArrayList<String>();
239     UrlLink urlLink = new UrlLink(link);
240     if (!urlLink.isValid())
241     {
242       System.err.println(urlLink.getInvalidMessage());
243       return null;
244     }
245     if (seq != null && urlLink.isDynamic())
246     {
247       urlSets.addAll(createDynamicLinks(seq, urlLink, uniques));
248     }
249     else
250     {
251       String target = urlLink.getTarget();
252       String label = urlLink.getLabel();
253       String unq = label + "|" + urlLink.getUrl_prefix();
254       if (!uniques.contains(unq))
255       {
256         uniques.add(unq);
257         urlSets.add(new String[] { target, label, null,
258             urlLink.getUrl_prefix() });
259       }
260     }
261
262     return urlSets.toArray(new String[][] {});
263   }
264
265   /**
266    * Formats and returns a list of dynamic href links
267    * 
268    * @param seq
269    * @param urlLink
270    * @param uniques
271    */
272   List<String[]> createDynamicLinks(SequenceI seq, UrlLink urlLink,
273           List<String> uniques)
274   {
275     List<String[]> result = new ArrayList<String[]>();
276     final String target = urlLink.getTarget();
277     final String label = urlLink.getLabel();
278
279     // collect matching db-refs
280     DBRefEntry[] dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
281             new String[] { target });
282     // collect id string too
283     String id = seq.getName();
284     String descr = seq.getDescription();
285     if (descr != null && descr.length() < 1)
286     {
287       descr = null;
288     }
289     if (dbr != null)
290     {
291       for (int r = 0; r < dbr.length; r++)
292       {
293         if (id != null && dbr[r].getAccessionId().equals(id))
294         {
295           // suppress duplicate link creation for the bare sequence ID
296           // string with this link
297           id = null;
298         }
299         // create Bare ID link for this URL
300         String[] urls = urlLink.makeUrls(dbr[r].getAccessionId(), true);
301         if (urls != null)
302         {
303           for (int u = 0; u < urls.length; u += 2)
304           {
305             String unq = urls[u] + "|" + urls[u + 1];
306             if (!uniques.contains(unq))
307             {
308               result.add(new String[] { target, label, urls[u],
309                   urls[u + 1] });
310               uniques.add(unq);
311             }
312           }
313         }
314       }
315     }
316     if (id != null)
317     {
318       // create Bare ID link for this URL
319       String[] urls = urlLink.makeUrls(id, true);
320       if (urls != null)
321       {
322         for (int u = 0; u < urls.length; u += 2)
323         {
324           String unq = urls[u] + "|" + urls[u + 1];
325           if (!uniques.contains(unq))
326           {
327             result.add(new String[] { target, label, urls[u],
328                 urls[u + 1] });
329             uniques.add(unq);
330           }
331         }
332       }
333     }
334     if (descr != null && urlLink.getRegexReplace() != null)
335     {
336       // create link for this URL from description only if regex matches
337       String[] urls = urlLink.makeUrls(descr, true);
338       if (urls != null)
339       {
340         for (int u = 0; u < urls.length; u += 2)
341         {
342           String unq = urls[u] + "|" + urls[u + 1];
343           if (!uniques.contains(unq))
344           {
345             result.add(new String[] { target, label, urls[u],
346                 urls[u + 1] });
347             uniques.add(unq);
348           }
349         }
350       }
351     }
352     return result;
353   }
354
355   public void createSequenceAnnotationReport(final StringBuffer tip,
356           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
357           Map<String, float[][]> minmax)
358   {
359     createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats,
360             true, minmax);
361   }
362
363   public void createSequenceAnnotationReport(final StringBuffer tip,
364           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
365           boolean tableWrap, Map<String, float[][]> minmax)
366   {
367     String tmp;
368     tip.append("<i>");
369
370     int maxWidth = 0;
371     if (sequence.getDescription() != null)
372     {
373       tmp = sequence.getDescription();
374       tip.append("<br>" + tmp);
375       maxWidth = Math.max(maxWidth, tmp.length());
376     }
377     SequenceI ds = sequence;
378     while (ds.getDatasetSequence() != null)
379     {
380       ds = ds.getDatasetSequence();
381     }
382     DBRefEntry[] dbrefs = ds.getDBRefs();
383     if (showDbRefs && dbrefs != null)
384     {
385       for (int i = 0; i < dbrefs.length; i++)
386       {
387         tip.append("<br>");
388         tmp = dbrefs[i].getSource() + " " + dbrefs[i].getAccessionId();
389         tip.append(tmp);
390         maxWidth = Math.max(maxWidth, tmp.length());
391       }
392     }
393
394     // ADD NON POSITIONAL SEQUENCE INFO
395     SequenceFeature[] features = sequence.getSequenceFeatures();
396     if (showNpFeats && features != null)
397     {
398       for (int i = 0; i < features.length; i++)
399       {
400         if (features[i].begin == 0 && features[i].end == 0)
401         {
402           int sz = -tip.length();
403           List<SequenceFeature> tfeat = new ArrayList<SequenceFeature>();
404           tfeat.add(features[i]);
405           appendFeatures(tip, 0, tfeat, minmax);
406           sz += tip.length();
407           maxWidth = Math.max(maxWidth, sz);
408         }
409       }
410     }
411
412     if (tableWrap && maxWidth > 60)
413     {
414       tip.insert(0, "<table width=350 border=0><tr><td><i>");
415       tip.append("</i></td></tr></table>");
416     }
417
418   }
419 }