Clover icon

Coverage Report

  1. Project Clover database Thu Nov 7 2024 17:01:39 GMT
  2. Package jalview.io

File SequenceAnnotationReport.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
0% of files have more coverage

Code metrics

118
195
13
1
649
428
91
0.47
15
13
7

Classes

Class Line # Actions
SequenceAnnotationReport 48 195 91
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

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 java.util.ArrayList;
24    import java.util.Collection;
25    import java.util.Comparator;
26    import java.util.LinkedHashMap;
27    import java.util.List;
28    import java.util.Locale;
29    import java.util.Map;
30   
31    import jalview.api.FeatureColourI;
32    import jalview.datamodel.DBRefEntry;
33    import jalview.datamodel.DBRefSource;
34    import jalview.datamodel.GeneLociI;
35    import jalview.datamodel.MappedFeatures;
36    import jalview.datamodel.SequenceFeature;
37    import jalview.datamodel.SequenceI;
38    import jalview.util.MessageManager;
39    import jalview.util.StringUtils;
40    import jalview.util.UrlLink;
41    import jalview.viewmodel.seqfeatures.FeatureRendererModel;
42   
43    /**
44    * generate HTML reports for a sequence
45    *
46    * @author jimp
47    */
 
48    public class SequenceAnnotationReport
49    {
50    private static final int MAX_DESCRIPTION_LENGTH = 40;
51   
52    private static final String COMMA = ",";
53   
54    private static final String ELLIPSIS = "...";
55   
56    private static final int MAX_REFS_PER_SOURCE = 4;
57   
58    private static final int MAX_SOURCES = 5;
59   
60    private static String linkImageURL;
61   
62    // public static final String[][] PRIMARY_SOURCES moved to DBRefSource.java
63   
64    /*
65    * Comparator to order DBRefEntry by Source + accession id (case-insensitive),
66    * with 'Primary' sources placed before others, and 'chromosome' first of all
67    */
68    private static Comparator<DBRefEntry> comparator = new Comparator<DBRefEntry>()
69    {
70   
 
71  0 toggle @Override
72    public int compare(DBRefEntry ref1, DBRefEntry ref2)
73    {
74  0 if (ref1 instanceof GeneLociI)
75    {
76  0 return -1;
77    }
78  0 if (ref2 instanceof GeneLociI)
79    {
80  0 return 1;
81    }
82   
83  0 String s1 = ref1.getSource();
84  0 String s2 = ref2.getSource();
85  0 boolean s1Primary = DBRefSource.isPrimarySource(s1);
86  0 boolean s2Primary = DBRefSource.isPrimarySource(s2);
87  0 if (ref1.isCanonical() && !ref2.isCanonical())
88    {
89  0 return -1;
90    }
91  0 if (!ref1.isCanonical() && ref2.isCanonical())
92    {
93  0 return 1;
94    }
95  0 if (s1Primary && !s2Primary)
96    {
97  0 return -1;
98    }
99  0 if (!s1Primary && s2Primary)
100    {
101  0 return 1;
102    }
103  0 int comp = s1 == null ? -1
104  0 : (s2 == null ? 1 : s1.compareToIgnoreCase(s2));
105  0 if (comp == 0)
106    {
107  0 String a1 = ref1.getAccessionId();
108  0 String a2 = ref2.getAccessionId();
109  0 comp = a1 == null ? -1
110  0 : (a2 == null ? 1 : a1.compareToIgnoreCase(a2));
111    }
112  0 return comp;
113    }
114   
115    // private boolean isPrimarySource(String source)
116    // {
117    // for (String[] primary : DBRefSource.PRIMARY_SOURCES)
118    // {
119    // for (String s : primary)
120    // {
121    // if (source.equals(s))
122    // {
123    // return true;
124    // }
125    // }
126    // }
127    // return false;
128    // }
129    };
130   
131    private boolean forTooltip;
132   
133    /**
134    * Constructor given a flag which affects behaviour
135    * <ul>
136    * <li>if true, generates feature details suitable to show in a tooltip</li>
137    * <li>if false, generates feature details in a form suitable for the sequence
138    * details report</li>
139    * </ul>
140    *
141    * @param isForTooltip
142    */
 
143  0 toggle public SequenceAnnotationReport(boolean isForTooltip)
144    {
145  0 this.forTooltip = isForTooltip;
146  0 if (linkImageURL == null)
147    {
148  0 linkImageURL = getClass().getResource("/images/link.gif").toString();
149    }
150    }
151   
152    /**
153    * Append text for the list of features to the tooltip. Returns the number of
154    * features not added if maxlength limit is (or would have been) reached.
155    *
156    * @param sb
157    * @param residuePos
158    * @param features
159    * @param minmax
160    * @param maxlength
161    */
 
162  0 toggle public int appendFeatures(final StringBuilder sb, int residuePos,
163    List<SequenceFeature> features, FeatureRendererModel fr,
164    int maxlength)
165    {
166  0 for (int i = 0; i < features.size(); i++)
167    {
168  0 SequenceFeature feature = features.get(i);
169  0 if (appendFeature(sb, residuePos, fr, feature, null, maxlength))
170    {
171  0 return features.size() - i;
172    }
173    }
174  0 return 0;
175    }
176   
177    /**
178    * Appends text for mapped features (e.g. CDS feature for peptide or vice
179    * versa) Returns number of features left if maxlength limit is (or would have
180    * been) reached.
181    *
182    * @param sb
183    * @param residuePos
184    * @param mf
185    * @param fr
186    * @param maxlength
187    */
 
188  0 toggle public int appendFeatures(StringBuilder sb, int residuePos,
189    MappedFeatures mf, FeatureRendererModel fr, int maxlength)
190    {
191  0 for (int i = 0; i < mf.features.size(); i++)
192    {
193  0 SequenceFeature feature = mf.features.get(i);
194  0 if (appendFeature(sb, residuePos, fr, feature, mf, maxlength))
195    {
196  0 return mf.features.size() - i;
197    }
198    }
199  0 return 0;
200    }
201   
202    /**
203    * Appends the feature at rpos to the given buffer
204    *
205    * @param sb
206    * @param rpos
207    * @param minmax
208    * @param feature
209    */
 
210  0 toggle boolean appendFeature(final StringBuilder sb0, int rpos,
211    FeatureRendererModel fr, SequenceFeature feature,
212    MappedFeatures mf, int maxlength)
213    {
214  0 int begin = feature.getBegin();
215  0 int end = feature.getEnd();
216   
217    /*
218    * if this is a virtual features, convert begin/end to the
219    * coordinates of the sequence it is mapped to
220    */
221  0 int[] beginRange = null; // feature start in local coordinates
222  0 int[] endRange = null; // feature end in local coordinates
223  0 if (mf != null)
224    {
225  0 if (feature.isContactFeature())
226    {
227    /*
228    * map start and end points individually
229    */
230  0 beginRange = mf.getMappedPositions(begin, begin);
231  0 endRange = begin == end ? beginRange
232    : mf.getMappedPositions(end, end);
233    }
234    else
235    {
236    /*
237    * map the feature extent
238    */
239  0 beginRange = mf.getMappedPositions(begin, end);
240  0 endRange = beginRange;
241    }
242  0 if (beginRange == null || endRange == null)
243    {
244    // something went wrong
245  0 return false;
246    }
247  0 begin = beginRange[0];
248  0 end = endRange[endRange.length - 1];
249    }
250   
251  0 StringBuilder sb = new StringBuilder();
252  0 if (feature.isContactFeature())
253    {
254    /*
255    * include if rpos is at start or end position of [mapped] feature
256    */
257  0 boolean showContact = (mf == null) && (rpos == begin || rpos == end);
258  0 boolean showMappedContact = (mf != null) && ((rpos >= beginRange[0]
259    && rpos <= beginRange[beginRange.length - 1])
260    || (rpos >= endRange[0]
261    && rpos <= endRange[endRange.length - 1]));
262  0 if (showContact || showMappedContact)
263    {
264  0 if (sb0.length() > 6)
265    {
266  0 sb.append("<br/>");
267    }
268  0 sb.append(feature.getType()).append(" ").append(begin).append(":")
269    .append(end);
270    }
271  0 return appendText(sb0, sb, maxlength);
272    }
273   
274  0 if (sb0.length() > 6)
275    {
276  0 sb.append("<br/>");
277    }
278    // TODO: remove this hack to display link only features
279  0 boolean linkOnly = feature.getValue("linkonly") != null;
280  0 if (!linkOnly)
281    {
282  0 sb.append(feature.getType()).append(" ");
283  0 if (rpos != 0)
284    {
285    // we are marking a positional feature
286  0 sb.append(begin);
287  0 if (begin != end)
288    {
289  0 sb.append(" ").append(end);
290    }
291    }
292   
293  0 String description = feature.getDescription();
294  0 if (description != null && !description.equals(feature.getType()))
295    {
296  0 description = StringUtils.stripHtmlTags(description);
297   
298    /*
299    * truncate overlong descriptions unless they contain an href
300    * before the truncation point (as truncation could leave corrupted html)
301    */
302  0 int linkindex = description.toLowerCase(Locale.ROOT).indexOf("<a ");
303  0 boolean hasLink = linkindex > -1
304    && linkindex < MAX_DESCRIPTION_LENGTH;
305  0 if (description.length() > MAX_DESCRIPTION_LENGTH && !hasLink)
306    {
307  0 description = description.substring(0, MAX_DESCRIPTION_LENGTH)
308    + ELLIPSIS;
309    }
310   
311  0 sb.append("; ").append(description);
312    }
313   
314  0 if (showScore(feature, fr))
315    {
316  0 sb.append(" Score=").append(String.valueOf(feature.getScore()));
317    }
318  0 String status = (String) feature.getValue("status");
319  0 if (status != null && status.length() > 0)
320    {
321  0 sb.append("; (").append(status).append(")");
322    }
323   
324    /*
325    * add attribute value if coloured by attribute
326    */
327  0 if (fr != null)
328    {
329  0 FeatureColourI fc = fr.getFeatureColours().get(feature.getType());
330  0 if (fc != null && fc.isColourByAttribute())
331    {
332  0 String[] attName = fc.getAttributeName();
333  0 String attVal = feature.getValueAsString(attName);
334  0 if (attVal != null)
335    {
336  0 sb.append("; ").append(String.join(":", attName)).append("=")
337    .append(attVal);
338    }
339    }
340    }
341   
342  0 if (mf != null)
343    {
344  0 String variants = mf.findProteinVariants(feature);
345  0 if (!variants.isEmpty())
346    {
347  0 sb.append(" ").append(variants);
348    }
349    }
350    }
351  0 return appendText(sb0, sb, maxlength);
352    }
353   
354    /**
355    * Appends sb to sb0, and returns false, unless maxlength is not zero and
356    * appending would make the result longer than or equal to maxlength, in which
357    * case the append is not done and returns true
358    *
359    * @param sb0
360    * @param sb
361    * @param maxlength
362    * @return
363    */
 
364  0 toggle private static boolean appendText(StringBuilder sb0, StringBuilder sb,
365    int maxlength)
366    {
367  0 if (maxlength == 0 || sb0.length() + sb.length() < maxlength)
368    {
369  0 sb0.append(sb);
370  0 return false;
371    }
372  0 return true;
373    }
374   
375    /**
376    * Answers true if score should be shown, else false. Score is shown if it is
377    * not NaN, and the feature type has a non-trivial min-max score range
378    */
 
379  0 toggle boolean showScore(SequenceFeature feature, FeatureRendererModel fr)
380    {
381  0 if (Float.isNaN(feature.getScore()))
382    {
383  0 return false;
384    }
385  0 if (fr == null)
386    {
387  0 return true;
388    }
389  0 float[][] minMax = fr.getMinMax().get(feature.getType());
390   
391    /*
392    * minMax[0] is the [min, max] score range for positional features
393    */
394  0 if (minMax == null || minMax[0] == null || minMax[0][0] == minMax[0][1])
395    {
396  0 return false;
397    }
398  0 return true;
399    }
400   
401    /**
402    * Format and appends any hyperlinks for the sequence feature to the string
403    * buffer
404    *
405    * @param sb
406    * @param feature
407    */
 
408  0 toggle void appendLinks(final StringBuffer sb, SequenceFeature feature)
409    {
410  0 if (feature.links != null)
411    {
412  0 if (linkImageURL != null)
413    {
414  0 sb.append(" <img src=\"" + linkImageURL + "\">");
415    }
416    else
417    {
418  0 for (String urlstring : feature.links)
419    {
420  0 try
421    {
422  0 for (List<String> urllink : createLinksFrom(null, urlstring))
423    {
424  0 sb.append("<br/> <a href=\"" + urllink.get(3) + "\" target=\""
425    + urllink.get(0) + "\">"
426  0 + (urllink.get(0).toLowerCase(Locale.ROOT).equals(
427    urllink.get(1).toLowerCase(Locale.ROOT))
428    ? urllink.get(0)
429    : (urllink.get(0) + ":"
430    + urllink.get(1)))
431    + "</a><br/>");
432    }
433    } catch (Exception x)
434    {
435  0 jalview.bin.Console.errPrintln(
436    "problem when creating links from " + urlstring);
437  0 x.printStackTrace();
438    }
439    }
440    }
441   
442    }
443    }
444   
445    /**
446    *
447    * @param seq
448    * @param link
449    * @return Collection< List<String> > { List<String> { link target, link
450    * label, dynamic component inserted (if any), url }}
451    */
 
452  0 toggle Collection<List<String>> createLinksFrom(SequenceI seq, String link)
453    {
454  0 Map<String, List<String>> urlSets = new LinkedHashMap<>();
455  0 UrlLink urlLink = new UrlLink(link);
456  0 if (!urlLink.isValid())
457    {
458  0 jalview.bin.Console.errPrintln(urlLink.getInvalidMessage());
459  0 return null;
460    }
461   
462  0 urlLink.createLinksFromSeq(seq, urlSets);
463   
464  0 return urlSets.values();
465    }
466   
 
467  0 toggle public void createSequenceAnnotationReport(final StringBuilder tip,
468    SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
469    FeatureRendererModel fr)
470    {
471  0 createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats,
472    fr, false);
473    }
474   
475    /**
476    * Builds an html formatted report of sequence details and appends it to the
477    * provided buffer.
478    *
479    * @param sb
480    * buffer to append report to
481    * @param sequence
482    * the sequence the report is for
483    * @param showDbRefs
484    * whether to include database references for the sequence
485    * @param showNpFeats
486    * whether to include non-positional sequence features
487    * @param fr
488    * @param summary
489    * @return
490    */
 
491  0 toggle int createSequenceAnnotationReport(final StringBuilder sb,
492    SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
493    FeatureRendererModel fr, boolean summary)
494    {
495  0 String tmp;
496  0 sb.append("<i>");
497   
498  0 int maxWidth = 0;
499  0 if (sequence.getDescription() != null)
500    {
501  0 tmp = sequence.getDescription();
502  0 sb.append(tmp);
503  0 maxWidth = Math.max(maxWidth, tmp.length());
504    }
505  0 sb.append("\n");
506  0 SequenceI ds = sequence;
507  0 while (ds.getDatasetSequence() != null)
508    {
509  0 ds = ds.getDatasetSequence();
510    }
511   
512  0 if (showDbRefs)
513    {
514  0 maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary));
515    }
516  0 sb.append("\n");
517   
518    /*
519    * add non-positional features if wanted
520    */
521  0 if (showNpFeats)
522    {
523  0 for (SequenceFeature sf : sequence.getFeatures()
524    .getNonPositionalFeatures())
525    {
526  0 int sz = -sb.length();
527  0 appendFeature(sb, 0, fr, sf, null, 0);
528  0 sz += sb.length();
529  0 maxWidth = Math.max(maxWidth, sz);
530    }
531    }
532  0 sb.append("</i>");
533  0 return maxWidth;
534    }
535   
536    /**
537    * A helper method that appends any DBRefs, returning the maximum line length
538    * added
539    *
540    * @param sb
541    * @param ds
542    * @param summary
543    * @return
544    */
 
545  0 toggle protected int appendDbRefs(final StringBuilder sb, SequenceI ds,
546    boolean summary)
547    {
548  0 List<DBRefEntry> dbrefs, dbrefset = ds.getDBRefs();
549   
550  0 if (dbrefset == null)
551    {
552  0 return 0;
553    }
554   
555    // PATCH for JAL-3980 defensive copy
556   
557  0 dbrefs = new ArrayList<DBRefEntry>();
558   
559  0 dbrefs.addAll(dbrefset);
560   
561    // note this sorts the refs held on the sequence!
562  0 dbrefs.sort(comparator);
563  0 boolean ellipsis = false;
564  0 String source = null;
565  0 String lastSource = null;
566  0 int countForSource = 0;
567  0 int sourceCount = 0;
568  0 boolean moreSources = false;
569  0 int maxLineLength = 0;
570  0 int lineLength = 0;
571   
572  0 for (DBRefEntry ref : dbrefs)
573    {
574  0 source = ref.getSource();
575  0 if (source == null)
576    {
577    // shouldn't happen
578  0 continue;
579    }
580  0 boolean sourceChanged = !source.equals(lastSource);
581  0 if (sourceChanged)
582    {
583  0 lineLength = 0;
584  0 countForSource = 0;
585  0 sourceCount++;
586    }
587  0 if (sourceCount > MAX_SOURCES && summary)
588    {
589  0 ellipsis = true;
590  0 moreSources = true;
591  0 break;
592    }
593  0 lastSource = source;
594  0 countForSource++;
595  0 if (countForSource == 1 || !summary)
596    {
597  0 sb.append("<br/>\n");
598    }
599  0 if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
600    {
601  0 String accessionId = ref.getAccessionId();
602  0 lineLength += accessionId.length() + 1;
603  0 if (countForSource > 1 && summary)
604    {
605  0 sb.append(",\n ").append(accessionId);
606  0 lineLength++;
607    }
608    else
609    {
610  0 sb.append(source).append(" ").append(accessionId);
611  0 lineLength += source.length();
612    }
613  0 maxLineLength = Math.max(maxLineLength, lineLength);
614    }
615  0 if (countForSource == MAX_REFS_PER_SOURCE && summary)
616    {
617  0 sb.append(COMMA).append(ELLIPSIS);
618  0 ellipsis = true;
619    }
620    }
621  0 if (moreSources)
622    {
623  0 sb.append("<br/>\n").append(source).append(COMMA).append(ELLIPSIS);
624    }
625  0 if (ellipsis)
626    {
627  0 sb.append("<br/>\n(");
628  0 sb.append(MessageManager.getString("label.output_seq_details"));
629  0 sb.append(")");
630    }
631   
632  0 return maxLineLength;
633    }
634   
 
635  0 toggle public void createTooltipAnnotationReport(final StringBuilder tip,
636    SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
637    FeatureRendererModel fr)
638    {
639  0 int maxWidth = createSequenceAnnotationReport(tip, sequence, showDbRefs,
640    showNpFeats, fr, true);
641   
642  0 if (maxWidth > 60)
643    {
644    // ? not sure this serves any useful purpose
645    // tip.insert(0, "<table width=350 border=0><tr><td>");
646    // tip.append("</td></tr></table>");
647    }
648    }
649    }