3 * $Date: 2007-04-05 09:07:28 -0500 (Thu, 05 Apr 2007) $
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.
9 * Copyright (C) 2003-2005 The Jmol Development Team
11 * Contact: jmol-developers@lists.sf.net
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.
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.
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.
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;
43 import javajs.api.Interface;
46 import javajs.J2SIgnoreImport;
47 import javajs.api.GenericCifDataParser;
48 import javajs.api.GenericLineReader;
49 import javajs.api.GenericZipTools;
52 * A general helper class for a variety of stream and reader functionality
55 * stream and byte magic-number decoding for PNG, PNGJ, ZIP, and GZIP streams
57 * various stream/reader methods, including UTF-encoded stream reading
59 * reflection-protected access to a CIF parser and ZIP tools
65 @J2SIgnoreImport(BufferedWriter.class)
66 public class Rdr implements GenericLineReader {
68 BufferedReader reader;
70 public Rdr(BufferedReader reader) {
75 public String readNextLine() throws Exception {
76 return reader.readLine();
79 public static Map<String, Object> readCifData(GenericCifDataParser parser, BufferedReader br) {
80 return parser.set(null, br, false).getAllCifData();
89 * Read a UTF-8 byte array fully, converting it to a String.
90 * Called by Jmol's XMLReaders
93 * @return a UTF-8 string
95 public static String bytesToUTF8String(byte[] bytes) {
96 return streamToUTF8String(new BufferedInputStream(new ByteArrayInputStream(bytes)));
101 * Read a UTF-8 stream fully, converting it to a String.
102 * Called by Jmol's XMLReaders
105 * @return a UTF-8 string
107 public static String streamToUTF8String(BufferedInputStream bis) {
108 String[] data = new String[1];
110 readAllAsString(getBufferedReader(bis, "UTF-8"), -1, true, data, 0);
111 } catch (IOException e) {
117 * Read an input stream fully, saving a byte array, then
118 * return a buffered reader to those bytes converted to string form.
123 * @throws IOException
125 public static BufferedReader getBufferedReader(BufferedInputStream bis, String charSet)
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);
132 return getBR(charSet == null ? fixUTF(bytes) : new String(bytes, charSet));
136 * This method is specifically for strings that are marked for UTF 8 or 16.
141 public static String fixUTF(byte[] bytes) {
142 Encoding encoding = getUTFEncoding(bytes);
143 if (encoding != Encoding.NONE)
145 String s = new String(bytes, encoding.name().replace('_', '-'));
150 // extra byte at beginning removed
157 } catch (UnsupportedEncodingException e) {
158 System.out.println(e);
160 return new String(bytes);
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;
180 ////////// stream type checking //////////
183 private static Encoding getUTFEncodingForStream(BufferedInputStream is) throws IOException {
192 byte[] abMagic = new byte[4];
196 } catch (Exception e) {
197 return Encoding.NONE;
199 is.read(abMagic, 0, 4);
201 return getUTFEncoding(abMagic);
204 public static boolean isBase64(SB sb) {
205 return (sb.indexOf(";base64,") == 0);
208 public static boolean isCompoundDocumentS(InputStream is) {
209 return isCompoundDocumentB(getMagic(is, 8));
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);
220 public static boolean isGzipS(InputStream is) {
221 return isGzipB(getMagic(is, 2));
224 public static boolean isGzipB(byte[] bytes) {
225 return (bytes != null && bytes.length >= 2
226 && (bytes[0] & 0xFF) == 0x1F && (bytes[1] & 0xFF) == 0x8B);
229 public static boolean isPickleS(InputStream is) {
230 return isPickleB(getMagic(is, 2));
233 public static boolean isPickleB(byte[] bytes) {
234 return (bytes != null && bytes.length >= 2
235 && (bytes[0] & 0xFF) == 0x7D && (bytes[1] & 0xFF) == 0x71);
238 public static boolean isMessagePackS(InputStream is) {
239 return isMessagePackB(getMagic(is, 1));
242 public static boolean isMessagePackB(byte[] bytes) {
243 // look for 'map' start
244 return (bytes != null && bytes.length >= 1 && (bytes[0] & 0xFF) == 0xDE);
247 public static boolean isPngZipStream(InputStream is) {
248 return isPngZipB(getMagic(is, 55));
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);
256 public static boolean isZipS(InputStream is) {
257 return isZipB(getMagic(is, 4));
260 public static boolean isZipB(byte[] bytes) {
261 return (bytes.length >= 4
262 && bytes[0] == 0x50 //PK<03><04>
265 && bytes[3] == 0x04);
268 public static byte[] getMagic(InputStream is, int n) {
269 byte[] abMagic = new byte[n];
280 is.read(abMagic, 0, n);
281 } catch (IOException e) {
285 } catch (IOException e) {
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]) {
294 return "image/jpg"; // 0xFF 0x00 ...
296 return "image/gif"; // GIF89a...
298 return "image/BMP"; // BM...
302 return "image/unknown";
307 ////////// stream/byte methods ///////////
309 public static BufferedInputStream getBIS(byte[] bytes) {
310 return new BufferedInputStream(new ByteArrayInputStream(bytes));
313 public static BufferedReader getBR(String string) {
314 return new BufferedReader(new StringReader(string));
318 * Drill down into a GZIP stack until no more layers.
322 * @return non-gzipped buffered input stream.
324 * @throws IOException
326 public static BufferedInputStream getUnzippedInputStream(GenericZipTools jzt, BufferedInputStream bis) throws IOException {
328 bis = new BufferedInputStream(jzt.newGZIPInputStream(bis));
333 * Allow for base64-encoding check.
338 public static byte[] getBytesFromSB(SB sb) {
339 return (isBase64(sb) ? Base64.decodeBase64(sb.substring(8)) : sb.toBytes(0, -1));
343 * Read a an entire BufferedInputStream for its bytes, and
344 * either return them or leave them in the designated output channel.
347 * @param out a destination output channel, or null
348 * @return byte[] (if out is null) or a message indicating length (if not)
350 * @throws IOException
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);
358 while ((len = bis.read(buf, 0, 1024)) > 0) {
361 if (totalLen >= bytes.length)
362 bytes = AU.ensureLengthByte(bytes, totalLen * 2);
363 System.arraycopy(buf, 0, bytes, totalLen - len, len);
365 out.write(buf, 0, len);
370 return AU.arrayCopyByte(bytes, totalLen);
372 return totalLen + " bytes";
376 * Read a possibly limited number of bytes (when n > 0) from a stream,
377 * leaving the stream open.
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
383 * @throws IOException
385 public static byte[] getLimitedStreamBytes(InputStream is, long n)
388 //Note: You cannot use InputStream.available() to reliably read
389 // zip data from the web.
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];
397 n = Integer.MAX_VALUE;
398 while (totalLen < n && (len = is.read(buf, 0, buflen)) > 0) {
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;
407 if (totalLen == bytes.length)
409 buf = new byte[totalLen];
410 System.arraycopy(bytes, 0, buf, 0, totalLen);
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.
424 * @return true if data[i] holds the data; false if data[i] holds an error message.
426 public static boolean readAllAsString(BufferedReader br, int nBytesMax, boolean allowBinary, String[] data, int i) {
428 SB sb = SB.newN(8192);
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');
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');
450 data[i] = sb.toString();
452 } catch (Exception ioe) {
453 data[i] = ioe.toString();
459 /////////// PNGJ support /////////////
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.
467 * @return same stream or byte stream
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.
477 static void getPngZipPointAndCount(BufferedInputStream bis, int[] pt_count) {
480 byte[] data = getLimitedStreamBytes(bis, 74);
483 for (int i = 64, f = 1; --i > 54; f *= 10)
484 pt += (data[i] - '0') * f;
486 for (int i = 74, f = 1; --i > 64; f *= 10)
487 n += (data[i] - '0') * f;
490 } catch (Throwable e) {
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
502 * @return new buffered ByteArrayInputStream, possibly with no data if there is an error
504 public static BufferedInputStream getPngZipStream(BufferedInputStream bis, boolean asNewStream) {
505 if (!isPngZipStream(bis))
507 byte[] data = new byte[0];
510 int pt_count[] = new int[2];
511 getPngZipPointAndCount(bis, pt_count);
512 if (pt_count[1] != 0) {
513 int pt = pt_count[0];
518 data = getLimitedStreamBytes(bis, pt_count[1]);
520 } catch (Throwable e) {
525 } catch (Exception e) {
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.
537 * @return filename trimmed of interior fileName
540 public static String getZipRoot(String fileName) {
541 int pt = fileName.indexOf("|");
542 return (pt < 0 ? fileName : fileName.substring(0, pt));
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});
550 * return osw.getBufferedWriter();
554 return new BufferedWriter(osw);