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 020package org.apache.james.mime4j.io; 021 022import org.apache.james.mime4j.util.ByteArrayBuffer; 023 024import java.io.IOException; 025import 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 */ 031public 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}