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    }