JAL-1807 Bob's JalviewJS prototype first commit
[jalviewjs.git] / src / javajs / util / Rdr.java
1 /* $RCSfile$\r
2  * $Author: hansonr $\r
3  * $Date: 2007-04-05 09:07:28 -0500 (Thu, 05 Apr 2007) $\r
4  * $Revision: 7326 $\r
5  *\r
6  * Copyright (C) 2003-2005  The Jmol Development Team\r
7  *\r
8  * Contact: jmol-developers@lists.sf.net\r
9  *\r
10  *  This library is free software; you can redistribute it and/or\r
11  *  modify it under the terms of the GNU Lesser General Public\r
12  *  License as published by the Free Software Foundation; either\r
13  *  version 2.1 of the License, or (at your option) any later version.\r
14  *\r
15  *  This library is distributed in the hope that it will be useful,\r
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
18  *  Lesser General Public License for more details.\r
19  *\r
20  *  You should have received a copy of the GNU Lesser General Public\r
21  *  License along with this library; if not, write to the Free Software\r
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\r
23  */\r
24 package javajs.util;\r
25 \r
26 import java.io.BufferedInputStream;\r
27 import java.io.BufferedReader;\r
28 import java.io.ByteArrayInputStream;\r
29 import java.io.IOException;\r
30 import java.io.InputStream;\r
31 import java.io.InputStreamReader;\r
32 import java.io.StringReader;\r
33 import java.io.UnsupportedEncodingException;\r
34 \r
35 import java.util.Map;\r
36 \r
37 \r
38 import javajs.api.GenericCifDataParser;\r
39 import javajs.api.GenericLineReader;\r
40 import javajs.api.GenericZipTools;\r
41 \r
42 /**\r
43  * A general helper class for a variety of stream and reader functionality\r
44  * including:\r
45  * \r
46  *  stream and byte magic-number decoding for PNG, PNGJ, ZIP, and GZIP streams\r
47  *  \r
48  *  various stream/reader methods, including UTF-encoded stream reading\r
49  *  \r
50  *  reflection-protected access to a CIF parser and ZIP tools\r
51  *  \r
52  *   \r
53  * \r
54  * \r
55  */\r
56 public class Rdr implements GenericLineReader {\r
57 \r
58   BufferedReader reader;\r
59 \r
60   public Rdr(BufferedReader reader) {\r
61     this.reader = reader;\r
62   }\r
63   \r
64   @Override\r
65   public String readNextLine() throws Exception {\r
66     return reader.readLine();\r
67   }\r
68 \r
69   public static Map<String, Object> readCifData(GenericCifDataParser parser, BufferedReader br) {\r
70     return parser.set(null, br).getAllCifData();\r
71   }\r
72   \r
73   \r
74   ///////////\r
75   \r
76   public static String fixUTF(byte[] bytes) {    \r
77     Encoding encoding = getUTFEncoding(bytes);\r
78     if (encoding != Encoding.NONE)\r
79     try {\r
80       String s = new String(bytes, encoding.name().replace('_', '-'));\r
81       switch (encoding) {\r
82       case UTF8:\r
83       case UTF_16BE:\r
84       case UTF_16LE:\r
85         // extra byte at beginning removed\r
86         s = s.substring(1);\r
87         break;\r
88       default:\r
89         break;        \r
90       }\r
91       return s;\r
92     } catch (UnsupportedEncodingException e) {\r
93       System.out.println(e);\r
94     }\r
95     return new String(bytes);\r
96   }\r
97 \r
98   private static Encoding getUTFEncoding(byte[] bytes) {\r
99     if (bytes.length >= 3 && bytes[0] == (byte) 0xEF && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF)\r
100       return Encoding.UTF8;\r
101     if (bytes.length >= 4 && bytes[0] == (byte) 0 && bytes[1] == (byte) 0 \r
102         && bytes[2] == (byte) 0xFE && bytes[3] == (byte) 0xFF)\r
103       return Encoding.UTF_32BE;\r
104     if (bytes.length >= 4 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE \r
105         && bytes[2] == (byte) 0 && bytes[3] == (byte) 0)\r
106       return Encoding.UTF_32LE;\r
107     if (bytes.length >= 2 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE)\r
108       return Encoding.UTF_16LE;\r
109     if (bytes.length >= 2 && bytes[0] == (byte) 0xFE && bytes[1] == (byte) 0xFF)\r
110       return Encoding.UTF_16BE;\r
111     return Encoding.NONE;\r
112   \r
113   }\r
114   \r
115   ////////// stream type checking //////////\r
116   \r
117 \r
118   private static Encoding getUTFEncodingForStream(BufferedInputStream is) throws IOException {\r
119     /**\r
120      * @j2sNative\r
121      * \r
122      *  is.resetStream();\r
123      * \r
124      */\r
125     {\r
126     }\r
127     byte[] abMagic = new byte[4];\r
128     abMagic[3] = 1;\r
129     try{\r
130     is.mark(5);\r
131     } catch (Exception e) {\r
132       return Encoding.NONE;\r
133     }\r
134     is.read(abMagic, 0, 4);\r
135     is.reset();\r
136     return getUTFEncoding(abMagic);\r
137   }\r
138 \r
139   public static boolean isBase64(SB sb) {\r
140     return (sb.indexOf(";base64,") == 0);\r
141   }\r
142 \r
143   public static boolean isCompoundDocumentS(InputStream is) {\r
144     return isCompoundDocumentB(getMagic(is, 8));\r
145   }\r
146 \r
147   public static boolean isCompoundDocumentB(byte[] bytes) {\r
148     return (bytes.length >= 8 && bytes[0] == (byte) 0xD0\r
149         && bytes[1] == (byte) 0xCF && bytes[2] == (byte) 0x11\r
150         && bytes[3] == (byte) 0xE0 && bytes[4] == (byte) 0xA1\r
151         && bytes[5] == (byte) 0xB1 && bytes[6] == (byte) 0x1A \r
152         && bytes[7] == (byte) 0xE1);\r
153   }\r
154 \r
155   public static boolean isGzipS(InputStream is) {\r
156     return isGzipB(getMagic(is, 2));\r
157   }\r
158 \r
159   public static boolean isGzipB(byte[] bytes) {    \r
160       return (bytes != null && bytes.length >= 2 \r
161           && bytes[0] == (byte) 0x1F && bytes[1] == (byte) 0x8B);\r
162   }\r
163 \r
164   public static boolean isPickleS(InputStream is) {\r
165     return Rdr.isPickleB(getMagic(is, 2));\r
166   }\r
167 \r
168   public static boolean isPickleB(byte[] bytes) {    \r
169       return (bytes != null && bytes.length >= 2 \r
170           && bytes[0] == (byte) 0x7D && bytes[1] == (byte) 0x71);\r
171   }\r
172 \r
173   public static boolean isPngZipStream(InputStream is) {\r
174     return isPngZipB(getMagic(is, 55));\r
175   }\r
176 \r
177   public static boolean isPngZipB(byte[] bytes) {\r
178     // \0PNGJ starting at byte 50\r
179     return (bytes[50] == 0 && bytes[51] == 0x50 && bytes[52] == 0x4E && bytes[53] == 0x47 && bytes[54] == 0x4A);\r
180   }\r
181 \r
182   public static boolean isZipS(InputStream is) {\r
183     return isZipB(getMagic(is, 4));\r
184   }\r
185 \r
186   public static boolean isZipB(byte[] bytes) {\r
187     return (bytes.length >= 4 \r
188         && bytes[0] == 0x50  //PK<03><04> \r
189         && bytes[1] == 0x4B\r
190         && bytes[2] == 0x03 \r
191         && bytes[3] == 0x04);\r
192   }\r
193 \r
194   private static byte[] getMagic(InputStream is, int n) {\r
195     byte[] abMagic = new byte[n];\r
196     /**\r
197      * @j2sNative\r
198      * \r
199      * is.resetStream();\r
200      * \r
201      */\r
202     {\r
203     }\r
204     try {\r
205       is.mark(n + 1);\r
206       is.read(abMagic, 0, n);\r
207     } catch (IOException e) {\r
208     }\r
209     try {\r
210       is.reset();\r
211     } catch (IOException e) {\r
212     }\r
213     return abMagic;\r
214   }\r
215 \r
216   public static String guessMimeTypeForBytes(byte[] bytes) {\r
217      // only options here are JPEG, PNG, GIF, and BMP\r
218     switch (bytes.length < 2 ? -1 : bytes[1]) {\r
219     case 0:\r
220       return "image/jpg"; // 0xFF 0x00 ...\r
221     case 0x49:\r
222       return "image/gif"; // GIF89a...\r
223     case 0x4D:\r
224       return "image/BMP"; // BM...\r
225     case 0x50:\r
226       return "image/png";\r
227     default:\r
228       return  "image/unknown";\r
229     }\r
230   }\r
231 \r
232 \r
233   ////////// stream/byte methods ///////////\r
234   \r
235   public static BufferedInputStream getBIS(byte[] bytes) {\r
236     return new BufferedInputStream(new ByteArrayInputStream(bytes));\r
237   }\r
238 \r
239   public static BufferedReader getBR(String string) {\r
240     return new BufferedReader(new StringReader(string));\r
241   }\r
242 \r
243   /**\r
244    * Drill down into a GZIP stack until no more layers.\r
245    * @param jzt \r
246    * \r
247    * @param bis\r
248    * @return non-gzipped buffered input stream.\r
249    * \r
250    * @throws IOException\r
251    */\r
252   public static BufferedInputStream getUnzippedInputStream(GenericZipTools jzt, BufferedInputStream bis) throws IOException {\r
253     while (isGzipS(bis))\r
254       bis = new BufferedInputStream(jzt.newGZIPInputStream(bis));\r
255     return bis;\r
256   }\r
257 \r
258   /**\r
259    * Allow for base64-encoding check.\r
260    * \r
261    * @param sb\r
262    * @return byte array\r
263    */\r
264   public static byte[] getBytesFromSB(SB sb) {\r
265     return (isBase64(sb) ? Base64.decodeBase64(sb.substring(8)) : sb.toBytes(0, -1));    \r
266   }\r
267 \r
268  /**\r
269    * Read a an entire BufferedInputStream for its bytes, and \r
270    * either return them or leave them in the designated output channel.\r
271    *  \r
272    * @param bis\r
273    * @param out a destination output channel, or null\r
274    * @return byte[] (if out is null) or a message indicating length (if not)\r
275    * \r
276    * @throws IOException\r
277    */\r
278   public static Object getStreamAsBytes(BufferedInputStream bis,\r
279                                          OC out) throws IOException {\r
280     byte[] buf = new byte[1024];\r
281     byte[] bytes = (out == null ? new byte[4096] : null);\r
282     int len = 0;\r
283     int totalLen = 0;\r
284     while ((len = bis.read(buf, 0, 1024)) > 0) {\r
285       totalLen += len;\r
286       if (out == null) {\r
287         if (totalLen >= bytes.length)\r
288           bytes = AU.ensureLengthByte(bytes, totalLen * 2);\r
289         System.arraycopy(buf, 0, bytes, totalLen - len, len);\r
290       } else {\r
291         out.write(buf, 0, len);\r
292       }\r
293     }\r
294     bis.close();\r
295     if (out == null) {\r
296       return AU.arrayCopyByte(bytes, totalLen);\r
297     }\r
298     return totalLen + " bytes";\r
299   }\r
300 \r
301   /**\r
302    * Read an input stream fully, saving a byte array, then\r
303    * return a buffered reader to those bytes converted to string form.\r
304    * \r
305    * @param bis\r
306    * @param charSet\r
307    * @return Reader\r
308    * @throws IOException\r
309    */\r
310   public static BufferedReader getBufferedReader(BufferedInputStream bis, String charSet)\r
311       throws IOException {\r
312     // could also just make sure we have a buffered input stream here.\r
313     if (getUTFEncodingForStream(bis) == Encoding.NONE)\r
314       return new BufferedReader(new InputStreamReader(bis, (charSet == null ? "UTF-8" : charSet)));\r
315     byte[] bytes = getLimitedStreamBytes(bis, -1);\r
316     bis.close();\r
317     return getBR(charSet == null ? fixUTF(bytes) : new String(bytes, charSet));\r
318   }\r
319 \r
320   /**\r
321    * Read a possibly limited number of bytes (when n > 0) from a stream, \r
322    * leaving the stream open.\r
323    * \r
324    * @param is an input stream, not necessarily buffered.\r
325    * @param n the maximum number of bytes to read, or -1 for all\r
326    * @return the bytes read\r
327    * \r
328    * @throws IOException\r
329    */\r
330   public static byte[] getLimitedStreamBytes(InputStream is, long n)\r
331       throws IOException {\r
332 \r
333     //Note: You cannot use InputStream.available() to reliably read\r
334     //      zip data from the web. \r
335 \r
336     int buflen = (n > 0 && n < 1024 ? (int) n : 1024);\r
337     byte[] buf = new byte[buflen];\r
338     byte[] bytes = new byte[n < 0 ? 4096 : (int) n];\r
339     int len = 0;\r
340     int totalLen = 0;\r
341     if (n < 0)\r
342       n = Integer.MAX_VALUE;\r
343     while (totalLen < n && (len = is.read(buf, 0, buflen)) > 0) {\r
344       totalLen += len;\r
345       if (totalLen > bytes.length)\r
346         bytes = AU.ensureLengthByte(bytes, totalLen * 2);\r
347       System.arraycopy(buf, 0, bytes, totalLen - len, len);\r
348       if (n != Integer.MAX_VALUE && totalLen + buflen > bytes.length)\r
349         buflen = bytes.length - totalLen;\r
350 \r
351     }\r
352     if (totalLen == bytes.length)\r
353       return bytes;\r
354     buf = new byte[totalLen];\r
355     System.arraycopy(bytes, 0, buf, 0, totalLen);\r
356     return buf;\r
357   }\r
358 \r
359   /**\r
360    * \r
361    * Read a UTF-8 stream fully, converting it to a String.\r
362    * Called by Jmol's XMLReaders\r
363    * \r
364    * @param bis\r
365    * @return a UTF-8 string\r
366    */\r
367   public static String StreamToUTF8String(BufferedInputStream bis) {\r
368     String[] data = new String[1];\r
369     try {\r
370       readAllAsString(getBufferedReader(bis, "UTF-8"), -1, true, data, 0);\r
371     } catch (IOException e) {\r
372     }\r
373     return data[0];\r
374   }\r
375 \r
376   /**\r
377    * This method fills data[i] with string data from a file that may or may not\r
378    * be binary even though it is being read by a reader. It is meant to be used\r
379    * simple text-based files only.\r
380    *  \r
381    * @param br\r
382    * @param nBytesMax\r
383    * @param allowBinary\r
384    * @param data\r
385    * @param i\r
386    * @return true if data[i] holds the data; false if data[i] holds an error message. \r
387    */\r
388   public static boolean readAllAsString(BufferedReader br, int nBytesMax, boolean allowBinary, String[] data, int i) {\r
389     try {\r
390       SB sb = SB.newN(8192);\r
391       String line;\r
392       if (nBytesMax < 0) {\r
393         line = br.readLine();\r
394         if (allowBinary || line != null && line.indexOf('\0') < 0\r
395             && (line.length() != 4 || line.charAt(0) != 65533\r
396             || line.indexOf("PNG") != 1)) {\r
397           sb.append(line).appendC('\n');\r
398           while ((line = br.readLine()) != null)\r
399             sb.append(line).appendC('\n');\r
400         }\r
401       } else {\r
402         int n = 0;\r
403         int len;\r
404         while (n < nBytesMax && (line = br.readLine()) != null) {\r
405           if (nBytesMax - n < (len = line.length()) + 1)\r
406             line = line.substring(0, nBytesMax - n - 1);\r
407           sb.append(line).appendC('\n');\r
408           n += len + 1;\r
409         }\r
410       }\r
411       br.close();\r
412       data[i] = sb.toString();\r
413       return true;\r
414     } catch (Exception ioe) {\r
415       data[i] = ioe.toString();\r
416       return false;\r
417     }\r
418   }\r
419 \r
420   \r
421   /////////// PNGJ support /////////////\r
422   \r
423   \r
424   /**\r
425    * Look at byte 50 for "\0PNGJxxxxxxxxx+yyyyyyyyy" where xxxxxxxxx is a byte\r
426    * offset to the JMOL data and yyyyyyyyy is the length of the data.\r
427    * \r
428    * @param bis\r
429    * @return same stream or byte stream\r
430    */\r
431 \r
432   /**\r
433    * Retrieve the two numbers in a PNG iTXt tag indicating the \r
434    * file pointer for the start of the ZIP data as well as its length.\r
435    * \r
436    * @param bis\r
437    * @param pt_count\r
438    */\r
439   static void getPngZipPointAndCount(BufferedInputStream bis, int[] pt_count) {\r
440     bis.mark(75);\r
441     try {\r
442       byte[] data = getLimitedStreamBytes(bis, 74);\r
443       bis.reset();\r
444       int pt = 0;\r
445       for (int i = 64, f = 1; --i > 54; f *= 10)\r
446         pt += (data[i] - '0') * f;\r
447       int n = 0;\r
448       for (int i = 74, f = 1; --i > 64; f *= 10)\r
449         n += (data[i] - '0') * f;\r
450       pt_count[0] = pt;\r
451       pt_count[1] = n;\r
452     } catch (Throwable e) {\r
453       pt_count[1] = 0;\r
454     }\r
455   }\r
456 \r
457   /**\r
458    * Either advance a PNGJ stream to its zip file data or pull out the ZIP data\r
459    * bytes and create a new stream for them from which a ZIP utility can start\r
460    * extracting files.\r
461    * \r
462    * @param bis\r
463    * @param asNewStream  \r
464    * @return new buffered ByteArrayInputStream, possibly with no data if there is an error\r
465    */\r
466   public static BufferedInputStream getPngZipStream(BufferedInputStream bis, boolean asNewStream) {\r
467     if (!isPngZipStream(bis))\r
468       return bis;\r
469     byte[] data = new byte[0];\r
470     bis.mark(75);\r
471     try {\r
472       int pt_count[] = new int[2];\r
473       getPngZipPointAndCount(bis, pt_count);\r
474       if (pt_count[1] != 0) {\r
475         int pt = pt_count[0];\r
476         while (pt > 0)\r
477           pt -= bis.skip(pt);\r
478         if (!asNewStream)\r
479           return bis;\r
480         data = getLimitedStreamBytes(bis, pt_count[1]);\r
481       }\r
482     } catch (Throwable e) {\r
483     } finally {\r
484       try {\r
485         if (asNewStream)\r
486           bis.close();\r
487       } catch (Exception e) {\r
488         // ignore\r
489       }\r
490     }\r
491     return getBIS(data);\r
492   }\r
493 \r
494   /** We define a request for zip file extraction by vertical bar:\r
495    *  zipName|interiorFileName. These may be nested if there is a\r
496    *  zip file contained in a zip file. \r
497    *  \r
498    * @param fileName\r
499    * @return filename trimmed of interior fileName\r
500    * \r
501    */\r
502   public static String getZipRoot(String fileName) {\r
503     int pt = fileName.indexOf("|");\r
504     return (pt < 0 ? fileName : fileName.substring(0, pt));\r
505   }\r
506 \r
507   \r
508 }\r
509 \r