JAL-2089 patch broken merge to master for Release 2.10.0b1
[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 || 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(String.valueOf(feature.getScore()));
170           }
171         }
172         String status = (String) feature.getValue("status");
173         if (status != null && status.length() > 0)
174         {
175           sb.append("; (").append(status).append(")");
176         }
177         String clinSig = (String) feature
178                 .getValue(GffConstants.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], urls[u + 1] });
314               uniques.add(unq);
315             }
316           }
317         }
318       }
319     }
320     if (id != null)
321     {
322       // create Bare ID link for this URL
323       String[] urls = urlLink.makeUrls(id, true);
324       if (urls != null)
325       {
326         for (int u = 0; u < urls.length; u += 2)
327         {
328           String unq = urls[u] + "|" + urls[u + 1];
329           if (!uniques.contains(unq))
330           {
331             result.add(new String[] { target, label, urls[u], urls[u + 1] });
332             uniques.add(unq);
333           }
334         }
335       }
336     }
337     if (descr != null && urlLink.getRegexReplace() != null)
338     {
339       // create link for this URL from description only if regex matches
340       String[] urls = urlLink.makeUrls(descr, true);
341       if (urls != null)
342       {
343         for (int u = 0; u < urls.length; u += 2)
344         {
345           String unq = urls[u] + "|" + urls[u + 1];
346           if (!uniques.contains(unq))
347           {
348             result.add(new String[] { target, label, urls[u], urls[u + 1] });
349             uniques.add(unq);
350           }
351         }
352       }
353     }
354     return result;
355   }
356
357   public void createSequenceAnnotationReport(final StringBuffer tip,
358           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
359           Map<String, float[][]> minmax)
360   {
361     createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats,
362             true, minmax);
363   }
364
365   public void createSequenceAnnotationReport(final StringBuffer tip,
366           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
367           boolean tableWrap, Map<String, float[][]> minmax)
368   {
369     String tmp;
370     tip.append("<i>");
371
372     int maxWidth = 0;
373     if (sequence.getDescription() != null)
374     {
375       tmp = sequence.getDescription();
376       tip.append("<br>" + tmp);
377       maxWidth = Math.max(maxWidth, tmp.length());
378     }
379     SequenceI ds = sequence;
380     while (ds.getDatasetSequence() != null)
381     {
382       ds = ds.getDatasetSequence();
383     }
384     DBRefEntry[] dbrefs = ds.getDBRefs();
385     if (showDbRefs && dbrefs != null)
386     {
387       for (int i = 0; i < dbrefs.length; i++)
388       {
389         tip.append("<br>");
390         tmp = dbrefs[i].getSource() + " " + dbrefs[i].getAccessionId();
391         tip.append(tmp);
392         maxWidth = Math.max(maxWidth, tmp.length());
393       }
394     }
395
396     // ADD NON POSITIONAL SEQUENCE INFO
397     SequenceFeature[] features = sequence.getSequenceFeatures();
398     if (showNpFeats && features != null)
399     {
400       for (int i = 0; i < features.length; i++)
401       {
402         if (features[i].begin == 0 && features[i].end == 0)
403         {
404           int sz = -tip.length();
405           List<SequenceFeature> tfeat = new ArrayList<SequenceFeature>();
406           tfeat.add(features[i]);
407           appendFeatures(tip, 0, tfeat, minmax);
408           sz += tip.length();
409           maxWidth = Math.max(maxWidth, sz);
410         }
411       }
412     }
413
414     if (tableWrap && maxWidth > 60)
415     {
416       tip.insert(0, "<table width=350 border=0><tr><td><i>");
417       tip.append("</i></td></tr></table>");
418     }
419
420   }
421 }