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