JAL-1260 refactored GenBank (and EMBL) flat file parser
[jalview.git] / src / jalview / io / EmblFlatFile.java
1 package jalview.io;
2
3 import java.io.IOException;
4
5 import jalview.bin.Cache;
6 import jalview.datamodel.DBRefEntry;
7 import jalview.util.DBRefUtils;
8
9 /**
10  * A class that provides selective parsing of the EMBL flatfile format.
11  * <p>
12  * The initial implementation is limited to extracting fields used by Jalview
13  * after fetching an EMBL or EMBLCDS entry:
14  * 
15  * <pre>
16  * accession, version, sequence, xref
17  * and (for CDS feature) location, protein_id, product, codon_start, translation
18  * </pre>
19  * 
20  * For a complete parser, it may be best to adopt that provided in
21  * https://github.com/enasequence/sequencetools/tree/master/src/main/java/uk/ac/ebi/embl/flatfile
22  * (but note this has a dependency on the Apache Commons library)
23  * 
24  * @author gmcarstairs
25  * @see ftp://ftp.ebi.ac.uk/pub/databases/ena/sequence/release/doc/usrman.txt
26  * @see ftp://ftp.ebi.ac.uk/pub/databases/embl/doc/FT_current.html
27  */
28 public class EmblFlatFile extends FlatFile
29 {
30   /**
31    * Constructor given a data source and the id of the source database
32    * 
33    * @param fp
34    * @param sourceId
35    * @throws IOException
36    */
37   public EmblFlatFile(FileParse fp, String sourceId) throws IOException
38   {
39     super(fp, sourceId);
40   }
41
42   /**
43    * Parses the flatfile, and if successful, saves as an annotated sequence
44    * which may be retrieved by calling {@code getSequence()}
45    * 
46    * @throws IOException
47    */
48   @Override
49   public void parse() throws IOException
50   {
51     String line = nextLine();
52     while (line != null)
53     {
54       if (line.startsWith("ID"))
55       {
56         line = parseID(line);
57       }
58       else if (line.startsWith("DE"))
59       {
60         line = parseDE(line);
61       }
62       else if (line.startsWith("DR"))
63       {
64         line = parseDR(line);
65       }
66       else if (line.startsWith("SQ"))
67       {
68         line = parseSequence();
69       }
70       else if (line.startsWith("FT"))
71       {
72         line = parseFeature(line.substring(2));
73       }
74       else
75       {
76         line = nextLine();
77       }
78     }
79     buildSequence();
80   }
81
82   /**
83    * Extracts and saves the primary accession and version (SV value) from an ID
84    * line, or null if not found. Returns the next line after the one processed.
85    * 
86    * @param line
87    * @throws IOException
88    */
89   String parseID(String line) throws IOException
90   {
91     String[] tokens = line.substring(2).split(";");
92
93     /*
94      * first is primary accession
95      */
96     String token = tokens[0].trim();
97     if (!token.isEmpty())
98     {
99       this.accession = token;
100     }
101
102     /*
103      * second token is 'SV versionNo'
104      */
105     if (tokens.length > 1)
106     {
107       token = tokens[1].trim();
108       if (token.startsWith("SV"))
109       {
110         String[] bits = token.trim().split(WHITESPACE);
111         this.version = bits[bits.length - 1];
112       }
113     }
114
115     /*
116      * seventh token is 'length BP'
117      */
118     if (tokens.length > 6)
119     {
120       token = tokens[6].trim();
121       String[] bits = token.trim().split(WHITESPACE);
122       try
123       {
124         this.length = Integer.valueOf(bits[0]);
125       } catch (NumberFormatException e)
126       {
127         Cache.log.error("bad length read in flatfile, line: " + line);
128       }
129     }
130
131     return nextLine();
132   }
133
134   /**
135    * Reads sequence description from the first DE line found. Any trailing
136    * period is discarded. If there are multiple DE lines, only the first (short
137    * description) is read, the rest are ignored.
138    * 
139    * @param line
140    * @return
141    * @throws IOException
142    */
143   String parseDE(String line) throws IOException
144   {
145     String desc = line.substring(2).trim();
146     if (desc.endsWith("."))
147     {
148       desc = desc.substring(0, desc.length() - 1);
149     }
150     this.description = desc;
151
152     /*
153      * pass over any additional DE lines
154      */
155     while ((line = nextLine()) != null)
156     {
157       if (!line.startsWith("DE"))
158       {
159         break;
160       }
161     }
162
163     return line;
164   }
165
166   /**
167    * Processes one DR line and saves as a DBRefEntry cross-reference. Returns
168    * the line following the line processed.
169    * 
170    * @param line
171    * @throws IOException
172    */
173   String parseDR(String line) throws IOException
174   {
175     String[] tokens = line.substring(2).split(";");
176     if (tokens.length > 1)
177     {
178       /*
179        * ensure UniProtKB/Swiss-Prot converted to UNIPROT
180        */
181       String db = tokens[0].trim();
182       db = DBRefUtils.getCanonicalName(db);
183       String acc = tokens[1].trim();
184       if (acc.endsWith("."))
185       {
186         acc = acc.substring(0, acc.length() - 1);
187       }
188       String version = "0";
189       if (tokens.length > 2)
190       {
191         String secondaryId = tokens[2].trim();
192         if (!secondaryId.isEmpty())
193         {
194           // todo: is this right? secondary id is not a version number
195           // version = secondaryId;
196         }
197       }
198       this.dbrefs.add(new DBRefEntry(db, version, acc));
199     }
200
201     return nextLine();
202   }
203
204   @Override
205   protected boolean isFeatureContinuationLine(String line)
206   {
207     return line.startsWith("FT    "); // 4 spaces
208   }
209 }