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