FileParse object can be re-used to read different files concatenated together
[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   public FeaturesFile(FileParse source) throws IOException\r
59   {\r
60     super(source);\r
61   }\r
62 \r
63   /**\r
64    * The Application can render HTML, but the applet will\r
65    * remove HTML tags and replace links with %LINK%\r
66    * Both need to read links in HTML however\r
67    *\r
68    * @throws IOException DOCUMENT ME!\r
69    */\r
70   public boolean parse(AlignmentI align,\r
71                        Hashtable colours,\r
72                        boolean removeHTML)\r
73   {\r
74     return parse(align, colours, null, removeHTML);\r
75   }\r
76 \r
77   /**\r
78    * The Application can render HTML, but the applet will\r
79    * remove HTML tags and replace links with %LINK%\r
80    * Both need to read links in HTML however\r
81    *\r
82    * @throws IOException DOCUMENT ME!\r
83    */\r
84   public boolean parse(AlignmentI align,\r
85                        Hashtable colours,\r
86                        Hashtable featureLink,\r
87                        boolean removeHTML)\r
88   {\r
89     String line = null;\r
90     try\r
91     {\r
92       SequenceI seq = null;\r
93       String type, desc, token = null;\r
94 \r
95       int index, start, end;\r
96       float score;\r
97       StringTokenizer st;\r
98       SequenceFeature sf;\r
99       String featureGroup = null, groupLink = null;\r
100       Hashtable typeLink = new Hashtable();\r
101 \r
102       boolean GFFFile = true;\r
103 \r
104       while ( (line = nextLine()) != null)\r
105       {\r
106         if (line.startsWith("#"))\r
107         {\r
108           continue;\r
109         }\r
110 \r
111         st = new StringTokenizer(line, "\t");\r
112         if (st.countTokens() > 1 && st.countTokens() < 4)\r
113         {\r
114           GFFFile = false;\r
115           type = st.nextToken();\r
116           if (type.equalsIgnoreCase("startgroup"))\r
117           {\r
118             featureGroup = st.nextToken();\r
119             if (st.hasMoreElements())\r
120             {\r
121               groupLink = st.nextToken();\r
122               featureLink.put(featureGroup, groupLink);\r
123             }\r
124           }\r
125           else if (type.equalsIgnoreCase("endgroup"))\r
126           {\r
127             //We should check whether this is the current group,\r
128             //but at present theres no way of showing more than 1 group\r
129             st.nextToken();\r
130             featureGroup = null;\r
131             groupLink = null;\r
132           }\r
133           else\r
134           {\r
135             UserColourScheme ucs = new UserColourScheme(st.nextToken());\r
136             colours.put(type, ucs.findColour('A'));\r
137             if (st.hasMoreElements())\r
138             {\r
139               String link = st.nextToken();\r
140               typeLink.put(type, link);\r
141               if (featureLink == null)\r
142               {\r
143                 featureLink = new Hashtable();\r
144               }\r
145               featureLink.put(type, link);\r
146             }\r
147 \r
148           }\r
149           continue;\r
150         }\r
151 \r
152         while (st.hasMoreElements())\r
153         {\r
154 \r
155           if (GFFFile)\r
156           {\r
157             // Still possible this is an old Jalview file,\r
158             // which does not have type colours at the beginning\r
159             token = st.nextToken();\r
160             seq = align.findName(token, true);\r
161             if (seq != null)\r
162             {\r
163               desc = st.nextToken();\r
164               type = st.nextToken();\r
165               try {\r
166               start = Integer.parseInt(st.nextToken());\r
167               } catch (NumberFormatException ex)\r
168               {\r
169                 start=0;\r
170               }\r
171               try {\r
172                 end = Integer.parseInt(st.nextToken());\r
173               }\r
174               catch (NumberFormatException ex)\r
175               {\r
176                 end=-1;\r
177               } \r
178               try\r
179               {\r
180                 score = new Float(st.nextToken()).floatValue();\r
181               }\r
182               catch (NumberFormatException ex)\r
183               {\r
184                 score = 0;\r
185               }\r
186 \r
187               sf = new SequenceFeature(type, desc, start, end, score, null);\r
188 \r
189               try\r
190               {\r
191                 sf.setValue("STRAND", st.nextToken());\r
192                 sf.setValue("FRAME", st.nextToken());\r
193               }\r
194               catch (Exception ex)\r
195               {}\r
196 \r
197               if (st.hasMoreTokens())\r
198               {\r
199                 StringBuffer attributes = new StringBuffer();\r
200                 while (st.hasMoreTokens())\r
201                 {\r
202                   attributes.append("\t" + st.nextElement());\r
203                 }\r
204                 sf.setValue("ATTRIBUTES", attributes.toString());\r
205               }\r
206 \r
207               seq.addSequenceFeature(sf);\r
208 \r
209               break;\r
210             }\r
211           }\r
212 \r
213           if (GFFFile && seq == null)\r
214           {\r
215             desc = token;\r
216           }\r
217           else\r
218           {\r
219             desc = st.nextToken();\r
220           }\r
221           if (!st.hasMoreTokens())\r
222           {\r
223             System.err.println("DEBUG: Run out of tokens when trying to identify the destination for the feature.. giving up.");\r
224             // in all probability, this isn't a file we understand, so bail quietly.\r
225             return false;\r
226           }\r
227           \r
228           token = st.nextToken();\r
229           \r
230           if (!token.equals("ID_NOT_SPECIFIED"))\r
231           {\r
232             seq = align.findName(token, true);\r
233             st.nextToken();\r
234           }\r
235           else\r
236           {\r
237             try\r
238             {\r
239               index = Integer.parseInt(st.nextToken());\r
240               seq = align.getSequenceAt(index);\r
241             }\r
242             catch (NumberFormatException ex)\r
243             {\r
244               seq = null;\r
245             }\r
246           }\r
247 \r
248           if (seq == null)\r
249           {\r
250             System.out.println("Sequence not found: " + line);\r
251             break;\r
252           }\r
253 \r
254           start = Integer.parseInt(st.nextToken());\r
255           end = Integer.parseInt(st.nextToken());\r
256 \r
257           type = st.nextToken();\r
258 \r
259           if (!colours.containsKey(type))\r
260           {\r
261             // Probably the old style groups file\r
262             UserColourScheme ucs = new UserColourScheme(type);\r
263             colours.put(type, ucs.findColour('A'));\r
264           }\r
265 \r
266           sf = new SequenceFeature(type, desc, "", start, end, featureGroup);\r
267 \r
268           seq.addSequenceFeature(sf);\r
269 \r
270           if (groupLink != null && removeHTML)\r
271           {\r
272             sf.addLink(groupLink);\r
273             sf.description += "%LINK%";\r
274           }\r
275           if (typeLink.containsKey(type) && removeHTML)\r
276           {\r
277             sf.addLink(typeLink.get(type).toString());\r
278             sf.description += "%LINK%";\r
279           }\r
280 \r
281           parseDescriptionHTML(sf, removeHTML);\r
282 \r
283           //If we got here, its not a GFFFile\r
284           GFFFile = false;\r
285         }\r
286       }\r
287     }\r
288     catch (Exception ex)\r
289     {\r
290       System.out.println(line);\r
291       System.out.println("Error parsing feature file: " + ex + "\n" + line);\r
292       ex.printStackTrace(System.err);\r
293       return false;\r
294     }\r
295 \r
296     return true;\r
297   }\r
298 \r
299   public void parseDescriptionHTML(SequenceFeature sf, boolean removeHTML)\r
300   {\r
301     if (sf.getDescription() == null)\r
302     {\r
303       return;\r
304     }\r
305 \r
306     if (removeHTML && sf.getDescription().toUpperCase().indexOf("<HTML>") == -1)\r
307     {\r
308       removeHTML = false;\r
309     }\r
310 \r
311     StringBuffer sb = new StringBuffer();\r
312     StringTokenizer st = new StringTokenizer(sf.getDescription(), "<");\r
313     String token, link;\r
314     int startTag;\r
315     String tag = null;\r
316     while (st.hasMoreElements())\r
317     {\r
318       token = st.nextToken("&>");\r
319       if (token.equalsIgnoreCase("html") || token.startsWith("/"))\r
320       {\r
321         continue;\r
322       }\r
323 \r
324       tag = null;\r
325       startTag = token.indexOf("<");\r
326 \r
327       if (startTag > -1)\r
328       {\r
329         tag = token.substring(startTag + 1);\r
330         token = token.substring(0, startTag);\r
331       }\r
332 \r
333       if (tag != null && tag.toUpperCase().startsWith("A HREF="))\r
334       {\r
335         if (token.length() > 0)\r
336         {\r
337           sb.append(token);\r
338         }\r
339         link = tag.substring(tag.indexOf("\"") + 1, tag.length() - 1);\r
340         String label = st.nextToken("<>");\r
341         sf.addLink(label + "|" + link);\r
342         sb.append(label + "%LINK%");\r
343       }\r
344       else if (tag != null && tag.equalsIgnoreCase("br"))\r
345       {\r
346         sb.append("\n");\r
347       }\r
348       else if (token.startsWith("lt;"))\r
349       {\r
350         sb.append("<" + token.substring(3));\r
351       }\r
352       else if (token.startsWith("gt;"))\r
353       {\r
354         sb.append(">" + token.substring(3));\r
355       }\r
356       else if (token.startsWith("amp;"))\r
357       {\r
358         sb.append("&" + token.substring(4));\r
359       }\r
360       else\r
361       {\r
362         sb.append(token);\r
363       }\r
364     }\r
365 \r
366     if (removeHTML)\r
367     {\r
368       sf.description = sb.toString();\r
369     }\r
370 \r
371   }\r
372 \r
373   /**\r
374    * DOCUMENT ME!\r
375    *\r
376    * @param s DOCUMENT ME!\r
377    * @param len DOCUMENT ME!\r
378    * @param gaps DOCUMENT ME!\r
379    * @param displayId DOCUMENT ME!\r
380    *\r
381    * @return DOCUMENT ME!\r
382    */\r
383   public String printJalviewFormat(SequenceI[] seqs,\r
384                                    Hashtable visible)\r
385   {\r
386     StringBuffer out = new StringBuffer();\r
387     SequenceFeature[] next;\r
388 \r
389     if (visible == null || visible.size() < 1)\r
390     {\r
391       return "No Features Visible";\r
392     }\r
393 \r
394     Enumeration en = visible.keys();\r
395     String type;\r
396     int color;\r
397     while (en.hasMoreElements())\r
398     {\r
399       type = en.nextElement().toString();\r
400       color = Integer.parseInt(visible.get(type).toString());\r
401       out.append(type + "\t"\r
402                  + jalview.util.Format.getHexString(\r
403                      new java.awt.Color(color))\r
404                  + "\n");\r
405     }\r
406 \r
407     //Work out which groups are both present and visible\r
408     Vector groups = new Vector();\r
409     int groupIndex = 0;\r
410 \r
411     for (int i = 0; i < seqs.length; i++)\r
412     {\r
413       next = seqs[i].getSequenceFeatures();\r
414       if (next != null)\r
415       {\r
416         for (int j = 0; j < next.length; j++)\r
417         {\r
418           if (!visible.containsKey(next[j].type))\r
419           {\r
420             continue;\r
421           }\r
422 \r
423           if (next[j].featureGroup != null\r
424               && !groups.contains(next[j].featureGroup))\r
425           {\r
426             groups.addElement(next[j].featureGroup);\r
427           }\r
428         }\r
429       }\r
430     }\r
431 \r
432     String group = null;\r
433 \r
434     do\r
435     {\r
436 \r
437       if (groups.size() > 0 && groupIndex < groups.size())\r
438       {\r
439         group = groups.elementAt(groupIndex).toString();\r
440         out.append("\nSTARTGROUP\t" + group + "\n");\r
441       }\r
442       else\r
443       {\r
444         group = null;\r
445       }\r
446 \r
447       for (int i = 0; i < seqs.length; i++)\r
448       {\r
449         next = seqs[i].getSequenceFeatures();\r
450         if (next != null)\r
451         {\r
452           for (int j = 0; j < next.length; j++)\r
453           {\r
454             if (!visible.containsKey(next[j].type))\r
455             {\r
456               continue;\r
457             }\r
458 \r
459             if (group != null\r
460                 && (next[j].featureGroup == null\r
461                     || !next[j].featureGroup.equals(group))\r
462                 )\r
463             {\r
464               continue;\r
465             }\r
466 \r
467             if (group == null && next[j].featureGroup != null)\r
468             {\r
469               continue;\r
470             }\r
471 \r
472             if (next[j].description == null || next[j].description.equals(""))\r
473             {\r
474               out.append(next[j].type + "\t");\r
475             }\r
476             else\r
477             {\r
478               if (next[j].links != null\r
479                   && next[j].getDescription().indexOf("<html>") == -1)\r
480               {\r
481                 out.append("<html>");\r
482               }\r
483 \r
484               out.append(next[j].description + " ");\r
485               if (next[j].links != null)\r
486               {\r
487                 for (int l = 0; l < next[j].links.size(); l++)\r
488                 {\r
489                   String label = next[j].links.elementAt(l).toString();\r
490                   String href = label.substring(label.indexOf("|") + 1);\r
491                   label = label.substring(0, label.indexOf("|"));\r
492 \r
493                   if (next[j].description.indexOf(href) == -1)\r
494                   {\r
495                     out.append("<a href=\""\r
496                                + href\r
497                                + "\">"\r
498                                + label\r
499                                + "</a>");\r
500                   }\r
501                 }\r
502 \r
503                 if (next[j].getDescription().indexOf("</html>") == -1)\r
504                 {\r
505                   out.append("</html>");\r
506                 }\r
507               }\r
508 \r
509               out.append("\t");\r
510             }\r
511 \r
512             out.append(seqs[i].getName() + "\t-1\t"\r
513                        + next[j].begin + "\t"\r
514                        + next[j].end + "\t"\r
515                        + next[j].type + "\n"\r
516                 );\r
517           }\r
518         }\r
519       }\r
520 \r
521       if (group != null)\r
522       {\r
523         out.append("ENDGROUP\t" + group + "\n");\r
524         groupIndex++;\r
525       }\r
526       else\r
527       {\r
528         break;\r
529       }\r
530 \r
531     }\r
532     while (groupIndex < groups.size() + 1);\r
533 \r
534     return out.toString();\r
535   }\r
536 \r
537   public String printGFFFormat(SequenceI[] seqs, Hashtable visible)\r
538   {\r
539     StringBuffer out = new StringBuffer();\r
540     SequenceFeature[] next;\r
541     String source;\r
542 \r
543     for (int i = 0; i < seqs.length; i++)\r
544     {\r
545       if (seqs[i].getSequenceFeatures() != null)\r
546       {\r
547         next = seqs[i].getSequenceFeatures();\r
548         for (int j = 0; j < next.length; j++)\r
549         {\r
550           if (!visible.containsKey(next[j].type))\r
551           {\r
552             continue;\r
553           }\r
554 \r
555           source = next[j].featureGroup;\r
556           if (source == null)\r
557           {\r
558             source = next[j].getDescription();\r
559           }\r
560 \r
561           out.append(seqs[i].getName() + "\t"\r
562                      + source + "\t"\r
563                      + next[j].type + "\t"\r
564                      + next[j].begin + "\t"\r
565                      + next[j].end + "\t"\r
566                      + next[j].score + "\t"\r
567               );\r
568 \r
569           if (next[j].getValue("STRAND") != null)\r
570           {\r
571             out.append(next[j].getValue("STRAND") + "\t");\r
572           }\r
573           else\r
574           {\r
575             out.append(".\t");\r
576           }\r
577 \r
578           if (next[j].getValue("FRAME") != null)\r
579           {\r
580             out.append(next[j].getValue("FRAME"));\r
581           }\r
582           else\r
583           {\r
584             out.append(".");\r
585           }\r
586 \r
587           if (next[j].getValue("ATTRIBUTES") != null)\r
588           {\r
589             out.append(next[j].getValue("ATTRIBUTES"));\r
590           }\r
591 \r
592           out.append("\n");\r
593 \r
594         }\r
595       }\r
596     }\r
597 \r
598     return out.toString();\r
599   }\r
600 \r
601   public void parse()\r
602   {\r
603     //IGNORED\r
604   }\r
605 \r
606   /**\r
607    * DOCUMENT ME!\r
608    *\r
609    * @return DOCUMENT ME!\r
610    */\r
611   public String print()\r
612   {\r
613     return "USE printGFFFormat() or printJalviewFormat()";\r
614   }\r
615 }\r