c3640bf36416fd8698e0fb5679840d613c4d2395
[jalview.git] / src / jalview / io / FeaturesFile.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)\r
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle\r
4  * \r
5  * This file is part of Jalview.\r
6  * \r
7  * Jalview is free software: you can redistribute it and/or\r
8  * modify it under the terms of the GNU General Public License \r
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\r
10  * \r
11  * Jalview is distributed in the hope that it will be useful, but \r
12  * WITHOUT ANY WARRANTY; without even the implied warranty \r
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR \r
14  * PURPOSE.  See the GNU General Public License for more details.\r
15  * \r
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.\r
17  */\r
18 package jalview.io;\r
19 \r
20 import java.io.*;\r
21 import java.util.*;\r
22 \r
23 import jalview.analysis.SequenceIdMatcher;\r
24 import jalview.datamodel.*;\r
25 import jalview.schemes.*;\r
26 import jalview.util.Format;\r
27 \r
28 /**\r
29  * Parse and create Jalview Features files Detects GFF format features files and\r
30  * parses. Does not implement standard print() - call specific printFeatures or\r
31  * printGFF. Uses AlignmentI.findSequence(String id) to find the sequence object\r
32  * for the features annotation - this normally works on an exact match.\r
33  * \r
34  * @author AMW\r
35  * @version $Revision$\r
36  */\r
37 public class FeaturesFile extends AlignFile\r
38 {\r
39   /**\r
40    * work around for GFF interpretation bug where source string becomes\r
41    * description rather than a group\r
42    */\r
43   private boolean doGffSource = true;\r
44 \r
45   /**\r
46    * Creates a new FeaturesFile object.\r
47    */\r
48   public FeaturesFile()\r
49   {\r
50   }\r
51 \r
52   /**\r
53    * Creates a new FeaturesFile object.\r
54    * \r
55    * @param inFile\r
56    *          DOCUMENT ME!\r
57    * @param type\r
58    *          DOCUMENT ME!\r
59    * \r
60    * @throws IOException\r
61    *           DOCUMENT ME!\r
62    */\r
63   public FeaturesFile(String inFile, String type) throws IOException\r
64   {\r
65     super(inFile, type);\r
66   }\r
67 \r
68   public FeaturesFile(FileParse source) throws IOException\r
69   {\r
70     super(source);\r
71   }\r
72 \r
73   /**\r
74    * Parse GFF or sequence features file using case-independent matching, discarding URLs\r
75    * @param align - alignment/dataset containing sequences that are to be annotated\r
76    * @param colours - hashtable to store feature colour definitions\r
77    * @param removeHTML - process html strings into plain text\r
78    * @return true if features were added\r
79    */\r
80   public boolean parse(AlignmentI align, Hashtable colours,\r
81           boolean removeHTML)\r
82   {\r
83     return parse(align, colours, null, removeHTML, false);\r
84   }\r
85 \r
86   /**\r
87    * Parse GFF or sequence features file optionally using case-independent matching, discarding URLs\r
88    * @param align - alignment/dataset containing sequences that are to be annotated\r
89    * @param colours - hashtable to store feature colour definitions\r
90    * @param removeHTML - process html strings into plain text\r
91    * @param relaxedIdmatching - when true, ID matches to compound sequence IDs are allowed\r
92    * @return true if features were added\r
93    */\r
94   public boolean parse(AlignmentI align, \r
95           Hashtable colours, boolean removeHTML, boolean relaxedIdMatching)\r
96   {\r
97     return parse(align, colours, null, removeHTML, relaxedIdMatching);\r
98   }\r
99 \r
100   /**\r
101    * Parse GFF or sequence features file optionally using case-independent matching\r
102    * @param align - alignment/dataset containing sequences that are to be annotated\r
103    * @param colours - hashtable to store feature colour definitions\r
104    * @param featureLink - hashtable to store associated URLs \r
105    * @param removeHTML - process html strings into plain text\r
106    * @return true if features were added\r
107    */\r
108   public boolean parse(AlignmentI align, Hashtable colours,\r
109           Hashtable featureLink, boolean removeHTML)\r
110   {\r
111     return parse(align, colours, featureLink, removeHTML, false);\r
112   }\r
113 \r
114   /**\r
115   /**\r
116    * Parse GFF or sequence features file \r
117    * @param align - alignment/dataset containing sequences that are to be annotated\r
118    * @param colours - hashtable to store feature colour definitions\r
119    * @param featureLink - hashtable to store associated URLs \r
120    * @param removeHTML - process html strings into plain text\r
121    * @param relaxedIdmatching - when true, ID matches to compound sequence IDs are allowed\r
122    * @return true if features were added\r
123    */\r
124   public boolean parse(AlignmentI align,\r
125           Hashtable colours, Hashtable featureLink, boolean removeHTML, boolean relaxedIdmatching)\r
126   {\r
127 \r
128     String line = null;\r
129     try\r
130     {\r
131       SequenceI seq = null;\r
132       String type, desc, token = null;\r
133 \r
134       int index, start, end;\r
135       float score;\r
136       StringTokenizer st;\r
137       SequenceFeature sf;\r
138       String featureGroup = null, groupLink = null;\r
139       Hashtable typeLink = new Hashtable();\r
140       /**\r
141        * when true, assume GFF style features rather than Jalview style.\r
142        */\r
143       boolean GFFFile = true;\r
144       while ((line = nextLine()) != null)\r
145       {\r
146         if (line.startsWith("#"))\r
147         {\r
148           continue;\r
149         }\r
150 \r
151         st = new StringTokenizer(line, "\t");\r
152         if (st.countTokens() == 1)\r
153         {\r
154           if (line.trim().equalsIgnoreCase("GFF"))\r
155           {\r
156             // Start parsing file as if it might be GFF again.\r
157             GFFFile = true;\r
158             continue;\r
159           }\r
160         }\r
161         if (st.countTokens() > 1 && st.countTokens() < 4)\r
162         {\r
163           GFFFile = false;\r
164           type = st.nextToken();\r
165           if (type.equalsIgnoreCase("startgroup"))\r
166           {\r
167             featureGroup = st.nextToken();\r
168             if (st.hasMoreElements())\r
169             {\r
170               groupLink = st.nextToken();\r
171               featureLink.put(featureGroup, groupLink);\r
172             }\r
173           }\r
174           else if (type.equalsIgnoreCase("endgroup"))\r
175           {\r
176             // We should check whether this is the current group,\r
177             // but at present theres no way of showing more than 1 group\r
178             st.nextToken();\r
179             featureGroup = null;\r
180             groupLink = null;\r
181           }\r
182           else\r
183           {\r
184             Object colour = null;\r
185             String colscheme = st.nextToken();\r
186             if (colscheme.indexOf("|") > -1\r
187                     || colscheme.trim().equalsIgnoreCase("label"))\r
188             {\r
189               // Parse '|' separated graduated colourscheme fields:\r
190               // [label|][mincolour|maxcolour|[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue]\r
191               // can either provide 'label' only, first is optional, next two\r
192               // colors are required (but may be\r
193               // left blank), next is optional, nxt two min/max are required.\r
194               // first is either 'label'\r
195               // first/second and third are both hexadecimal or word equivalent\r
196               // colour.\r
197               // next two are values parsed as floats.\r
198               // fifth is either 'above','below', or 'none'.\r
199               // sixth is a float value and only required when fifth is either\r
200               // 'above' or 'below'.\r
201               StringTokenizer gcol = new StringTokenizer(colscheme, "|",\r
202                       true);\r
203               // set defaults\r
204               int threshtype = AnnotationColourGradient.NO_THRESHOLD;\r
205               float min = Float.MIN_VALUE, max = Float.MAX_VALUE, threshval = Float.NaN;\r
206               boolean labelCol = false;\r
207               // Parse spec line\r
208               String mincol = gcol.nextToken();\r
209               if (mincol == "|")\r
210               {\r
211                 System.err\r
212                         .println("Expected either 'label' or a colour specification in the line: "\r
213                                 + line);\r
214                 continue;\r
215               }\r
216               String maxcol = null;\r
217               if (mincol.toLowerCase().indexOf("label") == 0)\r
218               {\r
219                 labelCol = true;\r
220                 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); // skip\r
221                                                                            // '|'\r
222                 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);\r
223               }\r
224               String abso = null, minval, maxval;\r
225               if (mincol != null)\r
226               {\r
227                 // at least four more tokens\r
228                 if (mincol.equals("|"))\r
229                 {\r
230                   mincol = "";\r
231                 }\r
232                 else\r
233                 {\r
234                   gcol.nextToken(); // skip next '|'\r
235                 }\r
236                 // continue parsing rest of line\r
237                 maxcol = gcol.nextToken();\r
238                 if (maxcol.equals("|"))\r
239                 {\r
240                   maxcol = "";\r
241                 }\r
242                 else\r
243                 {\r
244                   gcol.nextToken(); // skip next '|'\r
245                 }\r
246                 abso = gcol.nextToken();\r
247                 gcol.nextToken(); // skip next '|'\r
248                 if (abso.toLowerCase().indexOf("abso") != 0)\r
249                 {\r
250                   minval = abso;\r
251                   abso = null;\r
252                 }\r
253                 else\r
254                 {\r
255                   minval = gcol.nextToken();\r
256                   gcol.nextToken(); // skip next '|'\r
257                 }\r
258                 maxval = gcol.nextToken();\r
259                 if (gcol.hasMoreTokens())\r
260                 {\r
261                   gcol.nextToken(); // skip next '|'\r
262                 }\r
263                 try\r
264                 {\r
265                   if (minval.length() > 0)\r
266                   {\r
267                     min = new Float(minval).floatValue();\r
268                   }\r
269                 } catch (Exception e)\r
270                 {\r
271                   System.err\r
272                           .println("Couldn't parse the minimum value for graduated colour for type ("\r
273                                   + colscheme\r
274                                   + ") - did you misspell 'auto' for the optional automatic colour switch ?");\r
275                   e.printStackTrace();\r
276                 }\r
277                 try\r
278                 {\r
279                   if (maxval.length() > 0)\r
280                   {\r
281                     max = new Float(maxval).floatValue();\r
282                   }\r
283                 } catch (Exception e)\r
284                 {\r
285                   System.err\r
286                           .println("Couldn't parse the maximum value for graduated colour for type ("\r
287                                   + colscheme + ")");\r
288                   e.printStackTrace();\r
289                 }\r
290               }\r
291               else\r
292               {\r
293                 // add in some dummy min/max colours for the label-only\r
294                 // colourscheme.\r
295                 mincol = "FFFFFF";\r
296                 maxcol = "000000";\r
297               }\r
298               try\r
299               {\r
300                 colour = new jalview.schemes.GraduatedColor(\r
301                         new UserColourScheme(mincol).findColour('A'),\r
302                         new UserColourScheme(maxcol).findColour('A'), min,\r
303                         max);\r
304               } catch (Exception e)\r
305               {\r
306                 System.err\r
307                         .println("Couldn't parse the graduated colour scheme ("\r
308                                 + colscheme + ")");\r
309                 e.printStackTrace();\r
310               }\r
311               if (colour != null)\r
312               {\r
313                 ((jalview.schemes.GraduatedColor) colour)\r
314                         .setColourByLabel(labelCol);\r
315                 ((jalview.schemes.GraduatedColor) colour)\r
316                         .setAutoScaled(abso == null);\r
317                 // add in any additional parameters\r
318                 String ttype = null, tval = null;\r
319                 if (gcol.hasMoreTokens())\r
320                 {\r
321                   // threshold type and possibly a threshold value\r
322                   ttype = gcol.nextToken();\r
323                   if (ttype.toLowerCase().startsWith("below"))\r
324                   {\r
325                     ((jalview.schemes.GraduatedColor) colour)\r
326                             .setThreshType(AnnotationColourGradient.BELOW_THRESHOLD);\r
327                   }\r
328                   else if (ttype.toLowerCase().startsWith("above"))\r
329                   {\r
330                     ((jalview.schemes.GraduatedColor) colour)\r
331                             .setThreshType(AnnotationColourGradient.ABOVE_THRESHOLD);\r
332                   }\r
333                   else\r
334                   {\r
335                     ((jalview.schemes.GraduatedColor) colour)\r
336                             .setThreshType(AnnotationColourGradient.NO_THRESHOLD);\r
337                     if (!ttype.toLowerCase().startsWith("no"))\r
338                     {\r
339                       System.err\r
340                               .println("Ignoring unrecognised threshold type : "\r
341                                       + ttype);\r
342                     }\r
343                   }\r
344                 }\r
345                 if (((GraduatedColor) colour).getThreshType() != AnnotationColourGradient.NO_THRESHOLD)\r
346                 {\r
347                   try\r
348                   {\r
349                     gcol.nextToken();\r
350                     tval = gcol.nextToken();\r
351                     ((jalview.schemes.GraduatedColor) colour)\r
352                             .setThresh(new Float(tval).floatValue());\r
353                   } catch (Exception e)\r
354                   {\r
355                     System.err\r
356                             .println("Couldn't parse threshold value as a float: ("\r
357                                     + tval + ")");\r
358                     e.printStackTrace();\r
359                   }\r
360                 }\r
361                 // parse the thresh-is-min token ?\r
362                 if (gcol.hasMoreTokens())\r
363                 {\r
364                   System.err\r
365                           .println("Ignoring additional tokens in parameters in graduated colour specification\n");\r
366                   while (gcol.hasMoreTokens())\r
367                   {\r
368                     System.err.println("|" + gcol.nextToken());\r
369                   }\r
370                   System.err.println("\n");\r
371                 }\r
372               }\r
373             }\r
374             else\r
375             {\r
376               UserColourScheme ucs = new UserColourScheme(colscheme);\r
377               colour = ucs.findColour('A');\r
378             }\r
379             if (colour != null)\r
380             {\r
381               colours.put(type, colour);\r
382             }\r
383             if (st.hasMoreElements())\r
384             {\r
385               String link = st.nextToken();\r
386               typeLink.put(type, link);\r
387               if (featureLink == null)\r
388               {\r
389                 featureLink = new Hashtable();\r
390               }\r
391               featureLink.put(type, link);\r
392             }\r
393           }\r
394           continue;\r
395         }\r
396         String seqId = "";\r
397         while (st.hasMoreElements())\r
398         {\r
399 \r
400           if (GFFFile)\r
401           {\r
402             // Still possible this is an old Jalview file,\r
403             // which does not have type colours at the beginning\r
404             seqId = token = st.nextToken();\r
405             seq = findName(align, seqId, relaxedIdmatching);\r
406             if (seq != null)\r
407             {\r
408               desc = st.nextToken();\r
409               String group = null;\r
410               if (doGffSource && desc.indexOf(' ') == -1)\r
411               {\r
412                 // could also be a source term rather than description line\r
413                 group = new String(desc);\r
414               }\r
415               type = st.nextToken();\r
416               try\r
417               {\r
418                 String stt = st.nextToken();\r
419                 if (stt.length() == 0 || stt.equals("-"))\r
420                 {\r
421                   start = 0;\r
422                 }\r
423                 else\r
424                 {\r
425                   start = Integer.parseInt(stt);\r
426                 }\r
427               } catch (NumberFormatException ex)\r
428               {\r
429                 start = 0;\r
430               }\r
431               try\r
432               {\r
433                 String stt = st.nextToken();\r
434                 if (stt.length() == 0 || stt.equals("-"))\r
435                 {\r
436                   end = 0;\r
437                 }\r
438                 else\r
439                 {\r
440                   end = Integer.parseInt(stt);\r
441                 }\r
442               } catch (NumberFormatException ex)\r
443               {\r
444                 end = 0;\r
445               }\r
446               // TODO: decide if non positional feature assertion for input data\r
447               // where end==0 is generally valid\r
448               if (end == 0)\r
449               {\r
450                 // treat as non-positional feature, regardless.\r
451                 start = 0;\r
452               }\r
453               try\r
454               {\r
455                 score = new Float(st.nextToken()).floatValue();\r
456               } catch (NumberFormatException ex)\r
457               {\r
458                 score = 0;\r
459               }\r
460 \r
461               sf = new SequenceFeature(type, desc, start, end, score, group);\r
462 \r
463               try\r
464               {\r
465                 sf.setValue("STRAND", st.nextToken());\r
466                 sf.setValue("FRAME", st.nextToken());\r
467               } catch (Exception ex)\r
468               {\r
469               }\r
470 \r
471               if (st.hasMoreTokens())\r
472               {\r
473                 StringBuffer attributes = new StringBuffer();\r
474                 while (st.hasMoreTokens())\r
475                 {\r
476                   attributes.append("\t" + st.nextElement());\r
477                 }\r
478                 // TODO validate and split GFF2 attributes field ? parse out\r
479                 // ([A-Za-z][A-Za-z0-9_]*) <value> ; and add as\r
480                 // sf.setValue(attrib, val);\r
481                 sf.setValue("ATTRIBUTES", attributes.toString());\r
482               }\r
483 \r
484               seq.addSequenceFeature(sf);\r
485               while ((seq = align.findName(seq, seqId, true)) != null)\r
486               {\r
487                 seq.addSequenceFeature(new SequenceFeature(sf));\r
488               }\r
489               break;\r
490             }\r
491           }\r
492 \r
493           if (GFFFile && seq == null)\r
494           {\r
495             desc = token;\r
496           }\r
497           else\r
498           {\r
499             desc = st.nextToken();\r
500           }\r
501           if (!st.hasMoreTokens())\r
502           {\r
503             System.err\r
504                     .println("DEBUG: Run out of tokens when trying to identify the destination for the feature.. giving up.");\r
505             // in all probability, this isn't a file we understand, so bail\r
506             // quietly.\r
507             return false;\r
508           }\r
509 \r
510           token = st.nextToken();\r
511 \r
512           if (!token.equals("ID_NOT_SPECIFIED"))\r
513           {\r
514             seq = findName(align, seqId = token, relaxedIdmatching);\r
515             st.nextToken();\r
516           }\r
517           else\r
518           {\r
519             seqId = null;\r
520             try\r
521             {\r
522               index = Integer.parseInt(st.nextToken());\r
523               seq = align.getSequenceAt(index);\r
524             } catch (NumberFormatException ex)\r
525             {\r
526               seq = null;\r
527             }\r
528           }\r
529 \r
530           if (seq == null)\r
531           {\r
532             System.out.println("Sequence not found: " + line);\r
533             break;\r
534           }\r
535 \r
536           start = Integer.parseInt(st.nextToken());\r
537           end = Integer.parseInt(st.nextToken());\r
538 \r
539           type = st.nextToken();\r
540 \r
541           if (!colours.containsKey(type))\r
542           {\r
543             // Probably the old style groups file\r
544             UserColourScheme ucs = new UserColourScheme(type);\r
545             colours.put(type, ucs.findColour('A'));\r
546           }\r
547           sf = new SequenceFeature(type, desc, "", start, end, featureGroup);\r
548           if (st.hasMoreTokens())\r
549           {\r
550             try\r
551             {\r
552               score = new Float(st.nextToken()).floatValue();\r
553               // update colourgradient bounds if allowed to\r
554             } catch (NumberFormatException ex)\r
555             {\r
556               score = 0;\r
557             }\r
558             sf.setScore(score);\r
559           }\r
560           if (groupLink != null && removeHTML)\r
561           {\r
562             sf.addLink(groupLink);\r
563             sf.description += "%LINK%";\r
564           }\r
565           if (typeLink.containsKey(type) && removeHTML)\r
566           {\r
567             sf.addLink(typeLink.get(type).toString());\r
568             sf.description += "%LINK%";\r
569           }\r
570 \r
571           parseDescriptionHTML(sf, removeHTML);\r
572 \r
573           seq.addSequenceFeature(sf);\r
574 \r
575           while (seqId != null\r
576                   && (seq = align.findName(seq, seqId, false)) != null)\r
577           {\r
578             seq.addSequenceFeature(new SequenceFeature(sf));\r
579           }\r
580           // If we got here, its not a GFFFile\r
581           GFFFile = false;\r
582         }\r
583       }\r
584       resetMatcher();\r
585     } catch (Exception ex)\r
586     {\r
587       System.out.println(line);\r
588       System.out.println("Error parsing feature file: " + ex + "\n" + line);\r
589       ex.printStackTrace(System.err);\r
590       resetMatcher();\r
591       return false;\r
592     }\r
593 \r
594     return true;\r
595   }\r
596 \r
597   private AlignmentI lastmatchedAl = null;\r
598 \r
599   private SequenceIdMatcher matcher = null;\r
600 \r
601   /**\r
602    * clear any temporary handles used to speed up ID matching\r
603    */\r
604   private void resetMatcher()\r
605   {\r
606     lastmatchedAl = null;\r
607     matcher = null;\r
608   }\r
609 \r
610   private SequenceI findName(AlignmentI align, String seqId,\r
611           boolean relaxedIdMatching)\r
612   {\r
613     SequenceI match = null;\r
614     if (relaxedIdMatching)\r
615     {\r
616       if (lastmatchedAl != align)\r
617       {\r
618         matcher = new SequenceIdMatcher(\r
619                 (lastmatchedAl = align).getSequencesArray());\r
620       }\r
621       match = matcher.findIdMatch(seqId);\r
622     }\r
623     else\r
624     {\r
625       match = align.findName(seqId, true);\r
626     }\r
627     return match;\r
628   }\r
629 \r
630   public void parseDescriptionHTML(SequenceFeature sf, boolean removeHTML)\r
631   {\r
632     if (sf.getDescription() == null)\r
633     {\r
634       return;\r
635     }\r
636     jalview.util.ParseHtmlBodyAndLinks parsed = new jalview.util.ParseHtmlBodyAndLinks(sf.getDescription(), removeHTML, newline);\r
637 \r
638     sf.description = (removeHTML) ? parsed.getNonHtmlContent() : sf.description;\r
639     for (String link:parsed.getLinks())\r
640     {\r
641       sf.addLink(link);\r
642     }\r
643 \r
644   }\r
645 \r
646   /**\r
647    * generate a features file for seqs includes non-pos features by default.\r
648    * \r
649    * @param seqs\r
650    *          source of sequence features\r
651    * @param visible\r
652    *          hash of feature types and colours\r
653    * @return features file contents\r
654    */\r
655   public String printJalviewFormat(SequenceI[] seqs, Hashtable visible)\r
656   {\r
657     return printJalviewFormat(seqs, visible, true, true);\r
658   }\r
659 \r
660   /**\r
661    * generate a features file for seqs with colours from visible (if any)\r
662    * \r
663    * @param seqs\r
664    *          source of features\r
665    * @param visible\r
666    *          hash of Colours for each feature type\r
667    * @param visOnly\r
668    *          when true only feature types in 'visible' will be output\r
669    * @param nonpos\r
670    *          indicates if non-positional features should be output (regardless\r
671    *          of group or type)\r
672    * @return features file contents\r
673    */\r
674   public String printJalviewFormat(SequenceI[] seqs, Hashtable visible,\r
675           boolean visOnly, boolean nonpos)\r
676   {\r
677     StringBuffer out = new StringBuffer();\r
678     SequenceFeature[] next;\r
679     boolean featuresGen = false;\r
680     if (visOnly && !nonpos && (visible == null || visible.size() < 1))\r
681     {\r
682       // no point continuing.\r
683       return "No Features Visible";\r
684     }\r
685 \r
686     if (visible != null && visOnly)\r
687     {\r
688       // write feature colours only if we're given them and we are generating\r
689       // viewed features\r
690       // TODO: decide if feature links should also be written here ?\r
691       Enumeration en = visible.keys();\r
692       String type, color;\r
693       while (en.hasMoreElements())\r
694       {\r
695         type = en.nextElement().toString();\r
696 \r
697         if (visible.get(type) instanceof GraduatedColor)\r
698         {\r
699           GraduatedColor gc = (GraduatedColor) visible.get(type);\r
700           color = (gc.isColourByLabel() ? "label|" : "")\r
701                   + Format.getHexString(gc.getMinColor()) + "|"\r
702                   + Format.getHexString(gc.getMaxColor())\r
703                   + (gc.isAutoScale() ? "|" : "|abso|") + gc.getMin() + "|"\r
704                   + gc.getMax() + "|";\r
705           if (gc.getThreshType() != AnnotationColourGradient.NO_THRESHOLD)\r
706           {\r
707             if (gc.getThreshType() == AnnotationColourGradient.BELOW_THRESHOLD)\r
708             {\r
709               color += "below";\r
710             }\r
711             else\r
712             {\r
713               if (gc.getThreshType() != AnnotationColourGradient.ABOVE_THRESHOLD)\r
714               {\r
715                 System.err.println("WARNING: Unsupported threshold type ("\r
716                         + gc.getThreshType() + ") : Assuming 'above'");\r
717               }\r
718               color += "above";\r
719             }\r
720             // add the value\r
721             color += "|" + gc.getThresh();\r
722           }\r
723           else\r
724           {\r
725             color += "none";\r
726           }\r
727         }\r
728         else if (visible.get(type) instanceof java.awt.Color)\r
729         {\r
730           color = Format.getHexString((java.awt.Color) visible.get(type));\r
731         }\r
732         else\r
733         {\r
734           // legacy support for integer objects containing colour triplet values\r
735           color = Format.getHexString(new java.awt.Color(Integer\r
736                   .parseInt(visible.get(type).toString())));\r
737         }\r
738         out.append(type);\r
739         out.append("\t");\r
740         out.append(color);\r
741         out.append(newline);\r
742       }\r
743     }\r
744     // Work out which groups are both present and visible\r
745     Vector groups = new Vector();\r
746     int groupIndex = 0;\r
747     boolean isnonpos = false;\r
748 \r
749     for (int i = 0; i < seqs.length; i++)\r
750     {\r
751       next = seqs[i].getSequenceFeatures();\r
752       if (next != null)\r
753       {\r
754         for (int j = 0; j < next.length; j++)\r
755         {\r
756           isnonpos = next[j].begin == 0 && next[j].end == 0;\r
757           if ((!nonpos && isnonpos)\r
758                   || (!isnonpos && visOnly && !visible\r
759                           .containsKey(next[j].type)))\r
760           {\r
761             continue;\r
762           }\r
763 \r
764           if (next[j].featureGroup != null\r
765                   && !groups.contains(next[j].featureGroup))\r
766           {\r
767             groups.addElement(next[j].featureGroup);\r
768           }\r
769         }\r
770       }\r
771     }\r
772 \r
773     String group = null;\r
774     do\r
775     {\r
776 \r
777       if (groups.size() > 0 && groupIndex < groups.size())\r
778       {\r
779         group = groups.elementAt(groupIndex).toString();\r
780         out.append(newline);\r
781         out.append("STARTGROUP\t");\r
782         out.append(group);\r
783         out.append(newline);\r
784       }\r
785       else\r
786       {\r
787         group = null;\r
788       }\r
789 \r
790       for (int i = 0; i < seqs.length; i++)\r
791       {\r
792         next = seqs[i].getSequenceFeatures();\r
793         if (next != null)\r
794         {\r
795           for (int j = 0; j < next.length; j++)\r
796           {\r
797             isnonpos = next[j].begin == 0 && next[j].end == 0;\r
798             if ((!nonpos && isnonpos)\r
799                     || (!isnonpos && visOnly && !visible\r
800                             .containsKey(next[j].type)))\r
801             {\r
802               // skip if feature is nonpos and we ignore them or if we only\r
803               // output visible and it isn't non-pos and it's not visible\r
804               continue;\r
805             }\r
806 \r
807             if (group != null\r
808                     && (next[j].featureGroup == null || !next[j].featureGroup\r
809                             .equals(group)))\r
810             {\r
811               continue;\r
812             }\r
813 \r
814             if (group == null && next[j].featureGroup != null)\r
815             {\r
816               continue;\r
817             }\r
818             // we have features to output\r
819             featuresGen = true;\r
820             if (next[j].description == null\r
821                     || next[j].description.equals(""))\r
822             {\r
823               out.append(next[j].type + "\t");\r
824             }\r
825             else\r
826             {\r
827               if (next[j].links != null\r
828                       && next[j].getDescription().indexOf("<html>") == -1)\r
829               {\r
830                 out.append("<html>");\r
831               }\r
832 \r
833               out.append(next[j].description + " ");\r
834               if (next[j].links != null)\r
835               {\r
836                 for (int l = 0; l < next[j].links.size(); l++)\r
837                 {\r
838                   String label = next[j].links.elementAt(l).toString();\r
839                   String href = label.substring(label.indexOf("|") + 1);\r
840                   label = label.substring(0, label.indexOf("|"));\r
841 \r
842                   if (next[j].description.indexOf(href) == -1)\r
843                   {\r
844                     out.append("<a href=\"" + href + "\">" + label + "</a>");\r
845                   }\r
846                 }\r
847 \r
848                 if (next[j].getDescription().indexOf("</html>") == -1)\r
849                 {\r
850                   out.append("</html>");\r
851                 }\r
852               }\r
853 \r
854               out.append("\t");\r
855             }\r
856             out.append(seqs[i].getName());\r
857             out.append("\t-1\t");\r
858             out.append(next[j].begin);\r
859             out.append("\t");\r
860             out.append(next[j].end);\r
861             out.append("\t");\r
862             out.append(next[j].type);\r
863             if (next[j].score != Float.NaN)\r
864             {\r
865               out.append("\t");\r
866               out.append(next[j].score);\r
867             }\r
868             out.append(newline);\r
869           }\r
870         }\r
871       }\r
872 \r
873       if (group != null)\r
874       {\r
875         out.append("ENDGROUP\t");\r
876         out.append(group);\r
877         out.append(newline);\r
878         groupIndex++;\r
879       }\r
880       else\r
881       {\r
882         break;\r
883       }\r
884 \r
885     } while (groupIndex < groups.size() + 1);\r
886 \r
887     if (!featuresGen)\r
888     {\r
889       return "No Features Visible";\r
890     }\r
891 \r
892     return out.toString();\r
893   }\r
894 \r
895   /**\r
896    * generate a gff file for sequence features includes non-pos features by\r
897    * default.\r
898    * \r
899    * @param seqs\r
900    * @param visible\r
901    * @return\r
902    */\r
903   public String printGFFFormat(SequenceI[] seqs, Hashtable visible)\r
904   {\r
905     return printGFFFormat(seqs, visible, true, true);\r
906   }\r
907 \r
908   public String printGFFFormat(SequenceI[] seqs, Hashtable visible,\r
909           boolean visOnly, boolean nonpos)\r
910   {\r
911     StringBuffer out = new StringBuffer();\r
912     SequenceFeature[] next;\r
913     String source;\r
914     boolean isnonpos;\r
915     for (int i = 0; i < seqs.length; i++)\r
916     {\r
917       if (seqs[i].getSequenceFeatures() != null)\r
918       {\r
919         next = seqs[i].getSequenceFeatures();\r
920         for (int j = 0; j < next.length; j++)\r
921         {\r
922           isnonpos = next[j].begin == 0 && next[j].end == 0;\r
923           if ((!nonpos && isnonpos)\r
924                   || (!isnonpos && visOnly && !visible\r
925                           .containsKey(next[j].type)))\r
926           {\r
927             continue;\r
928           }\r
929 \r
930           source = next[j].featureGroup;\r
931           if (source == null)\r
932           {\r
933             source = next[j].getDescription();\r
934           }\r
935 \r
936           out.append(seqs[i].getName());\r
937           out.append("\t");\r
938           out.append(source);\r
939           out.append("\t");\r
940           out.append(next[j].type);\r
941           out.append("\t");\r
942           out.append(next[j].begin);\r
943           out.append("\t");\r
944           out.append(next[j].end);\r
945           out.append("\t");\r
946           out.append(next[j].score);\r
947           out.append("\t");\r
948 \r
949           if (next[j].getValue("STRAND") != null)\r
950           {\r
951             out.append(next[j].getValue("STRAND"));\r
952             out.append("\t");\r
953           }\r
954           else\r
955           {\r
956             out.append(".\t");\r
957           }\r
958 \r
959           if (next[j].getValue("FRAME") != null)\r
960           {\r
961             out.append(next[j].getValue("FRAME"));\r
962           }\r
963           else\r
964           {\r
965             out.append(".");\r
966           }\r
967           // TODO: verify/check GFF - should there be a /t here before attribute\r
968           // output ?\r
969 \r
970           if (next[j].getValue("ATTRIBUTES") != null)\r
971           {\r
972             out.append(next[j].getValue("ATTRIBUTES"));\r
973           }\r
974 \r
975           out.append(newline);\r
976 \r
977         }\r
978       }\r
979     }\r
980 \r
981     return out.toString();\r
982   }\r
983 \r
984   /**\r
985    * this is only for the benefit of object polymorphism - method does nothing.\r
986    */\r
987   public void parse()\r
988   {\r
989     // IGNORED\r
990   }\r
991 \r
992   /**\r
993    * this is only for the benefit of object polymorphism - method does nothing.\r
994    * \r
995    * @return error message\r
996    */\r
997   public String print()\r
998   {\r
999     return "USE printGFFFormat() or printJalviewFormat()";\r
1000   }\r
1001 \r
1002 }\r