3 * $Date: 2007-04-05 09:07:28 -0500 (Thu, 05 Apr 2007) $
\r
6 * Copyright (C) 2003-2005 The Jmol Development Team
\r
8 * Contact: jmol-developers@lists.sf.net
\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
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
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
24 package javajs.util;
\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
35 import java.util.Map;
\r
38 import javajs.api.GenericCifDataParser;
\r
39 import javajs.api.GenericLineReader;
\r
40 import javajs.api.GenericZipTools;
\r
43 * A general helper class for a variety of stream and reader functionality
\r
46 * stream and byte magic-number decoding for PNG, PNGJ, ZIP, and GZIP streams
\r
48 * various stream/reader methods, including UTF-encoded stream reading
\r
50 * reflection-protected access to a CIF parser and ZIP tools
\r
56 public class Rdr implements GenericLineReader {
\r
58 BufferedReader reader;
\r
60 public Rdr(BufferedReader reader) {
\r
61 this.reader = reader;
\r
65 public String readNextLine() throws Exception {
\r
66 return reader.readLine();
\r
69 public static Map<String, Object> readCifData(GenericCifDataParser parser, BufferedReader br) {
\r
70 return parser.set(null, br).getAllCifData();
\r
76 public static String fixUTF(byte[] bytes) {
\r
77 Encoding encoding = getUTFEncoding(bytes);
\r
78 if (encoding != Encoding.NONE)
\r
80 String s = new String(bytes, encoding.name().replace('_', '-'));
\r
85 // extra byte at beginning removed
\r
92 } catch (UnsupportedEncodingException e) {
\r
93 System.out.println(e);
\r
95 return new String(bytes);
\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
115 ////////// stream type checking //////////
\r
118 private static Encoding getUTFEncodingForStream(BufferedInputStream is) throws IOException {
\r
122 * is.resetStream();
\r
127 byte[] abMagic = new byte[4];
\r
131 } catch (Exception e) {
\r
132 return Encoding.NONE;
\r
134 is.read(abMagic, 0, 4);
\r
136 return getUTFEncoding(abMagic);
\r
139 public static boolean isBase64(SB sb) {
\r
140 return (sb.indexOf(";base64,") == 0);
\r
143 public static boolean isCompoundDocumentS(InputStream is) {
\r
144 return isCompoundDocumentB(getMagic(is, 8));
\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
155 public static boolean isGzipS(InputStream is) {
\r
156 return isGzipB(getMagic(is, 2));
\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
164 public static boolean isPickleS(InputStream is) {
\r
165 return Rdr.isPickleB(getMagic(is, 2));
\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
173 public static boolean isPngZipStream(InputStream is) {
\r
174 return isPngZipB(getMagic(is, 55));
\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
182 public static boolean isZipS(InputStream is) {
\r
183 return isZipB(getMagic(is, 4));
\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
194 private static byte[] getMagic(InputStream is, int n) {
\r
195 byte[] abMagic = new byte[n];
\r
199 * is.resetStream();
\r
206 is.read(abMagic, 0, n);
\r
207 } catch (IOException e) {
\r
211 } catch (IOException e) {
\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
220 return "image/jpg"; // 0xFF 0x00 ...
\r
222 return "image/gif"; // GIF89a...
\r
224 return "image/BMP"; // BM...
\r
226 return "image/png";
\r
228 return "image/unknown";
\r
233 ////////// stream/byte methods ///////////
\r
235 public static BufferedInputStream getBIS(byte[] bytes) {
\r
236 return new BufferedInputStream(new ByteArrayInputStream(bytes));
\r
239 public static BufferedReader getBR(String string) {
\r
240 return new BufferedReader(new StringReader(string));
\r
244 * Drill down into a GZIP stack until no more layers.
\r
248 * @return non-gzipped buffered input stream.
\r
250 * @throws IOException
\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
259 * Allow for base64-encoding check.
\r
262 * @return byte array
\r
264 public static byte[] getBytesFromSB(SB sb) {
\r
265 return (isBase64(sb) ? Base64.decodeBase64(sb.substring(8)) : sb.toBytes(0, -1));
\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
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
276 * @throws IOException
\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
284 while ((len = bis.read(buf, 0, 1024)) > 0) {
\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
291 out.write(buf, 0, len);
\r
296 return AU.arrayCopyByte(bytes, totalLen);
\r
298 return totalLen + " bytes";
\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
308 * @throws IOException
\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
317 return getBR(charSet == null ? fixUTF(bytes) : new String(bytes, charSet));
\r
321 * Read a possibly limited number of bytes (when n > 0) from a stream,
\r
322 * leaving the stream open.
\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
328 * @throws IOException
\r
330 public static byte[] getLimitedStreamBytes(InputStream is, long n)
\r
331 throws IOException {
\r
333 //Note: You cannot use InputStream.available() to reliably read
\r
334 // zip data from the web.
\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
342 n = Integer.MAX_VALUE;
\r
343 while (totalLen < n && (len = is.read(buf, 0, buflen)) > 0) {
\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
352 if (totalLen == bytes.length)
\r
354 buf = new byte[totalLen];
\r
355 System.arraycopy(bytes, 0, buf, 0, totalLen);
\r
361 * Read a UTF-8 stream fully, converting it to a String.
\r
362 * Called by Jmol's XMLReaders
\r
365 * @return a UTF-8 string
\r
367 public static String StreamToUTF8String(BufferedInputStream bis) {
\r
368 String[] data = new String[1];
\r
370 readAllAsString(getBufferedReader(bis, "UTF-8"), -1, true, data, 0);
\r
371 } catch (IOException e) {
\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
383 * @param allowBinary
\r
386 * @return true if data[i] holds the data; false if data[i] holds an error message.
\r
388 public static boolean readAllAsString(BufferedReader br, int nBytesMax, boolean allowBinary, String[] data, int i) {
\r
390 SB sb = SB.newN(8192);
\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
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
412 data[i] = sb.toString();
\r
414 } catch (Exception ioe) {
\r
415 data[i] = ioe.toString();
\r
421 /////////// PNGJ support /////////////
\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
429 * @return same stream or byte stream
\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
439 static void getPngZipPointAndCount(BufferedInputStream bis, int[] pt_count) {
\r
442 byte[] data = getLimitedStreamBytes(bis, 74);
\r
445 for (int i = 64, f = 1; --i > 54; f *= 10)
\r
446 pt += (data[i] - '0') * f;
\r
448 for (int i = 74, f = 1; --i > 64; f *= 10)
\r
449 n += (data[i] - '0') * f;
\r
452 } catch (Throwable e) {
\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
463 * @param asNewStream
\r
464 * @return new buffered ByteArrayInputStream, possibly with no data if there is an error
\r
466 public static BufferedInputStream getPngZipStream(BufferedInputStream bis, boolean asNewStream) {
\r
467 if (!isPngZipStream(bis))
\r
469 byte[] data = new byte[0];
\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
477 pt -= bis.skip(pt);
\r
480 data = getLimitedStreamBytes(bis, pt_count[1]);
\r
482 } catch (Throwable e) {
\r
487 } catch (Exception e) {
\r
491 return getBIS(data);
\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
499 * @return filename trimmed of interior fileName
\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