/* * #%L * Common package for I/O and related utilities * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package loci.common; import java.io.DataInput; import java.io.DataInputStream; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.nio.channels.Channel; /** * NIOInputStream provides methods for "intelligent" reading of files * and byte arrays. * */ public class NIOInputStream extends InputStream implements DataInput { // -- Constants -- /** * Block size to use when searching through the stream. * This value should not exceed MAX_OVERHEAD! */ protected static final int DEFAULT_BLOCK_SIZE = 256 * 1024; // 256 KB /** Maximum number of bytes to search when searching through the stream. */ protected static final int MAX_SEARCH_SIZE = 512 * 1024 * 1024; // 512 MB // -- Fields -- protected IRandomAccess raf; /** The file name. */ protected String filename; /** The file. */ protected File file; /** The file channel backed by the random access file. */ protected Channel channel; /** Endianness of the stream. */ protected boolean isLittleEndian; // -- Constructors -- /** Constructs an NIOInputStream around the given file. */ public NIOInputStream(String filename) throws IOException { this.filename = filename; file = new File(filename); raf = new FileHandle(file, "r"); } /** Constructs a random access stream around the given handle. */ public NIOInputStream(IRandomAccess handle) { raf = handle; } /** Constructs a random access stream around the given byte array. */ public NIOInputStream(byte[] array) { this(new ByteArrayHandle(array)); } // -- NIOInputStream API methods -- /** Returns the underlying InputStream. */ public DataInputStream getInputStream() { return null; } /** * Sets the number of bytes by which to extend the stream. This only applies * to InputStream API methods. */ public void setExtend(int extend) { } /** Seeks to the given offset within the stream. */ public void seek(long pos) throws IOException { raf.seek(pos); } /** Alias for readByte(). */ @Override public int read() throws IOException { return raf.readUnsignedByte(); } /** Gets the number of bytes in the file. */ public long length() throws IOException { return raf.length(); } /** Gets the current (absolute) file pointer. */ public long getFilePointer() { try { return raf.getFilePointer(); } catch (IOException e) { throw new RuntimeException(e); } } /** Closes the streams. */ @Override public void close() throws IOException { } /** Sets the endianness of the stream. */ public void order(boolean isLittleEndian) { this.isLittleEndian = isLittleEndian; } /** Gets the endianness of the stream. */ public boolean isLittleEndian() { return isLittleEndian; } /** * Reads a string ending with one of the characters in the given string. * * @see #findString(String...) */ public String readString(String lastChars) throws IOException { if (lastChars.length() == 1) return findString(lastChars); String[] terminators = new String[lastChars.length()]; for (int i=0; i<terminators.length; i++) { terminators[i] = lastChars.substring(i, i + 1); } return findString(terminators); } /** * Reads a string ending with one of the given terminating substrings. * * @param terminators The strings for which to search. * * @return The string from the initial position through the end of the * terminating sequence, or through the end of the stream if no * terminating sequence is found. */ public String findString(String... terminators) throws IOException { return findString(true, DEFAULT_BLOCK_SIZE, terminators); } /** * Reads or skips a string ending with * one of the given terminating substrings. * * @param saveString Whether to collect the string from the current file * pointer to the terminating bytes, and return it. If false, returns null. * @param terminators The strings for which to search. * * @throws IOException If saveString flag is set * and the maximum search length (512 MB) is exceeded. * * @return The string from the initial position through the end of the * terminating sequence, or through the end of the stream if no * terminating sequence is found, or null if saveString flag is unset. */ public String findString(boolean saveString, String... terminators) throws IOException { return findString(saveString, DEFAULT_BLOCK_SIZE, terminators); } /** * Reads a string ending with one of the given terminating * substrings, using the specified block size for buffering. * * @param blockSize The block size to use when reading bytes in chunks. * @param terminators The strings for which to search. * * @return The string from the initial position through the end of the * terminating sequence, or through the end of the stream if no * terminating sequence is found. */ public String findString(int blockSize, String... terminators) throws IOException { return findString(true, blockSize, terminators); } /** * Reads or skips a string ending with one of the given terminating * substrings, using the specified block size for buffering. * * @param saveString Whether to collect the string from the current file * pointer to the terminating bytes, and return it. If false, returns null. * @param blockSize The block size to use when reading bytes in chunks. * @param terminators The strings for which to search. * * @throws IOException If saveString flag is set * and the maximum search length (512 MB) is exceeded. * * @return The string from the initial position through the end of the * terminating sequence, or through the end of the stream if no * terminating sequence is found, or null if saveString flag is unset. */ public String findString(boolean saveString, int blockSize, String... terminators) throws IOException { StringBuilder out = new StringBuilder(); long startPos = getFilePointer(); long bytesDropped = 0; long inputLen = length(); long maxLen = inputLen - startPos; boolean tooLong = saveString && maxLen > MAX_SEARCH_SIZE; if (tooLong) maxLen = MAX_SEARCH_SIZE; boolean match = false; int maxTermLen = 0; for (String term : terminators) { int len = term.length(); if (len > maxTermLen) maxTermLen = len; } InputStreamReader in = new InputStreamReader(this, Constants.ENCODING); char[] buf = new char[blockSize]; long loc = 0; while (loc < maxLen) { long pos = startPos + loc; // if we're not saving the string, drop any old, unnecessary output if (!saveString) { int outLen = out.length(); if (outLen >= maxTermLen) { int dropIndex = outLen - maxTermLen + 1; String last = out.substring(dropIndex, outLen); out.setLength(0); out.append(last); bytesDropped += dropIndex; } } // read block from stream int num = blockSize; if (pos + blockSize > inputLen) num = (int) (inputLen - pos); int r = in.read(buf, 0, num); if (r <= 0) throw new IOException("Cannot read from stream: " + r); // append block to output out.append(buf, 0, r); // check output, returning smallest possible string int min = Integer.MAX_VALUE, tagLen = 0; for (int t=0; t<terminators.length; t++) { int len = terminators[t].length(); int start = (int) (loc - bytesDropped - len); int value = out.indexOf(terminators[t], start < 0 ? 0 : start); if (value >= 0 && value < min) { match = true; min = value; tagLen = len; } } if (match) { // reset stream to proper location seek(startPos + bytesDropped + min + tagLen); // trim output string if (saveString) { out.setLength(min + tagLen); return out.toString(); } return null; } loc += r; } // no match if (tooLong) throw new IOException("Maximum search length reached."); return null; } // -- DataInput API methods -- /** Read an input byte and return true if the byte is nonzero. */ @Override public boolean readBoolean() throws IOException { return raf.readBoolean(); } /** Read one byte and return it. */ @Override public byte readByte() throws IOException { return raf.readByte(); } /** Read an input char. */ @Override public char readChar() throws IOException { return raf.readChar(); } /** Read eight bytes and return a double value. */ @Override public double readDouble() throws IOException { return raf.readDouble(); } /** Read four bytes and return a float value. */ @Override public float readFloat() throws IOException { return raf.readFloat(); } /** Read four input bytes and return an int value. */ @Override public int readInt() throws IOException { return raf.readInt(); } /** Read the next line of text from the input stream. */ @Override public String readLine() throws IOException { return findString("\n"); } /** Read a string of arbitrary length, terminated by a null char. */ public String readCString() throws IOException { return findString("\0"); } /** Read a string of length n. */ public String readString(int n) throws IOException { byte[] b = new byte[n]; readFully(b); return new String(b, Constants.ENCODING); } /** Read eight input bytes and return a long value. */ @Override public long readLong() throws IOException { return raf.readLong(); } /** Read two input bytes and return a short value. */ @Override public short readShort() throws IOException { return raf.readShort(); } /** Read an input byte and zero extend it appropriately. */ @Override public int readUnsignedByte() throws IOException { return raf.readUnsignedByte(); } /** Read two bytes and return an int in the range 0 through 65535. */ @Override public int readUnsignedShort() throws IOException { return raf.readUnsignedShort(); } /** Read a string that has been encoded using a modified UTF-8 format. */ @Override public String readUTF() throws IOException { return null; } /** Skip n bytes within the stream. */ @Override public int skipBytes(int n) throws IOException { return raf.skipBytes(n); } /** Read bytes from the stream into the given array. */ @Override public int read(byte[] array) throws IOException { return read(array, 0, array.length); } /** * Read n bytes from the stream into the given array at the specified offset. */ @Override public int read(byte[] array, int offset, int n) throws IOException { return raf.read(array, offset, n); } /** Read bytes from the stream into the given array. */ @Override public void readFully(byte[] array) throws IOException { readFully(array, 0, array.length); } /** * Read n bytes from the stream into the given array at the specified offset. */ @Override public void readFully(byte[] array, int offset, int n) throws IOException { raf.readFully(array, offset, n); } // -- InputStream API methods -- @Override public int available() throws IOException { return 0; } @Override public void mark(int readLimit) { } @Override public boolean markSupported() { return false; } @Override public void reset() throws IOException { raf.seek(0); } }