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    }