View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.james.mime4j.io;
21  
22  import org.apache.james.mime4j.util.ByteArrayBuffer;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  
27  /**
28   * Input buffer that can be used to search for patterns using Quick Search 
29   * algorithm in data read from an {@link InputStream}. 
30   */
31  public class BufferedLineReaderInputStream extends LineReaderInputStream {
32  
33      private boolean truncated;
34      
35      private byte[] buffer;
36      
37      private int bufpos;
38      private int buflen;
39      
40      private final int maxLineLen;
41      
42      public BufferedLineReaderInputStream(
43              final InputStream instream, 
44              int buffersize,
45              int maxLineLen) {
46          super(instream);
47          if (instream == null) {
48              throw new IllegalArgumentException("Input stream may not be null");
49          }
50          if (buffersize <= 0) {
51              throw new IllegalArgumentException("Buffer size may not be negative or zero");
52          }
53          this.buffer = new byte[buffersize];
54          this.bufpos = 0;
55          this.buflen = 0;
56          this.maxLineLen = maxLineLen;
57          this.truncated = false;
58      }
59  
60      public BufferedLineReaderInputStream(
61              final InputStream instream, 
62              int buffersize) {
63          this(instream, buffersize, -1);
64      }
65  
66      private void expand(int newlen) {
67          byte newbuffer[] = new byte[newlen];
68          int len = this.buflen - this.bufpos;
69          if (len > 0) {
70              System.arraycopy(this.buffer, this.bufpos, newbuffer, this.bufpos, len);
71          }
72          this.buffer = newbuffer;
73      }
74      
75      public void ensureCapacity(int len) {
76          if (len > this.buffer.length) {
77              expand(len);
78          }
79      }
80      
81      public int fillBuffer() throws IOException {
82          // compact the buffer if necessary
83          if (this.bufpos > 0) {
84              int len = this.buflen - this.bufpos;
85              if (len > 0) {
86                  System.arraycopy(this.buffer, this.bufpos, this.buffer, 0, len);
87              }
88              this.bufpos = 0;
89              this.buflen = len;
90          }
91          int l;
92          int off = this.buflen;
93          int len = this.buffer.length - off;
94          l = in.read(this.buffer, off, len);
95          if (l == -1) {
96              return -1;
97          } else {
98              this.buflen = off + l;
99              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 }