9b3c3351c762e3c9b48fe9ffb82cab948f13b6ac
[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 GFF3File = "GFF v2 or v3";
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 length = 0;
90     boolean lineswereskipped = false;
91     boolean isBinary = false; // true if length is non-zero and non-printable
92     // characters are encountered
93     try
94     {
95       if (!closeSource)
96       {
97         source.mark();
98       }
99       while ((data = source.nextLine()) != null)
100       {
101         length += data.trim().length();
102         if (!lineswereskipped)
103         {
104           for (int i = 0; !isBinary && i < data.length(); i++)
105           {
106             char c = data.charAt(i);
107             isBinary = (c < 32 && c != '\t' && c != '\n' && c != '\r'
108                     && c != 5 && c != 27); // nominal binary character filter
109             // excluding CR, LF, tab,DEL and ^E
110             // for certain blast ids
111           }
112         }
113         if (isBinary)
114         {
115           // jar files are special - since they contain all sorts of random
116           // characters.
117           if (source.inFile != null)
118           {
119             String fileStr = source.inFile.getName();
120             // possibly a Jalview archive.
121             if (fileStr.lastIndexOf(".jar") > -1
122                     || fileStr.lastIndexOf(".zip") > -1)
123             {
124               reply = "Jalview";
125             }
126           }
127           if (!lineswereskipped && data.startsWith("PK"))
128           {
129             reply = "Jalview"; // archive.
130             break;
131           }
132         }
133         data = data.toUpperCase();
134
135         if (data.startsWith("##GFF-VERSION"))
136         {
137           reply = GFF3File;
138           break;
139         }
140         if (data.indexOf("# STOCKHOLM") > -1)
141         {
142           reply = "STH";
143           break;
144         }
145         if (data.indexOf("_ENTRY.ID") > -1
146                 || data.indexOf("_AUDIT_AUTHOR.NAME") > -1
147                 || data.indexOf("_ATOM_SITE.") > -1)
148         {
149           reply = "mmCIF";
150           break;
151         }
152         // if (data.indexOf(">") > -1)
153         if (data.startsWith(">"))
154         {
155           // FASTA, PIR file or BLC file
156           boolean checkPIR = false, starterm = false;
157           if ((data.indexOf(">P1;") > -1) || (data.indexOf(">DL;") > -1))
158           {
159             // watch for PIR file attributes
160             checkPIR = true;
161             reply = "PIR";
162           }
163           // could also be BLC file, read next line to confirm
164           data = source.nextLine();
165
166           if (data.indexOf(">") > -1)
167           {
168             reply = "BLC";
169           }
170           else
171           {
172             // Is this a single line BLC file?
173             String data1 = source.nextLine();
174             String data2 = source.nextLine();
175             int c1;
176             if (checkPIR)
177             {
178               starterm = (data1 != null && data1.indexOf("*") > -1)
179                       || (data2 != null && data2.indexOf("*") > -1);
180             }
181             if (data2 != null && (c1 = data.indexOf("*")) > -1)
182             {
183               if (c1 == 0 && c1 == data2.indexOf("*"))
184               {
185                 reply = "BLC";
186               }
187               else
188               {
189                 reply = "FASTA"; // possibly a bad choice - may be recognised as
190                 // PIR
191               }
192               // otherwise can still possibly be a PIR file
193             }
194             else
195             {
196               reply = "FASTA";
197               // TODO : AMSA File is indicated if there is annotation in the
198               // FASTA file - but FASTA will automatically generate this at the
199               // mo.
200               if (!checkPIR)
201               {
202                 break;
203               }
204             }
205           }
206           // final check for PIR content. require
207           // >P1;title\n<blah>\nterminated sequence to occur at least once.
208
209           // TODO the PIR/fasta ambiguity may be the use case that is needed to
210           // have
211           // a 'Parse as type XXX' parameter for the applet/application.
212           if (checkPIR)
213           {
214             String dta = null;
215             if (!starterm)
216             {
217               do
218               {
219                 try
220                 {
221                   dta = source.nextLine();
222                 } catch (IOException ex)
223                 {
224                 }
225                 ;
226                 if (dta != null && dta.indexOf("*") > -1)
227                 {
228                   starterm = true;
229                 }
230               } while (dta != null && !starterm);
231             }
232             if (starterm)
233             {
234               reply = "PIR";
235               break;
236             }
237             else
238             {
239               reply = "FASTA"; // probably a bad choice!
240             }
241           }
242           // read as a FASTA (probably)
243           break;
244         }
245         if ((data.indexOf("<") > -1)) // possible Markup Language data i.e HTML,
246                                       // RNAML, XML
247         {
248           boolean identified = false;
249           do
250           {
251             if (data.matches("<(?i)html(\"[^\"]*\"|'[^']*'|[^'\">])*>"))
252             {
253               reply = HtmlFile.FILE_DESC;
254               identified = true;
255               break;
256             }
257
258             if (data.matches("<(?i)rnaml (\"[^\"]*\"|'[^']*'|[^'\">])*>"))
259             {
260               reply = "RNAML";
261               identified = true;
262               break;
263             }
264           } while ((data = source.nextLine()) != null);
265
266           if (identified)
267           {
268             break;
269           }
270           if (data == null)
271           {
272             break;
273           }
274         }
275
276         if (data.indexOf("{\"") > -1)
277         {
278           reply = JSONFile.FILE_DESC;
279           break;
280         }
281         if ((data.length() < 1) || (data.indexOf("#") == 0))
282         {
283           lineswereskipped = true;
284           continue;
285         }
286
287         if (data.indexOf("PILEUP") > -1)
288         {
289           reply = "PileUp";
290
291           break;
292         }
293
294         if ((data.indexOf("//") == 0)
295                 || ((data.indexOf("!!") > -1) && (data.indexOf("!!") < data
296                         .indexOf("_MULTIPLE_ALIGNMENT "))))
297         {
298           reply = "MSF";
299
300           break;
301         }
302         else if (data.indexOf("CLUSTAL") > -1)
303         {
304           reply = "CLUSTAL";
305
306           break;
307         }
308
309         else if (data.indexOf("HEADER") == 0 || data.indexOf("ATOM") == 0)
310         {
311           reply = "PDB";
312           break;
313         }
314         else if (data.matches("\\s*\\d+\\s+\\d+\\s*"))
315         {
316           reply = PhylipFile.FILE_DESC;
317           break;
318         }
319
320         /*
321          * // TODO comment out SimpleBLAST identification for Jalview 2.4.1 else
322          * if (!lineswereskipped && data.indexOf("BLAST")<4) { reply =
323          * "SimpleBLAST"; break;
324          * 
325          * } // end comments for Jalview 2.4.1
326          */
327         else if (!lineswereskipped && data.charAt(0) != '*'
328                 && data.charAt(0) != ' '
329                 && data.indexOf(":") < data.indexOf(",")) // &&
330         // data.indexOf(",")<data.indexOf(",",
331         // data.indexOf(",")))
332         {
333           // file looks like a concise JNet file
334           reply = "JnetFile";
335           break;
336         }
337
338         lineswereskipped = true; // this means there was some junk before any
339         // key file signature
340       }
341       if (closeSource)
342       {
343         source.close();
344       }
345       else
346       {
347         source.reset(); // so the file can be parsed from the beginning again.
348       }
349     } catch (Exception ex)
350     {
351       System.err.println("File Identification failed!\n" + ex);
352       return source.errormessage;
353     }
354     if (length == 0)
355     {
356       System.err
357               .println("File Identification failed! - Empty file was read.");
358       return "EMPTY DATA FILE";
359     }
360     return reply;
361   }
362
363   public static void main(String[] args)
364   {
365
366     for (int i = 0; args != null && i < args.length; i++)
367     {
368       IdentifyFile ider = new IdentifyFile();
369       String type = ider.Identify(args[i], AppletFormatAdapter.FILE);
370       System.out.println("Type of " + args[i] + " is " + type);
371     }
372     if (args == null || args.length == 0)
373     {
374       System.err.println("Usage: <Filename> [<Filename> ...]");
375     }
376   }
377 }