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