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