/* * Copyright (C) 2014 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package okio; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import static okio.Util.checkOffsetAndCount; final class RealBufferedSource implements BufferedSource { public final Buffer buffer; public final Source source; private boolean closed; public RealBufferedSource(Source source, Buffer buffer) { if (source == null) throw new IllegalArgumentException("source == null"); this.buffer = buffer; this.source = source; } public RealBufferedSource(Source source) { this(source, new Buffer()); } @Override public Buffer buffer() { return buffer; } @Override public long read(Buffer sink, long byteCount) throws IOException { if (sink == null) throw new IllegalArgumentException("sink == null"); if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (closed) throw new IllegalStateException("closed"); if (buffer.size == 0) { long read = source.read(buffer, Segment.SIZE); if (read == -1) return -1; } long toRead = Math.min(byteCount, buffer.size); return buffer.read(sink, toRead); } @Override public boolean exhausted() throws IOException { if (closed) throw new IllegalStateException("closed"); return buffer.exhausted() && source.read(buffer, Segment.SIZE) == -1; } @Override public void require(long byteCount) throws IOException { if (!request(byteCount)) throw new EOFException(); } @Override public boolean request(long byteCount) throws IOException { if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (closed) throw new IllegalStateException("closed"); while (buffer.size < byteCount) { if (source.read(buffer, Segment.SIZE) == -1) return false; } return true; } @Override public byte readByte() throws IOException { require(1); return buffer.readByte(); } @Override public ByteString readByteString() throws IOException { buffer.writeAll(source); return buffer.readByteString(); } @Override public ByteString readByteString(long byteCount) throws IOException { require(byteCount); return buffer.readByteString(byteCount); } @Override public byte[] readByteArray() throws IOException { buffer.writeAll(source); return buffer.readByteArray(); } @Override public byte[] readByteArray(long byteCount) throws IOException { require(byteCount); return buffer.readByteArray(byteCount); } @Override public int read(byte[] sink) throws IOException { return read(sink, 0, sink.length); } @Override public void readFully(byte[] sink) throws IOException { try { require(sink.length); } catch (EOFException e) { // The underlying source is exhausted. Copy the bytes we got before rethrowing. int offset = 0; while (buffer.size > 0) { int read = buffer.read(sink, offset, (int) buffer.size); if (read == -1) throw new AssertionError(); offset += read; } throw e; } buffer.readFully(sink); } @Override public int read(byte[] sink, int offset, int byteCount) throws IOException { checkOffsetAndCount(sink.length, offset, byteCount); if (buffer.size == 0) { long read = source.read(buffer, Segment.SIZE); if (read == -1) return -1; } int toRead = (int) Math.min(byteCount, buffer.size); return buffer.read(sink, offset, toRead); } @Override public void readFully(Buffer sink, long byteCount) throws IOException { try { require(byteCount); } catch (EOFException e) { // The underlying source is exhausted. Copy the bytes we got before rethrowing. sink.writeAll(buffer); throw e; } buffer.readFully(sink, byteCount); } @Override public long readAll(Sink sink) throws IOException { if (sink == null) throw new IllegalArgumentException("sink == null"); long totalBytesWritten = 0; while (source.read(buffer, Segment.SIZE) != -1) { long emitByteCount = buffer.completeSegmentByteCount(); if (emitByteCount > 0) { totalBytesWritten += emitByteCount; sink.write(buffer, emitByteCount); } } if (buffer.size() > 0) { totalBytesWritten += buffer.size(); sink.write(buffer, buffer.size()); } return totalBytesWritten; } @Override public String readUtf8() throws IOException { buffer.writeAll(source); return buffer.readUtf8(); } @Override public String readUtf8(long byteCount) throws IOException { require(byteCount); return buffer.readUtf8(byteCount); } @Override public String readString(Charset charset) throws IOException { if (charset == null) throw new IllegalArgumentException("charset == null"); buffer.writeAll(source); return buffer.readString(charset); } @Override public String readString(long byteCount, Charset charset) throws IOException { require(byteCount); if (charset == null) throw new IllegalArgumentException("charset == null"); return buffer.readString(byteCount, charset); } @Override public String readUtf8Line() throws IOException { long newline = indexOf((byte) '\n'); if (newline == -1) { return buffer.size != 0 ? readUtf8(buffer.size) : null; } return buffer.readUtf8Line(newline); } @Override public String readUtf8LineStrict() throws IOException { long newline = indexOf((byte) '\n'); if (newline == -1L) { Buffer data = new Buffer(); buffer.copyTo(data, 0, Math.min(32, buffer.size())); throw new EOFException("\\n not found: size=" + buffer.size() + " content=" + data.readByteString().hex() + "..."); } return buffer.readUtf8Line(newline); } @Override public int readUtf8CodePoint() throws IOException { require(1); byte b0 = buffer.getByte(0); if ((b0 & 0xe0) == 0xc0) { require(2); } else if ((b0 & 0xf0) == 0xe0) { require(3); } else if ((b0 & 0xf8) == 0xf0) { require(4); } return buffer.readUtf8CodePoint(); } @Override public short readShort() throws IOException { require(2); return buffer.readShort(); } @Override public short readShortLe() throws IOException { require(2); return buffer.readShortLe(); } @Override public int readInt() throws IOException { require(4); return buffer.readInt(); } @Override public int readIntLe() throws IOException { require(4); return buffer.readIntLe(); } @Override public long readLong() throws IOException { require(8); return buffer.readLong(); } @Override public long readLongLe() throws IOException { require(8); return buffer.readLongLe(); } @Override public long readDecimalLong() throws IOException { require(1); for (int pos = 0; request(pos + 1); pos++) { byte b = buffer.getByte(pos); if ((b < '0' || b > '9') && (pos != 0 || b != '-')) { // Non-digit, or non-leading negative sign. if (pos == 0) { throw new NumberFormatException(String.format( "Expected leading [0-9] or '-' character but was %#x", b)); } break; } } return buffer.readDecimalLong(); } @Override public long readHexadecimalUnsignedLong() throws IOException { require(1); for (int pos = 0; request(pos + 1); pos++) { byte b = buffer.getByte(pos); if ((b < '0' || b > '9') && (b < 'a' || b > 'f') && (b < 'A' || b > 'F')) { // Non-digit, or non-leading negative sign. if (pos == 0) { throw new NumberFormatException(String.format( "Expected leading [0-9a-fA-F] character but was %#x", b)); } break; } } return buffer.readHexadecimalUnsignedLong(); } @Override public void skip(long byteCount) throws IOException { if (closed) throw new IllegalStateException("closed"); while (byteCount > 0) { if (buffer.size == 0 && source.read(buffer, Segment.SIZE) == -1) { throw new EOFException(); } long toSkip = Math.min(byteCount, buffer.size()); buffer.skip(toSkip); byteCount -= toSkip; } } @Override public long indexOf(byte b) throws IOException { return indexOf(b, 0); } @Override public long indexOf(byte b, long fromIndex) throws IOException { if (closed) throw new IllegalStateException("closed"); while (fromIndex >= buffer.size) { if (source.read(buffer, Segment.SIZE) == -1) return -1L; } long index; while ((index = buffer.indexOf(b, fromIndex)) == -1) { fromIndex = buffer.size; if (source.read(buffer, Segment.SIZE) == -1) return -1L; } return index; } @Override public long indexOf(ByteString bytes) throws IOException { return indexOf(bytes, 0); } @Override public long indexOf(ByteString bytes, long fromIndex) throws IOException { if (bytes.size() == 0) throw new IllegalArgumentException("bytes is empty"); while (true) { fromIndex = indexOf(bytes.getByte(0), fromIndex); if (fromIndex == -1) { return -1; } if (rangeEquals(fromIndex, bytes)) { return fromIndex; } fromIndex++; } } @Override public long indexOfElement(ByteString targetBytes) throws IOException { return indexOfElement(targetBytes, 0); } @Override public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException { if (closed) throw new IllegalStateException("closed"); while (fromIndex >= buffer.size) { if (source.read(buffer, Segment.SIZE) == -1) return -1L; } long index; while ((index = buffer.indexOfElement(targetBytes, fromIndex)) == -1) { fromIndex = buffer.size; if (source.read(buffer, Segment.SIZE) == -1) return -1L; } return index; } private boolean rangeEquals(long offset, ByteString bytes) throws IOException { return request(offset + bytes.size()) && buffer.rangeEquals(offset, bytes); } @Override public InputStream inputStream() { return new InputStream() { @Override public int read() throws IOException { if (closed) throw new IOException("closed"); if (buffer.size == 0) { long count = source.read(buffer, Segment.SIZE); if (count == -1) return -1; } return buffer.readByte() & 0xff; } @Override public int read(byte[] data, int offset, int byteCount) throws IOException { if (closed) throw new IOException("closed"); checkOffsetAndCount(data.length, offset, byteCount); if (buffer.size == 0) { long count = source.read(buffer, Segment.SIZE); if (count == -1) return -1; } return buffer.read(data, offset, byteCount); } @Override public int available() throws IOException { if (closed) throw new IOException("closed"); return (int) Math.min(buffer.size, Integer.MAX_VALUE); } @Override public void close() throws IOException { RealBufferedSource.this.close(); } @Override public String toString() { return RealBufferedSource.this + ".inputStream()"; } }; } @Override public void close() throws IOException { if (closed) return; closed = true; source.close(); buffer.clear(); } @Override public Timeout timeout() { return source.timeout(); } @Override public String toString() { return "buffer(" + source + ")"; } }