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