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