/**************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * * or more contributor license agreements. See the NOTICE file * * distributed with this work for additional information * * regarding copyright ownership. The ASF licenses this file * * to you under the Apache License, Version 2.0 (the * * "License"); you may not use this file except in compliance * * with the License. You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, * * software distributed under the License is distributed on an * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * * KIND, either express or implied. See the License for the * * specific language governing permissions and limitations * * under the License. * ****************************************************************/ package org.apache.james.mime4j.io; import org.apache.james.mime4j.util.ByteArrayBuffer; import java.io.IOException; import java.io.InputStream; /** * Input buffer that can be used to search for patterns using Quick Search * algorithm in data read from an {@link InputStream}. */ public class BufferedLineReaderInputStream extends LineReaderInputStream { private boolean truncated; private byte[] buffer; private int bufpos; private int buflen; private final int maxLineLen; public BufferedLineReaderInputStream( final InputStream instream, int buffersize, int maxLineLen) { super(instream); if (instream == null) { throw new IllegalArgumentException("Input stream may not be null"); } if (buffersize <= 0) { throw new IllegalArgumentException("Buffer size may not be negative or zero"); } this.buffer = new byte[buffersize]; this.bufpos = 0; this.buflen = 0; this.maxLineLen = maxLineLen; this.truncated = false; } public BufferedLineReaderInputStream( final InputStream instream, int buffersize) { this(instream, buffersize, -1); } private void expand(int newlen) { byte newbuffer[] = new byte[newlen]; int len = this.buflen - this.bufpos; if (len > 0) { System.arraycopy(this.buffer, this.bufpos, newbuffer, this.bufpos, len); } this.buffer = newbuffer; } public void ensureCapacity(int len) { if (len > this.buffer.length) { expand(len); } } public int fillBuffer() throws IOException { // compact the buffer if necessary if (this.bufpos > 0) { int len = this.buflen - this.bufpos; if (len > 0) { System.arraycopy(this.buffer, this.bufpos, this.buffer, 0, len); } this.bufpos = 0; this.buflen = len; } int l; int off = this.buflen; int len = this.buffer.length - off; l = in.read(this.buffer, off, len); if (l == -1) { return -1; } else { this.buflen = off + l; return l; } } public boolean hasBufferedData() { return this.bufpos < this.buflen; } public void truncate() { clear(); this.truncated = true; } @Override public int read() throws IOException { if (this.truncated) { return -1; } int noRead = 0; while (!hasBufferedData()) { noRead = fillBuffer(); if (noRead == -1) { return -1; } } return this.buffer[this.bufpos++] & 0xff; } @Override public int read(final byte[] b, int off, int len) throws IOException { if (this.truncated) { return -1; } if (b == null) { return 0; } int noRead = 0; while (!hasBufferedData()) { noRead = fillBuffer(); if (noRead == -1) { return -1; } } int chunk = this.buflen - this.bufpos; if (chunk > len) { chunk = len; } System.arraycopy(this.buffer, this.bufpos, b, off, chunk); this.bufpos += chunk; return chunk; } @Override public int read(final byte[] b) throws IOException { if (this.truncated) { return -1; } if (b == null) { return 0; } return read(b, 0, b.length); } @Override public boolean markSupported() { return false; } @Override public int readLine(final ByteArrayBuffer dst) throws IOException { if (dst == null) { throw new IllegalArgumentException("Buffer may not be null"); } if (this.truncated) { return -1; } int total = 0; boolean found = false; int bytesRead = 0; while (!found) { if (!hasBufferedData()) { bytesRead = fillBuffer(); if (bytesRead == -1) { break; } } int i = indexOf((byte)'\n'); int chunk; if (i != -1) { found = true; chunk = i + 1 - pos(); } else { chunk = length(); } if (chunk > 0) { dst.append(buf(), pos(), chunk); skip(chunk); total += chunk; } if (this.maxLineLen > 0 && dst.length() >= this.maxLineLen) { throw new MaxLineLimitException("Maximum line length limit exceeded"); } } if (total == 0 && bytesRead == -1) { return -1; } else { return total; } } /** * Implements quick search algorithm as published by * <p> * SUNDAY D.M., 1990, * A very fast substring search algorithm, * Communications of the ACM . 33(8):132-142. * </p> */ public int indexOf(final byte[] pattern, int off, int len) { if (pattern == null) { throw new IllegalArgumentException("Pattern may not be null"); } if (off < this.bufpos || len < 0 || off + len > this.buflen) { throw new IndexOutOfBoundsException(); } if (len < pattern.length) { return -1; } int[] shiftTable = new int[256]; for (int i = 0; i < shiftTable.length; i++) { shiftTable[i] = pattern.length + 1; } for (int i = 0; i < pattern.length; i++) { int x = pattern[i] & 0xff; shiftTable[x] = pattern.length - i; } int j = 0; while (j <= len - pattern.length) { int cur = off + j; boolean match = true; for (int i = 0; i < pattern.length; i++) { if (this.buffer[cur + i] != pattern[i]) { match = false; break; } } if (match) { return cur; } int pos = cur + pattern.length; if (pos >= this.buffer.length) { break; } int x = this.buffer[pos] & 0xff; j += shiftTable[x]; } return -1; } /** * Implements quick search algorithm as published by * <p> * SUNDAY D.M., 1990, * A very fast substring search algorithm, * Communications of the ACM . 33(8):132-142. * </p> */ public int indexOf(final byte[] pattern) { return indexOf(pattern, this.bufpos, this.buflen - this.bufpos); } public int indexOf(byte b, int off, int len) { if (off < this.bufpos || len < 0 || off + len > this.buflen) { throw new IndexOutOfBoundsException(); } for (int i = off; i < off + len; i++) { if (this.buffer[i] == b) { return i; } } return -1; } public int indexOf(byte b) { return indexOf(b, this.bufpos, this.buflen - this.bufpos); } public byte charAt(int pos) { if (pos < this.bufpos || pos > this.buflen) { throw new IndexOutOfBoundsException(); } return this.buffer[pos]; } public byte[] buf() { return this.buffer; } public int pos() { return this.bufpos; } public int limit() { return this.buflen; } public int length() { return this.buflen - this.bufpos; } public int capacity() { return this.buffer.length; } public int skip(int n) { int chunk = Math.min(n, this.buflen - this.bufpos); this.bufpos += chunk; return chunk; } public void clear() { this.bufpos = 0; this.buflen = 0; } @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("[pos: "); buffer.append(this.bufpos); buffer.append("]"); buffer.append("[limit: "); buffer.append(this.buflen); buffer.append("]"); buffer.append("["); for (int i = this.bufpos; i < this.buflen; i++) { buffer.append((char) this.buffer[i]); } buffer.append("]"); return buffer.toString(); } }