View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2000 Myles Chippendale
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     Myles Chippendale <mylesc At paneris.org>
42   *     http://paneris.org/
43   *     29 Stanley Road, Oxford, OX4 1QY, UK
44   */
45  
46  package org.melati.util;
47  
48  import java.io.BufferedInputStream;
49  import java.io.IOException;
50  import java.io.InputStream;
51  
52  /**
53   * Like a <code>BufferedInputStream</code> except it has a new function
54   * {@link #readToDelimiter} which will only read bytes upto the start
55   * of any occurrence of the delimiter in the InputStream.
56   *
57   * @see java.io.BufferedInputStream
58   */
59  
60  public class DelimitedBufferedInputStream extends BufferedInputStream {
61  
62      private int potentialMatch = -1;
63  
64      /**
65       * Creates a <code>DelimitedBufferedInputStream</code>
66       * and saves its  argument, the input stream
67       * <code>in</code>, for later use. An internal
68       * buffer array is created and  stored in <code>buf</code>.
69       *
70       * @param   in   the underlying input stream.
71       **/
72      public DelimitedBufferedInputStream(InputStream in) {
73         super(in);
74      }
75  
76      /**
77       * Creates a <code>DelimitedBufferedInputStream</code>
78       * with the specified buffer size,
79       * and saves its  argument, the input stream
80       * <code>in</code>, for later use.  An internal
81       * buffer array of length  <code>size</code>
82       * is created and stored in <code>buf</code>.
83       *
84       * @param   in     the underlying input stream.
85       * @param   size   the buffer size.
86       **/
87      public DelimitedBufferedInputStream(InputStream in, int size) {
88        super(in, size);
89      }
90  
91      /**
92       * Fills the buffer with more data, taking into account
93       * shuffling and other tricks for dealing with marks.
94       * Assumes that it is being called by a synchronized method.
95       * This method also assumes that all data has already been read in,
96       * hence pos > count.
97       * <p>
98       * This code is copy'n'pasted from BufferedInputString (it's private)
99       **/
100     private void fill() throws IOException {
101       if (markpos < 0)
102            pos = 0;                 /* no mark: throw away the buffer */
103       else if (pos >= buf.length)   /* no room left in buffer */
104            if (markpos > 0) {    /* can throw away early part of the buffer */
105                int sz = pos - markpos;
106                System.arraycopy(buf, markpos, buf, 0, sz);
107                pos = sz;
108                markpos = 0;
109             }
110             else if (buf.length >= marklimit) {
111                markpos = -1;    /* buffer got too big, invalidate mark */
112                pos = 0;         /* drop buffer contents */
113             }
114             else {              /* grow buffer */
115                int nsz = pos * 2;
116                if (nsz > marklimit)
117                    nsz = marklimit;
118                byte nbuf[] = new byte[nsz];
119                System.arraycopy(buf, 0, nbuf, 0, pos);
120                buf = nbuf;
121             }
122 
123       count = pos;
124       int n = in.read(buf, pos, buf.length - pos);
125       if (n > 0)
126         count = n + pos;
127     }
128 
129     /**
130      * Check to make sure that this stream has not been closed.
131      * <p>
132      * This code is copy'n'pasted from BufferedInputString (it's private)
133      **/
134     private void ensureOpen() throws IOException {
135       if (in == null)
136         throw new IOException("Stream closed");
137     }
138 
139     /**
140      * Read characters into a portion of an array, reading from the underlying
141      * stream at most twice if necessary.
142      **/
143     private int readToDelimiter1(byte[] b, int off, int len, byte delim[])
144           throws IOException {
145       int avail = count - pos;
146       if (avail <= 0) {
147           fill();
148           avail = count - pos;
149           if (avail <= 0) return -1;
150       }
151       int cnt = (avail < len) ? avail : len;
152 
153       // indexOf sets potentialMatch
154       int found = indexOf(buf, delim, pos, avail);
155       int max = cnt;
156       if (found != -1)
157         max = found - pos;
158       else if (potentialMatch != -1)
159         max = potentialMatch - pos;
160 
161       cnt = (max < cnt) ? max : cnt;
162       System.arraycopy(buf, pos, b, off, cnt);
163       pos += cnt;
164 
165       /* We want to shuffle the buffer so that it contains
166        * at least delim.length bytes after potentialMatch,
167        * if we have read that far into the buffer 
168        */
169       if (found == -1 && potentialMatch != -1 && pos == potentialMatch) {
170 
171         /* the number of bytes to shift the buffer down by */
172         int halfmark = (markpos == -1)
173                        ? potentialMatch
174                        : markpos;
175 
176         /* 
177            the number of bytes we need to read to get all our potential
178            delim into the buffer 
179          */
180         int extras = potentialMatch + delim.length - count;
181 
182         /* the number of bytes we want to end up in buf */
183         int nsz = potentialMatch + delim.length - halfmark;
184         if (nsz > buf.length) {
185           // we need to grow the buffer
186           byte nbuf[] = new byte[nsz];
187           System.arraycopy(buf, halfmark, nbuf, 0, count - halfmark);
188           buf = nbuf;
189         } else {
190           System.arraycopy(buf, halfmark, buf, 0, count - halfmark);
191         }
192 
193         pos = pos - halfmark;
194         count = count - halfmark;
195         markpos = (markpos == -1) ? -1 : 0;
196         int n = in.read(buf, count, buf.length - count);
197         if (n > 0)
198           count += n;
199 
200         /* If, after reading, we don't have enough bytes to match delim,
201            add the new bytes to our output */
202         if (n < extras) {
203           int unfilled = len - cnt;
204           if (unfilled > 0) {
205             avail = count - pos;
206             int cnt2 = (unfilled < avail) ? unfilled : avail;
207             System.arraycopy(buf, pos, b, off+cnt, cnt2);
208             cnt += cnt2;
209             pos += cnt2;
210           }
211         }
212       }
213       return cnt;
214     }
215 
216     /**
217      * Reads bytes from this byte-input stream into the specified byte array,
218      * starting at the given offset, but we stop at the first occurrence of
219      * delim.
220      *
221      * <p> This method implements the general contract of the corresponding
222      * <code>{@link InputStream#read(byte[], int, int) read}</code> method of
223      * the <code>{@link InputStream}</code> class.  As an additional
224      * convenience, it attempts to read as many bytes as possible by repeatedly
225      * invoking the <code>read</code> method of the underlying stream.  This
226      * iterated <code>read</code> continues until one of the following
227      * conditions becomes true: <ul>
228      *
229      *   <li> The specified number of bytes have been read,
230      *
231      *   <li> The <code>read</code> method of the underlying stream returns
232      *   <code>-1</code>, indicating end-of-file, or
233      *
234      *   <li> The <code>available</code> method of the underlying stream
235      *   returns zero, indicating that further input requests would block.
236      *
237      *   <li> We encounter an occurrence of delim in the underlying stream
238      *
239      * </ul> If the first <code>read</code> on the underlying stream returns
240      * <code>-1</code> to indicate end-of-file then this method returns
241      * <code>-1</code>.  Otherwise this method returns the number of bytes
242      * actually read.
243      *
244      * <p> Subclasses of this class are encouraged, but not required, to
245      * attempt to read as many bytes as possible in the same fashion.
246      *
247      * @param      b     destination buffer.
248      * @param      off   offset at which to start storing bytes.
249      * @param      len   maximum number of bytes to read.
250      * @param      delim the delimiter which we should stop reading at.
251      * @return     the number of bytes read, or <code>-1</code> if the end of
252      *             the stream has been reached.
253      * @exception  IOException  if an I/O error occurs.
254      **/
255   public synchronized int readToDelimiter(byte b[], int off, 
256                                           int len, byte delim[])
257         throws IOException  {
258     ensureOpen();
259     if ((off < 0) || (off > b.length) || (len < 0) ||
260             ((off + len) > b.length) || ((off + len) < 0)) {
261         throw new IndexOutOfBoundsException();
262     } else if (len == 0) {
263         return 0;
264     }
265 
266     int n = readToDelimiter1(b, off, len, delim);
267     if (n <= 0) return n;
268     while ((n < len) && (in.available() > 0)) {
269       int n1 = readToDelimiter1(b, off + n, len - n, delim);
270       if (n1 <= 0) break;
271       n += n1;
272     }
273     return n;
274   }
275 
276     /**
277      * Calculates length.
278      * <p>
279      * Returns the index within data1 of the
280      * byte array data2. It starts looking in data1 at fromIndex and only
281      * considers len bytes of data1
282      *  
283      * @param      data1 array to look in.
284      * @param      data2 array to look for.
285      * @param      fromIndex where to start looking from in data1.
286      * @return the index of data2 within data1 or
287      *         -1 if data2 does not occur within data1
288      */
289     public int indexOf(byte[] data1, byte[] data2, int fromIndex) {
290       if (fromIndex < 0)
291         fromIndex = 0;
292       return indexOf(data1, data2, fromIndex, data1.length - fromIndex);
293     }
294 
295     /**
296      * Returns the index within data1 of the
297      * byte array data2. It starts looking in data1 at fromIndex and only
298      * considers len bytes of data1
299      *
300      * @param      data1 array to look in.
301      * @param      data2 array to look for.
302      * @param      fromIndex where to start looking from in data1.
303      * @param      len   maximum number of bytes of data1 to look in.
304      * @return the index of data2 within data1 or
305      *         -1 if data2 does not occur within data1
306      **/
307     public int indexOf(byte[] data1, byte[] data2, int fromIndex, int len) {
308       byte[] v1 = data1;
309       byte[] v2 = data2;
310       if (len <= 0)
311         return -1;
312       if (fromIndex >= data1.length) {
313         if (data1.length == 0 && fromIndex == 0 && data2.length == 0)
314           return 0;
315         return -1;
316       }
317       if (fromIndex < 0)
318         fromIndex = 0;
319       if (data2.length == 0)
320         return fromIndex;
321       int end = (fromIndex + len > data1.length) ? 
322                            data1.length : fromIndex + len;
323       int maxStart = end - data2.length;
324       byte first = v2[0];
325       int i = fromIndex;
326 
327       startSearchForFirstByte:
328       while (true) {
329         potentialMatch = -1;
330         while (i < end && v1[i] != first)
331           i++;
332         if (i >= end)
333           return -1;
334         if (i > maxStart) {  // we might find an initial segment of delim
335           potentialMatch = i;
336         }
337         int j = i + 1;
338         int last = (potentialMatch == -1) ? i + data2.length : data1.length;
339         int k = 1;
340         while (j < last) {
341           if (v1[j++] != v2[k++]) {
342             i++;
343             continue startSearchForFirstByte;
344           }
345         }
346         return (potentialMatch == -1) ? i : -1;
347       }
348     }
349 
350 
351     /**
352      * Used in tests.
353      * @return the buffer length
354      */
355     public int getBufferLength() { 
356       return buf.length;
357     }
358     /**
359      * Used in tests.
360      * @return the potentialMatch
361      */
362     public int getPotentialMatch() {
363       return potentialMatch;
364     }
365 
366 }