JAL-1260 refactored GenBank (and EMBL) flat file parser
[jalview.git] / src / jalview / io / GenBankFile.java
1 package jalview.io;
2
3 import java.io.IOException;
4
5 import jalview.bin.Cache;
6
7 /**
8  * A class that provides selective parsing of the GenBank flatfile format.
9  * <p>
10  * The initial implementation is limited to extracting fields used by Jalview
11  * after fetching an EMBL or EMBLCDS entry:
12  * 
13  * <pre>
14  * accession, version, sequence, xref
15  * and (for CDS feature) location, protein_id, product, codon_start, translation
16  * </pre>
17  * 
18  * @author gmcarstairs
19  * @see https://www.ncbi.nlm.nih.gov/Sitemap/samplerecord.html
20  */
21 public class GenBankFile extends FlatFile
22 {
23   private static final String DEFINITION = "DEFINITION";
24
25   /**
26    * Constructor given a data source and the id of the source database
27    * 
28    * @param fp
29    * @param sourceId
30    * @throws IOException
31    */
32   public GenBankFile(FileParse fp, String sourceId) throws IOException
33   {
34     super(fp, sourceId);
35   }
36
37   /**
38    * Parses the flatfile, and if successful, saves as an annotated sequence
39    * which may be retrieved by calling {@code getSequence()}
40    * 
41    * @throws IOException
42    * @see https://www.ncbi.nlm.nih.gov/Sitemap/samplerecord.html
43    */
44   @Override
45   public void parse() throws IOException
46   {
47     String line = nextLine();
48     while (line != null)
49     {
50       if (line.startsWith(DEFINITION))
51       {
52         line = parseDefinition(line);
53       }
54       else if (line.startsWith("ACCESSION"))
55       {
56         this.accession = line.split(WHITESPACE)[1];
57         line = nextLine();
58       }
59       else if (line.startsWith("VERSION"))
60       {
61         line = parseVersion(line);
62       }
63       else if (line.startsWith("ORIGIN"))
64       {
65         line = parseSequence();
66       }
67       else if (line.startsWith("FEATURES"))
68       {
69         line = nextLine();
70         while (line.startsWith(" "))
71         {
72           line = parseFeature(line);
73         }
74       }
75       else
76       {
77         line = nextLine();
78       }
79     }
80     buildSequence();
81   }
82
83   /**
84    * Extracts and saves the primary accession and version (SV value) from an ID
85    * line, or null if not found. Returns the next line after the one processed.
86    * 
87    * @param line
88    * @throws IOException
89    */
90   String parseLocus(String line) throws IOException
91   {
92     String[] tokens = line.substring(2).split(";");
93
94     /*
95      * first is primary accession
96      */
97     String token = tokens[0].trim();
98     if (!token.isEmpty())
99     {
100       this.accession = token;
101     }
102
103     /*
104      * second token is 'SV versionNo'
105      */
106     if (tokens.length > 1)
107     {
108       token = tokens[1].trim();
109       if (token.startsWith("SV"))
110       {
111         String[] bits = token.trim().split(WHITESPACE);
112         this.version = bits[bits.length - 1];
113       }
114     }
115
116     /*
117      * seventh token is 'length BP'
118      */
119     if (tokens.length > 6)
120     {
121       token = tokens[6].trim();
122       String[] bits = token.trim().split(WHITESPACE);
123       try
124       {
125         this.length = Integer.valueOf(bits[0]);
126       } catch (NumberFormatException e)
127       {
128         Cache.log.error("bad length read in flatfile, line: " + line);
129       }
130     }
131
132     return nextLine();
133   }
134
135   /**
136    * Reads sequence description from DEFINITION lines. Any trailing period is
137    * discarded. Returns the next line after the definition line(s).
138    * 
139    * @param line
140    * @return
141    * @throws IOException
142    */
143   String parseDefinition(String line) throws IOException
144   {
145     String desc = line.substring(DEFINITION.length()).trim();
146     if (desc.endsWith("."))
147     {
148       desc = desc.substring(0, desc.length() - 1);
149     }
150
151     /*
152      * pass over any additional DE lines
153      */
154     while ((line = nextLine()) != null)
155     {
156       if (line.startsWith(" "))
157       {
158         // definition continuation line
159         desc += line.trim();
160       }
161       else
162       {
163         break;
164       }
165     }
166     this.description = desc;
167
168     return line;
169   }
170
171   /**
172    * Parses the VERSION line e.g.
173    * 
174    * <pre>
175    * VERSION     X81322.1
176    * </pre>
177    * 
178    * and returns the next line
179    * 
180    * @param line
181    * @throws IOException
182    */
183   String parseVersion(String line) throws IOException
184   {
185     /*
186      * extract version part of <accession>.<version>
187      * https://www.ncbi.nlm.nih.gov/Sitemap/samplerecord.html#VersionB
188      */
189     String[] tokens = line.split(WHITESPACE);
190     if (tokens.length > 1)
191     {
192       tokens = tokens[1].split("\\.");
193       if (tokens.length > 1)
194       {
195         this.version = tokens[1];
196       }
197     }
198
199     return nextLine();
200   }
201
202   @Override
203   protected boolean isFeatureContinuationLine(String line)
204   {
205     return line.startsWith("      "); // 6 spaces
206   }
207 }