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 private byte[] buffer; 036 037 private int bufpos; 038 private int buflen; 039 040 private final int maxLineLen; 041 042 public BufferedLineReaderInputStream( 043 final InputStream instream, 044 int buffersize, 045 int maxLineLen) { 046 super(instream); 047 if (instream == null) { 048 throw new IllegalArgumentException("Input stream may not be null"); 049 } 050 if (buffersize <= 0) { 051 throw new IllegalArgumentException("Buffer size may not be negative or zero"); 052 } 053 this.buffer = new byte[buffersize]; 054 this.bufpos = 0; 055 this.buflen = 0; 056 this.maxLineLen = maxLineLen; 057 this.truncated = false; 058 } 059 060 public BufferedLineReaderInputStream( 061 final InputStream instream, 062 int buffersize) { 063 this(instream, buffersize, -1); 064 } 065 066 private void expand(int newlen) { 067 byte newbuffer[] = new byte[newlen]; 068 int len = this.buflen - this.bufpos; 069 if (len > 0) { 070 System.arraycopy(this.buffer, this.bufpos, newbuffer, this.bufpos, len); 071 } 072 this.buffer = newbuffer; 073 } 074 075 public void ensureCapacity(int len) { 076 if (len > this.buffer.length) { 077 expand(len); 078 } 079 } 080 081 public int fillBuffer() throws IOException { 082 // compact the buffer if necessary 083 if (this.bufpos > 0) { 084 int len = this.buflen - this.bufpos; 085 if (len > 0) { 086 System.arraycopy(this.buffer, this.bufpos, this.buffer, 0, len); 087 } 088 this.bufpos = 0; 089 this.buflen = len; 090 } 091 int l; 092 int off = this.buflen; 093 int len = this.buffer.length - off; 094 l = in.read(this.buffer, off, len); 095 if (l == -1) { 096 return -1; 097 } else { 098 this.buflen = off + l; 099 return l; 100 } 101 } 102 103 public boolean hasBufferedData() { 104 return this.bufpos < this.buflen; 105 } 106 107 public void truncate() { 108 clear(); 109 this.truncated = true; 110 } 111 112 @Override 113 public int read() throws IOException { 114 if (this.truncated) { 115 return -1; 116 } 117 int noRead = 0; 118 while (!hasBufferedData()) { 119 noRead = fillBuffer(); 120 if (noRead == -1) { 121 return -1; 122 } 123 } 124 return this.buffer[this.bufpos++] & 0xff; 125 } 126 127 @Override 128 public int read(final byte[] b, int off, int len) throws IOException { 129 if (this.truncated) { 130 return -1; 131 } 132 if (b == null) { 133 return 0; 134 } 135 int noRead = 0; 136 while (!hasBufferedData()) { 137 noRead = fillBuffer(); 138 if (noRead == -1) { 139 return -1; 140 } 141 } 142 int chunk = this.buflen - this.bufpos; 143 if (chunk > len) { 144 chunk = len; 145 } 146 System.arraycopy(this.buffer, this.bufpos, b, off, chunk); 147 this.bufpos += chunk; 148 return chunk; 149 } 150 151 @Override 152 public int read(final byte[] b) throws IOException { 153 if (this.truncated) { 154 return -1; 155 } 156 if (b == null) { 157 return 0; 158 } 159 return read(b, 0, b.length); 160 } 161 162 @Override 163 public boolean markSupported() { 164 return false; 165 } 166 167 168 @Override 169 public int readLine(final ByteArrayBuffer dst) throws IOException { 170 if (dst == null) { 171 throw new IllegalArgumentException("Buffer may not be null"); 172 } 173 if (this.truncated) { 174 return -1; 175 } 176 int total = 0; 177 boolean found = false; 178 int bytesRead = 0; 179 while (!found) { 180 if (!hasBufferedData()) { 181 bytesRead = fillBuffer(); 182 if (bytesRead == -1) { 183 break; 184 } 185 } 186 int i = indexOf((byte)'\n'); 187 int chunk; 188 if (i != -1) { 189 found = true; 190 chunk = i + 1 - pos(); 191 } else { 192 chunk = length(); 193 } 194 if (chunk > 0) { 195 dst.append(buf(), pos(), chunk); 196 skip(chunk); 197 total += chunk; 198 } 199 if (this.maxLineLen > 0 && dst.length() >= this.maxLineLen) { 200 throw new MaxLineLimitException("Maximum line length limit exceeded"); 201 } 202 } 203 if (total == 0 && bytesRead == -1) { 204 return -1; 205 } else { 206 return total; 207 } 208 } 209 210 /** 211 * Implements quick search algorithm as published by 212 * <p> 213 * SUNDAY D.M., 1990, 214 * A very fast substring search algorithm, 215 * Communications of the ACM . 33(8):132-142. 216 * </p> 217 */ 218 public int indexOf(final byte[] pattern, int off, int len) { 219 if (pattern == null) { 220 throw new IllegalArgumentException("Pattern may not be null"); 221 } 222 if (off < this.bufpos || len < 0 || off + len > this.buflen) { 223 throw new IndexOutOfBoundsException(); 224 } 225 if (len < pattern.length) { 226 return -1; 227 } 228 229 int[] shiftTable = new int[256]; 230 for (int i = 0; i < shiftTable.length; i++) { 231 shiftTable[i] = pattern.length + 1; 232 } 233 for (int i = 0; i < pattern.length; i++) { 234 int x = pattern[i] & 0xff; 235 shiftTable[x] = pattern.length - i; 236 } 237 238 int j = 0; 239 while (j <= len - pattern.length) { 240 int cur = off + j; 241 boolean match = true; 242 for (int i = 0; i < pattern.length; i++) { 243 if (this.buffer[cur + i] != pattern[i]) { 244 match = false; 245 break; 246 } 247 } 248 if (match) { 249 return cur; 250 } 251 252 int pos = cur + pattern.length; 253 if (pos >= this.buffer.length) { 254 break; 255 } 256 int x = this.buffer[pos] & 0xff; 257 j += shiftTable[x]; 258 } 259 return -1; 260 } 261 262 /** 263 * Implements quick search algorithm as published by 264 * <p> 265 * SUNDAY D.M., 1990, 266 * A very fast substring search algorithm, 267 * Communications of the ACM . 33(8):132-142. 268 * </p> 269 */ 270 public int indexOf(final byte[] pattern) { 271 return indexOf(pattern, this.bufpos, this.buflen - this.bufpos); 272 } 273 274 public int indexOf(byte b, int off, int len) { 275 if (off < this.bufpos || len < 0 || off + len > this.buflen) { 276 throw new IndexOutOfBoundsException(); 277 } 278 for (int i = off; i < off + len; i++) { 279 if (this.buffer[i] == b) { 280 return i; 281 } 282 } 283 return -1; 284 } 285 286 public int indexOf(byte b) { 287 return indexOf(b, this.bufpos, this.buflen - this.bufpos); 288 } 289 290 public byte charAt(int pos) { 291 if (pos < this.bufpos || pos > this.buflen) { 292 throw new IndexOutOfBoundsException(); 293 } 294 return this.buffer[pos]; 295 } 296 297 public byte[] buf() { 298 return this.buffer; 299 } 300 301 public int pos() { 302 return this.bufpos; 303 } 304 305 public int limit() { 306 return this.buflen; 307 } 308 309 public int length() { 310 return this.buflen - this.bufpos; 311 } 312 313 public int capacity() { 314 return this.buffer.length; 315 } 316 317 public int skip(int n) { 318 int chunk = Math.min(n, this.buflen - this.bufpos); 319 this.bufpos += chunk; 320 return chunk; 321 } 322 323 public void clear() { 324 this.bufpos = 0; 325 this.buflen = 0; 326 } 327 328 @Override 329 public String toString() { 330 StringBuilder buffer = new StringBuilder(); 331 buffer.append("[pos: "); 332 buffer.append(this.bufpos); 333 buffer.append("]"); 334 buffer.append("[limit: "); 335 buffer.append(this.buflen); 336 buffer.append("]"); 337 buffer.append("["); 338 for (int i = this.bufpos; i < this.buflen; i++) { 339 buffer.append((char) this.buffer[i]); 340 } 341 buffer.append("]"); 342 return buffer.toString(); 343 } 344 345 }