JAL-2041 clinical_significance shown on sequence feature tooltip
[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         String clinSig = (String) feature.getValue("clinical_significance");
179         if (clinSig != null)
180         {
181           sb.append("; ").append(clinSig);
182         }
183       }
184     }
185     appendLinks(sb, feature);
186   }
187
188   /**
189    * Format and appends any hyperlinks for the sequence feature to the string
190    * buffer
191    * 
192    * @param sb
193    * @param feature
194    */
195   void appendLinks(final StringBuffer sb, SequenceFeature feature)
196   {
197     if (feature.links != null)
198     {
199       if (linkImageURL != null)
200       {
201         sb.append(" <img src=\"" + linkImageURL + "\">");
202       }
203       else
204       {
205         for (String urlstring : feature.links)
206         {
207           try
208           {
209             for (String[] urllink : createLinksFrom(null, urlstring))
210             {
211               sb.append("<br/> <a href=\""
212                       + urllink[3]
213                       + "\" target=\""
214                       + urllink[0]
215                       + "\">"
216                       + (urllink[0].toLowerCase().equals(
217                               urllink[1].toLowerCase()) ? urllink[0]
218                               : (urllink[0] + ":" + urllink[1]))
219                       + "</a></br>");
220             }
221           } catch (Exception x)
222           {
223             System.err.println("problem when creating links from "
224                     + urlstring);
225             x.printStackTrace();
226           }
227         }
228       }
229
230     }
231   }
232
233   /**
234    * 
235    * @param seq
236    * @param link
237    * @return String[][] { String[] { link target, link label, dynamic component
238    *         inserted (if any), url }}
239    */
240   String[][] createLinksFrom(SequenceI seq, String link)
241   {
242     List<String[]> urlSets = new ArrayList<String[]>();
243     List<String> uniques = new ArrayList<String>();
244     UrlLink urlLink = new UrlLink(link);
245     if (!urlLink.isValid())
246     {
247       System.err.println(urlLink.getInvalidMessage());
248       return null;
249     }
250     if (seq != null && urlLink.isDynamic())
251     {
252       urlSets.addAll(createDynamicLinks(seq, urlLink, uniques));
253     }
254     else
255     {
256       String target = urlLink.getTarget();
257       String label = urlLink.getLabel();
258       String unq = label + "|" + urlLink.getUrl_prefix();
259       if (!uniques.contains(unq))
260       {
261         uniques.add(unq);
262         urlSets.add(new String[] { target, label, null,
263             urlLink.getUrl_prefix() });
264       }
265     }
266
267     return urlSets.toArray(new String[][] {});
268   }
269
270   /**
271    * Formats and returns a list of dynamic href links
272    * 
273    * @param seq
274    * @param urlLink
275    * @param uniques
276    */
277   List<String[]> createDynamicLinks(SequenceI seq, UrlLink urlLink,
278           List<String> uniques)
279   {
280     List<String[]> result = new ArrayList<String[]>();
281     final String target = urlLink.getTarget();
282     final String label = urlLink.getLabel();
283
284     // collect matching db-refs
285     DBRefEntry[] dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
286             new String[] { target });
287     // collect id string too
288     String id = seq.getName();
289     String descr = seq.getDescription();
290     if (descr != null && descr.length() < 1)
291     {
292       descr = null;
293     }
294     if (dbr != null)
295     {
296       for (int r = 0; r < dbr.length; r++)
297       {
298         if (id != null && dbr[r].getAccessionId().equals(id))
299         {
300           // suppress duplicate link creation for the bare sequence ID
301           // string with this link
302           id = null;
303         }
304         // create Bare ID link for this URL
305         String[] urls = urlLink.makeUrls(dbr[r].getAccessionId(), true);
306         if (urls != null)
307         {
308           for (int u = 0; u < urls.length; u += 2)
309           {
310             String unq = urls[u] + "|" + urls[u + 1];
311             if (!uniques.contains(unq))
312             {
313               result.add(new String[] { target, label, urls[u],
314                   urls[u + 1] });
315               uniques.add(unq);
316             }
317           }
318         }
319       }
320     }
321     if (id != null)
322     {
323       // create Bare ID link for this URL
324       String[] urls = urlLink.makeUrls(id, true);
325       if (urls != null)
326       {
327         for (int u = 0; u < urls.length; u += 2)
328         {
329           String unq = urls[u] + "|" + urls[u + 1];
330           if (!uniques.contains(unq))
331           {
332             result.add(new String[] { target, label, urls[u],
333                 urls[u + 1] });
334             uniques.add(unq);
335           }
336         }
337       }
338     }
339     if (descr != null && urlLink.getRegexReplace() != null)
340     {
341       // create link for this URL from description only if regex matches
342       String[] urls = urlLink.makeUrls(descr, true);
343       if (urls != null)
344       {
345         for (int u = 0; u < urls.length; u += 2)
346         {
347           String unq = urls[u] + "|" + urls[u + 1];
348           if (!uniques.contains(unq))
349           {
350             result.add(new String[] { target, label, urls[u],
351                 urls[u + 1] });
352             uniques.add(unq);
353           }
354         }
355       }
356     }
357     return result;
358   }
359
360   public void createSequenceAnnotationReport(final StringBuffer tip,
361           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
362           Map<String, float[][]> minmax)
363   {
364     createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats,
365             true, minmax);
366   }
367
368   public void createSequenceAnnotationReport(final StringBuffer tip,
369           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
370           boolean tableWrap, Map<String, float[][]> minmax)
371   {
372     String tmp;
373     tip.append("<i>");
374
375     int maxWidth = 0;
376     if (sequence.getDescription() != null)
377     {
378       tmp = sequence.getDescription();
379       tip.append("<br>" + tmp);
380       maxWidth = Math.max(maxWidth, tmp.length());
381     }
382     SequenceI ds = sequence;
383     while (ds.getDatasetSequence() != null)
384     {
385       ds = ds.getDatasetSequence();
386     }
387     DBRefEntry[] dbrefs = ds.getDBRefs();
388     if (showDbRefs && dbrefs != null)
389     {
390       for (int i = 0; i < dbrefs.length; i++)
391       {
392         tip.append("<br>");
393         tmp = dbrefs[i].getSource() + " " + dbrefs[i].getAccessionId();
394         tip.append(tmp);
395         maxWidth = Math.max(maxWidth, tmp.length());
396       }
397     }
398
399     // ADD NON POSITIONAL SEQUENCE INFO
400     SequenceFeature[] features = sequence.getSequenceFeatures();
401     if (showNpFeats && features != null)
402     {
403       for (int i = 0; i < features.length; i++)
404       {
405         if (features[i].begin == 0 && features[i].end == 0)
406         {
407           int sz = -tip.length();
408           List<SequenceFeature> tfeat = new ArrayList<SequenceFeature>();
409           tfeat.add(features[i]);
410           appendFeatures(tip, 0, tfeat, minmax);
411           sz += tip.length();
412           maxWidth = Math.max(maxWidth, sz);
413         }
414       }
415     }
416
417     if (tableWrap && maxWidth > 60)
418     {
419       tip.insert(0, "<table width=350 border=0><tr><td><i>");
420       tip.append("</i></td></tr></table>");
421     }
422
423   }
424 }