Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
SequenceFeature | 43 | 150 | 95 | ||
SFSortByEnd | 750 | 1 | 1 | ||
SFSortByBegin | 759 | 1 | 1 |
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.datamodel; | |
22 | ||
23 | import java.util.Comparator; | |
24 | import java.util.LinkedHashMap; | |
25 | import java.util.Map; | |
26 | import java.util.Map.Entry; | |
27 | import java.util.SortedMap; | |
28 | import java.util.TreeMap; | |
29 | import java.util.Vector; | |
30 | ||
31 | import jalview.datamodel.features.FeatureAttributeType; | |
32 | import jalview.datamodel.features.FeatureAttributes; | |
33 | import jalview.datamodel.features.FeatureLocationI; | |
34 | import jalview.datamodel.features.FeatureSourceI; | |
35 | import jalview.datamodel.features.FeatureSources; | |
36 | import jalview.util.StringUtils; | |
37 | ||
38 | /** | |
39 | * A class that models a single contiguous feature on a sequence. If flag | |
40 | * 'contactFeature' is true, the start and end positions are interpreted instead | |
41 | * as two contact points. | |
42 | */ | |
43 | public class SequenceFeature implements FeatureLocationI | |
44 | { | |
45 | /* | |
46 | * score value if none is set; preferably Float.Nan, but see | |
47 | * JAL-2060 and JAL-2554 for a couple of blockers to that | |
48 | */ | |
49 | private static final float NO_SCORE = 0f; | |
50 | ||
51 | private static final String STATUS = "status"; | |
52 | ||
53 | public static final String STRAND = "STRAND"; | |
54 | ||
55 | // key for Phase designed not to conflict with real GFF data | |
56 | public static final String PHASE = "!Phase"; | |
57 | ||
58 | // private key for ENA location designed not to conflict with real GFF data | |
59 | private static final String LOCATION = "!Location"; | |
60 | ||
61 | private static final String ROW_DATA = "<tr><td>%s</td><td>%s</td><td>%s</td></tr>"; | |
62 | ||
63 | /* | |
64 | * type, begin, end, featureGroup, score and contactFeature are final | |
65 | * to ensure that the integrity of SequenceFeatures data store | |
66 | * can't be broken by direct update of these fields | |
67 | */ | |
68 | public final String type; | |
69 | ||
70 | public final int begin; | |
71 | ||
72 | public final int end; | |
73 | ||
74 | public final String featureGroup; | |
75 | ||
76 | public final float score; | |
77 | ||
78 | private final boolean contactFeature; | |
79 | ||
80 | public String description; | |
81 | ||
82 | /* | |
83 | * a map of key-value pairs; may be populated from GFF 'column 9' data, | |
84 | * other data sources (e.g. GenBank file), or programmatically | |
85 | */ | |
86 | public Map<String, Object> otherDetails; | |
87 | ||
88 | public Vector<String> links; | |
89 | ||
90 | /* | |
91 | * the identifier (if known) for the FeatureSource held in FeatureSources, | |
92 | * as a provider of metadata about feature attributes | |
93 | */ | |
94 | private String source; | |
95 | ||
96 | /** | |
97 | * Constructs a duplicate feature. Note: Uses makes a shallow copy of the | |
98 | * otherDetails map, so the new and original SequenceFeature may reference the | |
99 | * same objects in the map. | |
100 | * | |
101 | * @param cpy | |
102 | */ | |
103 | 200 | public SequenceFeature(SequenceFeature cpy) |
104 | { | |
105 | 200 | this(cpy, cpy.getBegin(), cpy.getEnd(), cpy.getFeatureGroup(), |
106 | cpy.getScore()); | |
107 | } | |
108 | ||
109 | /** | |
110 | * Constructor | |
111 | * | |
112 | * @param theType | |
113 | * @param theDesc | |
114 | * @param theBegin | |
115 | * @param theEnd | |
116 | * @param group | |
117 | */ | |
118 | 67558 | public SequenceFeature(String theType, String theDesc, int theBegin, |
119 | int theEnd, String group) | |
120 | { | |
121 | 67558 | this(theType, theDesc, theBegin, theEnd, NO_SCORE, group); |
122 | } | |
123 | ||
124 | /** | |
125 | * Constructor including a score value | |
126 | * | |
127 | * @param theType | |
128 | * @param theDesc | |
129 | * @param theBegin | |
130 | * @param theEnd | |
131 | * @param theScore | |
132 | * @param group | |
133 | */ | |
134 | 234002 | public SequenceFeature(String theType, String theDesc, int theBegin, |
135 | int theEnd, float theScore, String group) | |
136 | { | |
137 | 234002 | this.type = theType; |
138 | 234002 | this.description = theDesc; |
139 | 234002 | this.begin = theBegin; |
140 | 234002 | this.end = theEnd; |
141 | 234002 | this.featureGroup = group; |
142 | 234002 | this.score = theScore; |
143 | ||
144 | /* | |
145 | * for now, only "Disulfide/disulphide bond" is treated as a contact feature | |
146 | */ | |
147 | 234002 | this.contactFeature = "disulfide bond".equalsIgnoreCase(type) |
148 | || "disulphide bond".equalsIgnoreCase(type); | |
149 | } | |
150 | ||
151 | /** | |
152 | * A copy constructor that allows the value of final fields to be 'modified' | |
153 | * | |
154 | * @param sf | |
155 | * @param newType | |
156 | * @param newBegin | |
157 | * @param newEnd | |
158 | * @param newGroup | |
159 | * @param newScore | |
160 | */ | |
161 | 29912 | public SequenceFeature(SequenceFeature sf, String newType, int newBegin, |
162 | int newEnd, String newGroup, float newScore) | |
163 | { | |
164 | 29912 | this(newType, sf.getDescription(), newBegin, newEnd, newScore, |
165 | newGroup); | |
166 | ||
167 | 29912 | this.source = sf.source; |
168 | ||
169 | 29912 | if (sf.otherDetails != null) |
170 | { | |
171 | 5306 | otherDetails = new LinkedHashMap<>(); |
172 | 5306 | otherDetails.putAll(sf.otherDetails); |
173 | } | |
174 | 29912 | if (sf.links != null && sf.links.size() > 0) |
175 | { | |
176 | 0 | links = new Vector<>(); |
177 | 0 | links.addAll(sf.links); |
178 | } | |
179 | } | |
180 | ||
181 | /** | |
182 | * A copy constructor that allows the value of final fields to be 'modified' | |
183 | * | |
184 | * @param sf | |
185 | * @param newBegin | |
186 | * @param newEnd | |
187 | * @param newGroup | |
188 | * @param newScore | |
189 | */ | |
190 | 29911 | public SequenceFeature(SequenceFeature sf, int newBegin, int newEnd, |
191 | String newGroup, float newScore) | |
192 | { | |
193 | 29911 | this(sf, sf.getType(), newBegin, newEnd, newGroup, newScore); |
194 | } | |
195 | ||
196 | /** | |
197 | * Two features are considered equal if they have the same type, group, | |
198 | * description, start, end, phase, strand, and (if present) 'Name', ID' and | |
199 | * 'Parent' attributes. | |
200 | * | |
201 | * Note we need to check Parent to distinguish the same exon occurring in | |
202 | * different transcripts (in Ensembl GFF). This allows assembly of transcript | |
203 | * sequences from their component exon regions. | |
204 | */ | |
205 | 97581 | @Override |
206 | public boolean equals(Object o) | |
207 | { | |
208 | 97581 | return equals(o, false); |
209 | } | |
210 | ||
211 | /** | |
212 | * Overloaded method allows the equality test to optionally ignore the | |
213 | * 'Parent' attribute of a feature. This supports avoiding adding many | |
214 | * superficially duplicate 'exon' or CDS features to genomic or protein | |
215 | * sequence. | |
216 | * | |
217 | * @param o | |
218 | * @param ignoreParent | |
219 | * @return | |
220 | */ | |
221 | 97581 | public boolean equals(Object o, boolean ignoreParent) |
222 | { | |
223 | 97581 | if (o == null || !(o instanceof SequenceFeature)) |
224 | { | |
225 | 1 | return false; |
226 | } | |
227 | ||
228 | 97580 | SequenceFeature sf = (SequenceFeature) o; |
229 | 97580 | boolean sameScore = Float.isNaN(score) ? Float.isNaN(sf.score) |
230 | : score == sf.score; | |
231 | 97580 | if (begin != sf.begin || end != sf.end || !sameScore) |
232 | { | |
233 | 12102 | return false; |
234 | } | |
235 | ||
236 | 85478 | if (getStrand() != sf.getStrand()) |
237 | { | |
238 | 1 | return false; |
239 | } | |
240 | ||
241 | 85477 | if (!(type + description + featureGroup + getPhase()).equals( |
242 | sf.type + sf.description + sf.featureGroup + sf.getPhase())) | |
243 | { | |
244 | 6633 | return false; |
245 | } | |
246 | 78844 | if (!equalAttribute(getValue("ID"), sf.getValue("ID"))) |
247 | { | |
248 | 1 | return false; |
249 | } | |
250 | 78843 | if (!equalAttribute(getValue("Name"), sf.getValue("Name"))) |
251 | { | |
252 | 1 | return false; |
253 | } | |
254 | 78842 | if (!ignoreParent) |
255 | { | |
256 | 78842 | if (!equalAttribute(getValue("Parent"), sf.getValue("Parent"))) |
257 | { | |
258 | 11 | return false; |
259 | } | |
260 | } | |
261 | 78831 | return true; |
262 | } | |
263 | ||
264 | /** | |
265 | * Returns true if both values are null, are both non-null and equal | |
266 | * | |
267 | * @param att1 | |
268 | * @param att2 | |
269 | * @return | |
270 | */ | |
271 | 236529 | protected static boolean equalAttribute(Object att1, Object att2) |
272 | { | |
273 | 236529 | if (att1 == null && att2 == null) |
274 | { | |
275 | 236113 | return true; |
276 | } | |
277 | 416 | if (att1 != null) |
278 | { | |
279 | 410 | return att1.equals(att2); |
280 | } | |
281 | 6 | return att2.equals(att1); |
282 | } | |
283 | ||
284 | /** | |
285 | * DOCUMENT ME! | |
286 | * | |
287 | * @return DOCUMENT ME! | |
288 | */ | |
289 | 2897708 | @Override |
290 | public int getBegin() | |
291 | { | |
292 | 2897737 | return begin; |
293 | } | |
294 | ||
295 | /** | |
296 | * DOCUMENT ME! | |
297 | * | |
298 | * @return DOCUMENT ME! | |
299 | */ | |
300 | 939093 | @Override |
301 | public int getEnd() | |
302 | { | |
303 | 939107 | return end; |
304 | } | |
305 | ||
306 | /** | |
307 | * DOCUMENT ME! | |
308 | * | |
309 | * @return DOCUMENT ME! | |
310 | */ | |
311 | 576208 | public String getType() |
312 | { | |
313 | 576213 | return type; |
314 | } | |
315 | ||
316 | /** | |
317 | * DOCUMENT ME! | |
318 | * | |
319 | * @return DOCUMENT ME! | |
320 | */ | |
321 | 45614 | public String getDescription() |
322 | { | |
323 | 45614 | return description; |
324 | } | |
325 | ||
326 | 342 | public void setDescription(String desc) |
327 | { | |
328 | 342 | description = desc; |
329 | } | |
330 | ||
331 | 218034 | public String getFeatureGroup() |
332 | { | |
333 | 218034 | return featureGroup; |
334 | } | |
335 | ||
336 | /** | |
337 | * Adds a hyperlink for the feature. This should have the format label|url. | |
338 | * | |
339 | * @param labelLink | |
340 | */ | |
341 | 6862 | public void addLink(String labelLink) |
342 | { | |
343 | 6862 | if (links == null) |
344 | { | |
345 | 6862 | links = new Vector<>(); |
346 | } | |
347 | ||
348 | 6862 | if (!links.contains(labelLink)) |
349 | { | |
350 | 6862 | links.insertElementAt(labelLink, 0); |
351 | } | |
352 | } | |
353 | ||
354 | 177969 | public float getScore() |
355 | { | |
356 | 177969 | return score; |
357 | } | |
358 | ||
359 | /** | |
360 | * Used for getting values which are not in the basic set. eg STRAND, PHASE | |
361 | * for GFF file | |
362 | * | |
363 | * @param key | |
364 | * String | |
365 | */ | |
366 | 666854 | public Object getValue(String key) |
367 | { | |
368 | 666854 | if (otherDetails == null) |
369 | { | |
370 | 52909 | return null; |
371 | } | |
372 | else | |
373 | { | |
374 | 613945 | return otherDetails.get(key); |
375 | } | |
376 | } | |
377 | ||
378 | /** | |
379 | * Answers the value of the specified attribute as string, or null if no such | |
380 | * value. If more than one attribute name is provided, tries to resolve as | |
381 | * keys to nested maps. For example, if attribute "CSQ" holds a map of | |
382 | * key-value pairs, then getValueAsString("CSQ", "Allele") returns the value | |
383 | * of "Allele" in that map. | |
384 | * | |
385 | * @param key | |
386 | * @return | |
387 | */ | |
388 | 117 | public String getValueAsString(String... key) |
389 | { | |
390 | 117 | if (otherDetails == null) |
391 | { | |
392 | 10 | return null; |
393 | } | |
394 | 107 | Object value = otherDetails.get(key[0]); |
395 | 107 | if (key.length > 1 && value instanceof Map<?, ?>) |
396 | { | |
397 | 26 | value = ((Map) value).get(key[1]); |
398 | } | |
399 | 107 | return value == null ? null : value.toString(); |
400 | } | |
401 | ||
402 | /** | |
403 | * Returns a property value for the given key if known, else the specified | |
404 | * default value | |
405 | * | |
406 | * @param key | |
407 | * @param defaultValue | |
408 | * @return | |
409 | */ | |
410 | 2 | public Object getValue(String key, Object defaultValue) |
411 | { | |
412 | 2 | Object value = getValue(key); |
413 | 2 | return value == null ? defaultValue : value; |
414 | } | |
415 | ||
416 | /** | |
417 | * Used for setting values which are not in the basic set. eg STRAND, FRAME | |
418 | * for GFF file | |
419 | * | |
420 | * @param key | |
421 | * eg STRAND | |
422 | * @param value | |
423 | * eg + | |
424 | */ | |
425 | 668916 | public void setValue(String key, Object value) |
426 | { | |
427 | 668916 | if (value != null) |
428 | { | |
429 | 532976 | if (otherDetails == null) |
430 | { | |
431 | /* | |
432 | * LinkedHashMap preserves insertion order of attributes | |
433 | */ | |
434 | 151412 | otherDetails = new LinkedHashMap<>(); |
435 | } | |
436 | ||
437 | 532976 | otherDetails.put(key, value); |
438 | 532976 | recordAttribute(key, value); |
439 | } | |
440 | } | |
441 | ||
442 | /** | |
443 | * Notifies the addition of a feature attribute. This lets us keep track of | |
444 | * which attributes are present on each feature type, and also the range of | |
445 | * numerical-valued attributes. | |
446 | * | |
447 | * @param key | |
448 | * @param value | |
449 | */ | |
450 | 532976 | protected void recordAttribute(String key, Object value) |
451 | { | |
452 | 532976 | String attDesc = null; |
453 | 532976 | if (source != null) |
454 | { | |
455 | 289 | attDesc = FeatureSources.getInstance().getSource(source) |
456 | .getAttributeName(key); | |
457 | } | |
458 | ||
459 | 532976 | FeatureAttributes.getInstance().addAttribute(this.type, attDesc, value, |
460 | key); | |
461 | } | |
462 | ||
463 | /* | |
464 | * The following methods are added to maintain the castor Uniprot mapping file | |
465 | * for the moment. | |
466 | */ | |
467 | 157153 | public void setStatus(String status) |
468 | { | |
469 | 157153 | setValue(STATUS, status); |
470 | } | |
471 | ||
472 | 21210 | public String getStatus() |
473 | { | |
474 | 21210 | return (String) getValue(STATUS); |
475 | } | |
476 | ||
477 | /** | |
478 | * Return 1 for forward strand ('+' in GFF), -1 for reverse strand ('-' in | |
479 | * GFF), and 0 for unknown or not (validly) specified | |
480 | * | |
481 | * @return | |
482 | */ | |
483 | 170998 | public int getStrand() |
484 | { | |
485 | 170998 | int strand = 0; |
486 | 170998 | if (otherDetails != null) |
487 | { | |
488 | 162529 | Object str = otherDetails.get(STRAND); |
489 | 162529 | if ("-".equals(str)) |
490 | { | |
491 | 754 | strand = -1; |
492 | } | |
493 | 161775 | else if ("+".equals(str)) |
494 | { | |
495 | 126591 | strand = 1; |
496 | } | |
497 | } | |
498 | 170998 | return strand; |
499 | } | |
500 | ||
501 | /** | |
502 | * Set the value of strand | |
503 | * | |
504 | * @param strand | |
505 | * should be "+" for forward, or "-" for reverse | |
506 | */ | |
507 | 77 | public void setStrand(String strand) |
508 | { | |
509 | 77 | setValue(STRAND, strand); |
510 | } | |
511 | ||
512 | 63 | public void setPhase(String phase) |
513 | { | |
514 | 63 | setValue(PHASE, phase); |
515 | } | |
516 | ||
517 | 170997 | public String getPhase() |
518 | { | |
519 | 170997 | return (String) getValue(PHASE); |
520 | } | |
521 | ||
522 | /** | |
523 | * Sets the 'raw' ENA format location specifier e.g. join(12..45,89..121) | |
524 | * | |
525 | * @param loc | |
526 | */ | |
527 | 32 | public void setEnaLocation(String loc) |
528 | { | |
529 | 32 | setValue(LOCATION, loc); |
530 | } | |
531 | ||
532 | /** | |
533 | * Gets the 'raw' ENA format location specifier e.g. join(12..45,89..121) | |
534 | * | |
535 | * @param loc | |
536 | */ | |
537 | 8 | public String getEnaLocation() |
538 | { | |
539 | 8 | return (String) getValue(LOCATION); |
540 | } | |
541 | ||
542 | /** | |
543 | * Readable representation, for debug only, not guaranteed not to change | |
544 | * between versions | |
545 | */ | |
546 | 918 | @Override |
547 | public String toString() | |
548 | { | |
549 | 918 | return String.format("%d %d %s %s", getBegin(), getEnd(), getType(), |
550 | getDescription()); | |
551 | } | |
552 | ||
553 | /** | |
554 | * Overridden to ensure that whenever two objects are equal, they have the | |
555 | * same hashCode | |
556 | */ | |
557 | 4 | @Override |
558 | public int hashCode() | |
559 | { | |
560 | 4 | String s = getType() + getDescription() + getFeatureGroup() |
561 | + getValue("ID") + getValue("Name") + getValue("Parent") | |
562 | + getPhase(); | |
563 | 4 | return s.hashCode() + getBegin() + getEnd() + (int) getScore() |
564 | + getStrand(); | |
565 | } | |
566 | ||
567 | /** | |
568 | * Answers true if the feature's start/end values represent two related | |
569 | * positions, rather than ends of a range. Such features may be visualised or | |
570 | * reported differently to features on a range. | |
571 | */ | |
572 | 500828 | @Override |
573 | public boolean isContactFeature() | |
574 | { | |
575 | 500828 | return contactFeature; |
576 | } | |
577 | ||
578 | /** | |
579 | * Answers true if the sequence has zero start and end position | |
580 | * | |
581 | * @return | |
582 | */ | |
583 | 750752 | public boolean isNonPositional() |
584 | { | |
585 | 750752 | return begin == 0 && end == 0; |
586 | } | |
587 | ||
588 | /** | |
589 | * Answers an html-formatted report of feature details. If parameter | |
590 | * {@code mf} is not null, the feature is a virtual linked feature, and | |
591 | * details included both the original location and the mapped location | |
592 | * (CDS/peptide). | |
593 | * | |
594 | * @param seqName | |
595 | * @param mf | |
596 | * | |
597 | * @return | |
598 | */ | |
599 | 6 | public String getDetailsReport(String seqName, MappedFeatures mf) |
600 | { | |
601 | 6 | FeatureSourceI metadata = FeatureSources.getInstance() |
602 | .getSource(source); | |
603 | ||
604 | 6 | StringBuilder sb = new StringBuilder(128); |
605 | 6 | sb.append("<br>"); |
606 | 6 | sb.append("<table>"); |
607 | 6 | String name = mf == null ? seqName : mf.getLinkedSequenceName(); |
608 | 6 | sb.append(String.format(ROW_DATA, "Location", name, begin == end ? begin |
609 | 4 | : begin + (isContactFeature() ? ":" : "-") + end)); |
610 | ||
611 | 6 | String consequence = ""; |
612 | 6 | if (mf != null) |
613 | { | |
614 | 2 | int[] localRange = mf.getMappedPositions(begin, end); |
615 | 2 | int from = localRange[0]; |
616 | 2 | int to = localRange[localRange.length - 1]; |
617 | 2 | String s = mf.isFromCds() ? "Peptide Location" : "Coding location"; |
618 | 2 | sb.append(String.format(ROW_DATA, s, seqName, from == to ? from |
619 | 1 | : from + (isContactFeature() ? ":" : "-") + to)); |
620 | 2 | if (mf.isFromCds()) |
621 | { | |
622 | 2 | consequence = mf.findProteinVariants(this); |
623 | } | |
624 | } | |
625 | 6 | sb.append(String.format(ROW_DATA, "Type", type, "")); |
626 | 6 | String desc = StringUtils.stripHtmlTags(description); |
627 | 6 | sb.append(String.format(ROW_DATA, "Description", desc, "")); |
628 | 6 | if (!Float.isNaN(score) && score != 0f) |
629 | { | |
630 | 1 | sb.append(String.format(ROW_DATA, "Score", score, "")); |
631 | } | |
632 | 6 | if (featureGroup != null) |
633 | { | |
634 | 2 | sb.append(String.format(ROW_DATA, "Group", featureGroup, "")); |
635 | } | |
636 | ||
637 | 6 | if (!consequence.isEmpty()) |
638 | { | |
639 | 1 | sb.append(String.format(ROW_DATA, "Consequence", |
640 | "<i>Translated by Jalview</i>", consequence)); | |
641 | } | |
642 | ||
643 | 6 | if (otherDetails != null) |
644 | { | |
645 | 2 | TreeMap<String, Object> ordered = new TreeMap<>( |
646 | String.CASE_INSENSITIVE_ORDER); | |
647 | 2 | ordered.putAll(otherDetails); |
648 | ||
649 | 2 | for (Entry<String, Object> entry : ordered.entrySet()) |
650 | { | |
651 | 3 | String key = entry.getKey(); |
652 | ||
653 | 3 | Object value = entry.getValue(); |
654 | 3 | if (value instanceof Map<?, ?>) |
655 | { | |
656 | /* | |
657 | * expand values in a Map attribute across separate lines | |
658 | * copy to a TreeMap for alphabetical ordering | |
659 | */ | |
660 | 0 | Map<String, Object> values = (Map<String, Object>) value; |
661 | 0 | SortedMap<String, Object> sm = new TreeMap<>( |
662 | String.CASE_INSENSITIVE_ORDER); | |
663 | 0 | sm.putAll(values); |
664 | 0 | for (Entry<?, ?> e : sm.entrySet()) |
665 | { | |
666 | 0 | sb.append(String.format(ROW_DATA, key, e.getKey().toString(), |
667 | e.getValue().toString())); | |
668 | } | |
669 | } | |
670 | else | |
671 | { | |
672 | // tried <td title="key"> but it failed to provide a tooltip :-( | |
673 | 3 | String attDesc = null; |
674 | 3 | if (metadata != null) |
675 | { | |
676 | 0 | attDesc = metadata.getAttributeName(key); |
677 | } | |
678 | 3 | String s = entry.getValue().toString(); |
679 | 3 | if (isValueInteresting(key, s, metadata)) |
680 | { | |
681 | 3 | sb.append(String.format(ROW_DATA, key, |
682 | 3 | attDesc == null ? "" : attDesc, s)); |
683 | } | |
684 | } | |
685 | } | |
686 | } | |
687 | 6 | sb.append("</table>"); |
688 | ||
689 | 6 | String text = sb.toString(); |
690 | 6 | return text; |
691 | } | |
692 | ||
693 | /** | |
694 | * Answers true if we judge the value is worth displaying, by some heuristic | |
695 | * rules, else false | |
696 | * | |
697 | * @param key | |
698 | * @param value | |
699 | * @param metadata | |
700 | * @return | |
701 | */ | |
702 | 3 | boolean isValueInteresting(String key, String value, |
703 | FeatureSourceI metadata) | |
704 | { | |
705 | /* | |
706 | * currently suppressing zero values as well as null or empty | |
707 | */ | |
708 | 3 | if (value == null || "".equals(value) || ".".equals(value) |
709 | || "0".equals(value)) | |
710 | { | |
711 | 0 | return false; |
712 | } | |
713 | ||
714 | 3 | if (metadata == null) |
715 | { | |
716 | 3 | return true; |
717 | } | |
718 | ||
719 | 0 | FeatureAttributeType attType = metadata.getAttributeType(key); |
720 | 0 | if (attType != null && (attType == FeatureAttributeType.Float |
721 | || attType.equals(FeatureAttributeType.Integer))) | |
722 | { | |
723 | 0 | try |
724 | { | |
725 | 0 | float fval = Float.valueOf(value); |
726 | 0 | if (fval == 0f) |
727 | { | |
728 | 0 | return false; |
729 | } | |
730 | } catch (NumberFormatException e) | |
731 | { | |
732 | // ignore | |
733 | } | |
734 | } | |
735 | ||
736 | 0 | return true; // default to interesting |
737 | } | |
738 | ||
739 | /** | |
740 | * Sets the feature source identifier | |
741 | * | |
742 | * @param theSource | |
743 | */ | |
744 | 51 | public void setSource(String theSource) |
745 | { | |
746 | 51 | source = theSource; |
747 | } | |
748 | } | |
749 | ||
750 | class SFSortByEnd implements Comparator<SequenceFeature> | |
751 | { | |
752 | 32 | @Override |
753 | public int compare(SequenceFeature a, SequenceFeature b) | |
754 | { | |
755 | 32 | return a.getEnd() - b.getEnd(); |
756 | } | |
757 | } | |
758 | ||
759 | class SFSortByBegin implements Comparator<SequenceFeature> | |
760 | { | |
761 | 0 | @Override |
762 | public int compare(SequenceFeature a, SequenceFeature b) | |
763 | { | |
764 | 0 | return a.getBegin() - b.getBegin(); |
765 | } | |
766 | } |