001 /****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one *
003 * or more contributor license agreements. See the NOTICE file *
004 * distributed with this work for additional information *
005 * regarding copyright ownership. The ASF licenses this file *
006 * to you under the Apache License, Version 2.0 (the *
007 * "License"); you may not use this file except in compliance *
008 * with the License. You may obtain a copy of the License at *
009 * *
010 * http://www.apache.org/licenses/LICENSE-2.0 *
011 * *
012 * Unless required by applicable law or agreed to in writing, *
013 * software distributed under the License is distributed on an *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015 * KIND, either express or implied. See the License for the *
016 * specific language governing permissions and limitations *
017 * under the License. *
018 ****************************************************************/
019
020 package org.apache.james.mime4j.io;
021
022 import org.apache.james.mime4j.util.ByteArrayBuffer;
023
024 import java.io.IOException;
025 import java.io.InputStream;
026
027 /**
028 * Input buffer that can be used to search for patterns using Quick Search
029 * algorithm in data read from an {@link InputStream}.
030 */
031 public class BufferedLineReaderInputStream extends LineReaderInputStream {
032
033 private boolean truncated;
034
035 boolean tempBuffer = false;
036
037 private byte[] origBuffer;
038 private int origBufpos;
039 private int origBuflen;
040
041 private byte[] buffer;
042 private int bufpos;
043 private int buflen;
044
045 private final int maxLineLen;
046
047 public BufferedLineReaderInputStream(
048 final InputStream instream,
049 int buffersize,
050 int maxLineLen) {
051 super(instream);
052 if (instream == null) {
053 throw new IllegalArgumentException("Input stream may not be null");
054 }
055 if (buffersize <= 0) {
056 throw new IllegalArgumentException("Buffer size may not be negative or zero");
057 }
058 this.buffer = new byte[buffersize];
059 this.bufpos = 0;
060 this.buflen = 0;
061 this.maxLineLen = maxLineLen;
062 this.truncated = false;
063 }
064
065 public BufferedLineReaderInputStream(
066 final InputStream instream,
067 int buffersize) {
068 this(instream, buffersize, -1);
069 }
070
071 private void expand(int newlen) {
072 byte newbuffer[] = new byte[newlen];
073 int len = bufferLen();
074 if (len > 0) {
075 System.arraycopy(this.buffer, this.bufpos, newbuffer, this.bufpos, len);
076 }
077 this.buffer = newbuffer;
078 }
079
080 public void ensureCapacity(int len) {
081 if (len > this.buffer.length) {
082 expand(len);
083 }
084 }
085
086 public int fillBuffer() throws IOException {
087 if (tempBuffer) {
088 // we was on tempBuffer.
089 // check that we completed the tempBuffer
090 if (bufpos != buflen) throw new IllegalStateException("unread only works when a buffer is fully read before the next refill is asked!");
091 // restore the original buffer
092 buffer = origBuffer;
093 buflen = origBuflen;
094 bufpos = origBufpos;
095 tempBuffer = false;
096 // return that we just read bufferLen data.
097 return bufferLen();
098 }
099 // compact the buffer if necessary
100 if (this.bufpos > 0) { // could swtich to (this.buffer.length / 2) but needs a 4*boundary capacity, then (instead of 2).
101 int len = bufferLen();
102 if (len > 0) {
103 System.arraycopy(this.buffer, this.bufpos, this.buffer, 0, len);
104 }
105 this.bufpos = 0;
106 this.buflen = len;
107 }
108 int l;
109 int off = this.buflen;
110 int len = this.buffer.length - off;
111 l = in.read(this.buffer, off, len);
112 if (l == -1) {
113 return -1;
114 } else {
115 this.buflen = off + l;
116 return l;
117 }
118 }
119
120 private int bufferLen() {
121 return this.buflen - this.bufpos;
122 }
123
124 public boolean hasBufferedData() {
125 return bufferLen() > 0;
126 }
127
128 public void truncate() {
129 clear();
130 this.truncated = true;
131 }
132
133 protected boolean readAllowed() {
134 return !this.truncated;
135 }
136
137 @Override
138 public int read() throws IOException {
139 if (!readAllowed()) return -1;
140 int noRead = 0;
141 while (!hasBufferedData()) {
142 noRead = fillBuffer();
143 if (noRead == -1) {
144 return -1;
145 }
146 }
147 return this.buffer[this.bufpos++] & 0xff;
148 }
149
150 @Override
151 public int read(final byte[] b, int off, int len) throws IOException {
152 if (!readAllowed()) return -1;
153 if (b == null) {
154 return 0;
155 }
156 int noRead = 0;
157 while (!hasBufferedData()) {
158 noRead = fillBuffer();
159 if (noRead == -1) {
160 return -1;
161 }
162 }
163 int chunk = bufferLen();
164 if (chunk > len) {
165 chunk = len;
166 }
167 System.arraycopy(this.buffer, this.bufpos, b, off, chunk);
168 this.bufpos += chunk;
169 return chunk;
170 }
171
172 @Override
173 public int read(final byte[] b) throws IOException {
174 if (!readAllowed()) return -1;
175 if (b == null) {
176 return 0;
177 }
178 return read(b, 0, b.length);
179 }
180
181 @Override
182 public boolean markSupported() {
183 return false;
184 }
185
186 @Override
187 public int readLine(final ByteArrayBuffer dst)
188 throws MaxLineLimitException, IOException {
189 if (dst == null) {
190 throw new IllegalArgumentException("Buffer may not be null");
191 }
192 if (!readAllowed()) return -1;
193
194 int total = 0;
195 boolean found = false;
196 int bytesRead = 0;
197 while (!found) {
198 if (!hasBufferedData()) {
199 bytesRead = fillBuffer();
200 if (bytesRead == -1) {
201 break;
202 }
203 }
204 int i = indexOf((byte)'\n');
205 int chunk;
206 if (i != -1) {
207 found = true;
208 chunk = i + 1 - pos();
209 } else {
210 chunk = length();
211 }
212 if (chunk > 0) {
213 dst.append(buf(), pos(), chunk);
214 skip(chunk);
215 total += chunk;
216 }
217 if (this.maxLineLen > 0 && dst.length() >= this.maxLineLen) {
218 throw new MaxLineLimitException("Maximum line length limit exceeded");
219 }
220 }
221 if (total == 0 && bytesRead == -1) {
222 return -1;
223 } else {
224 return total;
225 }
226 }
227
228 /**
229 * Implements quick search algorithm as published by
230 * <p>
231 * SUNDAY D.M., 1990,
232 * A very fast substring search algorithm,
233 * Communications of the ACM . 33(8):132-142.
234 * </p>
235 */
236 public int indexOf(final byte[] pattern, int off, int len) {
237 if (pattern == null) {
238 throw new IllegalArgumentException("Pattern may not be null");
239 }
240 if (off < this.bufpos || len < 0 || off + len > this.buflen) {
241 throw new IndexOutOfBoundsException("looking for "+off+"("+len+")"+" in "+bufpos+"/"+buflen);
242 }
243 if (len < pattern.length) {
244 return -1;
245 }
246
247 int[] shiftTable = new int[256];
248 for (int i = 0; i < shiftTable.length; i++) {
249 shiftTable[i] = pattern.length + 1;
250 }
251 for (int i = 0; i < pattern.length; i++) {
252 int x = pattern[i] & 0xff;
253 shiftTable[x] = pattern.length - i;
254 }
255
256 int j = 0;
257 while (j <= len - pattern.length) {
258 int cur = off + j;
259 boolean match = true;
260 for (int i = 0; i < pattern.length; i++) {
261 if (this.buffer[cur + i] != pattern[i]) {
262 match = false;
263 break;
264 }
265 }
266 if (match) {
267 return cur;
268 }
269
270 int pos = cur + pattern.length;
271 if (pos >= this.buffer.length) {
272 break;
273 }
274 int x = this.buffer[pos] & 0xff;
275 j += shiftTable[x];
276 }
277 return -1;
278 }
279
280 /**
281 * Implements quick search algorithm as published by
282 * <p>
283 * SUNDAY D.M., 1990,
284 * A very fast substring search algorithm,
285 * Communications of the ACM . 33(8):132-142.
286 * </p>
287 */
288 public int indexOf(final byte[] pattern) {
289 return indexOf(pattern, this.bufpos, this.buflen - this.bufpos);
290 }
291
292 public int indexOf(byte b, int off, int len) {
293 if (off < this.bufpos || len < 0 || off + len > this.buflen) {
294 throw new IndexOutOfBoundsException();
295 }
296 for (int i = off; i < off + len; i++) {
297 if (this.buffer[i] == b) {
298 return i;
299 }
300 }
301 return -1;
302 }
303
304 public int indexOf(byte b) {
305 return indexOf(b, this.bufpos, bufferLen());
306 }
307
308 public int byteAt(int pos) {
309 if (pos < this.bufpos || pos > this.buflen) {
310 throw new IndexOutOfBoundsException("looking for "+pos+" in "+bufpos+"/"+buflen);
311 }
312 return this.buffer[pos] & 0xff;
313 }
314
315 protected byte[] buf() {
316 return this.buffer;
317 }
318
319 protected int pos() {
320 return this.bufpos;
321 }
322
323 protected int limit() {
324 return this.buflen;
325 }
326
327 protected int length() {
328 return bufferLen();
329 }
330
331 public int capacity() {
332 return this.buffer.length;
333 }
334
335 protected int skip(int n) {
336 int chunk = Math.min(n, bufferLen());
337 this.bufpos += chunk;
338 return chunk;
339 }
340
341 private void clear() {
342 this.bufpos = 0;
343 this.buflen = 0;
344 }
345
346 @Override
347 public String toString() {
348 StringBuilder buffer = new StringBuilder();
349 buffer.append("[pos: ");
350 buffer.append(this.bufpos);
351 buffer.append("]");
352 buffer.append("[limit: ");
353 buffer.append(this.buflen);
354 buffer.append("]");
355 buffer.append("[");
356 for (int i = this.bufpos; i < this.buflen; i++) {
357 buffer.append((char) this.buffer[i]);
358 }
359 buffer.append("]");
360 if (tempBuffer) {
361 buffer.append("-ORIG[pos: ");
362 buffer.append(this.origBufpos);
363 buffer.append("]");
364 buffer.append("[limit: ");
365 buffer.append(this.origBuflen);
366 buffer.append("]");
367 buffer.append("[");
368 for (int i = this.origBufpos; i < this.origBuflen; i++) {
369 buffer.append((char) this.origBuffer[i]);
370 }
371 buffer.append("]");
372 }
373 return buffer.toString();
374 }
375
376 @Override
377 public boolean unread(ByteArrayBuffer buf) {
378 if (tempBuffer) return false;
379 origBuffer = buffer;
380 origBuflen = buflen;
381 origBufpos = bufpos;
382 bufpos = 0;
383 buflen = buf.length();
384 buffer = buf.buffer();
385 tempBuffer = true;
386 return true;
387 }
388
389 }