added graduated colourscheme support for feature file
[jalview.git] / src / jalview / io / FeaturesFile.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)\r
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  * \r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  * \r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  * \r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.io;\r
20 \r
21 import java.io.*;\r
22 import java.util.*;\r
23 \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    * The Application can render HTML, but the applet will remove HTML tags and\r
75    * replace links with %LINK% Both need to read links in HTML however\r
76    * \r
77    * @throws IOException\r
78    *           DOCUMENT ME!\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);\r
84   }\r
85 \r
86   /**\r
87    * The Application can render HTML, but the applet will remove HTML tags and\r
88    * replace links with %LINK% Both need to read links in HTML however\r
89    * \r
90    * @throws IOException\r
91    *           DOCUMENT ME!\r
92    */\r
93   public boolean parse(AlignmentI align, Hashtable colours,\r
94           Hashtable featureLink, boolean removeHTML)\r
95   {\r
96     String line = null;\r
97     try\r
98     {\r
99       SequenceI seq = null;\r
100       String type, desc, token = null;\r
101 \r
102       int index, start, end;\r
103       float score;\r
104       StringTokenizer st;\r
105       SequenceFeature sf;\r
106       String featureGroup = null, groupLink = null;\r
107       Hashtable typeLink = new Hashtable();\r
108 \r
109       boolean GFFFile = true;\r
110 \r
111       while ((line = nextLine()) != null)\r
112       {\r
113         if (line.startsWith("#"))\r
114         {\r
115           continue;\r
116         }\r
117 \r
118         st = new StringTokenizer(line, "\t");\r
119         if (st.countTokens() > 1 && st.countTokens() < 4)\r
120         {\r
121           GFFFile = false;\r
122           type = st.nextToken();\r
123           if (type.equalsIgnoreCase("startgroup"))\r
124           {\r
125             featureGroup = st.nextToken();\r
126             if (st.hasMoreElements())\r
127             {\r
128               groupLink = st.nextToken();\r
129               featureLink.put(featureGroup, groupLink);\r
130             }\r
131           }\r
132           else if (type.equalsIgnoreCase("endgroup"))\r
133           {\r
134             // We should check whether this is the current group,\r
135             // but at present theres no way of showing more than 1 group\r
136             st.nextToken();\r
137             featureGroup = null;\r
138             groupLink = null;\r
139           }\r
140           else\r
141           {\r
142             Object colour = null;\r
143             String colscheme = st.nextToken();\r
144             if (colscheme.indexOf("|") > -1)\r
145             {\r
146               // Parse '|' separated graduated colourscheme fields:\r
147               // mincolour|maxcolour|[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue\r
148               // first four are required.\r
149               // first two are hexadecimal or word equivalent colours.\r
150               // second two are values parsed as floats.\r
151               // fifth is either 'above','below', or 'none'.\r
152               // sixth is a float value and only required when fifth is either\r
153               // 'above' or 'below'.\r
154               StringTokenizer gcol = new StringTokenizer(colscheme, "|");\r
155               String mincol = gcol.nextToken(), maxcol = gcol.nextToken();\r
156               String abso = gcol\r
157                       .nextToken(), minval, maxval;\r
158               if (abso.toLowerCase().indexOf("abso")!=0) {\r
159                 minval = abso;\r
160                 abso = null;\r
161               } else {\r
162                 minval = gcol.nextToken();\r
163               }\r
164               maxval = gcol.nextToken();\r
165               // set defaults\r
166               int threshtype = AnnotationColourGradient.NO_THRESHOLD;\r
167               float min=Float.MIN_VALUE,max=Float.MAX_VALUE,threshval=Float.NaN;\r
168               try\r
169               {\r
170                 if (minval.length()>0) { \r
171                   min = new Float(minval).floatValue();\r
172                 }\r
173               } catch (Exception e)\r
174               {\r
175                 System.err\r
176                         .println("Couldn't parse the minimum value for graduated colour for type ("\r
177                                 + colscheme + ") - did you misspell 'auto' for the optional automatic colour switch ?");\r
178                 e.printStackTrace();\r
179               }\r
180               try\r
181               {\r
182                 if (maxval.length()>0) { \r
183                   max = new Float(maxval).floatValue();\r
184                 }\r
185               } catch (Exception e)\r
186               {\r
187                 System.err\r
188                 .println("Couldn't parse the maximum value for graduated colour for type ("\r
189                                 + colscheme + ")");\r
190                 e.printStackTrace();\r
191               }\r
192               try\r
193               {\r
194                 colour = new jalview.schemes.GraduatedColor(\r
195                         new UserColourScheme(mincol).findColour('A'),\r
196                         new UserColourScheme(maxcol).findColour('A'),\r
197                         min, max);\r
198               } catch (Exception e)\r
199               {\r
200                 System.err\r
201                         .println("Couldn't parse the graduated colour scheme ("\r
202                                 + colscheme + ")");\r
203                 e.printStackTrace();\r
204               }\r
205               if (colour != null)\r
206               {\r
207                 ((jalview.schemes.GraduatedColor)colour).setAutoScaled(abso==null);\r
208                 // add in any additional parameters\r
209                 String ttype = null, tval = null;\r
210                 if (gcol.hasMoreTokens())\r
211                 {\r
212                   // threshold type and possibly a threshold value\r
213                   ttype = gcol.nextToken();\r
214                   if (ttype.toLowerCase().startsWith("below"))\r
215                   {\r
216                     ((jalview.schemes.GraduatedColor) colour)\r
217                             .setThreshType(AnnotationColourGradient.BELOW_THRESHOLD);\r
218                   }\r
219                   else if (ttype.toLowerCase().startsWith("above"))\r
220                   {\r
221                     ((jalview.schemes.GraduatedColor) colour)\r
222                             .setThreshType(AnnotationColourGradient.ABOVE_THRESHOLD);\r
223                   }\r
224                   else\r
225                   {\r
226                     ((jalview.schemes.GraduatedColor) colour)\r
227                             .setThreshType(AnnotationColourGradient.NO_THRESHOLD);\r
228                     if (!ttype.toLowerCase().startsWith("no"))\r
229                     {\r
230                       System.err\r
231                               .println("Ignoring unrecognised threshold type : "\r
232                                       + ttype);\r
233                     }\r
234                   }\r
235                 }\r
236                 if (((GraduatedColor)colour).getThreshType()!=AnnotationColourGradient.NO_THRESHOLD)\r
237                 {\r
238                   tval = gcol.nextToken();\r
239                   try\r
240                   {\r
241                     ((jalview.schemes.GraduatedColor) colour)\r
242                             .setThresh(new Float(tval).floatValue());\r
243                   } catch (Exception e)\r
244                   {\r
245                     System.err\r
246                             .println("Couldn't parse threshold value as a float: ("\r
247                                     + tval + ")");\r
248                     e.printStackTrace();\r
249                   }\r
250                 }\r
251                 // parse the thresh-is-min token ?\r
252                 if (gcol.hasMoreTokens())\r
253                 {\r
254                   System.err\r
255                           .println("Ignoring additional tokens in parameters in graduated colour specification\n");\r
256                   while (gcol.hasMoreTokens())\r
257                   {\r
258                     System.err.println("|" + gcol);\r
259                   }\r
260                   System.err.println("\n");\r
261                 }\r
262               }\r
263             }\r
264             else\r
265             {\r
266               UserColourScheme ucs = new UserColourScheme(colscheme);\r
267               colour = ucs.findColour('A');\r
268             }\r
269             if (colour != null)\r
270             {\r
271               colours.put(type, colour);\r
272             }\r
273             if (st.hasMoreElements())\r
274             {\r
275               String link = st.nextToken();\r
276               typeLink.put(type, link);\r
277               if (featureLink == null)\r
278               {\r
279                 featureLink = new Hashtable();\r
280               }\r
281               featureLink.put(type, link);\r
282             }\r
283           }\r
284           continue;\r
285         }\r
286         String seqId = "";\r
287         while (st.hasMoreElements())\r
288         {\r
289 \r
290           if (GFFFile)\r
291           {\r
292             // Still possible this is an old Jalview file,\r
293             // which does not have type colours at the beginning\r
294             seqId = token = st.nextToken();\r
295             seq = align.findName(seqId, true);\r
296             if (seq != null)\r
297             {\r
298               desc = st.nextToken();\r
299               String group = null;\r
300               if (doGffSource && desc.indexOf(' ') == -1)\r
301               {\r
302                 // could also be a source term rather than description line\r
303                 group = new String(desc);\r
304               }\r
305               type = st.nextToken();\r
306               try\r
307               {\r
308                 String stt = st.nextToken();\r
309                 if (stt.length() == 0 || stt.equals("-"))\r
310                 {\r
311                   start = 0;\r
312                 }\r
313                 else\r
314                 {\r
315                   start = Integer.parseInt(stt);\r
316                 }\r
317               } catch (NumberFormatException ex)\r
318               {\r
319                 start = 0;\r
320               }\r
321               try\r
322               {\r
323                 String stt = st.nextToken();\r
324                 if (stt.length() == 0 || stt.equals("-"))\r
325                 {\r
326                   end = 0;\r
327                 }\r
328                 else\r
329                 {\r
330                   end = Integer.parseInt(stt);\r
331                 }\r
332               } catch (NumberFormatException ex)\r
333               {\r
334                 end = 0;\r
335               }\r
336               // TODO: decide if non positional feature assertion for input data\r
337               // where end==0 is generally valid\r
338               if (end == 0)\r
339               {\r
340                 // treat as non-positional feature, regardless.\r
341                 start = 0;\r
342               }\r
343               try\r
344               {\r
345                 score = new Float(st.nextToken()).floatValue();\r
346               } catch (NumberFormatException ex)\r
347               {\r
348                 score = 0;\r
349               }\r
350 \r
351               sf = new SequenceFeature(type, desc, start, end, score, group);\r
352 \r
353               try\r
354               {\r
355                 sf.setValue("STRAND", st.nextToken());\r
356                 sf.setValue("FRAME", st.nextToken());\r
357               } catch (Exception ex)\r
358               {\r
359               }\r
360 \r
361               if (st.hasMoreTokens())\r
362               {\r
363                 StringBuffer attributes = new StringBuffer();\r
364                 while (st.hasMoreTokens())\r
365                 {\r
366                   attributes.append("\t" + st.nextElement());\r
367                 }\r
368                 // TODO validate and split GFF2 attributes field ? parse out\r
369                 // ([A-Za-z][A-Za-z0-9_]*) <value> ; and add as\r
370                 // sf.setValue(attrib, val);\r
371                 sf.setValue("ATTRIBUTES", attributes.toString());\r
372               }\r
373 \r
374               seq.addSequenceFeature(sf);\r
375               while ((seq = align.findName(seq, seqId, true)) != null)\r
376               {\r
377                 seq.addSequenceFeature(new SequenceFeature(sf));\r
378               }\r
379               break;\r
380             }\r
381           }\r
382 \r
383           if (GFFFile && seq == null)\r
384           {\r
385             desc = token;\r
386           }\r
387           else\r
388           {\r
389             desc = st.nextToken();\r
390           }\r
391           if (!st.hasMoreTokens())\r
392           {\r
393             System.err\r
394                     .println("DEBUG: Run out of tokens when trying to identify the destination for the feature.. giving up.");\r
395             // in all probability, this isn't a file we understand, so bail\r
396             // quietly.\r
397             return false;\r
398           }\r
399 \r
400           token = st.nextToken();\r
401 \r
402           if (!token.equals("ID_NOT_SPECIFIED"))\r
403           {\r
404             seq = align.findName(seqId = token, true);\r
405             st.nextToken();\r
406           }\r
407           else\r
408           {\r
409             seqId = null;\r
410             try\r
411             {\r
412               index = Integer.parseInt(st.nextToken());\r
413               seq = align.getSequenceAt(index);\r
414             } catch (NumberFormatException ex)\r
415             {\r
416               seq = null;\r
417             }\r
418           }\r
419 \r
420           if (seq == null)\r
421           {\r
422             System.out.println("Sequence not found: " + line);\r
423             break;\r
424           }\r
425 \r
426           start = Integer.parseInt(st.nextToken());\r
427           end = Integer.parseInt(st.nextToken());\r
428 \r
429           type = st.nextToken();\r
430 \r
431           if (!colours.containsKey(type))\r
432           {\r
433             // Probably the old style groups file\r
434             UserColourScheme ucs = new UserColourScheme(type);\r
435             colours.put(type, ucs.findColour('A'));\r
436           }\r
437           sf = new SequenceFeature(type, desc, "", start, end, featureGroup);\r
438           if (st.hasMoreTokens())\r
439           {\r
440             try\r
441             {\r
442               score = new Float(st.nextToken()).floatValue();\r
443               // update colourgradient bounds if allowed to \r
444             } catch (NumberFormatException ex)\r
445             {\r
446               score = 0;\r
447             }\r
448             sf.setScore(score);\r
449           }\r
450           if (groupLink != null && removeHTML)\r
451           {\r
452             sf.addLink(groupLink);\r
453             sf.description += "%LINK%";\r
454           }\r
455           if (typeLink.containsKey(type) && removeHTML)\r
456           {\r
457             sf.addLink(typeLink.get(type).toString());\r
458             sf.description += "%LINK%";\r
459           }\r
460 \r
461           parseDescriptionHTML(sf, removeHTML);\r
462 \r
463           seq.addSequenceFeature(sf);\r
464 \r
465           while (seqId != null\r
466                   && (seq = align.findName(seq, seqId, false)) != null)\r
467           {\r
468             seq.addSequenceFeature(new SequenceFeature(sf));\r
469           }\r
470           // If we got here, its not a GFFFile\r
471           GFFFile = false;\r
472         }\r
473       }\r
474     } catch (Exception ex)\r
475     {\r
476       System.out.println(line);\r
477       System.out.println("Error parsing feature file: " + ex + "\n" + line);\r
478       ex.printStackTrace(System.err);\r
479       return false;\r
480     }\r
481 \r
482     return true;\r
483   }\r
484 \r
485   public void parseDescriptionHTML(SequenceFeature sf, boolean removeHTML)\r
486   {\r
487     if (sf.getDescription() == null)\r
488     {\r
489       return;\r
490     }\r
491 \r
492     if (removeHTML\r
493             && sf.getDescription().toUpperCase().indexOf("<HTML>") == -1)\r
494     {\r
495       removeHTML = false;\r
496     }\r
497 \r
498     StringBuffer sb = new StringBuffer();\r
499     StringTokenizer st = new StringTokenizer(sf.getDescription(), "<");\r
500     String token, link;\r
501     int startTag;\r
502     String tag = null;\r
503     while (st.hasMoreElements())\r
504     {\r
505       token = st.nextToken("&>");\r
506       if (token.equalsIgnoreCase("html") || token.startsWith("/"))\r
507       {\r
508         continue;\r
509       }\r
510 \r
511       tag = null;\r
512       startTag = token.indexOf("<");\r
513 \r
514       if (startTag > -1)\r
515       {\r
516         tag = token.substring(startTag + 1);\r
517         token = token.substring(0, startTag);\r
518       }\r
519 \r
520       if (tag != null && tag.toUpperCase().startsWith("A HREF="))\r
521       {\r
522         if (token.length() > 0)\r
523         {\r
524           sb.append(token);\r
525         }\r
526         link = tag.substring(tag.indexOf("\"") + 1, tag.length() - 1);\r
527         String label = st.nextToken("<>");\r
528         sf.addLink(label + "|" + link);\r
529         sb.append(label + "%LINK%");\r
530       }\r
531       else if (tag != null && tag.equalsIgnoreCase("br"))\r
532       {\r
533         sb.append("\n");\r
534       }\r
535       else if (token.startsWith("lt;"))\r
536       {\r
537         sb.append("<" + token.substring(3));\r
538       }\r
539       else if (token.startsWith("gt;"))\r
540       {\r
541         sb.append(">" + token.substring(3));\r
542       }\r
543       else if (token.startsWith("amp;"))\r
544       {\r
545         sb.append("&" + token.substring(4));\r
546       }\r
547       else\r
548       {\r
549         sb.append(token);\r
550       }\r
551     }\r
552 \r
553     if (removeHTML)\r
554     {\r
555       sf.description = sb.toString();\r
556     }\r
557 \r
558   }\r
559 \r
560   /**\r
561    * generate a features file for seqs\r
562    * \r
563    * @param seqs\r
564    *          source of sequence features\r
565    * @param visible\r
566    *          hash of feature types and colours\r
567    * @return features file contents\r
568    */\r
569   public String printJalviewFormat(SequenceI[] seqs, Hashtable visible)\r
570   {\r
571     return printJalviewFormat(seqs, visible, true);\r
572   }\r
573 \r
574   /**\r
575    * generate a features file for seqs with colours from visible (if any)\r
576    * \r
577    * @param seqs\r
578    *          source of features\r
579    * @param visible\r
580    *          hash of Colours for each feature type\r
581    * @param visOnly\r
582    *          when true only feature types in 'visible' will be output\r
583    * @return features file contents\r
584    */\r
585   public String printJalviewFormat(SequenceI[] seqs, Hashtable visible,\r
586           boolean visOnly)\r
587   {\r
588     StringBuffer out = new StringBuffer();\r
589     SequenceFeature[] next;\r
590 \r
591     if (visOnly && (visible == null || visible.size() < 1))\r
592     {\r
593       return "No Features Visible";\r
594     }\r
595     if (visible != null && visOnly)\r
596     {\r
597       // write feature colours only if we're given them and we are generating\r
598       // viewed features\r
599       // TODO: decide if feature links should also be written here ?\r
600       Enumeration en = visible.keys();\r
601       String type, color;\r
602       while (en.hasMoreElements())\r
603       {\r
604         type = en.nextElement().toString();\r
605 \r
606         if (visible.get(type) instanceof GraduatedColor)\r
607         {\r
608           GraduatedColor gc = (GraduatedColor) visible.get(type);\r
609           color = Format.getHexString(gc.getMinColor()) + "|"\r
610                   + Format.getHexString(gc.getMaxColor()) +\r
611                   (gc.isAutoScale() ? "|" : "|abso|")+ gc.getMin() + "|" + gc.getMax() + "|";\r
612           if (gc.getThreshType() != AnnotationColourGradient.NO_THRESHOLD)\r
613           {\r
614             if (gc.getThreshType() == AnnotationColourGradient.BELOW_THRESHOLD)\r
615             {\r
616               color += "below";\r
617             }\r
618             else\r
619             {\r
620               if (gc.getThreshType() != AnnotationColourGradient.ABOVE_THRESHOLD)\r
621               {\r
622                 System.err.println("WARNING: Unsupported threshold type ("\r
623                         + gc.getThreshType() + ") : Assuming 'above'");\r
624               }\r
625               color += "above";\r
626             }\r
627             // add the value\r
628             color += "|" + gc.getThresh();\r
629           }\r
630           else\r
631           {\r
632             color += "none";\r
633           }\r
634         }\r
635         else\r
636           if (visible.get(type) instanceof java.awt.Color) {\r
637             color = Format.getHexString((java.awt.Color)visible.get(type));\r
638           } else {\r
639             // legacy support for integer objects containing colour triplet values\r
640           color = Format.getHexString(new java.awt.Color(Integer\r
641                   .parseInt(visible.get(type).toString())));\r
642         }\r
643         out.append(type + "\t" + color + "\n");\r
644       }\r
645     }\r
646     // Work out which groups are both present and visible\r
647     Vector groups = new Vector();\r
648     int groupIndex = 0;\r
649 \r
650     for (int i = 0; i < seqs.length; i++)\r
651     {\r
652       next = seqs[i].getSequenceFeatures();\r
653       if (next != null)\r
654       {\r
655         for (int j = 0; j < next.length; j++)\r
656         {\r
657           if (visOnly && !visible.containsKey(next[j].type))\r
658           {\r
659             continue;\r
660           }\r
661 \r
662           if (next[j].featureGroup != null\r
663                   && !groups.contains(next[j].featureGroup))\r
664           {\r
665             groups.addElement(next[j].featureGroup);\r
666           }\r
667         }\r
668       }\r
669     }\r
670 \r
671     String group = null;\r
672 \r
673     do\r
674     {\r
675 \r
676       if (groups.size() > 0 && groupIndex < groups.size())\r
677       {\r
678         group = groups.elementAt(groupIndex).toString();\r
679         out.append("\nSTARTGROUP\t" + group + "\n");\r
680       }\r
681       else\r
682       {\r
683         group = null;\r
684       }\r
685 \r
686       for (int i = 0; i < seqs.length; i++)\r
687       {\r
688         next = seqs[i].getSequenceFeatures();\r
689         if (next != null)\r
690         {\r
691           for (int j = 0; j < next.length; j++)\r
692           {\r
693             if (visOnly && !visible.containsKey(next[j].type))\r
694             {\r
695               continue;\r
696             }\r
697 \r
698             if (group != null\r
699                     && (next[j].featureGroup == null || !next[j].featureGroup\r
700                             .equals(group)))\r
701             {\r
702               continue;\r
703             }\r
704 \r
705             if (group == null && next[j].featureGroup != null)\r
706             {\r
707               continue;\r
708             }\r
709 \r
710             if (next[j].description == null\r
711                     || next[j].description.equals(""))\r
712             {\r
713               out.append(next[j].type + "\t");\r
714             }\r
715             else\r
716             {\r
717               if (next[j].links != null\r
718                       && next[j].getDescription().indexOf("<html>") == -1)\r
719               {\r
720                 out.append("<html>");\r
721               }\r
722 \r
723               out.append(next[j].description + " ");\r
724               if (next[j].links != null)\r
725               {\r
726                 for (int l = 0; l < next[j].links.size(); l++)\r
727                 {\r
728                   String label = next[j].links.elementAt(l).toString();\r
729                   String href = label.substring(label.indexOf("|") + 1);\r
730                   label = label.substring(0, label.indexOf("|"));\r
731 \r
732                   if (next[j].description.indexOf(href) == -1)\r
733                   {\r
734                     out\r
735                             .append("<a href=\"" + href + "\">" + label\r
736                                     + "</a>");\r
737                   }\r
738                 }\r
739 \r
740                 if (next[j].getDescription().indexOf("</html>") == -1)\r
741                 {\r
742                   out.append("</html>");\r
743                 }\r
744               }\r
745 \r
746               out.append("\t");\r
747             }\r
748 \r
749             out.append(seqs[i].getName() + "\t-1\t" + next[j].begin + "\t"\r
750                     + next[j].end + "\t" + next[j].type + ((visible.get(next[j].type) instanceof GraduatedColor) ? "\t"+next[j].score+"\n" : "\n"));\r
751           }\r
752         }\r
753       }\r
754 \r
755       if (group != null)\r
756       {\r
757         out.append("ENDGROUP\t" + group + "\n");\r
758         groupIndex++;\r
759       }\r
760       else\r
761       {\r
762         break;\r
763       }\r
764 \r
765     } while (groupIndex < groups.size() + 1);\r
766 \r
767     return out.toString();\r
768   }\r
769 \r
770   public String printGFFFormat(SequenceI[] seqs, Hashtable visible)\r
771   {\r
772     return printGFFFormat(seqs, visible, true);\r
773   }\r
774 \r
775   public String printGFFFormat(SequenceI[] seqs, Hashtable visible,\r
776           boolean visOnly)\r
777   {\r
778     StringBuffer out = new StringBuffer();\r
779     SequenceFeature[] next;\r
780     String source;\r
781 \r
782     for (int i = 0; i < seqs.length; i++)\r
783     {\r
784       if (seqs[i].getSequenceFeatures() != null)\r
785       {\r
786         next = seqs[i].getSequenceFeatures();\r
787         for (int j = 0; j < next.length; j++)\r
788         {\r
789           if (visOnly && visible != null\r
790                   && !visible.containsKey(next[j].type))\r
791           {\r
792             continue;\r
793           }\r
794 \r
795           source = next[j].featureGroup;\r
796           if (source == null)\r
797           {\r
798             source = next[j].getDescription();\r
799           }\r
800 \r
801           out.append(seqs[i].getName() + "\t" + source + "\t"\r
802                   + next[j].type + "\t" + next[j].begin + "\t"\r
803                   + next[j].end + "\t" + next[j].score + "\t");\r
804 \r
805           if (next[j].getValue("STRAND") != null)\r
806           {\r
807             out.append(next[j].getValue("STRAND") + "\t");\r
808           }\r
809           else\r
810           {\r
811             out.append(".\t");\r
812           }\r
813 \r
814           if (next[j].getValue("FRAME") != null)\r
815           {\r
816             out.append(next[j].getValue("FRAME"));\r
817           }\r
818           else\r
819           {\r
820             out.append(".");\r
821           }\r
822 \r
823           if (next[j].getValue("ATTRIBUTES") != null)\r
824           {\r
825             out.append(next[j].getValue("ATTRIBUTES"));\r
826           }\r
827 \r
828           out.append("\n");\r
829 \r
830         }\r
831       }\r
832     }\r
833 \r
834     return out.toString();\r
835   }\r
836 \r
837   /**\r
838    * this is only for the benefit of object polymorphism - method does nothing.\r
839    */\r
840   public void parse()\r
841   {\r
842     // IGNORED\r
843   }\r
844 \r
845   /**\r
846    * this is only for the benefit of object polymorphism - method does nothing.\r
847    * \r
848    * @return error message\r
849    */\r
850   public String print()\r
851   {\r
852     return "USE printGFFFormat() or printJalviewFormat()";\r
853   }\r
854 }\r