JAL-1807 - Bob's last(?) before leaving Dundee -- adds fast file loading
[jalviewjs.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 jalview.jsdev.GenericFileAdapter;
24
25 import java.io.IOException;
26
27 /**
28  * DOCUMENT ME!
29  *
30  * @author $author$
31  * @version $Revision$
32  */
33 public class IdentifyFile
34 {
35   public static final String GFF3File = "GFF v2 or v3";
36
37   /**
38    * Identify a datasource's file content.
39    *
40    * @note Do not use this method for stream sources - create a FileParse object
41    *       instead.
42    *
43    * @param file
44    *          DOCUMENT ME!
45    * @param protocol
46    *          DOCUMENT ME!
47    * @return ID String
48    */
49   public String Identify(String file, String protocol)
50   {
51     String emessage = "UNIDENTIFIED FILE PARSING ERROR";
52     FileParse parser = null;
53     try
54     {
55       parser = new FileParse(file, protocol);
56       if (parser.isValid())
57       {
58         return Identify(parser);
59       }
60     } catch (Exception e)
61     {
62       System.err.println("Error whilst identifying");
63       e.printStackTrace(System.err);
64       emessage = e.getMessage();
65     }
66     if (parser != null)
67     {
68       return parser.errormessage;
69     }
70     return emessage;
71   }
72
73   public String Identify(FileParse source)
74   {
75     return Identify(source, true); // preserves original behaviour prior to
76     // version 2.3
77   }
78
79   /**
80    * Identify contents of source, closing it or resetting source to start
81    * afterwards.
82    *
83    * @param source
84    * @param closeSource
85    * @return filetype string
86    */
87   public String Identify(FileParse source, boolean closeSource)
88   {
89     String reply = "PFAM";
90     String data;
91     int length = 0;
92     boolean lineswereskipped = false;
93     boolean isBinary = false; // true if length is non-zero and non-printable
94     // characters are encountered
95     try
96     {
97       if (!closeSource)
98       {
99         source.mark();
100       }
101       while ((data = source.nextLine()) != null)
102       {
103         length += 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           reply = GFF3File;
140           break;
141         }
142         if (data.indexOf("# STOCKHOLM") > -1)
143         {
144           reply = "STH";
145           break;
146         }
147         // if (data.matches("<(\"[^\"]*\"|'[^']*'|[^'\">])*>"))
148         if (data.matches("<HTML(\"[^\"]*\"|'[^']*'|[^'\">])*>")) // BH was (?i)rnaml, but JavaScript regex does not support (?i); data is already upper case
149         {
150           reply = GenericFileAdapter.Html_FILE_DESC;
151           break;
152         }
153
154         if (data.matches("<RNAML (\"[^\"]*\"|'[^']*'|[^'\">])*>")) // BH was (?i)rnaml, but JavaScript regex does not support (?i); data is already upper case
155         {
156           reply = "RNAML";
157           break;
158         }
159
160          if (data.indexOf("{\"") > -1)
161          {
162          reply = GenericFileAdapter.JSON_FILE_DESC;
163          break;
164          }
165         if ((data.length() < 1) || (data.indexOf("#") == 0))
166         {
167           lineswereskipped = true;
168           continue;
169         }
170
171         if (data.indexOf("PILEUP") > -1)
172         {
173           reply = "PileUp";
174
175           break;
176         }
177
178         if ((data.indexOf("//") == 0)
179                 || ((data.indexOf("!!") > -1) && (data.indexOf("!!") < data
180                         .indexOf("_MULTIPLE_ALIGNMENT "))))
181         {
182           reply = "MSF";
183
184           break;
185         }
186         else if (data.indexOf("CLUSTAL") > -1)
187         {
188           reply = "CLUSTAL";
189
190           break;
191         }
192
193         else if (data.indexOf(">") > -1)
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 = "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 = "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 = "BLC";
226               }
227               else
228               {
229                 reply = "FASTA"; // possibly a bad choice - may be recognised as
230                 // PIR
231               }
232               // otherwise can still possibly be a PIR file
233             }
234             else
235             {
236               reply = "FASTA";
237               // TODO : AMSA File is indicated if there is annotation in the
238               // FASTA file - but FASTA will automatically generate this at the
239               // mo.
240               if (!checkPIR)
241               {
242                 break;
243               }
244             }
245           }
246           // final check for PIR content. require
247           // >P1;title\n<blah>\nterminated sequence to occur at least once.
248
249           // TODO the PIR/fasta ambiguity may be the use case that is needed to
250           // have
251           // a 'Parse as type XXX' parameter for the applet/application.
252           if (checkPIR)
253           {
254             String dta = null;
255             if (!starterm)
256             {
257               do
258               {
259                 try
260                 {
261                   dta = source.nextLine();
262                 } catch (IOException ex)
263                 {
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 = "PIR";
275               break;
276             }
277             else
278             {
279               reply = "FASTA"; // probably a bad choice!
280             }
281           }
282           // read as a FASTA (probably)
283           break;
284         }
285         else if (data.indexOf("HEADER") == 0 || data.indexOf("ATOM") == 0)
286         {
287           reply = "PDB";
288           break;
289         }
290         else if (data.matches("\\s*\\d+\\s+\\d+\\s*"))
291         {
292           reply = GenericFileAdapter.Phylip_FILE_DESC;
293           break;
294         }
295
296
297         /*
298          * // TODO comment out SimpleBLAST identification for Jalview 2.4.1 else
299          * if (!lineswereskipped && data.indexOf("BLAST")<4) { reply =
300          * "SimpleBLAST"; break;
301          *
302          * } // end comments for Jalview 2.4.1
303          */
304         else if (!lineswereskipped && data.charAt(0) != '*'
305                 && data.charAt(0) != ' '
306                 && data.indexOf(":") < data.indexOf(",")) // &&
307           // data.indexOf(",")<data.indexOf(",",
308           // data.indexOf(",")))
309         {
310           // file looks like a concise JNet file
311           reply = "JnetFile";
312           break;
313         }
314
315         lineswereskipped = true; // this means there was some junk before any
316         // key file signature
317       }
318       if (closeSource)
319       {
320         source.close();
321       }
322       else
323       {
324         source.reset(); // so the file can be parsed from the beginning again.
325       }
326     } catch (Exception ex)
327     {
328       System.err.println("File Identification failed!\n" + ex);
329       return source.errormessage;
330     }
331     if (length == 0)
332     {
333       System.err
334       .println("File Identification failed! - Empty file was read.");
335       return "EMPTY DATA FILE";
336     }
337     return reply;
338   }
339
340   /**
341    * @j2sIgnore
342    * 
343    * @param args
344    */
345   public static void main(String[] args)
346   {
347
348     for (int i = 0; args != null && i < args.length; i++)
349     {
350       IdentifyFile ider = new IdentifyFile();
351       String type = ider.Identify(args[i], AppletFormatAdapter.FILE);
352       System.out.println("Type of " + args[i] + " is " + type);
353     }
354     if (args == null || args.length == 0)
355     {
356       System.err.println("Usage: <Filename> [<Filename> ...]");
357     }
358   }
359 }