gff parsing when no start or end position is defined.
[jalview.git] / src / jalview / io / FeaturesFile.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  *\r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  *\r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  *\r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.io;\r
20 \r
21 import java.io.*;\r
22 import java.util.*;\r
23 \r
24 import jalview.datamodel.*;\r
25 import jalview.schemes.*;\r
26 \r
27 /**\r
28  * Parse and create Jalview Features files\r
29  * Detects GFF format features files and parses.\r
30  * Does not implement standard print() - call specific printFeatures or printGFF.\r
31  * Uses AlignmentI.findSequence(String id) to find the sequence object for the features annotation - this normally works on an exact match.\r
32  * @author AMW\r
33  * @version $Revision$\r
34  */\r
35 public class FeaturesFile\r
36     extends AlignFile\r
37 {\r
38   /**\r
39    * Creates a new FeaturesFile object.\r
40    */\r
41   public FeaturesFile()\r
42   {\r
43   }\r
44 \r
45   /**\r
46    * Creates a new FeaturesFile object.\r
47    *\r
48    * @param inFile DOCUMENT ME!\r
49    * @param type DOCUMENT ME!\r
50    *\r
51    * @throws IOException DOCUMENT ME!\r
52    */\r
53   public FeaturesFile(String inFile, String type)\r
54       throws IOException\r
55   {\r
56     super(inFile, type);\r
57   }\r
58 \r
59   /**\r
60    * The Application can render HTML, but the applet will\r
61    * remove HTML tags and replace links with %LINK%\r
62    * Both need to read links in HTML however\r
63    *\r
64    * @throws IOException DOCUMENT ME!\r
65    */\r
66   public boolean parse(AlignmentI align,\r
67                        Hashtable colours,\r
68                        boolean removeHTML)\r
69   {\r
70     return parse(align, colours, null, removeHTML);\r
71   }\r
72 \r
73   /**\r
74    * The Application can render HTML, but the applet will\r
75    * remove HTML tags and replace links with %LINK%\r
76    * Both need to read links in HTML however\r
77    *\r
78    * @throws IOException DOCUMENT ME!\r
79    */\r
80   public boolean parse(AlignmentI align,\r
81                        Hashtable colours,\r
82                        Hashtable featureLink,\r
83                        boolean removeHTML)\r
84   {\r
85     String line = null;\r
86     try\r
87     {\r
88       SequenceI seq = null;\r
89       String type, desc, token = null;\r
90 \r
91       int index, start, end;\r
92       float score;\r
93       StringTokenizer st;\r
94       SequenceFeature sf;\r
95       String featureGroup = null, groupLink = null;\r
96       Hashtable typeLink = new Hashtable();\r
97 \r
98       boolean GFFFile = true;\r
99 \r
100       while ( (line = nextLine()) != null)\r
101       {\r
102         if (line.startsWith("#"))\r
103         {\r
104           continue;\r
105         }\r
106 \r
107         st = new StringTokenizer(line, "\t");\r
108         if (st.countTokens() > 1 && st.countTokens() < 4)\r
109         {\r
110           GFFFile = false;\r
111           type = st.nextToken();\r
112           if (type.equalsIgnoreCase("startgroup"))\r
113           {\r
114             featureGroup = st.nextToken();\r
115             if (st.hasMoreElements())\r
116             {\r
117               groupLink = st.nextToken();\r
118               featureLink.put(featureGroup, groupLink);\r
119             }\r
120           }\r
121           else if (type.equalsIgnoreCase("endgroup"))\r
122           {\r
123             //We should check whether this is the current group,\r
124             //but at present theres no way of showing more than 1 group\r
125             st.nextToken();\r
126             featureGroup = null;\r
127             groupLink = null;\r
128           }\r
129           else\r
130           {\r
131             UserColourScheme ucs = new UserColourScheme(st.nextToken());\r
132             colours.put(type, ucs.findColour('A'));\r
133             if (st.hasMoreElements())\r
134             {\r
135               String link = st.nextToken();\r
136               typeLink.put(type, link);\r
137               if (featureLink == null)\r
138               {\r
139                 featureLink = new Hashtable();\r
140               }\r
141               featureLink.put(type, link);\r
142             }\r
143 \r
144           }\r
145           continue;\r
146         }\r
147 \r
148         while (st.hasMoreElements())\r
149         {\r
150 \r
151           if (GFFFile)\r
152           {\r
153             // Still possible this is an old Jalview file,\r
154             // which does not have type colours at the beginning\r
155             token = st.nextToken();\r
156             seq = align.findName(token);\r
157             if (seq != null)\r
158             {\r
159               desc = st.nextToken();\r
160               type = st.nextToken();\r
161               try {\r
162               start = Integer.parseInt(st.nextToken());\r
163               } catch (NumberFormatException ex)\r
164               {\r
165                 start=0;\r
166               }\r
167               try {\r
168                 end = Integer.parseInt(st.nextToken());\r
169               }\r
170               catch (NumberFormatException ex)\r
171               {\r
172                 end=-1;\r
173               } \r
174               try\r
175               {\r
176                 score = new Float(st.nextToken()).floatValue();\r
177               }\r
178               catch (NumberFormatException ex)\r
179               {\r
180                 score = 0;\r
181               }\r
182 \r
183               sf = new SequenceFeature(type, desc, start, end, score, null);\r
184 \r
185               try\r
186               {\r
187                 sf.setValue("STRAND", st.nextToken());\r
188                 sf.setValue("FRAME", st.nextToken());\r
189               }\r
190               catch (Exception ex)\r
191               {}\r
192 \r
193               if (st.hasMoreTokens())\r
194               {\r
195                 StringBuffer attributes = new StringBuffer();\r
196                 while (st.hasMoreTokens())\r
197                 {\r
198                   attributes.append("\t" + st.nextElement());\r
199                 }\r
200                 sf.setValue("ATTRIBUTES", attributes.toString());\r
201               }\r
202 \r
203               seq.addSequenceFeature(sf);\r
204 \r
205               break;\r
206             }\r
207           }\r
208 \r
209           if (GFFFile && seq == null)\r
210           {\r
211             desc = token;\r
212           }\r
213           else\r
214           {\r
215             desc = st.nextToken();\r
216           }\r
217 \r
218           token = st.nextToken();\r
219           if (!token.equals("ID_NOT_SPECIFIED"))\r
220           {\r
221             seq = align.findName(token);\r
222             st.nextToken();\r
223           }\r
224           else\r
225           {\r
226             try\r
227             {\r
228               index = Integer.parseInt(st.nextToken());\r
229               seq = align.getSequenceAt(index);\r
230             }\r
231             catch (NumberFormatException ex)\r
232             {\r
233               seq = null;\r
234             }\r
235           }\r
236 \r
237           if (seq == null)\r
238           {\r
239             System.out.println("Sequence not found: " + line);\r
240             break;\r
241           }\r
242 \r
243           start = Integer.parseInt(st.nextToken());\r
244           end = Integer.parseInt(st.nextToken());\r
245 \r
246           type = st.nextToken();\r
247 \r
248           if (!colours.containsKey(type))\r
249           {\r
250             // Probably the old style groups file\r
251             UserColourScheme ucs = new UserColourScheme(type);\r
252             colours.put(type, ucs.findColour('A'));\r
253           }\r
254 \r
255           sf = new SequenceFeature(type, desc, "", start, end, featureGroup);\r
256 \r
257           seq.addSequenceFeature(sf);\r
258 \r
259           if (groupLink != null && removeHTML)\r
260           {\r
261             sf.addLink(groupLink);\r
262             sf.description += "%LINK%";\r
263           }\r
264           if (typeLink.containsKey(type) && removeHTML)\r
265           {\r
266             sf.addLink(typeLink.get(type).toString());\r
267             sf.description += "%LINK%";\r
268           }\r
269 \r
270           parseDescriptionHTML(sf, removeHTML);\r
271 \r
272           //If we got here, its not a GFFFile\r
273           GFFFile = false;\r
274         }\r
275       }\r
276     }\r
277     catch (Exception ex)\r
278     {\r
279       System.out.println(line);\r
280       System.out.println("Error parsing feature file: " + ex + "\n" + line);\r
281       ex.printStackTrace(System.err);\r
282       return false;\r
283     }\r
284 \r
285     return true;\r
286   }\r
287 \r
288   public void parseDescriptionHTML(SequenceFeature sf, boolean removeHTML)\r
289   {\r
290     if (sf.getDescription() == null)\r
291     {\r
292       return;\r
293     }\r
294 \r
295     if (removeHTML && sf.getDescription().toUpperCase().indexOf("<HTML>") == -1)\r
296     {\r
297       removeHTML = false;\r
298     }\r
299 \r
300     StringBuffer sb = new StringBuffer();\r
301     StringTokenizer st = new StringTokenizer(sf.getDescription(), "<");\r
302     String token, link;\r
303     int startTag;\r
304     String tag = null;\r
305     while (st.hasMoreElements())\r
306     {\r
307       token = st.nextToken("&>");\r
308       if (token.equalsIgnoreCase("html") || token.startsWith("/"))\r
309       {\r
310         continue;\r
311       }\r
312 \r
313       tag = null;\r
314       startTag = token.indexOf("<");\r
315 \r
316       if (startTag > -1)\r
317       {\r
318         tag = token.substring(startTag + 1);\r
319         token = token.substring(0, startTag);\r
320       }\r
321 \r
322       if (tag != null && tag.toUpperCase().startsWith("A HREF="))\r
323       {\r
324         if (token.length() > 0)\r
325         {\r
326           sb.append(token);\r
327         }\r
328         link = tag.substring(tag.indexOf("\"") + 1, tag.length() - 1);\r
329         String label = st.nextToken("<>");\r
330         sf.addLink(label + "|" + link);\r
331         sb.append(label + "%LINK%");\r
332       }\r
333       else if (tag != null && tag.equalsIgnoreCase("br"))\r
334       {\r
335         sb.append("\n");\r
336       }\r
337       else if (token.startsWith("lt;"))\r
338       {\r
339         sb.append("<" + token.substring(3));\r
340       }\r
341       else if (token.startsWith("gt;"))\r
342       {\r
343         sb.append(">" + token.substring(3));\r
344       }\r
345       else if (token.startsWith("amp;"))\r
346       {\r
347         sb.append("&" + token.substring(4));\r
348       }\r
349       else\r
350       {\r
351         sb.append(token);\r
352       }\r
353     }\r
354 \r
355     if (removeHTML)\r
356     {\r
357       sf.description = sb.toString();\r
358     }\r
359 \r
360   }\r
361 \r
362   /**\r
363    * DOCUMENT ME!\r
364    *\r
365    * @param s DOCUMENT ME!\r
366    * @param len DOCUMENT ME!\r
367    * @param gaps DOCUMENT ME!\r
368    * @param displayId DOCUMENT ME!\r
369    *\r
370    * @return DOCUMENT ME!\r
371    */\r
372   public String printJalviewFormat(SequenceI[] seqs,\r
373                                    Hashtable visible)\r
374   {\r
375     StringBuffer out = new StringBuffer();\r
376     SequenceFeature[] next;\r
377 \r
378     if (visible == null || visible.size() < 1)\r
379     {\r
380       return "No Features Visible";\r
381     }\r
382 \r
383     Enumeration en = visible.keys();\r
384     String type;\r
385     int color;\r
386     while (en.hasMoreElements())\r
387     {\r
388       type = en.nextElement().toString();\r
389       color = Integer.parseInt(visible.get(type).toString());\r
390       out.append(type + "\t"\r
391                  + jalview.util.Format.getHexString(\r
392                      new java.awt.Color(color))\r
393                  + "\n");\r
394     }\r
395 \r
396     //Work out which groups are both present and visible\r
397     Vector groups = new Vector();\r
398     int groupIndex = 0;\r
399 \r
400     for (int i = 0; i < seqs.length; i++)\r
401     {\r
402       next = seqs[i].getSequenceFeatures();\r
403       if (next != null)\r
404       {\r
405         for (int j = 0; j < next.length; j++)\r
406         {\r
407           if (!visible.containsKey(next[j].type))\r
408           {\r
409             continue;\r
410           }\r
411 \r
412           if (next[j].featureGroup != null\r
413               && !groups.contains(next[j].featureGroup))\r
414           {\r
415             groups.addElement(next[j].featureGroup);\r
416           }\r
417         }\r
418       }\r
419     }\r
420 \r
421     String group = null;\r
422 \r
423     do\r
424     {\r
425 \r
426       if (groups.size() > 0 && groupIndex < groups.size())\r
427       {\r
428         group = groups.elementAt(groupIndex).toString();\r
429         out.append("\nSTARTGROUP\t" + group + "\n");\r
430       }\r
431       else\r
432       {\r
433         group = null;\r
434       }\r
435 \r
436       for (int i = 0; i < seqs.length; i++)\r
437       {\r
438         next = seqs[i].getSequenceFeatures();\r
439         if (next != null)\r
440         {\r
441           for (int j = 0; j < next.length; j++)\r
442           {\r
443             if (!visible.containsKey(next[j].type))\r
444             {\r
445               continue;\r
446             }\r
447 \r
448             if (group != null\r
449                 && (next[j].featureGroup == null\r
450                     || !next[j].featureGroup.equals(group))\r
451                 )\r
452             {\r
453               continue;\r
454             }\r
455 \r
456             if (group == null && next[j].featureGroup != null)\r
457             {\r
458               continue;\r
459             }\r
460 \r
461             if (next[j].description == null || next[j].description.equals(""))\r
462             {\r
463               out.append(next[j].type + "\t");\r
464             }\r
465             else\r
466             {\r
467               if (next[j].links != null\r
468                   && next[j].getDescription().indexOf("<html>") == -1)\r
469               {\r
470                 out.append("<html>");\r
471               }\r
472 \r
473               out.append(next[j].description + " ");\r
474               if (next[j].links != null)\r
475               {\r
476                 for (int l = 0; l < next[j].links.size(); l++)\r
477                 {\r
478                   String label = next[j].links.elementAt(l).toString();\r
479                   String href = label.substring(label.indexOf("|") + 1);\r
480                   label = label.substring(0, label.indexOf("|"));\r
481 \r
482                   if (next[j].description.indexOf(href) == -1)\r
483                   {\r
484                     out.append("<a href=\""\r
485                                + href\r
486                                + "\">"\r
487                                + label\r
488                                + "</a>");\r
489                   }\r
490                 }\r
491 \r
492                 if (next[j].getDescription().indexOf("</html>") == -1)\r
493                 {\r
494                   out.append("</html>");\r
495                 }\r
496               }\r
497 \r
498               out.append("\t");\r
499             }\r
500 \r
501             out.append(seqs[i].getName() + "\t-1\t"\r
502                        + next[j].begin + "\t"\r
503                        + next[j].end + "\t"\r
504                        + next[j].type + "\n"\r
505                 );\r
506           }\r
507         }\r
508       }\r
509 \r
510       if (group != null)\r
511       {\r
512         out.append("ENDGROUP\t" + group + "\n");\r
513         groupIndex++;\r
514       }\r
515       else\r
516       {\r
517         break;\r
518       }\r
519 \r
520     }\r
521     while (groupIndex < groups.size() + 1);\r
522 \r
523     return out.toString();\r
524   }\r
525 \r
526   public String printGFFFormat(SequenceI[] seqs, Hashtable visible)\r
527   {\r
528     StringBuffer out = new StringBuffer();\r
529     SequenceFeature[] next;\r
530     String source;\r
531 \r
532     for (int i = 0; i < seqs.length; i++)\r
533     {\r
534       if (seqs[i].getSequenceFeatures() != null)\r
535       {\r
536         next = seqs[i].getSequenceFeatures();\r
537         for (int j = 0; j < next.length; j++)\r
538         {\r
539           if (!visible.containsKey(next[j].type))\r
540           {\r
541             continue;\r
542           }\r
543 \r
544           source = next[j].featureGroup;\r
545           if (source == null)\r
546           {\r
547             source = next[j].getDescription();\r
548           }\r
549 \r
550           out.append(seqs[i].getName() + "\t"\r
551                      + source + "\t"\r
552                      + next[j].type + "\t"\r
553                      + next[j].begin + "\t"\r
554                      + next[j].end + "\t"\r
555                      + next[j].score + "\t"\r
556               );\r
557 \r
558           if (next[j].getValue("STRAND") != null)\r
559           {\r
560             out.append(next[j].getValue("STRAND") + "\t");\r
561           }\r
562           else\r
563           {\r
564             out.append(".\t");\r
565           }\r
566 \r
567           if (next[j].getValue("FRAME") != null)\r
568           {\r
569             out.append(next[j].getValue("FRAME"));\r
570           }\r
571           else\r
572           {\r
573             out.append(".");\r
574           }\r
575 \r
576           if (next[j].getValue("ATTRIBUTES") != null)\r
577           {\r
578             out.append(next[j].getValue("ATTRIBUTES"));\r
579           }\r
580 \r
581           out.append("\n");\r
582 \r
583         }\r
584       }\r
585     }\r
586 \r
587     return out.toString();\r
588   }\r
589 \r
590   public void parse()\r
591   {\r
592     //IGNORED\r
593   }\r
594 \r
595   /**\r
596    * DOCUMENT ME!\r
597    *\r
598    * @return DOCUMENT ME!\r
599    */\r
600   public String print()\r
601   {\r
602     return "USE printGFFFormat() or printJalviewFormat()";\r
603   }\r
604 }\r