| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| DelimitedBufferedInputStream |
|
| 7.1;7.1 |
| 1 | /* | |
| 2 | * $Source: /usr/cvsroot/melati/melati/src/site/resources/withWebmacro/org.melati.util.DelimitedBufferedInputStream.html,v $ | |
| 3 | * $Revision: 1.1 $ | |
| 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 | 48 | 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 | 4 | super(in); |
| 74 | 4 | } |
| 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 | 44 | super(in, size); |
| 89 | 44 | } |
| 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 | 166 | if (markpos < 0) |
| 102 | 166 | 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 | 166 | count = pos; |
| 124 | 166 | int n = in.read(buf, pos, buf.length - pos); |
| 125 | 166 | if (n > 0) |
| 126 | 166 | count = n + pos; |
| 127 | 166 | } |
| 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 | 1038 | if (in == null) |
| 136 | 0 | throw new IOException("Stream closed"); |
| 137 | 1038 | } |
| 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 | 1098 | int avail = count - pos; |
| 146 | 1098 | if (avail <= 0) { |
| 147 | 166 | fill(); |
| 148 | 166 | avail = count - pos; |
| 149 | 166 | if (avail <= 0) return -1; |
| 150 | } | |
| 151 | 1098 | int cnt = (avail < len) ? avail : len; |
| 152 | ||
| 153 | // indexOf sets potentialMatch | |
| 154 | 1098 | int found = indexOf(buf, delim, pos, avail); |
| 155 | 1098 | int max = cnt; |
| 156 | 1098 | if (found != -1) |
| 157 | 912 | max = found - pos; |
| 158 | 186 | else if (potentialMatch != -1) |
| 159 | 18 | max = potentialMatch - pos; |
| 160 | ||
| 161 | 1098 | cnt = (max < cnt) ? max : cnt; |
| 162 | 1098 | System.arraycopy(buf, pos, b, off, cnt); |
| 163 | 1098 | 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 | 1098 | if (found == -1 && potentialMatch != -1 && pos == potentialMatch) { |
| 170 | ||
| 171 | /* the number of bytes to shift the buffer down by */ | |
| 172 | 12 | 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 | 12 | int extras = potentialMatch + delim.length - count; |
| 181 | ||
| 182 | /* the number of bytes we want to end up in buf */ | |
| 183 | 12 | int nsz = potentialMatch + delim.length - halfmark; |
| 184 | 12 | if (nsz > buf.length) { |
| 185 | // we need to grow the buffer | |
| 186 | 6 | byte nbuf[] = new byte[nsz]; |
| 187 | 6 | System.arraycopy(buf, halfmark, nbuf, 0, count - halfmark); |
| 188 | 6 | buf = nbuf; |
| 189 | 6 | } else { |
| 190 | 6 | System.arraycopy(buf, halfmark, buf, 0, count - halfmark); |
| 191 | } | |
| 192 | ||
| 193 | 12 | pos = pos - halfmark; |
| 194 | 12 | count = count - halfmark; |
| 195 | 12 | markpos = (markpos == -1) ? -1 : 0; |
| 196 | 12 | int n = in.read(buf, count, buf.length - count); |
| 197 | 12 | if (n > 0) |
| 198 | 12 | 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 | 12 | if (n < extras) { |
| 203 | 6 | int unfilled = len - cnt; |
| 204 | 6 | if (unfilled > 0) { |
| 205 | 6 | avail = count - pos; |
| 206 | 6 | int cnt2 = (unfilled < avail) ? unfilled : avail; |
| 207 | 6 | System.arraycopy(buf, pos, b, off+cnt, cnt2); |
| 208 | 6 | cnt += cnt2; |
| 209 | 6 | pos += cnt2; |
| 210 | } | |
| 211 | } | |
| 212 | } | |
| 213 | 1098 | 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 | 1038 | ensureOpen(); |
| 259 | 1038 | if ((off < 0) || (off > b.length) || (len < 0) || |
| 260 | ((off + len) > b.length) || ((off + len) < 0)) { | |
| 261 | 0 | throw new IndexOutOfBoundsException(); |
| 262 | 1038 | } else if (len == 0) { |
| 263 | 0 | return 0; |
| 264 | } | |
| 265 | ||
| 266 | 1038 | int n = readToDelimiter1(b, off, len, delim); |
| 267 | 1038 | if (n <= 0) return n; |
| 268 | 758 | while ((n < len) && (in.available() > 0)) { |
| 269 | 60 | int n1 = readToDelimiter1(b, off + n, len - n, delim); |
| 270 | 60 | if (n1 <= 0) break; |
| 271 | 18 | n += n1; |
| 272 | 18 | } |
| 273 | 740 | 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 | 6 | if (fromIndex < 0) |
| 291 | 0 | fromIndex = 0; |
| 292 | 6 | 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 | 1104 | byte[] v1 = data1; |
| 309 | 1104 | byte[] v2 = data2; |
| 310 | 1104 | if (len <= 0) |
| 311 | 0 | return -1; |
| 312 | 1104 | 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 | 1104 | if (fromIndex < 0) |
| 318 | 0 | fromIndex = 0; |
| 319 | 1104 | if (data2.length == 0) |
| 320 | 0 | return fromIndex; |
| 321 | 1104 | int end = (fromIndex + len > data1.length) ? |
| 322 | data1.length : fromIndex + len; | |
| 323 | 1104 | int maxStart = end - data2.length; |
| 324 | 1104 | byte first = v2[0]; |
| 325 | 1104 | int i = fromIndex; |
| 326 | ||
| 327 | startSearchForFirstByte: | |
| 328 | while (true) { | |
| 329 | 1114 | potentialMatch = -1; |
| 330 | 300287 | while (i < end && v1[i] != first) |
| 331 | 299173 | i++; |
| 332 | 1114 | if (i >= end) |
| 333 | 170 | return -1; |
| 334 | 944 | if (i > maxStart) { // we might find an initial segment of delim |
| 335 | 22 | potentialMatch = i; |
| 336 | } | |
| 337 | 944 | int j = i + 1; |
| 338 | 944 | int last = (potentialMatch == -1) ? i + data2.length : data1.length; |
| 339 | 944 | int k = 1; |
| 340 | 16665 | while (j < last) { |
| 341 | 15731 | if (v1[j++] != v2[k++]) { |
| 342 | 10 | i++; |
| 343 | 10 | continue startSearchForFirstByte; |
| 344 | } | |
| 345 | } | |
| 346 | 934 | 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 | 2 | return buf.length; |
| 357 | } | |
| 358 | /** | |
| 359 | * Used in tests. | |
| 360 | * @return the potentialMatch | |
| 361 | */ | |
| 362 | public int getPotentialMatch() { | |
| 363 | 6 | return potentialMatch; |
| 364 | } | |
| 365 | ||
| 366 | } |