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