1/* 2 * Copyright (C) 2014 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package okio; 17 18import java.io.EOFException; 19import java.io.IOException; 20import java.io.InputStream; 21import java.nio.charset.Charset; 22 23import static okio.Util.checkOffsetAndCount; 24 25final class RealBufferedSource implements BufferedSource { 26 public final Buffer buffer; 27 public final Source source; 28 private boolean closed; 29 30 public RealBufferedSource(Source source, Buffer buffer) { 31 if (source == null) throw new IllegalArgumentException("source == null"); 32 this.buffer = buffer; 33 this.source = source; 34 } 35 36 public RealBufferedSource(Source source) { 37 this(source, new Buffer()); 38 } 39 40 @Override public Buffer buffer() { 41 return buffer; 42 } 43 44 @Override public long read(Buffer sink, long byteCount) throws IOException { 45 if (sink == null) throw new IllegalArgumentException("sink == null"); 46 if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); 47 if (closed) throw new IllegalStateException("closed"); 48 49 if (buffer.size == 0) { 50 long read = source.read(buffer, Segment.SIZE); 51 if (read == -1) return -1; 52 } 53 54 long toRead = Math.min(byteCount, buffer.size); 55 return buffer.read(sink, toRead); 56 } 57 58 @Override public boolean exhausted() throws IOException { 59 if (closed) throw new IllegalStateException("closed"); 60 return buffer.exhausted() && source.read(buffer, Segment.SIZE) == -1; 61 } 62 63 @Override public void require(long byteCount) throws IOException { 64 if (!request(byteCount)) throw new EOFException(); 65 } 66 67 @Override public boolean request(long byteCount) throws IOException { 68 if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); 69 if (closed) throw new IllegalStateException("closed"); 70 while (buffer.size < byteCount) { 71 if (source.read(buffer, Segment.SIZE) == -1) return false; 72 } 73 return true; 74 } 75 76 @Override public byte readByte() throws IOException { 77 require(1); 78 return buffer.readByte(); 79 } 80 81 @Override public ByteString readByteString() throws IOException { 82 buffer.writeAll(source); 83 return buffer.readByteString(); 84 } 85 86 @Override public ByteString readByteString(long byteCount) throws IOException { 87 require(byteCount); 88 return buffer.readByteString(byteCount); 89 } 90 91 @Override public byte[] readByteArray() throws IOException { 92 buffer.writeAll(source); 93 return buffer.readByteArray(); 94 } 95 96 @Override public byte[] readByteArray(long byteCount) throws IOException { 97 require(byteCount); 98 return buffer.readByteArray(byteCount); 99 } 100 101 @Override public int read(byte[] sink) throws IOException { 102 return read(sink, 0, sink.length); 103 } 104 105 @Override public void readFully(byte[] sink) throws IOException { 106 try { 107 require(sink.length); 108 } catch (EOFException e) { 109 // The underlying source is exhausted. Copy the bytes we got before rethrowing. 110 int offset = 0; 111 while (buffer.size > 0) { 112 int read = buffer.read(sink, offset, (int) buffer.size); 113 if (read == -1) throw new AssertionError(); 114 offset += read; 115 } 116 throw e; 117 } 118 buffer.readFully(sink); 119 } 120 121 @Override public int read(byte[] sink, int offset, int byteCount) throws IOException { 122 checkOffsetAndCount(sink.length, offset, byteCount); 123 124 if (buffer.size == 0) { 125 long read = source.read(buffer, Segment.SIZE); 126 if (read == -1) return -1; 127 } 128 129 int toRead = (int) Math.min(byteCount, buffer.size); 130 return buffer.read(sink, offset, toRead); 131 } 132 133 @Override public void readFully(Buffer sink, long byteCount) throws IOException { 134 try { 135 require(byteCount); 136 } catch (EOFException e) { 137 // The underlying source is exhausted. Copy the bytes we got before rethrowing. 138 sink.writeAll(buffer); 139 throw e; 140 } 141 buffer.readFully(sink, byteCount); 142 } 143 144 @Override public long readAll(Sink sink) throws IOException { 145 if (sink == null) throw new IllegalArgumentException("sink == null"); 146 147 long totalBytesWritten = 0; 148 while (source.read(buffer, Segment.SIZE) != -1) { 149 long emitByteCount = buffer.completeSegmentByteCount(); 150 if (emitByteCount > 0) { 151 totalBytesWritten += emitByteCount; 152 sink.write(buffer, emitByteCount); 153 } 154 } 155 if (buffer.size() > 0) { 156 totalBytesWritten += buffer.size(); 157 sink.write(buffer, buffer.size()); 158 } 159 return totalBytesWritten; 160 } 161 162 @Override public String readUtf8() throws IOException { 163 buffer.writeAll(source); 164 return buffer.readUtf8(); 165 } 166 167 @Override public String readUtf8(long byteCount) throws IOException { 168 require(byteCount); 169 return buffer.readUtf8(byteCount); 170 } 171 172 @Override public String readString(Charset charset) throws IOException { 173 if (charset == null) throw new IllegalArgumentException("charset == null"); 174 175 buffer.writeAll(source); 176 return buffer.readString(charset); 177 } 178 179 @Override public String readString(long byteCount, Charset charset) throws IOException { 180 require(byteCount); 181 if (charset == null) throw new IllegalArgumentException("charset == null"); 182 return buffer.readString(byteCount, charset); 183 } 184 185 @Override public String readUtf8Line() throws IOException { 186 long newline = indexOf((byte) '\n'); 187 188 if (newline == -1) { 189 return buffer.size != 0 ? readUtf8(buffer.size) : null; 190 } 191 192 return buffer.readUtf8Line(newline); 193 } 194 195 @Override public String readUtf8LineStrict() throws IOException { 196 long newline = indexOf((byte) '\n'); 197 if (newline == -1L) { 198 Buffer data = new Buffer(); 199 buffer.copyTo(data, 0, Math.min(32, buffer.size())); 200 throw new EOFException("\\n not found: size=" + buffer.size() 201 + " content=" + data.readByteString().hex() + "..."); 202 } 203 return buffer.readUtf8Line(newline); 204 } 205 206 @Override public int readUtf8CodePoint() throws IOException { 207 require(1); 208 209 byte b0 = buffer.getByte(0); 210 if ((b0 & 0xe0) == 0xc0) { 211 require(2); 212 } else if ((b0 & 0xf0) == 0xe0) { 213 require(3); 214 } else if ((b0 & 0xf8) == 0xf0) { 215 require(4); 216 } 217 218 return buffer.readUtf8CodePoint(); 219 } 220 221 @Override public short readShort() throws IOException { 222 require(2); 223 return buffer.readShort(); 224 } 225 226 @Override public short readShortLe() throws IOException { 227 require(2); 228 return buffer.readShortLe(); 229 } 230 231 @Override public int readInt() throws IOException { 232 require(4); 233 return buffer.readInt(); 234 } 235 236 @Override public int readIntLe() throws IOException { 237 require(4); 238 return buffer.readIntLe(); 239 } 240 241 @Override public long readLong() throws IOException { 242 require(8); 243 return buffer.readLong(); 244 } 245 246 @Override public long readLongLe() throws IOException { 247 require(8); 248 return buffer.readLongLe(); 249 } 250 251 @Override public long readDecimalLong() throws IOException { 252 require(1); 253 254 for (int pos = 0; request(pos + 1); pos++) { 255 byte b = buffer.getByte(pos); 256 if ((b < '0' || b > '9') && (pos != 0 || b != '-')) { 257 // Non-digit, or non-leading negative sign. 258 if (pos == 0) { 259 throw new NumberFormatException(String.format( 260 "Expected leading [0-9] or '-' character but was %#x", b)); 261 } 262 break; 263 } 264 } 265 266 return buffer.readDecimalLong(); 267 } 268 269 @Override public long readHexadecimalUnsignedLong() throws IOException { 270 require(1); 271 272 for (int pos = 0; request(pos + 1); pos++) { 273 byte b = buffer.getByte(pos); 274 if ((b < '0' || b > '9') && (b < 'a' || b > 'f') && (b < 'A' || b > 'F')) { 275 // Non-digit, or non-leading negative sign. 276 if (pos == 0) { 277 throw new NumberFormatException(String.format( 278 "Expected leading [0-9a-fA-F] character but was %#x", b)); 279 } 280 break; 281 } 282 } 283 284 return buffer.readHexadecimalUnsignedLong(); 285 } 286 287 @Override public void skip(long byteCount) throws IOException { 288 if (closed) throw new IllegalStateException("closed"); 289 while (byteCount > 0) { 290 if (buffer.size == 0 && source.read(buffer, Segment.SIZE) == -1) { 291 throw new EOFException(); 292 } 293 long toSkip = Math.min(byteCount, buffer.size()); 294 buffer.skip(toSkip); 295 byteCount -= toSkip; 296 } 297 } 298 299 @Override public long indexOf(byte b) throws IOException { 300 return indexOf(b, 0); 301 } 302 303 @Override public long indexOf(byte b, long fromIndex) throws IOException { 304 if (closed) throw new IllegalStateException("closed"); 305 while (fromIndex >= buffer.size) { 306 if (source.read(buffer, Segment.SIZE) == -1) return -1L; 307 } 308 long index; 309 while ((index = buffer.indexOf(b, fromIndex)) == -1) { 310 fromIndex = buffer.size; 311 if (source.read(buffer, Segment.SIZE) == -1) return -1L; 312 } 313 return index; 314 } 315 316 @Override public long indexOf(ByteString bytes) throws IOException { 317 return indexOf(bytes, 0); 318 } 319 320 @Override public long indexOf(ByteString bytes, long fromIndex) throws IOException { 321 if (bytes.size() == 0) throw new IllegalArgumentException("bytes is empty"); 322 while (true) { 323 fromIndex = indexOf(bytes.getByte(0), fromIndex); 324 if (fromIndex == -1) { 325 return -1; 326 } 327 if (rangeEquals(fromIndex, bytes)) { 328 return fromIndex; 329 } 330 fromIndex++; 331 } 332 } 333 334 @Override public long indexOfElement(ByteString targetBytes) throws IOException { 335 return indexOfElement(targetBytes, 0); 336 } 337 338 @Override public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException { 339 if (closed) throw new IllegalStateException("closed"); 340 while (fromIndex >= buffer.size) { 341 if (source.read(buffer, Segment.SIZE) == -1) return -1L; 342 } 343 long index; 344 while ((index = buffer.indexOfElement(targetBytes, fromIndex)) == -1) { 345 fromIndex = buffer.size; 346 if (source.read(buffer, Segment.SIZE) == -1) return -1L; 347 } 348 return index; 349 } 350 351 private boolean rangeEquals(long offset, ByteString bytes) throws IOException { 352 return request(offset + bytes.size()) && buffer.rangeEquals(offset, bytes); 353 } 354 355 @Override public InputStream inputStream() { 356 return new InputStream() { 357 @Override public int read() throws IOException { 358 if (closed) throw new IOException("closed"); 359 if (buffer.size == 0) { 360 long count = source.read(buffer, Segment.SIZE); 361 if (count == -1) return -1; 362 } 363 return buffer.readByte() & 0xff; 364 } 365 366 @Override public int read(byte[] data, int offset, int byteCount) throws IOException { 367 if (closed) throw new IOException("closed"); 368 checkOffsetAndCount(data.length, offset, byteCount); 369 370 if (buffer.size == 0) { 371 long count = source.read(buffer, Segment.SIZE); 372 if (count == -1) return -1; 373 } 374 375 return buffer.read(data, offset, byteCount); 376 } 377 378 @Override public int available() throws IOException { 379 if (closed) throw new IOException("closed"); 380 return (int) Math.min(buffer.size, Integer.MAX_VALUE); 381 } 382 383 @Override public void close() throws IOException { 384 RealBufferedSource.this.close(); 385 } 386 387 @Override public String toString() { 388 return RealBufferedSource.this + ".inputStream()"; 389 } 390 }; 391 } 392 393 @Override public void close() throws IOException { 394 if (closed) return; 395 closed = true; 396 source.close(); 397 buffer.clear(); 398 } 399 400 @Override public Timeout timeout() { 401 return source.timeout(); 402 } 403 404 @Override public String toString() { 405 return "buffer(" + source + ")"; 406 } 407} 408