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