diff --git a/src/main/java/info/fetter/logstashforwarder/util/KMPMatch.java b/src/main/java/info/fetter/logstashforwarder/util/KMPMatch.java new file mode 100644 index 0000000..f34006f --- /dev/null +++ b/src/main/java/info/fetter/logstashforwarder/util/KMPMatch.java @@ -0,0 +1,127 @@ +/* + * Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata + * + * Portions of this software were developed by the Unidata Program at the + * University Corporation for Atmospheric Research. + * + * Access and use of this software shall impose the following obligations + * and understandings on the user. The user is granted the right, without + * any fee or cost, to use, copy, modify, alter, enhance and distribute + * this software, and any derivative works thereof, and its supporting + * documentation for any purpose whatsoever, provided that this entire + * notice appears in all copies of the software, derivative works and + * supporting documentation. Further, UCAR requests that the user credit + * UCAR/Unidata in any publications that result from the use of this + * software or in any product that includes this software. The names UCAR + * and/or Unidata, however, may not be used in any advertising or publicity + * to endorse or promote any products or commercial entity unless specific + * written permission is obtained from UCAR/Unidata. The user also + * understands that UCAR/Unidata is not obligated to provide the user with + * any support, consulting, training or assistance of any kind with regard + * to the use, operation and performance of this software nor to provide + * the user with any updates, revisions, new versions or "bug fixes." + * + * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "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 UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. + */ +package info.fetter.logstashforwarder.util; + +/** + * Knuth-Morris-Pratt Algorithm for Pattern Matching. + * Immutable + * + * @author caron + * @see http://www.fmi.uni-sofia.bg/fmi/logic/vboutchkova/sources/KMPMatch_java.html + * @since May 9, 2008 + */ +public class KMPMatch { + + private final byte[] match; + private final int[] failure; + + /** + * Constructor + * @param match search for this byte pattern + */ + public KMPMatch(byte[] match) { + this.match = match; + failure = computeFailure(match); + } + + public int getMatchLength() { return match.length; } + + /** + * Finds the first occurrence of match in data. + * @param data search in this byte block + * @param start start at data[start] + * @param max end at data[start+max] + * @return index into data[] of first match, else -1 if not found. + */ + public int indexOf(byte[] data, int start, int max) { + int j = 0; + if (data.length == 0) return -1; + + for (int i = start; i < start + max; i++) { + while (j > 0 && match[j] != data[i]) + j = failure[j - 1]; + + if (match[j] == data[i]) + j++; + + if (j == match.length) + return i - match.length + 1; + + } + return -1; + } + + /* + * Finds the first occurrence of match in data. + * @param data search in this byte block + * @param start start at data[start] + * @param max end at data[start+max] + * @return index into block of first match, else -1 if not found. + * + public int scan(InputStream is, int start, int max) { + int j = 0; + if (data.length == 0) return -1; + + for (int i = start; i < start + max; i++) { + while (j > 0 && match[j] != data[i]) + j = failure[j - 1]; + + if (match[j] == data[i]) + j++; + + if (j == match.length) + return i - match.length + 1; + + } + return -1; + } // */ + + + private int[] computeFailure(byte[] match) { + int[] result = new int[match.length]; + + int j = 0; + for (int i = 1; i < match.length; i++) { + while (j > 0 && match[j] != match[i]) + j = result[j - 1]; + + if (match[i] == match[i]) + j++; + + result[i] = j; + } + + return result; + } +} + diff --git a/src/main/java/info/fetter/logstashforwarder/util/RandomAccessFile.java b/src/main/java/info/fetter/logstashforwarder/util/RandomAccessFile.java new file mode 100644 index 0000000..6a2afd4 --- /dev/null +++ b/src/main/java/info/fetter/logstashforwarder/util/RandomAccessFile.java @@ -0,0 +1,1738 @@ +/* + * Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata + * + * Portions of this software were developed by the Unidata Program at the + * University Corporation for Atmospheric Research. + * + * Access and use of this software shall impose the following obligations + * and understandings on the user. The user is granted the right, without + * any fee or cost, to use, copy, modify, alter, enhance and distribute + * this software, and any derivative works thereof, and its supporting + * documentation for any purpose whatsoever, provided that this entire + * notice appears in all copies of the software, derivative works and + * supporting documentation. Further, UCAR requests that the user credit + * UCAR/Unidata in any publications that result from the use of this + * software or in any product that includes this software. The names UCAR + * and/or Unidata, however, may not be used in any advertising or publicity + * to endorse or promote any products or commercial entity unless specific + * written permission is obtained from UCAR/Unidata. The user also + * understands that UCAR/Unidata is not obligated to provide the user with + * any support, consulting, training or assistance of any kind with regard + * to the use, operation and performance of this software nor to provide + * the user with any updates, revisions, new versions or "bug fixes." + * + * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "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 UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package info.fetter.logstashforwarder.util; + +import java.io.*; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.nio.channels.WritableByteChannel; + + +/** + * A buffered drop-in replacement for java.io.RandomAccessFile. + * Instances of this class realise substantial speed increases over + * java.io.RandomAccessFile through the use of buffering. This is a + * subclass of Object, as it was not possible to subclass + * java.io.RandomAccessFile because many of the methods are + * final. However, if it is necessary to use RandomAccessFile and + * java.io.RandomAccessFile interchangeably, both classes implement the + * DataInput and DataOutput interfaces. + *

+ *

By Russ Rew, based on + * BufferedRandomAccessFile by Alex McManus, based on Sun's source code + * for java.io.RandomAccessFile. For Alex McManus version from which + * this derives, see his + * Freeware Java Classes. + *

+ * + * @author Alex McManus + * @author Russ Rew + * @author john caron + * @see DataInput + * @see DataOutput + * @see java.io.RandomAccessFile + */ + +public class RandomAccessFile implements DataInput, DataOutput { + + static public final int BIG_ENDIAN = 0; + static public final int LITTLE_ENDIAN = 1; + + // debug leaks - keep track of open files + + + static protected boolean debugLeaks = false; + static protected boolean debugAccess = false; + static protected Set allFiles = new HashSet(); + static protected List openFiles = Collections.synchronizedList(new ArrayList()); + static private AtomicInteger debug_nseeks = new AtomicInteger(); + static private AtomicLong debug_nbytes = new AtomicLong(); + + /** + * Debugging, do not use. + * + * @return true if debugLeaks is on + */ + static public boolean getDebugLeaks() { + return debugLeaks; + } + + /** + * Debugging, do not use. + * + * @param b set true to track java.io.RandomAccessFile + */ + static public void setDebugLeaks(boolean b) { + debugLeaks = b; + } + + /** + * Debugging, do not use. + * + * @return list of open files. + */ + static public List getOpenFiles() { + return openFiles; + } + + static public List getAllFiles() { + List result = new ArrayList(); + if (null == allFiles) return null; + Iterator iter = allFiles.iterator(); + while (iter.hasNext()) { + result.add( iter.next()); + } + Collections.sort(result); + return result; + } + + /** + * Debugging, do not use. + * + * @param b to debug file reading + */ + static public void setDebugAccess(boolean b) { + debugAccess = b; + if (b) { + debug_nseeks = new AtomicInteger(); + debug_nbytes = new AtomicLong(); + } + } + + static public int getDebugNseeks() { + return (debug_nseeks == null) ? 0 : debug_nseeks.intValue(); + } + + static public long getDebugNbytes() { + return (debug_nbytes == null) ? 0 : debug_nbytes.longValue(); + } + + static protected boolean showOpen = false; + static protected boolean showRead = false; + + /** + * The default buffer size, in bytes. + */ + protected static final int defaultBufferSize = 8092; + + ///////////////////////////////////////////////////////////////////////////////////////////// + + /** + * File location + */ + protected String location; + + /** + * The underlying java.io.RandomAccessFile. + */ + protected java.io.RandomAccessFile file; + protected java.nio.channels.FileChannel fileChannel; + + /** + * The offset in bytes from the file start, of the next read or + * write operation. + */ + protected long filePosition; + + /** + * The buffer used for reading the data. + */ + protected byte buffer[]; + + /** + * The offset in bytes of the start of the buffer, from the start of the file. + */ + protected long bufferStart; + + /** + * The offset in bytes of the end of the data in the buffer, from + * the start of the file. This can be calculated from + * bufferStart + dataSize, but it is cached to speed + * up the read( ) method. + */ + protected long dataEnd; + + /** + * The size of the data stored in the buffer, in bytes. This may be + * less than the size of the buffer. + */ + protected int dataSize; + + /** + * True if we are at the end of the file. + */ + protected boolean endOfFile; + + /** + * The access mode of the file. + */ + protected boolean readonly; + + /** + * The current endian (big or little) mode of the file. + */ + protected boolean bigEndian; + + /** + * True if the data in the buffer has been modified. + */ + boolean bufferModified = false; + + /** + * make sure file is this long when closed + */ + private long minLength = 0; + + /** + * stupid extendMode for truncated, yet valid files - old code allowed NOFILL to do this + */ + boolean extendMode = false; + + /** + * Constructor, for subclasses + * + * @param bufferSize size of read buffer + */ + protected RandomAccessFile(int bufferSize) { + file = null; + readonly = true; + init(bufferSize); + } + + /** + * Constructor, default buffer size. + * + * @param location location of the file + * @param mode same as for java.io.RandomAccessFile + * @throws IOException on open error + */ + public RandomAccessFile(String location, String mode) throws IOException { + this(location, mode, defaultBufferSize); + this.location = location; + } + + /** + * Constructor. + * + * @param location location of the file + * @param mode same as for java.io.RandomAccessFile + * @param bufferSize size of buffer to use. + * @throws IOException on open error + */ + public RandomAccessFile(String location, String mode, int bufferSize) throws IOException { + this.location = location; + if (debugLeaks) { + allFiles.add(location); + if ((location.indexOf("01janN") >= 0) || (location.indexOf("02febN") >= 0)) + System.out.printf("HEY!%n"); + } + + this.file = new java.io.RandomAccessFile(location, mode); + this.readonly = mode.equals("r"); + init(bufferSize); + + if (debugLeaks) { + openFiles.add(location); + if (showOpen) System.out.println(" open " + location); + } + } + + /** + * Allow access to the underlying java.io.RandomAccessFile. + * WARNING! BROKEN ENCAPSOLATION, DO NOT USE. May change implementation in the future. + * + * @return the underlying java.io.RandomAccessFile. + */ + public java.io.RandomAccessFile getRandomAccessFile() { + return this.file; + } + + private void init(int bufferSize) { + // Initialise the buffer + bufferStart = 0; + dataEnd = 0; + dataSize = 0; + filePosition = 0; + buffer = new byte[bufferSize]; + endOfFile = false; + } + + /** + * Set the buffer size. + * If writing, call flush() first. + * + * @param bufferSize length in bytes + */ + public void setBufferSize(int bufferSize) { + init(bufferSize); + } + + /** + * Get the buffer size + * + * @return bufferSize length in bytes + */ + public int getBufferSize() { + return buffer.length; + } + + /** + * Close the file, and release any associated system resources. + * + * @throws IOException if an I/O error occurrs. + */ + public void close() throws IOException { + if (debugLeaks) { + openFiles.remove(location); + if (showOpen) System.out.println(" close " + location); + } + + if (file == null) + return; + + // If we are writing and the buffer has been modified, flush the contents + // of the buffer. + flush(); + + /* + if (!readonly && bufferModified) { + file.seek(bufferStart); + file.write(buffer, 0, dataSize); + } */ + + // may need to extend file, in case no fill is being used + // may need to truncate file in case overwriting a longer file + // use only if minLength is set (by N3iosp) + long fileSize = file.length(); + if (!readonly && (minLength != 0) && (minLength != fileSize)) { + file.setLength(minLength); + // System.out.println("TRUNCATE!!! minlength="+minLength); + } + + // Close the underlying file object. + file.close(); + file = null; // help the gc + } + + /** + * Return true if file pointer is at end of file. + * + * @return true if file pointer is at end of file + */ + public boolean isAtEndOfFile() { + return endOfFile; + } + + /** + * Set the position in the file for the next read or write. + * + * @param pos the offset (in bytes) from the start of the file. + * @throws IOException if an I/O error occurrs. + */ + public void seek(long pos) throws IOException { + + // If the seek is into the buffer, just update the file pointer. + if ((pos >= bufferStart) && (pos < dataEnd)) { + filePosition = pos; + return; + } + + // need new buffer, starting at pos + readBuffer(pos); + } + + protected void readBuffer(long pos) throws IOException { + // If the current buffer is modified, write it to disk. + if (bufferModified) { + flush(); + } + + bufferStart = pos; + filePosition = pos; + + dataSize = read_(pos, buffer, 0, buffer.length); + + if (dataSize <= 0) { + dataSize = 0; + endOfFile = true; + } else { + endOfFile = false; + } + + // Cache the position of the buffer end. + dataEnd = bufferStart + dataSize; + } + + /** + * Returns the current position in the file, where the next read or + * write will occur. + * + * @return the offset from the start of the file in bytes. + * @throws IOException if an I/O error occurrs. + */ + public long getFilePointer() throws IOException { + return filePosition; + } + + /** + * Get the file location, or name. + * + * @return file location + */ + public String getLocation() { + return location; + } + + /** + * Get the length of the file. The data in the buffer (which may not + * have been written the disk yet) is taken into account. + * + * @return the length of the file in bytes. + * @throws IOException if an I/O error occurrs. + */ + public long length() throws IOException { + long fileLength = file.length(); + if (fileLength < dataEnd) { + return dataEnd; + } else { + return fileLength; + } + } + + /** + * Change the current endian mode. Subsequent reads of short, int, float, double, long, char will + * use this. Does not currently affect writes. + * Default values is BIG_ENDIAN. + * + * @param endian RandomAccessFile.BIG_ENDIAN or RandomAccessFile.LITTLE_ENDIAN + */ + public void order(int endian) { + if (endian < 0) return; + this.bigEndian = (endian == BIG_ENDIAN); + } + + /** + * Returns the opaque file descriptor object associated with this file. + * + * @return the file descriptor object associated with this file. + * @throws IOException if an I/O error occurs. + */ + public FileDescriptor getFD() throws IOException { + return (file == null) ? null : file.getFD(); + } + + /** + * Copy the contents of the buffer to the disk. + * + * @throws IOException if an I/O error occurs. + */ + public void flush() throws IOException { + if (bufferModified) { + file.seek(bufferStart); + file.write(buffer, 0, dataSize); + //System.out.println("--flush at "+bufferStart+" dataSize= "+dataSize+ " filePosition= "+filePosition); + bufferModified = false; + } + + /* check min length + if (!readonly && (minLength != 0) && (minLength != file.length())) { + file.setLength(minLength); + } */ + } + + /** + * Make sure file is at least this long when its closed. + * needed when not using fill mode, and not all data is written. + * + * @param minLength minimum length of the file. + */ + public void setMinLength(long minLength) { + this.minLength = minLength; + } + + /** + * Set extendMode for truncated, yet valid files - old NetCDF code allowed this + * when NOFILL on, and user doesnt write all variables. + */ + public void setExtendMode() { + this.extendMode = true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Read primitives. + // + + /** + * Read a byte of data from the file, blocking until data is + * available. + * + * @return the next byte of data, or -1 if the end of the file is + * reached. + * @throws IOException if an I/O error occurrs. + */ + public int read() throws IOException { + + // If the file position is within the data, return the byte... + if (filePosition < dataEnd) { + int pos = (int) (filePosition - bufferStart); + filePosition++; + return (buffer[pos] & 0xff); + + // ...or should we indicate EOF... + } else if (endOfFile) { + return -1; + + // ...or seek to fill the buffer, and try again. + } else { + seek(filePosition); + return read(); + } + } + + /** + * Read up to len bytes into an array, at a specified + * offset. This will block until at least one byte has been read. + * + * @param b the byte array to receive the bytes. + * @param off the offset in the array where copying will start. + * @param len the number of bytes to copy. + * @return the actual number of bytes read, or -1 if there is not + * more data due to the end of the file being reached. + * @throws IOException if an I/O error occurrs. + */ + protected int readBytes(byte b[], int off, int len) throws IOException { + + // Check for end of file. + if (endOfFile) { + return -1; + } + + // See how many bytes are available in the buffer - if none, + // seek to the file position to update the buffer and try again. + int bytesAvailable = (int) (dataEnd - filePosition); + if (bytesAvailable < 1) { + seek(filePosition); + return readBytes(b, off, len); + } + + // Copy as much as we can. + int copyLength = (bytesAvailable >= len) + ? len + : bytesAvailable; + System.arraycopy(buffer, (int) (filePosition - bufferStart), b, off, copyLength); + filePosition += copyLength; + + // If there is more to copy... + if (copyLength < len) { + int extraCopy = len - copyLength; + + // If the amount remaining is more than a buffer's length, read it + // directly from the file. + if (extraCopy > buffer.length) { + extraCopy = read_(filePosition, b, off + copyLength, len - copyLength); + + // ...or read a new buffer full, and copy as much as possible... + } else { + seek(filePosition); + if (!endOfFile) { + extraCopy = (extraCopy > dataSize) + ? dataSize + : extraCopy; + System.arraycopy(buffer, 0, b, off + copyLength, extraCopy); + } else { + extraCopy = -1; + } + } + + // If we did manage to copy any more, update the file position and + // return the amount copied. + if (extraCopy > 0) { + filePosition += extraCopy; + return copyLength + extraCopy; + } + } + + // Return the amount copied. + return copyLength; + } + + /** + * Read nbytes bytes, at the specified file offset, send to a WritableByteChannel. + * This will block until all bytes are read. + * This uses the underlying file channel directly, bypassing all user buffers. + * + * @param dest write to this WritableByteChannel. + * @param offset the offset in the file where copying will start. + * @param nbytes the number of bytes to read. + * @return the actual number of bytes read and transfered + * @throws IOException if an I/O error occurs. + */ + public long readToByteChannel(WritableByteChannel dest, long offset, long nbytes) throws IOException { + + if (fileChannel == null) + fileChannel = file.getChannel(); + + long need = nbytes; + while (need > 0) { + long count = fileChannel.transferTo(offset, need, dest); + //if (count == 0) break; // LOOK not sure what the EOF condition is + need -= count; + offset += count; + } + return nbytes - need; + } + + + /** + * Read directly from file, without going through the buffer. + * All reading goes through here or readToByteChannel; + * + * @param pos start here in the file + * @param b put data into this buffer + * @param offset buffer offset + * @param len this number of bytes + * @return actual number of bytes read + * @throws IOException on io error + */ + protected int read_(long pos, byte[] b, int offset, int len) throws IOException { + file.seek(pos); + int n = file.read(b, offset, len); + if (debugAccess) { + if (showRead) System.out.println(" **read_ " + location + " = " + len + " bytes at " + pos + "; block = " + (pos / buffer.length)); + debug_nseeks.incrementAndGet(); + debug_nbytes.addAndGet(len); + } + + if (extendMode && (n < len)) { + //System.out.println(" read_ = "+len+" at "+pos+"; got = "+n); + n = len; + } + return n; + } + + /** + * Read up to len bytes into an array, at a specified + * offset. This will block until at least one byte has been read. + * + * @param b the byte array to receive the bytes. + * @param off the offset in the array where copying will start. + * @param len the number of bytes to copy. + * @return the actual number of bytes read, or -1 if there is not + * more data due to the end of the file being reached. + * @throws IOException if an I/O error occurrs. + */ + public int read(byte b[], int off, int len) throws IOException { + return readBytes(b, off, len); + } + + /** + * Read up to b.length( ) bytes into an array. This + * will block until at least one byte has been read. + * + * @param b the byte array to receive the bytes. + * @return the actual number of bytes read, or -1 if there is not + * more data due to the end of the file being reached. + * @throws IOException if an I/O error occurrs. + */ + public int read(byte b[]) throws IOException { + return readBytes(b, 0, b.length); + } + + /** + * Read fully count number of bytes + * + * @param count how many bytes tp read + * @return a byte array of length count, fully read in + * @throws IOException if an I/O error occurrs. + */ + public byte[] readBytes(int count) throws IOException { + byte[] b = new byte[count]; + readFully(b); + return b; + } + + /** + * Reads b.length bytes from this file into the byte + * array. This method reads repeatedly from the file until all the + * bytes are read. This method blocks until all the bytes are read, + * the end of the stream is detected, or an exception is thrown. + * + * @param b the buffer into which the data is read. + * @throws EOFException if this file reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ + public final void readFully(byte b[]) throws IOException { + readFully(b, 0, b.length); + } + + /** + * Reads exactly len bytes from this file into the byte + * array. This method reads repeatedly from the file until all the + * bytes are read. This method blocks until all the bytes are read, + * the end of the stream is detected, or an exception is thrown. + * + * @param b the buffer into which the data is read. + * @param off the start offset of the data. + * @param len the number of bytes to read. + * @throws EOFException if this file reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ + public final void readFully(byte b[], int off, int len) throws IOException { + int n = 0; + while (n < len) { + int count = this.read(b, off + n, len - n); + if (count < 0) { + throw new EOFException(); + } + n += count; + } + } + + /** + * Skips exactly n bytes of input. + * This method blocks until all the bytes are skipped, the end of + * the stream is detected, or an exception is thrown. + * + * @param n the number of bytes to be skipped. + * @return the number of bytes skipped, which is always n. + * @throws EOFException if this file reaches the end before skipping + * all the bytes. + * @throws IOException if an I/O error occurs. + */ + public int skipBytes(int n) throws IOException { + seek(getFilePointer() + n); + return n; + } + + /* public void skipToMultiple( int multipleOfBytes) throws IOException { + long pos = getFilePointer(); + int pad = (int) (pos % multipleOfBytes); + if (pad != 0) pad = multipleOfBytes - pad; + if (pad > 0) skipBytes(pad); + } */ + + /** + * Unread the last byte read. + * This method should not be used more than once + * between reading operations, or strange things might happen. + */ + public void unread() { + filePosition--; + } + + // + // Write primitives. + // + + /** + * Write a byte to the file. If the file has not been opened for + * writing, an IOException will be raised only when an attempt is + * made to write the buffer to the file. + *

+ * Caveat: the effects of seek( )ing beyond the end of the file are + * undefined. + * + * @param b write this byte + * @throws IOException if an I/O error occurrs. + */ + public void write(int b) throws IOException { + + // If the file position is within the block of data... + if (filePosition < dataEnd) { + int pos = (int) (filePosition - bufferStart); + buffer[pos] = (byte) b; + bufferModified = true; + filePosition++; + + // ...or (assuming that seek will not allow the file pointer + // to move beyond the end of the file) get the correct block of + // data... + } else { + + // If there is room in the buffer, expand it... + if (dataSize != buffer.length) { + int pos = (int) (filePosition - bufferStart); + buffer[pos] = (byte) b; + bufferModified = true; + filePosition++; + dataSize++; + dataEnd++; + + // ...or do another seek to get a new buffer, and start again... + } else { + seek(filePosition); + write(b); + } + } + } + + /** + * Write len bytes from an array to the file. + * + * @param b the array containing the data. + * @param off the offset in the array to the data. + * @param len the length of the data. + * @throws IOException if an I/O error occurrs. + */ + public void writeBytes(byte b[], int off, int len) throws IOException { + // If the amount of data is small (less than a full buffer)... + if (len < buffer.length) { + + // If any of the data fits within the buffer... + int spaceInBuffer = 0; + int copyLength = 0; + if (filePosition >= bufferStart) { + spaceInBuffer = (int) ((bufferStart + buffer.length) - filePosition); + } + + if (spaceInBuffer > 0) { + // Copy as much as possible to the buffer. + copyLength = (spaceInBuffer > len) ? len : spaceInBuffer; + System.arraycopy(b, off, buffer, (int) (filePosition - bufferStart), copyLength); + bufferModified = true; + long myDataEnd = filePosition + copyLength; + dataEnd = (myDataEnd > dataEnd) ? myDataEnd : dataEnd; + dataSize = (int) (dataEnd - bufferStart); + filePosition += copyLength; + ///System.out.println("--copy to buffer "+copyLength+" "+len); + } + + // If there is any data remaining, move to the new position and copy to + // the new buffer. + if (copyLength < len) { + //System.out.println("--need more "+copyLength+" "+len+" space= "+spaceInBuffer); + seek(filePosition); // triggers a flush + System.arraycopy(b, off + copyLength, buffer, (int) (filePosition - bufferStart), len - copyLength); + bufferModified = true; + long myDataEnd = filePosition + (len - copyLength); + dataEnd = (myDataEnd > dataEnd) ? myDataEnd : dataEnd; + dataSize = (int) (dataEnd - bufferStart); + filePosition += (len - copyLength); + } + + // ...or write a lot of data... + } else { + + // Flush the current buffer, and write this data to the file. + if (bufferModified) { + flush(); + } + file.seek(filePosition); // moved per Steve Cerruti; Jan 14, 2005 + file.write(b, off, len); + //System.out.println("--write at "+filePosition+" "+len); + + filePosition += len; + bufferStart = filePosition; // an empty buffer + dataSize = 0; + dataEnd = bufferStart + dataSize; + } + } + + /** + * Writes b.length bytes from the specified byte array + * starting at offset off to this file. + * + * @param b the data. + * @throws IOException if an I/O error occurs. + */ + public void write(byte b[]) throws IOException { + writeBytes(b, 0, b.length); + } + + /** + * Writes len bytes from the specified byte array + * starting at offset off to this file. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @throws IOException if an I/O error occurs. + */ + public void write(byte b[], int off, int len) throws IOException { + writeBytes(b, off, len); + } + + // + // DataInput methods. + // + + /** + * Reads a boolean from this file. This method reads a + * single byte from the file. A value of 0 represents + * false. Any other value represents true. + * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the boolean value read. + * @throws EOFException if this file has reached the end. + * @throws IOException if an I/O error occurs. + */ + public final boolean readBoolean() throws IOException { + int ch = this.read(); + if (ch < 0) { + throw new EOFException(); + } + return (ch != 0); + } + + /** + * Reads a signed 8-bit value from this file. This method reads a + * byte from the file. If the byte read is b, where + * 0 <= b <= 255, + * then the result is: + *

+ *

+ * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the next byte of this file as a signed 8-bit + * byte. + * @throws EOFException if this file has reached the end. + * @throws IOException if an I/O error occurs. + */ + public final byte readByte() throws IOException { + int ch = this.read(); + if (ch < 0) { + throw new EOFException(); + } + return (byte) (ch); + } + + /** + * Reads an unsigned 8-bit number from this file. This method reads + * a byte from this file and returns that byte. + *

+ * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the next byte of this file, interpreted as an unsigned + * 8-bit number. + * @throws EOFException if this file has reached the end. + * @throws IOException if an I/O error occurs. + */ + public final int readUnsignedByte() throws IOException { + int ch = this.read(); + if (ch < 0) { + throw new EOFException(); + } + return ch; + } + + /** + * Reads a signed 16-bit number from this file. The method reads 2 + * bytes from this file. If the two bytes read, in order, are + * b1 and b2, where each of the two values is + * between 0 and 255, inclusive, then the + * result is equal to: + *

+ *

+ * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this file, interpreted as a signed + * 16-bit number. + * @throws EOFException if this file reaches the end before reading + * two bytes. + * @throws IOException if an I/O error occurs. + */ + public final short readShort() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) { + throw new EOFException(); + } + if (bigEndian) { + return (short) ((ch1 << 8) + (ch2)); + } else { + return (short) ((ch2 << 8) + (ch1)); + } + } + + /** + * Read an array of shorts + * + * @param pa read into this array + * @param start starting at pa[start] + * @param n read this many elements + * @throws IOException on read error + */ + public final void readShort(short[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + pa[start + i] = readShort(); + } + } + + /** + * Reads an unsigned 16-bit number from this file. This method reads + * two bytes from the file. If the bytes read, in order, are + * b1 and b2, where + * 0 <= b1, b2 <= 255, + * then the result is equal to: + *

+ *

+ * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this file, interpreted as an unsigned + * 16-bit integer. + * @throws EOFException if this file reaches the end before reading + * two bytes. + * @throws IOException if an I/O error occurs. + */ + public final int readUnsignedShort() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) { + throw new EOFException(); + } + if (bigEndian) { + return ((ch1 << 8) + (ch2)); + } else { + return ((ch2 << 8) + (ch1)); + } + } + + + /* + * Reads a signed 24-bit integer from this file. This method reads 3 + * bytes from the file. If the bytes read, in order, are b1, + * b2, and b3, where + * 0 <= b1, b2, b3 <= 255, + * then the result is equal to: + *

+ *

+ * This method blocks until the three bytes are read, the end of the + * stream is detected, or an exception is thrown. + */ + + /** + * Reads a Unicode character from this file. This method reads two + * bytes from the file. If the bytes read, in order, are + * b1 and b2, where + * 0 <= b1, b2 <= 255, + * then the result is equal to: + *

+ *

+ * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this file as a Unicode character. + * @throws EOFException if this file reaches the end before reading + * two bytes. + * @throws IOException if an I/O error occurs. + */ + public final char readChar() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) { + throw new EOFException(); + } + if (bigEndian) { + return (char) ((ch1 << 8) + (ch2)); + } else { + return (char) ((ch2 << 8) + (ch1)); + } + } + + /** + * Reads a signed 32-bit integer from this file. This method reads 4 + * bytes from the file. If the bytes read, in order, are b1, + * b2, b3, and b4, where + * 0 <= b1, b2, b3, b4 <= 255, + * then the result is equal to: + *

+ *

+ * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this file, interpreted as an + * int. + * @throws EOFException if this file reaches the end before reading + * four bytes. + * @throws IOException if an I/O error occurs. + */ + public final int readInt() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + int ch3 = this.read(); + int ch4 = this.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) { + throw new EOFException(); + } + + if (bigEndian) { + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4)); + } else { + return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1)); + } + } + + /** + * Read an integer at the given position, bypassing all buffering. + * + * @param pos read a byte at this position + * @return The int that was read + * @throws IOException if an I/O error occurs. + */ + public final int readIntUnbuffered(long pos) throws IOException { + byte[] bb = new byte[4]; + read_(pos, bb, 0, 4); + int ch1 = bb[0] & 0xff; + int ch2 = bb[1] & 0xff; + int ch3 = bb[2] & 0xff; + int ch4 = bb[3] & 0xff; + if ((ch1 | ch2 | ch3 | ch4) < 0) { + throw new EOFException(); + } + + if (bigEndian) { + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4)); + } else { + return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1)); + } + } + + + /** + * Read an array of ints + * + * @param pa read into this array + * @param start starting at pa[start] + * @param n read this many elements + * @throws IOException on read error + */ + public final void readInt(int[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + pa[start + i] = readInt(); + } + } + + /** + * Reads a signed 64-bit integer from this file. This method reads eight + * bytes from the file. If the bytes read, in order, are + * b1, b2, b3, + * b4, b5, b6, + * b7, and b8, where: + *

+ *

+ * then the result is equal to: + *

+   *     ((long)b1 << 56) + ((long)b2 << 48)
+   *     + ((long)b3 << 40) + ((long)b4 << 32)
+   *     + ((long)b5 << 24) + ((long)b6 << 16)
+   *     + ((long)b7 << 8) + b8
+   * 
+ *

+ * This method blocks until the eight bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next eight bytes of this file, interpreted as a + * long. + * @throws EOFException if this file reaches the end before reading + * eight bytes. + * @throws IOException if an I/O error occurs. + */ + public final long readLong() throws IOException { + if (bigEndian) { + return ((long) (readInt()) << 32) + (readInt() & 0xFFFFFFFFL); // tested ok + } else { + return ((readInt() & 0xFFFFFFFFL) + ((long) readInt() << 32)); // not tested yet ?? + } + + /* int ch1 = this.read(); + int ch2 = this.read(); + int ch3 = this.read(); + int ch4 = this.read(); + int ch5 = this.read(); + int ch6 = this.read(); + int ch7 = this.read(); + int ch8 = this.read(); + if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) + throw new EOFException(); + + if (bigEndian) + return ((long)(ch1 << 56)) + (ch2 << 48) + (ch3 << 40) + (ch4 << 32) + (ch5 << 24) + (ch6 << 16) + (ch7 << 8) + (ch8 << 0)); + else + return ((long)(ch8 << 56) + (ch7 << 48) + (ch6 << 40) + (ch5 << 32) + (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0)); + */ + } + + /** + * Read an array of longs + * + * @param pa read into this array + * @param start starting at pa[start] + * @param n read this many elements + * @throws IOException on read error + */ + public final void readLong(long[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + pa[start + i] = readLong(); + } + } + + + /** + * Reads a float from this file. This method reads an + * int value as if by the readInt method + * and then converts that int to a float + * using the intBitsToFloat method in class + * Float. + *

+ * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this file, interpreted as a + * float. + * @throws EOFException if this file reaches the end before reading + * four bytes. + * @throws IOException if an I/O error occurs. + * @see java.io.RandomAccessFile#readInt() + * @see java.lang.Float#intBitsToFloat(int) + */ + public final float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + /** + * Read an array of floats + * + * @param pa read into this array + * @param start starting at pa[start] + * @param n read this many elements + * @throws IOException on read error + */ + public final void readFloat(float[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + pa[start + i] = Float.intBitsToFloat(readInt()); + } + } + + + /** + * Reads a double from this file. This method reads a + * long value as if by the readLong method + * and then converts that long to a double + * using the longBitsToDouble method in + * class Double. + *

+ * This method blocks until the eight bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next eight bytes of this file, interpreted as a + * double. + * @throws EOFException if this file reaches the end before reading + * eight bytes. + * @throws IOException if an I/O error occurs. + * @see java.io.RandomAccessFile#readLong() + * @see java.lang.Double#longBitsToDouble(long) + */ + public final double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + /** + * Read an array of doubles + * + * @param pa read into this array + * @param start starting at pa[start] + * @param n read this many elements + * @throws IOException on read error + */ + public final void readDouble(double[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + pa[start + i] = Double.longBitsToDouble(readLong()); + } + } + + /** + * Reads the next line of text from this file. This method + * successively reads bytes from the file until it reaches the end of + * a line of text. + *

+ *

+ * A line of text is terminated by a carriage-return character + * ('\r'), a newline character ('\n'), a + * carriage-return character immediately followed by a newline + * character, or the end of the input stream. The line-terminating + * character(s), if any, are included as part of the string returned. + *

+ *

+ * This method blocks until a newline character is read, a carriage + * return and the byte following it are read (to see if it is a + * newline), the end of the stream is detected, or an exception is thrown. + * + * @return the next line of text from this file. + * @throws IOException if an I/O error occurs. + */ + public final String readLine() throws IOException { + StringBuilder input = new StringBuilder(); + int c; + + while (((c = read()) != -1) && (c != '\n')) { + input.append((char) c); + } + if ((c == -1) && (input.length() == 0)) { + return null; + } + return input.toString(); + } + + /** + * Reads in a string from this file. The string has been encoded + * using a modified UTF-8 format. + *

+ * The first two bytes are read as if by + * readUnsignedShort. This value gives the number of + * following bytes that are in the encoded string, not + * the length of the resulting string. The following bytes are then + * interpreted as bytes encoding characters in the UTF-8 format + * and are converted into characters. + *

+ * This method blocks until all the bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return a Unicode string. + * @throws EOFException if this file reaches the end before + * reading all the bytes. + * @throws IOException if an I/O error occurs. + * @throws UTFDataFormatException if the bytes do not represent + * valid UTF-8 encoding of a Unicode string. + * @see java.io.RandomAccessFile#readUnsignedShort() + */ + public final String readUTF() throws IOException { + return DataInputStream.readUTF(this); + } + + /** + * Read a String of knoen length. + * + * @param nbytes number of bytes to read + * @return String wrapping the bytes. + * @throws IOException if an I/O error occurs. + */ + public String readString(int nbytes) throws IOException { + byte[] data = new byte[nbytes]; + readFully(data); + return new String(data); + } + + // + // DataOutput methods. + // + + /** + * Writes a boolean to the file as a 1-byte value. The + * value true is written out as the value + * (byte)1; the value false is written out + * as the value (byte)0. + * + * @param v a boolean value to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeBoolean(boolean v) throws IOException { + write(v ? 1 : 0); + } + + /** + * Write an array of booleans + * + * @param pa write from this array + * @param start starting with this element in the array + * @param n write this number of elements + * @throws IOException on read error + */ + public final void writeBoolean(boolean[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + writeBoolean(pa[start + i]); + } + } + + /** + * Writes a byte to the file as a 1-byte value. + * + * @param v a byte value to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeByte(int v) throws IOException { + write(v); + } + + /** + * Writes a short to the file as two bytes, high byte first. + * + * @param v a short to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeShort(int v) throws IOException { + write((v >>> 8) & 0xFF); + write((v) & 0xFF); + } + + /** + * Write an array of shorts + * + * @param pa write from this array + * @param start starting with this element in the array + * @param n this number of elements + * @throws IOException on read error + */ + public final void writeShort(short[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + writeShort(pa[start + i]); + } + } + + /** + * Writes a char to the file as a 2-byte value, high + * byte first. + * + * @param v a char value to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeChar(int v) throws IOException { + write((v >>> 8) & 0xFF); + write((v) & 0xFF); + } + + /** + * Write an array of chars + * + * @param pa write from this array + * @param start starting with this element in the array + * @param n this number of elements + * @throws IOException on read error + */ + public final void writeChar(char[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + writeChar(pa[start + i]); + } + } + + + /** + * Writes an int to the file as four bytes, high byte first. + * + * @param v an int to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeInt(int v) throws IOException { + write((v >>> 24) & 0xFF); + write((v >>> 16) & 0xFF); + write((v >>> 8) & 0xFF); + write((v) & 0xFF); + } + + /** + * Write an array of ints + * + * @param pa write from this array + * @param start starting with this element in the array + * @param n write this number of elements + * @throws IOException on read error + */ + public final void writeInt(int[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + writeInt(pa[start + i]); + } + } + + /** + * Writes a long to the file as eight bytes, high byte first. + * + * @param v a long to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeLong(long v) throws IOException { + write((int) (v >>> 56) & 0xFF); + write((int) (v >>> 48) & 0xFF); + write((int) (v >>> 40) & 0xFF); + write((int) (v >>> 32) & 0xFF); + write((int) (v >>> 24) & 0xFF); + write((int) (v >>> 16) & 0xFF); + write((int) (v >>> 8) & 0xFF); + write((int) (v) & 0xFF); + } + + /** + * Write an array of longs + * + * @param pa write from this array + * @param start starting with this element in the array + * @param n write this number of elements + * @throws IOException on read error + */ + public final void writeLong(long[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + writeLong(pa[start + i]); + } + } + + /** + * Converts the float argument to an int using the + * floatToIntBits method in class Float, + * and then writes that int value to the file as a + * 4-byte quantity, high byte first. + * + * @param v a float value to be written. + * @throws IOException if an I/O error occurs. + * @see java.lang.Float#floatToIntBits(float) + */ + public final void writeFloat(float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + /** + * Write an array of floats + * + * @param pa write from this array + * @param start starting with this element in the array + * @param n write this number of elements + * @throws IOException on read error + */ + public final void writeFloat(float[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + writeFloat(pa[start + i]); + } + } + + + /** + * Converts the double argument to a long using the + * doubleToLongBits method in class Double, + * and then writes that long value to the file as an + * 8-byte quantity, high byte first. + * + * @param v a double value to be written. + * @throws IOException if an I/O error occurs. + * @see java.lang.Double#doubleToLongBits(double) + */ + public final void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + /** + * Write an array of doubles + * + * @param pa write from this array + * @param start starting with this element in the array + * @param n write this number of elements + * @throws IOException on read error + */ + public final void writeDouble(double[] pa, int start, int n) throws IOException { + for (int i = 0; i < n; i++) { + writeDouble(pa[start + i]); + } + } + + /** + * Writes the string to the file as a sequence of bytes. Each + * character in the string is written out, in sequence, by discarding + * its high eight bits. + * + * @param s a string of bytes to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeBytes(String s) throws IOException { + int len = s.length(); + for (int i = 0; i < len; i++) { + write((byte) s.charAt(i)); + } + } + + /** + * Writes the character array to the file as a sequence of bytes. Each + * character in the string is written out, in sequence, by discarding + * its high eight bits. + * + * @param b a character array of bytes to be written. + * @param off the index of the first character to write. + * @param len the number of characters to write. + * @throws IOException if an I/O error occurs. + */ + public final void writeBytes(char b[], int off, int len) throws IOException { + for (int i = off; i < len; i++) { + write((byte) b[i]); + } + } + + /** + * Writes a string to the file as a sequence of characters. Each + * character is written to the data output stream as if by the + * writeChar method. + * + * @param s a String value to be written. + * @throws IOException if an I/O error occurs. + * @see java.io.RandomAccessFile#writeChar(int) + */ + public final void writeChars(String s) throws IOException { + int len = s.length(); + for (int i = 0; i < len; i++) { + int v = s.charAt(i); + write((v >>> 8) & 0xFF); + write((v) & 0xFF); + } + } + + /** + * Writes a string to the file using UTF-8 encoding in a + * machine-independent manner. + *

+ * First, two bytes are written to the file as if by the + * writeShort method giving the number of bytes to + * follow. This value is the number of bytes actually written out, + * not the length of the string. Following the length, each character + * of the string is output, in sequence, using the UTF-8 encoding + * for each character. + * + * @param str a string to be written. + * @throws IOException if an I/O error occurs. + */ + public final void writeUTF(String str) throws IOException { + int strlen = str.length(); + int utflen = 0; + + for (int i = 0; i < strlen; i++) { + int c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + utflen++; + } else if (c > 0x07FF) { + utflen += 3; + } else { + utflen += 2; + } + } + if (utflen > 65535) { + throw new UTFDataFormatException(); + } + + write((utflen >>> 8) & 0xFF); + write((utflen) & 0xFF); + for (int i = 0; i < strlen; i++) { + int c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + write(c); + } else if (c > 0x07FF) { + write(0xE0 | ((c >> 12) & 0x0F)); + write(0x80 | ((c >> 6) & 0x3F)); + write(0x80 | ((c) & 0x3F)); + } else { + write(0xC0 | ((c >> 6) & 0x1F)); + write(0x80 | ((c) & 0x3F)); + } + } + } + + /** + * Create a string representation of this object. + * + * @return a string representation of the state of the object. + */ + public String toString() { + return "fp=" + filePosition + ", bs=" + bufferStart + ", de=" + + dataEnd + ", ds=" + dataSize + ", bl=" + buffer.length + + ", readonly=" + readonly + ", bm=" + bufferModified; + } + + ///////////////////////////////////////////////// + + /** + * Search forward from the current pos, looking for a match. + * + * @param match the match to look for. + * @param maxBytes maximum number of bytes to search. use -1 for all + * @return true if found, file position will be at the start of the match. + * @throws IOException on read error + */ + public boolean searchForward(KMPMatch match, int maxBytes) throws IOException { + long start = getFilePointer(); + long last = (maxBytes < 0) ? length() : Math.min(length(), start + maxBytes); + long needToScan = last - start; + + // check what ever is now in the buffer + int bytesAvailable = (int) (dataEnd - filePosition); + if (bytesAvailable < 1) { + seek(filePosition); // read a new buffer + bytesAvailable = (int) (dataEnd - filePosition); + } + int bufStart = (int) (filePosition - bufferStart); + int scanBytes = (int) Math.min(bytesAvailable, needToScan); + int pos = match.indexOf(buffer, bufStart, scanBytes); + if (pos >= 0) { + seek(bufferStart + pos); + return true; + } + + int matchLen = match.getMatchLength(); + needToScan -= scanBytes - matchLen; + + while (needToScan > matchLen) { + readBuffer(dataEnd - matchLen); // force new buffer + + scanBytes = (int) Math.min(buffer.length, needToScan); + pos = match.indexOf(buffer, 0, scanBytes); + if (pos > 0) { + seek(bufferStart + pos); + return true; + } + + needToScan -= scanBytes - matchLen; + } + + // failure + seek(last); + return false; + } + +} + +