JAL-1953 2.11.2 with Archeopteryx!
[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.util.Locale;
24
25 import java.io.File;
26 import java.io.IOException;
27
28 /**
29  * DOCUMENT ME!
30  *
31  * @author $author$
32  * @version $Revision$
33  */
34 public class IdentifyFile
35 {
36
37   public FileFormatI identify(Object file, DataSourceType protocol)
38           throws FileFormatException
39   {
40     // BH 2018
41     return (file instanceof File ? identify((File) file, protocol)
42             : identify((String) file, protocol));
43
44   }
45
46   public FileFormatI identify(File file, DataSourceType sourceType)
47           throws FileFormatException
48   {
49     // BH 2018
50     String emessage = "UNIDENTIFIED FILE PARSING ERROR";
51     FileParse parser = null;
52     try
53     {
54       parser = new FileParse(file, sourceType);
55       if (parser.isValid())
56       {
57         return identify(parser);
58       }
59     } catch (Exception e)
60     {
61       System.err.println("Error whilst identifying " + file);
62       e.printStackTrace(System.err);
63       emessage = e.getMessage();
64     }
65     if (parser != null)
66     {
67       throw new FileFormatException(parser.errormessage);
68     }
69     throw new FileFormatException(emessage);
70   }
71
72   /**
73    * Identify a datasource's file content.
74    *
75    * @note Do not use this method for stream sources - create a FileParse object
76    *       instead.
77    *
78    * @param file
79    * @param sourceType
80    * @return
81    * @throws FileFormatException
82    */
83   public FileFormatI identify(String file, DataSourceType sourceType)
84           throws FileFormatException
85   {
86     String emessage = "UNIDENTIFIED FILE PARSING ERROR";
87     FileParse parser = null;
88     try
89     {
90       parser = new FileParse(file, sourceType);
91       if (parser.isValid())
92       {
93         return identify(parser);
94       }
95     } catch (Exception e)
96     {
97       System.err.println("Error whilst identifying " + file);
98       e.printStackTrace(System.err);
99       emessage = e.getMessage();
100     }
101     if (parser != null)
102     {
103       throw new FileFormatException(parser.errormessage);
104     }
105     throw new FileFormatException(emessage);
106   }
107
108   public FileFormatI identify(FileParse source) throws FileFormatException
109   {
110     return identify(source, true);
111     // preserves original behaviour prior to version 2.3
112   }
113
114   public FileFormatI identify(AlignmentFileReaderI file,
115           boolean closeSource) throws IOException
116   {
117     FileParse fp = new FileParse(file.getInFile(),
118             file.getDataSourceType());
119     return identify(fp, closeSource);
120   }
121
122   /**
123    * Identify contents of source, closing it or resetting source to start
124    * afterwards.
125    *
126    * @param source
127    * @param closeSource
128    * @return (best guess at) file format
129    * @throws FileFormatException
130    */
131   public FileFormatI identify(FileParse source, boolean closeSource)
132           throws FileFormatException
133   {
134     FileFormatI reply = FileFormat.Pfam;
135     String data;
136     int bytesRead = 0;
137     int trimmedLength = 0;
138     boolean lineswereskipped = false;
139     boolean isBinary = false; // true if length is non-zero and non-printable
140     // characters are encountered
141
142     try
143     {
144       if (!closeSource)
145       {
146         source.mark();
147       }
148       boolean aaIndexHeaderRead = false;
149
150       while ((data = source.nextLine()) != null)
151       {
152         bytesRead += data.length();
153         trimmedLength += data.trim().length();
154         if (!lineswereskipped)
155         {
156           for (int i = 0; !isBinary && i < data.length(); i++)
157           {
158             char c = data.charAt(i);
159             isBinary = (c < 32 && c != '\t' && c != '\n' && c != '\r'
160                     && c != 5 && c != 27); // nominal binary character filter
161             // excluding CR, LF, tab,DEL and ^E
162             // for certain blast ids
163           }
164         }
165         if (isBinary)
166         {
167           // jar files are special - since they contain all sorts of random
168           // characters.
169           if (source.inFile != null)
170           {
171             String fileStr = source.inFile.getName();
172             if (fileStr.contains(".jar") || fileStr.contains(".zip")
173                     || fileStr.contains(".jvp"))
174             {
175               // possibly a Jalview archive (but check further)
176               reply = FileFormat.Jalview;
177             }
178           }
179           if (!lineswereskipped && data.startsWith("PK"))
180           {
181             reply = FileFormat.Jalview; // archive
182             break;
183           }
184         }
185         data = data.toUpperCase(Locale.ROOT);
186
187         if (data.startsWith(ScoreMatrixFile.SCOREMATRIX))
188         {
189           reply = FileFormat.ScoreMatrix;
190           break;
191         }
192         if (data.startsWith("LOCUS"))
193         {
194           reply = FileFormat.GenBank;
195           break;
196         }
197         if (data.startsWith("ID "))
198         {
199           if (data.substring(2).trim().split(";").length == 7)
200           {
201             reply = FileFormat.Embl;
202             break;
203           }
204         }
205         if (data.startsWith("H ") && !aaIndexHeaderRead)
206         {
207           aaIndexHeaderRead = true;
208         }
209         if (data.startsWith("D ") && aaIndexHeaderRead)
210         {
211           reply = FileFormat.ScoreMatrix;
212           break;
213         }
214         if (data.startsWith("##GFF-VERSION"))
215         {
216           // GFF - possibly embedded in a Jalview features file!
217           reply = FileFormat.Features;
218           break;
219         }
220         if (looksLikeFeatureData(data))
221         {
222           reply = FileFormat.Features;
223           break;
224         }
225         if (data.indexOf("# STOCKHOLM") > -1)
226         {
227           reply = FileFormat.Stockholm;
228           break;
229         }
230         if (data.indexOf("_ENTRY.ID") > -1
231                 || data.indexOf("_AUDIT_AUTHOR.NAME") > -1
232                 || data.indexOf("_ATOM_SITE.") > -1)
233         {
234           reply = FileFormat.MMCif;
235           break;
236         }
237         // if (data.indexOf(">") > -1)
238         if (data.startsWith(">"))
239         {
240           // FASTA, PIR file or BLC file
241           boolean checkPIR = false, starterm = false;
242           if ((data.indexOf(">P1;") > -1) || (data.indexOf(">DL;") > -1))
243           {
244             // watch for PIR file attributes
245             checkPIR = true;
246             reply = FileFormat.PIR;
247           }
248           // could also be BLC file, read next line to confirm
249           data = source.nextLine();
250
251           if (data.indexOf(">") > -1)
252           {
253             reply = FileFormat.BLC;
254           }
255           else
256           {
257             // Is this a single line BLC file?
258             String data1 = source.nextLine();
259             String data2 = source.nextLine();
260             int c1;
261             if (checkPIR)
262             {
263               starterm = (data1 != null && data1.indexOf("*") > -1)
264                       || (data2 != null && data2.indexOf("*") > -1);
265             }
266             if (data2 != null && (c1 = data.indexOf("*")) > -1)
267             {
268               if (c1 == 0 && c1 == data2.indexOf("*"))
269               {
270                 reply = FileFormat.BLC;
271               }
272               else
273               {
274                 reply = FileFormat.Fasta; // possibly a bad choice - may be
275                                           // recognised as
276                 // PIR
277               }
278               // otherwise can still possibly be a PIR file
279             }
280             else
281             {
282               reply = FileFormat.Fasta;
283               // TODO : AMSA File is indicated if there is annotation in the
284               // FASTA file - but FASTA will automatically generate this at the
285               // mo.
286               if (!checkPIR)
287               {
288                 break;
289               }
290             }
291           }
292           // final check for PIR content. require
293           // >P1;title\n<blah>\nterminated sequence to occur at least once.
294
295           // TODO the PIR/fasta ambiguity may be the use case that is needed to
296           // have
297           // a 'Parse as type XXX' parameter for the applet/application.
298           if (checkPIR)
299           {
300             String dta = null;
301             if (!starterm)
302             {
303               do
304               {
305                 try
306                 {
307                   dta = source.nextLine();
308                 } catch (IOException ex)
309                 {
310                 }
311                 if (dta != null && dta.indexOf("*") > -1)
312                 {
313                   starterm = true;
314                 }
315               } while (dta != null && !starterm);
316             }
317             if (starterm)
318             {
319               reply = FileFormat.PIR;
320               break;
321             }
322             else
323             {
324               reply = FileFormat.Fasta; // probably a bad choice!
325             }
326           }
327           // read as a FASTA (probably)
328           break;
329         }
330         if (data.indexOf("{\"") > -1)
331         {
332           reply = FileFormat.Json;
333           break;
334         }
335         int lessThan = data.indexOf("<");
336         if ((lessThan > -1)) // possible Markup Language data i.e HTML,
337                              // RNAML, XML
338         {
339           String upper = data.toUpperCase(Locale.ROOT);
340           if (upper.substring(lessThan).startsWith("<HTML"))
341           {
342             reply = FileFormat.Html;
343             break;
344           }
345           if (upper.substring(lessThan).startsWith("<RNAML"))
346           {
347             reply = FileFormat.Rnaml;
348             break;
349           }
350         }
351
352         if ((data.length() < 1) || (data.indexOf("#") == 0))
353         {
354           lineswereskipped = true;
355           continue;
356         }
357
358         if (data.indexOf("PILEUP") > -1)
359         {
360           reply = FileFormat.Pileup;
361
362           break;
363         }
364
365         if ((data.indexOf("//") == 0) || ((data.indexOf("!!") > -1) && (data
366                 .indexOf("!!") < data.indexOf("_MULTIPLE_ALIGNMENT "))))
367         {
368           reply = FileFormat.MSF;
369
370           break;
371         }
372         else if (data.indexOf("CLUSTAL") > -1)
373         {
374           reply = FileFormat.Clustal;
375
376           break;
377         }
378
379         else if (data.indexOf("HEADER") == 0 || data.indexOf("ATOM") == 0)
380         {
381           reply = FileFormat.PDB;
382           break;
383         }
384         else if (data.matches("\\s*\\d+\\s+\\d+\\s*"))
385         {
386           reply = FileFormat.Phylip;
387           break;
388         }
389         else if (!lineswereskipped && looksLikeJnetData(data))
390         {
391             reply = FileFormat.Jnet;
392             break;
393
394         }
395         else // phylogenetic file
396         {
397           String identifier = data.toLowerCase();
398           String secondLine = source.nextLine().toLowerCase();
399           if (identifier.startsWith("<phyloxml")
400                   || secondLine.startsWith("<phyloxml"))
401           {
402             reply = FileFormat.PhyloXML;
403             break;
404           }
405           // commented out, nexus parser is troublesome
406
407           // else if (((identifier.startsWith("nexus"))
408           // || (identifier.startsWith("#nexus"))
409           // || (identifier.startsWith("# nexus"))
410           // || (identifier.startsWith("begin"))))
411           // {
412           // reply = FileFormat.Nexus;
413           // break;
414           // }
415         }
416         lineswereskipped = true; // this means there was some junk before any
417         // key file signature
418       }
419       if (closeSource)
420       {
421         source.close();
422       }
423       else
424       {
425         source.reset(bytesRead); // so the file can be parsed from the mark
426       }
427     } catch (Exception ex)
428     {
429       System.err.println("File Identification failed!\n" + ex);
430       throw new FileFormatException(source.errormessage);
431     }
432     if (trimmedLength == 0)
433     {
434       System.err.println(
435               "File Identification failed! - Empty file was read.");
436       throw new FileFormatException("EMPTY DATA FILE");
437     }
438     System.out.println("File format identified as " + reply.toString());
439     return reply;
440   }
441
442   /**
443    * Returns true if the data appears to be Jnet concise annotation format
444    * 
445    * @param data
446    * @return
447    */
448   protected boolean looksLikeJnetData(String data)
449   {
450     char firstChar = data.charAt(0);
451     int colonPos = data.indexOf(":");
452     int commaPos = data.indexOf(",");
453     boolean isJnet = firstChar != '*' && firstChar != ' ' && colonPos > -1
454             && commaPos > -1 && colonPos < commaPos;
455     // && data.indexOf(",")<data.indexOf(",", data.indexOf(","))) / ??
456     return isJnet;
457   }
458
459   /**
460    * Returns true if the data has at least 6 tab-delimited fields _and_ fields 4
461    * and 5 are integer (start/end)
462    * 
463    * @param data
464    * @return
465    */
466   protected boolean looksLikeFeatureData(String data)
467   {
468     if (data == null)
469     {
470       return false;
471     }
472     String[] columns = data.split("\t");
473     if (columns.length < 6)
474     {
475       return false;
476     }
477     for (int col = 3; col < 5; col++)
478     {
479       try
480       {
481         Integer.parseInt(columns[col]);
482       } catch (NumberFormatException e)
483       {
484         return false;
485       }
486     }
487     return true;
488   }
489
490   /**
491    * 
492    * @param args
493    * @j2sIgnore
494    */
495   public static void main(String[] args)
496   {
497     for (int i = 0; args != null && i < args.length; i++)
498     {
499       IdentifyFile ider = new IdentifyFile();
500       FileFormatI type = null;
501       try
502       {
503         type = ider.identify(args[i], DataSourceType.FILE);
504       } catch (FileFormatException e)
505       {
506         System.err.println(
507                 String.format("Error '%s' identifying file type for %s",
508                         args[i], e.getMessage()));
509       }
510       System.out.println("Type of " + args[i] + " is " + type);
511     }
512     if (args == null || args.length == 0)
513     {
514       System.err.println("Usage: <Filename> [<Filename> ...]");
515     }
516   }
517
518 }