JAL-2136 JAL-2137 Improved STRUCTMODEL annotation syntax to reinstated fasta file...
[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.DBRefSource;
25 import jalview.datamodel.PDBEntry;
26 import jalview.datamodel.SequenceFeature;
27 import jalview.datamodel.SequenceI;
28 import jalview.io.gff.GffConstants;
29 import jalview.util.MessageManager;
30 import jalview.util.UrlLink;
31
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.Comparator;
35 import java.util.LinkedHashMap;
36 import java.util.List;
37 import java.util.Map;
38
39 /**
40  * generate HTML reports for a sequence
41  * 
42  * @author jimp
43  */
44 public class SequenceAnnotationReport
45 {
46   private static final String COMMA = ",";
47
48   private static final String ELLIPSIS = "...";
49
50   private static final int MAX_REFS_PER_SOURCE = 4;
51
52   private static final int MAX_SOURCES = 40;
53
54   private static final String[][] PRIMARY_SOURCES = new String[][] {
55       DBRefSource.CODINGDBS, DBRefSource.DNACODINGDBS,
56       DBRefSource.PROTEINDBS };
57
58   final String linkImageURL;
59
60   /*
61    * Comparator to order DBRefEntry by Source + accession id (case-insensitive)
62    */
63   private static Comparator<DBRefEntry> comparator = new Comparator<DBRefEntry>()
64   {
65
66     @Override
67     public int compare(DBRefEntry ref1, DBRefEntry ref2)
68     {
69       String s1 = ref1.getSource();
70       String s2 = ref2.getSource();
71       boolean s1Primary = isPrimarySource(s1);
72       boolean s2Primary = isPrimarySource(s2);
73       if (s1Primary && !s2Primary)
74       {
75         return -1;
76       }
77       if (!s1Primary && s2Primary)
78       {
79         return 1;
80       }
81       int comp = s1 == null ? -1 : (s2 == null ? 1 : s1
82               .compareToIgnoreCase(s2));
83       if (comp == 0)
84       {
85         String a1 = ref1.getAccessionId();
86         String a2 = ref2.getAccessionId();
87         comp = a1 == null ? -1 : (a2 == null ? 1 : a1
88                 .compareToIgnoreCase(a2));
89       }
90       return comp;
91     }
92
93     private boolean isPrimarySource(String source)
94     {
95       for (String[] primary : PRIMARY_SOURCES)
96       {
97         for (String s : primary)
98         {
99           if (source.equals(s))
100           {
101             return true;
102           }
103         }
104       }
105       return false;
106     }
107   };
108
109   public SequenceAnnotationReport(String linkImageURL)
110   {
111     this.linkImageURL = linkImageURL;
112   }
113
114   /**
115    * Append text for the list of features to the tooltip
116    * 
117    * @param sb
118    * @param rpos
119    * @param features
120    * @param minmax
121    */
122   public void appendFeatures(final StringBuilder sb, int rpos,
123           List<SequenceFeature> features, Map<String, float[][]> minmax)
124   {
125     if (features != null)
126     {
127       for (SequenceFeature feature : features)
128       {
129         appendFeature(sb, rpos, minmax, feature);
130       }
131     }
132   }
133
134   /**
135    * Appends the feature at rpos to the given buffer
136    * 
137    * @param sb
138    * @param rpos
139    * @param minmax
140    * @param feature
141    */
142   void appendFeature(final StringBuilder sb, int rpos,
143           Map<String, float[][]> minmax, SequenceFeature feature)
144   {
145     if (feature.isContactFeature())
146     {
147       if (feature.getBegin() == rpos || feature.getEnd() == rpos)
148       {
149         if (sb.length() > 6)
150         {
151           sb.append("<br>");
152         }
153         sb.append(feature.getType()).append(" ").append(feature.getBegin())
154                 .append(":")
155                 .append(feature.getEnd());
156       }
157     }
158     else
159     {
160       if (sb.length() > 6)
161       {
162         sb.append("<br>");
163       }
164       // TODO: remove this hack to display link only features
165       boolean linkOnly = feature.getValue("linkonly") != null;
166       if (!linkOnly)
167       {
168         sb.append(feature.getType()).append(" ");
169         if (rpos != 0)
170         {
171           // we are marking a positional feature
172           sb.append(feature.begin);
173         }
174         if (feature.begin != feature.end)
175         {
176           sb.append(" ").append(feature.end);
177         }
178
179         if (feature.getDescription() != null
180                 && !feature.description.equals(feature.getType()))
181         {
182           String tmpString = feature.getDescription();
183           String tmp2up = tmpString.toUpperCase();
184           int startTag = tmp2up.indexOf("<HTML>");
185           if (startTag > -1)
186           {
187             tmpString = tmpString.substring(startTag + 6);
188             tmp2up = tmp2up.substring(startTag + 6);
189           }
190           int endTag = tmp2up.indexOf("</BODY>");
191           if (endTag > -1)
192           {
193             tmpString = tmpString.substring(0, endTag);
194             tmp2up = tmp2up.substring(0, endTag);
195           }
196           endTag = tmp2up.indexOf("</HTML>");
197           if (endTag > -1)
198           {
199             tmpString = tmpString.substring(0, endTag);
200           }
201
202           if (startTag > -1)
203           {
204             sb.append("; ").append(tmpString);
205           }
206           else
207           {
208             if (tmpString.indexOf("<") > -1 || tmpString.indexOf(">") > -1)
209             {
210               // The description does not specify html is to
211               // be used, so we must remove < > symbols
212               tmpString = tmpString.replaceAll("<", "&lt;");
213               tmpString = tmpString.replaceAll(">", "&gt;");
214
215               sb.append("; ");
216               sb.append(tmpString);
217             }
218             else
219             {
220               sb.append("; ").append(tmpString);
221             }
222           }
223         }
224         // check score should be shown
225         if (!Float.isNaN(feature.getScore()))
226         {
227           float[][] rng = (minmax == null) ? null : minmax.get(feature
228                   .getType());
229           if (rng != null && rng[0] != null && rng[0][0] != rng[0][1])
230           {
231             sb.append(" Score=").append(String.valueOf(feature.getScore()));
232           }
233         }
234         String status = (String) feature.getValue("status");
235         if (status != null && status.length() > 0)
236         {
237           sb.append("; (").append(status).append(")");
238         }
239         String clinSig = (String) feature
240                 .getValue(GffConstants.CLINICAL_SIGNIFICANCE);
241         if (clinSig != null)
242         {
243           sb.append("; ").append(clinSig);
244         }
245       }
246     }
247   }
248
249   /**
250    * Format and appends any hyperlinks for the sequence feature to the string
251    * buffer
252    * 
253    * @param sb
254    * @param feature
255    */
256   void appendLinks(final StringBuffer sb, SequenceFeature feature)
257   {
258     if (feature.links != null)
259     {
260       if (linkImageURL != null)
261       {
262         sb.append(" <img src=\"" + linkImageURL + "\">");
263       }
264       else
265       {
266         for (String urlstring : feature.links)
267         {
268           try
269           {
270             for (List<String> urllink : createLinksFrom(null, urlstring))
271             {
272               sb.append("<br/> <a href=\""
273                       + urllink.get(3)
274                       + "\" target=\""
275                       + urllink.get(0)
276                       + "\">"
277                       + (urllink.get(0).toLowerCase()
278                               .equals(urllink.get(1).toLowerCase()) ? urllink
279                               .get(0) : (urllink.get(0) + ":" + urllink
280                               .get(1)))
281                       + "</a></br>");
282             }
283           } catch (Exception x)
284           {
285             System.err.println("problem when creating links from "
286                     + urlstring);
287             x.printStackTrace();
288           }
289         }
290       }
291
292     }
293   }
294
295   /**
296    * 
297    * @param seq
298    * @param link
299    * @return Collection< List<String> > { List<String> { link target, link
300    *         label, dynamic component inserted (if any), url }}
301    */
302   Collection<List<String>> createLinksFrom(SequenceI seq, String link)
303   {
304     Map<String, List<String>> urlSets = new LinkedHashMap<String, List<String>>();
305     UrlLink urlLink = new UrlLink(link);
306     if (!urlLink.isValid())
307     {
308       System.err.println(urlLink.getInvalidMessage());
309       return null;
310     }
311
312     urlLink.createLinksFromSeq(seq, urlSets);
313
314     return urlSets.values();
315   }
316
317   public void createSequenceAnnotationReport(final StringBuilder tip,
318           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
319           Map<String, float[][]> minmax)
320   {
321     createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats,
322             minmax, false);
323   }
324
325   /**
326    * Builds an html formatted report of sequence details and appends it to the
327    * provided buffer.
328    * 
329    * @param sb
330    *          buffer to append report to
331    * @param sequence
332    *          the sequence the report is for
333    * @param showDbRefs
334    *          whether to include database references for the sequence
335    * @param showNpFeats
336    *          whether to include non-positional sequence features
337    * @param minmax
338    * @param summary
339    * @return
340    */
341   int createSequenceAnnotationReport(final StringBuilder sb,
342           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
343           Map<String, float[][]> minmax, boolean summary)
344   {
345     String tmp;
346     sb.append("<i>");
347
348     int maxWidth = 0;
349     if (sequence.getDescription() != null)
350     {
351       tmp = sequence.getDescription();
352       sb.append("<br>").append(tmp);
353       maxWidth = Math.max(maxWidth, tmp.length());
354     }
355     SequenceI ds = sequence;
356     while (ds.getDatasetSequence() != null)
357     {
358       ds = ds.getDatasetSequence();
359     }
360     DBRefEntry[] dbrefs = ds.getDBRefs();
361     if (showDbRefs && dbrefs != null)
362     {
363       // note this sorts the refs held on the sequence!
364       Arrays.sort(dbrefs, comparator);
365       boolean ellipsis = false;
366       String source = null;
367       String lastSource = null;
368       int countForSource = 0;
369       int sourceCount = 0;
370       boolean moreSources = false;
371       int lineLength = 0;
372
373       for (DBRefEntry ref : dbrefs)
374       {
375         source = ref.getSource();
376         if (source == null)
377         {
378           // shouldn't happen
379           continue;
380         }
381         boolean sourceChanged = !source.equals(lastSource);
382         if (sourceChanged)
383         {
384           lineLength = 0;
385           countForSource = 0;
386           sourceCount++;
387         }
388         if (sourceCount > MAX_SOURCES && summary)
389         {
390           ellipsis = true;
391           moreSources = true;
392           break;
393         }
394         lastSource = source;
395         countForSource++;
396         if (countForSource == 1 || !summary)
397         {
398           sb.append("<br>");
399         }
400         if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
401         {
402           String accessionId = ref.getAccessionId();
403           lineLength += accessionId.length() + 1;
404           if (countForSource > 1 && summary)
405           {
406             sb.append(", ").append(accessionId);
407             lineLength++;
408           }
409           else
410           {
411             sb.append(source).append(" ").append(accessionId);
412             lineLength += source.length();
413           }
414           maxWidth = Math.max(maxWidth, lineLength);
415         }
416         if (countForSource == MAX_REFS_PER_SOURCE && summary)
417         {
418           sb.append(COMMA).append(ELLIPSIS);
419           ellipsis = true;
420         }
421       }
422       if (moreSources)
423       {
424         sb.append("<br>").append(ELLIPSIS).append(COMMA).append(source)
425                 .append(COMMA).append(ELLIPSIS);
426       }
427       if (ellipsis)
428       {
429         sb.append("<br>(");
430         sb.append(MessageManager.getString("label.output_seq_details"));
431         sb.append(")");
432       }
433     }
434
435     /*
436      * add non-positional features if wanted
437      */
438     SequenceFeature[] features = sequence.getSequenceFeatures();
439     if (showNpFeats && features != null)
440     {
441       for (int i = 0; i < features.length; i++)
442       {
443         if (features[i].begin == 0 && features[i].end == 0)
444         {
445           int sz = -sb.length();
446           appendFeature(sb, 0, minmax, features[i]);
447           sz += sb.length();
448           maxWidth = Math.max(maxWidth, sz);
449         }
450       }
451     }
452     sb.append("</i>");
453
454     for (PDBEntry pdb : ds.getAllPDBEntries())
455     {
456       if (pdb != null && pdb.getProperty("PHYRE2_MODEL_INFO") != null)
457       {
458         sb.append(pdb.getProperty("PHYRE2_MODEL_INFO"));
459       }
460     }
461     return maxWidth;
462   }
463
464   public void createTooltipAnnotationReport(final StringBuilder tip,
465           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
466           Map<String, float[][]> minmax)
467   {
468     int maxWidth = createSequenceAnnotationReport(tip, sequence,
469             showDbRefs, showNpFeats, minmax, true);
470
471     if (maxWidth > 60)
472     {
473       // ? not sure this serves any useful purpose
474       // tip.insert(0, "<table width=350 border=0><tr><td>");
475       // tip.append("</td></tr></table>");
476     }
477   }
478 }