Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DelimitedBufferedInputStream |
|
| 7.1;7.1 |
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 | 24 | 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 | 2 | super(in); |
74 | 2 | } |
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 | 22 | super(in, size); |
89 | 22 | } |
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 | 65 | if (markpos < 0) |
102 | 65 | pos = 0; /* no mark: throw away the buffer */ |
103 | 0 | else if (pos >= buf.length) /* no room left in buffer */ |
104 | 0 | if (markpos > 0) { /* can throw away early part of the buffer */ |
105 | 0 | int sz = pos - markpos; |
106 | 0 | System.arraycopy(buf, markpos, buf, 0, sz); |
107 | 0 | pos = sz; |
108 | 0 | markpos = 0; |
109 | 0 | } |
110 | 0 | else if (buf.length >= marklimit) { |
111 | 0 | markpos = -1; /* buffer got too big, invalidate mark */ |
112 | 0 | pos = 0; /* drop buffer contents */ |
113 | } | |
114 | else { /* grow buffer */ | |
115 | 0 | int nsz = pos * 2; |
116 | 0 | if (nsz > marklimit) |
117 | 0 | nsz = marklimit; |
118 | 0 | byte nbuf[] = new byte[nsz]; |
119 | 0 | System.arraycopy(buf, 0, nbuf, 0, pos); |
120 | 0 | buf = nbuf; |
121 | } | |
122 | ||
123 | 65 | count = pos; |
124 | 65 | int n = in.read(buf, pos, buf.length - pos); |
125 | 65 | if (n > 0) |
126 | 65 | count = n + pos; |
127 | 65 | } |
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 | 493 | if (in == null) |
136 | 0 | throw new IOException("Stream closed"); |
137 | 493 | } |
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 | 539 | int avail = count - pos; |
146 | 539 | if (avail <= 0) { |
147 | 65 | fill(); |
148 | 65 | avail = count - pos; |
149 | 65 | if (avail <= 0) return -1; |
150 | } | |
151 | 539 | int cnt = (avail < len) ? avail : len; |
152 | ||
153 | // indexOf sets potentialMatch | |
154 | 539 | int found = indexOf(buf, delim, pos, avail); |
155 | 539 | int max = cnt; |
156 | 539 | if (found != -1) |
157 | 473 | max = found - pos; |
158 | 66 | else if (potentialMatch != -1) |
159 | 3 | max = potentialMatch - pos; |
160 | ||
161 | 539 | cnt = (max < cnt) ? max : cnt; |
162 | 539 | System.arraycopy(buf, pos, b, off, cnt); |
163 | 539 | 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 | 539 | if (found == -1 && potentialMatch != -1 && pos == potentialMatch) { |
170 | ||
171 | /* the number of bytes to shift the buffer down by */ | |
172 | 3 | 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 | 3 | int extras = potentialMatch + delim.length - count; |
181 | ||
182 | /* the number of bytes we want to end up in buf */ | |
183 | 3 | int nsz = potentialMatch + delim.length - halfmark; |
184 | 3 | if (nsz > buf.length) { |
185 | // we need to grow the buffer | |
186 | 3 | byte nbuf[] = new byte[nsz]; |
187 | 3 | System.arraycopy(buf, halfmark, nbuf, 0, count - halfmark); |
188 | 3 | buf = nbuf; |
189 | 3 | } else { |
190 | 0 | System.arraycopy(buf, halfmark, buf, 0, count - halfmark); |
191 | } | |
192 | ||
193 | 3 | pos = pos - halfmark; |
194 | 3 | count = count - halfmark; |
195 | 3 | markpos = (markpos == -1) ? -1 : 0; |
196 | 3 | int n = in.read(buf, count, buf.length - count); |
197 | 3 | if (n > 0) |
198 | 3 | 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 | 3 | if (n < extras) { |
203 | 3 | int unfilled = len - cnt; |
204 | 3 | if (unfilled > 0) { |
205 | 3 | avail = count - pos; |
206 | 3 | int cnt2 = (unfilled < avail) ? unfilled : avail; |
207 | 3 | System.arraycopy(buf, pos, b, off+cnt, cnt2); |
208 | 3 | cnt += cnt2; |
209 | 3 | pos += cnt2; |
210 | } | |
211 | } | |
212 | } | |
213 | 539 | 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 | 493 | ensureOpen(); |
259 | 493 | if ((off < 0) || (off > b.length) || (len < 0) || |
260 | ((off + len) > b.length) || ((off + len) < 0)) { | |
261 | 0 | throw new IndexOutOfBoundsException(); |
262 | 493 | } else if (len == 0) { |
263 | 0 | return 0; |
264 | } | |
265 | ||
266 | 493 | int n = readToDelimiter1(b, off, len, delim); |
267 | 493 | if (n <= 0) return n; |
268 | 355 | while ((n < len) && (in.available() > 0)) { |
269 | 46 | int n1 = readToDelimiter1(b, off + n, len - n, delim); |
270 | 46 | if (n1 <= 0) break; |
271 | 11 | n += n1; |
272 | 11 | } |
273 | 344 | 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 | 3 | if (fromIndex < 0) |
291 | 0 | fromIndex = 0; |
292 | 3 | 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 | 542 | byte[] v1 = data1; |
309 | 542 | byte[] v2 = data2; |
310 | 542 | if (len <= 0) |
311 | 0 | return -1; |
312 | 542 | if (fromIndex >= data1.length) { |
313 | 0 | if (data1.length == 0 && fromIndex == 0 && data2.length == 0) |
314 | 0 | return 0; |
315 | 0 | return -1; |
316 | } | |
317 | 542 | if (fromIndex < 0) |
318 | 0 | fromIndex = 0; |
319 | 542 | if (data2.length == 0) |
320 | 0 | return fromIndex; |
321 | 542 | int end = (fromIndex + len > data1.length) ? |
322 | data1.length : fromIndex + len; | |
323 | 542 | int maxStart = end - data2.length; |
324 | 542 | byte first = v2[0]; |
325 | 542 | int i = fromIndex; |
326 | ||
327 | startSearchForFirstByte: | |
328 | while (true) { | |
329 | 547 | potentialMatch = -1; |
330 | 102718 | while (i < end && v1[i] != first) |
331 | 102171 | i++; |
332 | 547 | if (i >= end) |
333 | 64 | return -1; |
334 | 483 | if (i > maxStart) { // we might find an initial segment of delim |
335 | 5 | potentialMatch = i; |
336 | } | |
337 | 483 | int j = i + 1; |
338 | 483 | int last = (potentialMatch == -1) ? i + data2.length : data1.length; |
339 | 483 | int k = 1; |
340 | 8440 | while (j < last) { |
341 | 7962 | if (v1[j++] != v2[k++]) { |
342 | 5 | i++; |
343 | 5 | continue startSearchForFirstByte; |
344 | } | |
345 | } | |
346 | 478 | 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 | 1 | return buf.length; |
357 | } | |
358 | /** | |
359 | * Used in tests. | |
360 | * @return the potentialMatch | |
361 | */ | |
362 | public int getPotentialMatch() { | |
363 | 3 | return potentialMatch; |
364 | } | |
365 | ||
366 | } |