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