FileFormatI further tweaks, clean compile
[jalview.git] / src / jalview / io / IdentifyFile.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  *
5  * This file is part of Jalview.
6  *
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * Jalview is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE.  See the GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io;
22
23 import java.io.IOException;
24
25 /**
26  * DOCUMENT ME!
27  *
28  * @author $author$
29  * @version $Revision$
30  */
31 public class IdentifyFile
32 {
33   public static final String FeaturesFile = "GFF or Jalview features";
34
35   /**
36    * Identify a datasource's file content.
37    *
38    * @note Do not use this method for stream sources - create a FileParse object
39    *       instead.
40    *
41    * @param file
42    * @param sourceType
43    * @return
44    * @throws FileFormatException
45    */
46   public FileFormatI identify(String file, DataSourceType sourceType)
47           throws FileFormatException
48   {
49     String emessage = "UNIDENTIFIED FILE PARSING ERROR";
50     FileParse parser = null;
51     try
52     {
53       parser = new FileParse(file, sourceType);
54       if (parser.isValid())
55       {
56         return identify(parser);
57       }
58     } catch (Exception e)
59     {
60       System.err.println("Error whilst identifying");
61       e.printStackTrace(System.err);
62       emessage = e.getMessage();
63     }
64     if (parser != null)
65     {
66       throw new FileFormatException(parser.errormessage);
67     }
68     throw new FileFormatException(emessage);
69   }
70
71   public FileFormatI identify(FileParse source) throws FileFormatException
72   {
73     return identify(source, true);
74     // preserves original behaviour prior to version 2.3
75   }
76
77   public FileFormatI identify(AlignmentFileI file, boolean closeSource)
78           throws IOException
79   {
80     FileParse fp = new FileParse(file.getInFile(), file.getDataSourceType());
81     return identify(fp, closeSource);
82   }
83
84   /**
85    * Identify contents of source, closing it or resetting source to start
86    * afterwards.
87    *
88    * @param source
89    * @param closeSource
90    * @return (best guess at) file format
91    * @throws FileFormatException
92    */
93   public FileFormatI identify(FileParse source, boolean closeSource)
94           throws FileFormatException
95   {
96     FileFormatI reply = FileFormat.Pfam;
97     String data;
98     int bytesRead = 0;
99     int trimmedLength = 0;
100     boolean lineswereskipped = false;
101     boolean isBinary = false; // true if length is non-zero and non-printable
102     // characters are encountered
103     try
104     {
105       if (!closeSource)
106       {
107         source.mark();
108       }
109       while ((data = source.nextLine()) != null)
110       {
111         bytesRead += data.length();
112         trimmedLength += data.trim().length();
113         if (!lineswereskipped)
114         {
115           for (int i = 0; !isBinary && i < data.length(); i++)
116           {
117             char c = data.charAt(i);
118             isBinary = (c < 32 && c != '\t' && c != '\n' && c != '\r'
119                     && c != 5 && c != 27); // nominal binary character filter
120             // excluding CR, LF, tab,DEL and ^E
121             // for certain blast ids
122           }
123         }
124         if (isBinary)
125         {
126           // jar files are special - since they contain all sorts of random
127           // characters.
128           if (source.inFile != null)
129           {
130             String fileStr = source.inFile.getName();
131             // possibly a Jalview archive.
132             if (fileStr.lastIndexOf(".jar") > -1
133                     || fileStr.lastIndexOf(".zip") > -1)
134             {
135               reply = FileFormat.Jalview;
136             }
137           }
138           if (!lineswereskipped && data.startsWith("PK"))
139           {
140             reply = FileFormat.Jalview; // archive.
141             break;
142           }
143         }
144         data = data.toUpperCase();
145
146         if (data.startsWith("##GFF-VERSION"))
147         {
148           // GFF - possibly embedded in a Jalview features file!
149           reply = FileFormat.Features;
150           break;
151         }
152         if (looksLikeFeatureData(data))
153         {
154           reply = FileFormat.Features;
155           break;
156         }
157         if (data.indexOf("# STOCKHOLM") > -1)
158         {
159           reply = FileFormat.Stockholm;
160           break;
161         }
162         if (data.indexOf("_ENTRY.ID") > -1
163                 || data.indexOf("_AUDIT_AUTHOR.NAME") > -1
164                 || data.indexOf("_ATOM_SITE.") > -1)
165         {
166           reply = FileFormat.MMCif;
167           break;
168         }
169         // if (data.indexOf(">") > -1)
170         if (data.startsWith(">"))
171         {
172           // FASTA, PIR file or BLC file
173           boolean checkPIR = false, starterm = false;
174           if ((data.indexOf(">P1;") > -1) || (data.indexOf(">DL;") > -1))
175           {
176             // watch for PIR file attributes
177             checkPIR = true;
178             reply = FileFormat.PIR;
179           }
180           // could also be BLC file, read next line to confirm
181           data = source.nextLine();
182
183           if (data.indexOf(">") > -1)
184           {
185             reply = FileFormat.BLC;
186           }
187           else
188           {
189             // Is this a single line BLC file?
190             String data1 = source.nextLine();
191             String data2 = source.nextLine();
192             int c1;
193             if (checkPIR)
194             {
195               starterm = (data1 != null && data1.indexOf("*") > -1)
196                       || (data2 != null && data2.indexOf("*") > -1);
197             }
198             if (data2 != null && (c1 = data.indexOf("*")) > -1)
199             {
200               if (c1 == 0 && c1 == data2.indexOf("*"))
201               {
202                 reply = FileFormat.BLC;
203               }
204               else
205               {
206                 reply = FileFormat.Fasta; // possibly a bad choice - may be
207                                           // recognised as
208                 // PIR
209               }
210               // otherwise can still possibly be a PIR file
211             }
212             else
213             {
214               reply = FileFormat.Fasta;
215               // TODO : AMSA File is indicated if there is annotation in the
216               // FASTA file - but FASTA will automatically generate this at the
217               // mo.
218               if (!checkPIR)
219               {
220                 break;
221               }
222             }
223           }
224           // final check for PIR content. require
225           // >P1;title\n<blah>\nterminated sequence to occur at least once.
226
227           // TODO the PIR/fasta ambiguity may be the use case that is needed to
228           // have
229           // a 'Parse as type XXX' parameter for the applet/application.
230           if (checkPIR)
231           {
232             String dta = null;
233             if (!starterm)
234             {
235               do
236               {
237                 try
238                 {
239                   dta = source.nextLine();
240                 } catch (IOException ex)
241                 {
242                 }
243                 if (dta != null && dta.indexOf("*") > -1)
244                 {
245                   starterm = true;
246                 }
247               } while (dta != null && !starterm);
248             }
249             if (starterm)
250             {
251               reply = FileFormat.PIR;
252               break;
253             }
254             else
255             {
256               reply = FileFormat.Fasta; // probably a bad choice!
257             }
258           }
259           // read as a FASTA (probably)
260           break;
261         }
262         int lessThan = data.indexOf("<");
263         if ((lessThan > -1)) // possible Markup Language data i.e HTML,
264                                       // RNAML, XML
265         {
266           String upper = data.toUpperCase();
267           if (upper.substring(lessThan).startsWith("<HTML"))
268           {
269             reply = FileFormat.Html;
270             break;
271           }
272           if (upper.substring(lessThan).startsWith("<RNAML"))
273           {
274             reply = FileFormat.Rnaml;
275             break;
276           }
277         }
278
279         if (data.indexOf("{\"") > -1)
280         {
281           reply = FileFormat.Json;
282           break;
283         }
284         if ((data.length() < 1) || (data.indexOf("#") == 0))
285         {
286           lineswereskipped = true;
287           continue;
288         }
289
290         if (data.indexOf("PILEUP") > -1)
291         {
292           reply = FileFormat.Pileup;
293
294           break;
295         }
296
297         if ((data.indexOf("//") == 0)
298                 || ((data.indexOf("!!") > -1) && (data.indexOf("!!") < data
299                         .indexOf("_MULTIPLE_ALIGNMENT "))))
300         {
301           reply = FileFormat.MSF;
302
303           break;
304         }
305         else if (data.indexOf("CLUSTAL") > -1)
306         {
307           reply = FileFormat.Clustal;
308
309           break;
310         }
311
312         else if (data.indexOf("HEADER") == 0 || data.indexOf("ATOM") == 0)
313         {
314           reply = FileFormat.PDB;
315           break;
316         }
317         else if (data.matches("\\s*\\d+\\s+\\d+\\s*"))
318         {
319           reply = FileFormat.Phylip;
320           break;
321         }
322         else
323         {
324           if (!lineswereskipped && looksLikeJnetData(data))
325           {
326             reply = FileFormat.Jnet;
327             break;
328           }
329         }
330
331         lineswereskipped = true; // this means there was some junk before any
332         // key file signature
333       }
334       if (closeSource)
335       {
336         source.close();
337       }
338       else
339       {
340         source.reset(bytesRead); // so the file can be parsed from the mark
341       }
342     } catch (Exception ex)
343     {
344       System.err.println("File Identification failed!\n" + ex);
345       throw new FileFormatException(source.errormessage);
346     }
347     if (trimmedLength == 0)
348     {
349       System.err
350               .println("File Identification failed! - Empty file was read.");
351       throw new FileFormatException("EMPTY DATA FILE");
352     }
353     System.out.println("File format identified as " + reply.toString());
354     return reply;
355   }
356
357   /**
358    * Returns true if the data appears to be Jnet concise annotation format
359    * 
360    * @param data
361    * @return
362    */
363   protected boolean looksLikeJnetData(String data)
364   {
365     char firstChar = data.charAt(0);
366     int colonPos = data.indexOf(":");
367     int commaPos = data.indexOf(",");
368     boolean isJnet = firstChar != '*' && firstChar != ' ' && colonPos > -1
369             && commaPos > -1 && colonPos < commaPos;
370     // && data.indexOf(",")<data.indexOf(",", data.indexOf(","))) / ??
371     return isJnet;
372   }
373
374   /**
375    * Returns true if the data has at least 6 tab-delimited fields _and_ 
376    * fields 4 and 5 are integer (start/end) 
377    * @param data
378    * @return
379    */
380   protected boolean looksLikeFeatureData(String data)
381   {
382     if (data == null)
383     {
384       return false;
385     }
386     String[] columns = data.split("\t");
387     if (columns.length < 6) {
388       return false;
389     }
390     for (int col = 3; col < 5; col++)
391     {
392       try {
393         Integer.parseInt(columns[col]);
394       } catch (NumberFormatException e) {
395         return false;
396       }
397     }
398     return true;
399   }
400
401   public static void main(String[] args)
402   {
403     for (int i = 0; args != null && i < args.length; i++)
404     {
405       IdentifyFile ider = new IdentifyFile();
406       FileFormatI type = null;
407       try
408       {
409         type = ider.identify(args[i], DataSourceType.FILE);
410       } catch (FileFormatException e)
411       {
412         System.err.println(String.format(
413                 "Error '%s' identifying file type for %s", args[i],
414                 e.getMessage()));
415       }
416       System.out.println("Type of " + args[i] + " is " + type);
417     }
418     if (args == null || args.length == 0)
419     {
420       System.err.println("Usage: <Filename> [<Filename> ...]");
421     }
422   }
423 }